diff --git a/CLOBBER b/CLOBBER index 596659994e1..d2b7619c600 100644 --- a/CLOBBER +++ b/CLOBBER @@ -22,4 +22,4 @@ # changes to stick? As of bug 928195, this shouldn't be necessary! Please # don't change CLOBBER for WebIDL changes any more. -Intermittent Android startup crashes leading to test bustage. +Bug 983185 requires a clobber. This may not affect all platforms. diff --git a/accessible/src/xul/XULTreeGridAccessible.cpp b/accessible/src/xul/XULTreeGridAccessible.cpp index 717aff226f5..3e64403571a 100644 --- a/accessible/src/xul/XULTreeGridAccessible.cpp +++ b/accessible/src/xul/XULTreeGridAccessible.cpp @@ -397,6 +397,7 @@ XULTreeGridRowAccessible::RowInvalidated(int32_t aStartColIdx, if (!treeColumns) return; + bool nameChanged = false; for (int32_t colIdx = aStartColIdx; colIdx <= aEndColIdx; ++colIdx) { nsCOMPtr column; treeColumns->GetColumnAt(colIdx, getter_AddRefs(column)); @@ -405,18 +406,14 @@ XULTreeGridRowAccessible::RowInvalidated(int32_t aStartColIdx, if (cellAccessible) { nsRefPtr cellAcc = do_QueryObject(cellAccessible); - cellAcc->CellInvalidated(); + nameChanged |= cellAcc->CellInvalidated(); } } } - nsAutoString name; - Name(name); - - if (name != mCachedName) { + if (nameChanged) nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this); - mCachedName = name; - } + } //////////////////////////////////////////////////////////////////////////////// @@ -763,7 +760,7 @@ XULTreeGridCellAccessible::RelationByType(RelationType aType) //////////////////////////////////////////////////////////////////////////////// // XULTreeGridCellAccessible: public implementation -void +bool XULTreeGridCellAccessible::CellInvalidated() { @@ -780,16 +777,20 @@ XULTreeGridCellAccessible::CellInvalidated() nsEventShell::FireEvent(accEvent); mCachedTextEquiv = textEquiv; + return true; } - return; + return false; } mTreeView->GetCellText(mRow, mColumn, textEquiv); if (mCachedTextEquiv != textEquiv) { nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this); mCachedTextEquiv = textEquiv; + return true; } + + return false; } //////////////////////////////////////////////////////////////////////////////// diff --git a/accessible/src/xul/XULTreeGridAccessible.h b/accessible/src/xul/XULTreeGridAccessible.h index 34f3fe4a6e6..a01053030c0 100644 --- a/accessible/src/xul/XULTreeGridAccessible.h +++ b/accessible/src/xul/XULTreeGridAccessible.h @@ -106,7 +106,6 @@ protected: // XULTreeItemAccessibleBase mutable AccessibleHashtable mAccessibleCache; - nsString mCachedName; }; @@ -180,8 +179,9 @@ public: /** * Fire name or state change event if the accessible text or value has been * changed. + * @return true if name has changed */ - void CellInvalidated(); + bool CellInvalidated(); protected: // Accessible diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js index 70c80b17cef..ee8158c731c 100644 --- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -879,9 +879,9 @@ pref("osfile.reset_worker_delay", 5000); pref("apz.asyncscroll.throttle", 40); pref("apz.pan_repaint_interval", 16); -// Maximum fling velocity in px/ms. Slower devices may need to reduce this +// Maximum fling velocity in inches/ms. Slower devices may need to reduce this // to avoid checkerboarding. Note, float value must be set as a string. -pref("apz.max_velocity_pixels_per_ms", "6.0"); +pref("apz.max_velocity_inches_per_ms", "0.0375"); // Tweak default displayport values to reduce the risk of running out of // memory when zooming in diff --git a/b2g/installer/package-manifest.in b/b2g/installer/package-manifest.in index 4a8ea6286eb..9fa8a099894 100644 --- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -297,6 +297,7 @@ @BINPATH@/components/storage.xpt @BINPATH@/components/telemetry.xpt @BINPATH@/components/toolkit_finalizationwitness.xpt +@BINPATH@/components/toolkit_osfile.xpt @BINPATH@/components/toolkitprofile.xpt #ifdef MOZ_ENABLE_XREMOTE @BINPATH@/components/toolkitremote.xpt diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index db942813228..4594d9f2f6e 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -11,6 +11,8 @@ Cu.import("resource://gre/modules/NotificationDB.jsm"); Cu.import("resource:///modules/RecentWindow.jsm"); Cu.import("resource://gre/modules/WindowsPrefSync.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils", + "resource://gre/modules/BrowserUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu", @@ -4840,13 +4842,11 @@ function getBrowserSelection(aCharLen) { // selections of more than 150 characters aren't useful const kMaxSelectionLen = 150; const charLen = Math.min(aCharLen || kMaxSelectionLen, kMaxSelectionLen); - let commandDispatcher = document.commandDispatcher; - var focusedWindow = commandDispatcher.focusedWindow; + let [element, focusedWindow] = BrowserUtils.getFocusSync(document); var selection = focusedWindow.getSelection().toString(); // try getting a selected text in text input. if (!selection) { - let element = commandDispatcher.focusedElement; var isOnTextInput = function isOnTextInput(elem) { // we avoid to return a value if a selection is in password field. // ref. bug 565717 diff --git a/browser/base/content/content.js b/browser/base/content/content.js index ff5db8adb04..91376b83f24 100644 --- a/browser/base/content/content.js +++ b/browser/base/content/content.js @@ -8,6 +8,7 @@ let Ci = Components.interfaces; let Cu = Components.utils; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "ContentLinkHandler", "resource:///modules/ContentLinkHandler.jsm"); diff --git a/browser/base/content/nsContextMenu.js b/browser/base/content/nsContextMenu.js index e3db1a4963f..952355dec49 100644 --- a/browser/base/content/nsContextMenu.js +++ b/browser/base/content/nsContextMenu.js @@ -80,8 +80,7 @@ nsContextMenu.prototype = { // isn't actually linked. if (this.isTextSelected && !this.onLink) { // Ok, we have some text, let's figure out if it looks like a URL. - let selection = document.commandDispatcher.focusedWindow - .getSelection(); + let selection = this.focusedWindow.getSelection(); let linkText = selection.toString().trim(); let uri; if (/^(?:https?|ftp):/i.test(linkText)) { @@ -549,6 +548,10 @@ nsContextMenu.prototype = { // Remember the node that was clicked. this.target = aNode; + let [elt, win] = BrowserUtils.getFocusSync(document); + this.focusedWindow = win; + this.focusedElement = elt; + // If this is a remote context menu event, use the information from // gContextMenuContentData instead. if (this.isRemote) { @@ -1252,7 +1255,7 @@ nsContextMenu.prototype = { var linkText; // If selected text is found to match valid URL pattern. if (this.onPlainTextLink) - linkText = document.commandDispatcher.focusedWindow.getSelection().toString().trim(); + linkText = this.focusedWindow.getSelection().toString().trim(); else linkText = this.linkText(); urlSecurityCheck(this.linkURL, this._unremotePrincipal(doc.nodePrincipal)); @@ -1449,7 +1452,7 @@ nsContextMenu.prototype = { // Returns true if anything is selected. isContentSelection: function() { - return !document.commandDispatcher.focusedWindow.getSelection().isCollapsed; + return !this.focusedWindow.getSelection().isCollapsed; }, toString: function () { @@ -1551,7 +1554,7 @@ nsContextMenu.prototype = { var linkText; // If selected text is found to match valid URL pattern. if (this.onPlainTextLink) - linkText = document.commandDispatcher.focusedWindow.getSelection().toString().trim(); + linkText = this.focusedWindow.getSelection().toString().trim(); else linkText = this.linkText(); window.top.PlacesCommandHook.bookmarkLink(PlacesUtils.bookmarksMenuFolderId, this.linkURL, diff --git a/browser/base/content/test/general/browser_save_link-perwindowpb.js b/browser/base/content/test/general/browser_save_link-perwindowpb.js index 47f3f2dde02..8a9492dea0c 100644 --- a/browser/base/content/test/general/browser_save_link-perwindowpb.js +++ b/browser/base/content/test/general/browser_save_link-perwindowpb.js @@ -4,39 +4,43 @@ var MockFilePicker = SpecialPowers.MockFilePicker; MockFilePicker.init(window); -let tempScope = {}; -Cu.import("resource://gre/modules/NetUtil.jsm", tempScope); -let NetUtil = tempScope.NetUtil; - // Trigger a save of a link in public mode, then trigger an identical save // in private mode and ensure that the second request is differentiated from // the first by checking that cookies set by the first response are not sent // during the second request. function triggerSave(aWindow, aCallback) { + info("started triggerSave"); var fileName; let testBrowser = aWindow.gBrowser.selectedBrowser; // This page sets a cookie if and only if a cookie does not exist yet let testURI = "http://mochi.test:8888/browser/browser/base/content/test/general/bug792517-2.html"; testBrowser.loadURI(testURI); testBrowser.addEventListener("pageshow", function pageShown(event) { + info("got pageshow with " + event.target.location); if (event.target.location != testURI) { + info("try again!"); testBrowser.loadURI(testURI); return; } + info("found our page!"); testBrowser.removeEventListener("pageshow", pageShown, false); - executeSoon(function () { - aWindow.document.addEventListener("popupshown", function(e) contextMenuOpened(aWindow, e), false); + waitForFocus(function () { + info("register to handle popupshown"); + aWindow.document.addEventListener("popupshown", contextMenuOpened, false); var link = testBrowser.contentDocument.getElementById("fff"); + info("link: " + link); EventUtils.synthesizeMouseAtCenter(link, { type: "contextmenu", button: 2 }, testBrowser.contentWindow); - }); + info("right clicked!"); + }, testBrowser.contentWindow); }, false); - function contextMenuOpened(aWindow, event) { - event.currentTarget.removeEventListener("popupshown", contextMenuOpened, false); + function contextMenuOpened(event) { + info("contextMenuOpened"); + aWindow.document.removeEventListener("popupshown", contextMenuOpened); // Create the folder the link will be saved into. var destDir = createTemporarySaveDirectory(); @@ -44,50 +48,62 @@ function triggerSave(aWindow, aCallback) { MockFilePicker.displayDirectory = destDir; MockFilePicker.showCallback = function(fp) { + info("showCallback"); fileName = fp.defaultString; + info("fileName: " + fileName); destFile.append (fileName); MockFilePicker.returnFiles = [destFile]; MockFilePicker.filterIndex = 1; // kSaveAsType_URL + info("done showCallback"); }; mockTransferCallback = function(downloadSuccess) { + info("mockTransferCallback"); onTransferComplete(aWindow, downloadSuccess, destDir); destDir.remove(true); ok(!destDir.exists(), "Destination dir should be removed"); ok(!destFile.exists(), "Destination file should be removed"); - mockTransferCallback = function(){}; + mockTransferCallback = null; + info("done mockTransferCallback"); } // Select "Save Link As" option from context menu var saveLinkCommand = aWindow.document.getElementById("context-savelink"); + info("saveLinkCommand: " + saveLinkCommand); saveLinkCommand.doCommand(); event.target.hidePopup(); + info("popup hidden"); } function onTransferComplete(aWindow, downloadSuccess, destDir) { ok(downloadSuccess, "Link should have been downloaded successfully"); - aWindow.gBrowser.removeCurrentTab(); + aWindow.close(); executeSoon(function() aCallback()); } } function test() { + info("Start the test"); waitForExplicitFinish(); - var windowsToClose = []; var gNumSet = 0; function testOnWindow(options, callback) { + info("testOnWindow(" + options + ")"); var win = OpenBrowserWindow(options); + info("got " + win); whenDelayedStartupFinished(win, () => callback(win)); } function whenDelayedStartupFinished(aWindow, aCallback) { + info("whenDelayedStartupFinished"); Services.obs.addObserver(function observer(aSubject, aTopic) { + info("whenDelayedStartupFinished, got topic: " + aTopic + ", got subject: " + aSubject + ", waiting for " + aWindow); if (aWindow == aSubject) { Services.obs.removeObserver(observer, aTopic); executeSoon(aCallback); + info("whenDelayedStartupFinished found our window"); } }, "browser-delayed-startup-finished", false); } @@ -95,16 +111,16 @@ function test() { mockTransferRegisterer.register(); registerCleanupFunction(function () { + info("Running the cleanup code"); mockTransferRegisterer.unregister(); MockFilePicker.cleanup(); - windowsToClose.forEach(function(win) { - win.close(); - }); Services.obs.removeObserver(observer, "http-on-modify-request"); Services.obs.removeObserver(observer, "http-on-examine-response"); + info("Finished running the cleanup code"); }); function observer(subject, topic, state) { + info("observer called with " + topic); if (topic == "http-on-modify-request") { onModifyRequest(subject); } else if (topic == "http-on-examine-response") { @@ -114,7 +130,9 @@ function test() { function onExamineResponse(subject) { let channel = subject.QueryInterface(Ci.nsIHttpChannel); + info("onExamineResponse with " + channel.URI.spec); if (channel.URI.spec != "http://mochi.test:8888/browser/browser/base/content/test/general/bug792517.sjs") { + info("returning"); return; } try { @@ -123,21 +141,32 @@ function test() { // header with foopy=1 when there are no cookies for that domain. is(cookies, "foopy=1", "Cookie should be foopy=1"); gNumSet += 1; - } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { } + info("gNumSet = " + gNumSet); + } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { + info("onExamineResponse caught NOTAVAIL" + ex); + } catch (ex) { + info("ionExamineResponse caught " + ex); + } } function onModifyRequest(subject) { let channel = subject.QueryInterface(Ci.nsIHttpChannel); + info("onModifyRequest with " + channel.URI.spec); if (channel.URI.spec != "http://mochi.test:8888/browser/browser/base/content/test/general/bug792517.sjs") { return; } try { let cookies = channel.getRequestHeader("cookie"); + info("cookies: " + cookies); // From browser/base/content/test/general/bug792715.sjs, we should never send a // cookie because we are making only 2 requests: one in public mode, and // one in private mode. throw "We should never send a cookie in this test"; - } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { } + } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { + info("onModifyRequest caught NOTAVAIL" + ex); + } catch (ex) { + info("ionModifyRequest caught " + ex); + } } Services.obs.addObserver(observer, "http-on-modify-request", false); @@ -169,7 +198,10 @@ function createTemporarySaveDirectory() { .getService(Ci.nsIProperties) .get("TmpD", Ci.nsIFile); saveDir.append("testsavedir"); - if (!saveDir.exists()) + if (!saveDir.exists()) { + info("create testsavedir!"); saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755); + } + info("return from createTempSaveDir: " + saveDir.path); return saveDir; } diff --git a/browser/components/customizableui/content/panelUI.js b/browser/components/customizableui/content/panelUI.js index 37011b54e24..bd4cba130cb 100644 --- a/browser/components/customizableui/content/panelUI.js +++ b/browser/components/customizableui/content/panelUI.js @@ -460,6 +460,7 @@ const PanelUI = { * @return the selected locale or "en-US" if none is selected */ function getLocale() { + const PREF_SELECTED_LOCALE = "general.useragent.locale"; try { let locale = Services.prefs.getComplexValue(PREF_SELECTED_LOCALE, Ci.nsIPrefLocalizedString); diff --git a/browser/components/customizableui/src/CustomizableUI.jsm b/browser/components/customizableui/src/CustomizableUI.jsm index c66761894fc..356b471fdb7 100644 --- a/browser/components/customizableui/src/CustomizableUI.jsm +++ b/browser/components/customizableui/src/CustomizableUI.jsm @@ -3681,7 +3681,11 @@ OverflowableToolbar.prototype = { if (!this._enabled) return; - this._moveItemsBackToTheirOrigin(); + if (this._target.scrollLeftMax > 0) { + this.onOverflow(); + } else { + this._moveItemsBackToTheirOrigin(); + } }, _disable: function() { diff --git a/browser/components/customizableui/test/browser.ini b/browser/components/customizableui/test/browser.ini index b51b8ba2e7e..f1b187a4b22 100644 --- a/browser/components/customizableui/test/browser.ini +++ b/browser/components/customizableui/test/browser.ini @@ -65,6 +65,7 @@ skip-if = os == "linux" [browser_948985_non_removable_defaultArea.js] [browser_952963_areaType_getter_no_area.js] [browser_956602_remove_special_widget.js] +[browser_963639_customizing_attribute_non_customizable_toolbar.js] [browser_968447_bookmarks_toolbar_items_in_panel.js] [browser_968565_insert_before_hidden_items.js] [browser_969427_recreate_destroyed_widget_after_reset.js] diff --git a/browser/components/customizableui/test/browser_963639_customizing_attribute_non_customizable_toolbar.js b/browser/components/customizableui/test/browser_963639_customizing_attribute_non_customizable_toolbar.js new file mode 100644 index 00000000000..2b105c39785 --- /dev/null +++ b/browser/components/customizableui/test/browser_963639_customizing_attribute_non_customizable_toolbar.js @@ -0,0 +1,34 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const kToolbar = "test-toolbar-963639-non-customizable-customizing-attribute"; + +add_task(function() { + info("Test for Bug 963639 - CustomizeMode _onToolbarVisibilityChange sets @customizing on non-customizable toolbars"); + + let toolbar = document.createElement("toolbar"); + toolbar.id = kToolbar; + gNavToolbox.appendChild(toolbar); + + let testToolbar = document.getElementById(kToolbar) + ok(testToolbar, "Toolbar was created."); + is(gNavToolbox.getElementsByAttribute("id", kToolbar).length, 1, + "Toolbar was added to the navigator toolbox"); + + toolbar.setAttribute("toolbarname", "NonCustomizableToolbarCustomizingAttribute"); + toolbar.setAttribute("collapsed", "true"); + + yield startCustomizing(); + window.setToolbarVisibility(toolbar, "true"); + isnot(toolbar.getAttribute("customizing"), "true", + "Toolbar doesn't have the customizing attribute"); + + yield endCustomizing(); + gNavToolbox.removeChild(toolbar); + + is(gNavToolbox.getElementsByAttribute("id", kToolbar).length, 0, + "Toolbar was removed from the navigator toolbox"); +}); diff --git a/browser/devtools/debugger/test/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js b/browser/devtools/debugger/test/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js index 48856a8f56a..d33d00cfc87 100644 --- a/browser/devtools/debugger/test/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js +++ b/browser/devtools/debugger/test/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js @@ -23,7 +23,7 @@ function test() { Task.spawn(function* () { try { - yield waitForSourceShown(gPanel, CODE_URL); + yield ensureSourceIs(gPanel, CODE_URL, true); // Pause and set our breakpoints. yield doInterrupt(); diff --git a/browser/devtools/netmonitor/test/browser_net_filter-03.js b/browser/devtools/netmonitor/test/browser_net_filter-03.js index 958389b8c7a..fc177271dc6 100644 --- a/browser/devtools/netmonitor/test/browser_net_filter-03.js +++ b/browser/devtools/netmonitor/test/browser_net_filter-03.js @@ -10,6 +10,9 @@ function test() { initNetMonitor(FILTERING_URL).then(([aTab, aDebuggee, aMonitor]) => { info("Starting test... "); + // It seems that this test may be slow on Ubuntu builds running on ec2. + requestLongerTimeout(2); + let { $, NetMonitorView } = aMonitor.panelWin; let { RequestsMenu } = NetMonitorView; diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 6d8ef3c6839..17762485515 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -310,6 +310,7 @@ @BINPATH@/components/spellchecker.xpt @BINPATH@/components/storage.xpt @BINPATH@/components/toolkit_finalizationwitness.xpt +@BINPATH@/components/toolkit_osfile.xpt @BINPATH@/components/toolkitprofile.xpt #ifdef MOZ_ENABLE_XREMOTE @BINPATH@/components/toolkitremote.xpt diff --git a/browser/metro/base/content/bindings/browser.xml b/browser/metro/base/content/bindings/browser.xml index 468e7b29477..e0f958b9fe7 100644 --- a/browser/metro/base/content/bindings/browser.xml +++ b/browser/metro/base/content/bindings/browser.xml @@ -68,10 +68,6 @@ null - - null diff --git a/browser/themes/linux/jar.mn b/browser/themes/linux/jar.mn index fa7ccf2f9a4..d70c77781bd 100644 --- a/browser/themes/linux/jar.mn +++ b/browser/themes/linux/jar.mn @@ -84,6 +84,10 @@ browser.jar: * skin/classic/browser/customizableui/panelUIOverlay.css (customizableui/panelUIOverlay.css) skin/classic/browser/customizableui/subView-arrow-back-inverted.png (../shared/customizableui/subView-arrow-back-inverted.png) skin/classic/browser/customizableui/subView-arrow-back-inverted-rtl.png (../shared/customizableui/subView-arrow-back-inverted-rtl.png) + skin/classic/browser/customizableui/whimsy.png (../shared/customizableui/whimsy.png) + skin/classic/browser/customizableui/whimsy@2x.png (../shared/customizableui/whimsy@2x.png) + skin/classic/browser/customizableui/whimsy-bw.png (../shared/customizableui/whimsy-bw.png) + skin/classic/browser/customizableui/whimsy-bw@2x.png (../shared/customizableui/whimsy-bw@2x.png) skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css) skin/classic/browser/downloads/buttons.png (downloads/buttons.png) skin/classic/browser/downloads/contentAreaDownloadsView.css (downloads/contentAreaDownloadsView.css) diff --git a/browser/themes/osx/jar.mn b/browser/themes/osx/jar.mn index 914d1e6428f..8eb614fd521 100644 --- a/browser/themes/osx/jar.mn +++ b/browser/themes/osx/jar.mn @@ -141,6 +141,10 @@ browser.jar: skin/classic/browser/customizableui/subView-arrow-back-inverted-rtl.png (../shared/customizableui/subView-arrow-back-inverted-rtl.png) skin/classic/browser/customizableui/subView-arrow-back-inverted-rtl@2x.png (../shared/customizableui/subView-arrow-back-inverted-rtl@2x.png) * skin/classic/browser/customizableui/panelUIOverlay.css (customizableui/panelUIOverlay.css) + skin/classic/browser/customizableui/whimsy.png (../shared/customizableui/whimsy.png) + skin/classic/browser/customizableui/whimsy@2x.png (../shared/customizableui/whimsy@2x.png) + skin/classic/browser/customizableui/whimsy-bw.png (../shared/customizableui/whimsy-bw.png) + skin/classic/browser/customizableui/whimsy-bw@2x.png (../shared/customizableui/whimsy-bw@2x.png) skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css) skin/classic/browser/downloads/buttons.png (downloads/buttons.png) skin/classic/browser/downloads/buttons@2x.png (downloads/buttons@2x.png) diff --git a/browser/themes/shared/customizableui/panelUIOverlay.inc.css b/browser/themes/shared/customizableui/panelUIOverlay.inc.css index d9b6a0f048d..19844aca6ec 100644 --- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css +++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css @@ -19,6 +19,44 @@ %include ../browser.inc +#PanelUI-popup #PanelUI-contents:empty { + height: 128px; +} + +#PanelUI-popup #PanelUI-contents:empty::before { + content: ""; + background-image: url(chrome://browser/skin/customizableui/whimsy-bw.png); + display: block; + width: 64px; + height: 64px; + position: absolute; + animation: moveX 3.05s linear 0s infinite alternate, + moveY 3.4s linear 0s infinite alternate; +} + +#PanelUI-popup #PanelUI-contents:empty:hover::before { + background-image: url(chrome://browser/skin/customizableui/whimsy.png); +} + +@media (min-resolution: 2dppx) { + #PanelUI-popup #PanelUI-contents:empty::before { + background-image: url(chrome://browser/skin/customizableui/whimsy-bw@2x.png); + background-size: 64px 64px; + } + #PanelUI-popup #PanelUI-contents:empty:hover::before { + background-image: url(chrome://browser/skin/customizableui/whimsy@2x.png); + } +} + +@keyframes moveX { + /* These values are adjusted for the padding on the panel. */ + from { margin-left: -15px; } to { margin-left: calc(100% - 49px); } +} +@keyframes moveY { + /* These values are adjusted for the padding and height of the panel. */ + from { margin-top: -6px; } to { margin-top: 58px; } +} + #PanelUI-button { background-image: linear-gradient(to bottom, hsla(0,0%,100%,0), hsla(0,0%,100%,.3) 30%, hsla(0,0%,100%,.3) 70%, hsla(0,0%,100%,0)), linear-gradient(to bottom, hsla(210,54%,20%,0), hsla(210,54%,20%,.3) 30%, hsla(210,54%,20%,.3) 70%, hsla(210,54%,20%,0)), diff --git a/browser/themes/shared/customizableui/whimsy-bw.png b/browser/themes/shared/customizableui/whimsy-bw.png new file mode 100644 index 00000000000..481d3fcd6e6 Binary files /dev/null and b/browser/themes/shared/customizableui/whimsy-bw.png differ diff --git a/browser/themes/shared/customizableui/whimsy-bw@2x.png b/browser/themes/shared/customizableui/whimsy-bw@2x.png new file mode 100644 index 00000000000..09516ab62ee Binary files /dev/null and b/browser/themes/shared/customizableui/whimsy-bw@2x.png differ diff --git a/browser/themes/shared/customizableui/whimsy.png b/browser/themes/shared/customizableui/whimsy.png new file mode 100644 index 00000000000..2e9fdd7ebca Binary files /dev/null and b/browser/themes/shared/customizableui/whimsy.png differ diff --git a/browser/themes/shared/customizableui/whimsy@2x.png b/browser/themes/shared/customizableui/whimsy@2x.png new file mode 100644 index 00000000000..61f55f60fff Binary files /dev/null and b/browser/themes/shared/customizableui/whimsy@2x.png differ diff --git a/browser/themes/windows/jar.mn b/browser/themes/windows/jar.mn index cc48553eb4d..91af4ad95a8 100644 --- a/browser/themes/windows/jar.mn +++ b/browser/themes/windows/jar.mn @@ -103,6 +103,10 @@ browser.jar: * skin/classic/browser/customizableui/panelUIOverlay.css (customizableui/panelUIOverlay.css) skin/classic/browser/customizableui/subView-arrow-back-inverted.png (../shared/customizableui/subView-arrow-back-inverted.png) skin/classic/browser/customizableui/subView-arrow-back-inverted-rtl.png (../shared/customizableui/subView-arrow-back-inverted-rtl.png) + skin/classic/browser/customizableui/whimsy.png (../shared/customizableui/whimsy.png) + skin/classic/browser/customizableui/whimsy@2x.png (../shared/customizableui/whimsy@2x.png) + skin/classic/browser/customizableui/whimsy-bw.png (../shared/customizableui/whimsy-bw.png) + skin/classic/browser/customizableui/whimsy-bw@2x.png (../shared/customizableui/whimsy-bw@2x.png) * skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css) skin/classic/browser/downloads/buttons.png (downloads/buttons.png) skin/classic/browser/downloads/contentAreaDownloadsView.css (downloads/contentAreaDownloadsView.css) @@ -431,6 +435,10 @@ browser.jar: * skin/classic/aero/browser/customizableui/panelUIOverlay.css (customizableui/panelUIOverlay-aero.css) skin/classic/aero/browser/customizableui/subView-arrow-back-inverted.png (../shared/customizableui/subView-arrow-back-inverted.png) skin/classic/aero/browser/customizableui/subView-arrow-back-inverted-rtl.png (../shared/customizableui/subView-arrow-back-inverted-rtl.png) + skin/classic/aero/browser/customizableui/whimsy.png (../shared/customizableui/whimsy.png) + skin/classic/aero/browser/customizableui/whimsy@2x.png (../shared/customizableui/whimsy@2x.png) + skin/classic/aero/browser/customizableui/whimsy-bw.png (../shared/customizableui/whimsy-bw.png) + skin/classic/aero/browser/customizableui/whimsy-bw@2x.png (../shared/customizableui/whimsy-bw@2x.png) * skin/classic/aero/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay-aero.css) skin/classic/aero/browser/downloads/buttons.png (downloads/buttons-aero.png) skin/classic/aero/browser/downloads/contentAreaDownloadsView.css (downloads/contentAreaDownloadsView.css) diff --git a/build/pgo/profileserver.py b/build/pgo/profileserver.py index 912684a6a27..a02e5b26bc0 100644 --- a/build/pgo/profileserver.py +++ b/build/pgo/profileserver.py @@ -17,6 +17,7 @@ import shutil import tempfile from datetime import datetime from mozbuild.base import MozbuildObject +from buildconfig import substs PORT = 8888 @@ -54,6 +55,14 @@ if __name__ == '__main__': env = os.environ.copy() env["MOZ_CRASHREPORTER_NO_REPORT"] = "1" env["XPCOM_DEBUG_BREAK"] = "warn" + + # For VC12, make sure we can find the right bitness of pgort120.dll + if "VS120COMNTOOLS" in env and not substs["HAVE_64BIT_OS"]: + vc12dir = os.path.abspath(os.path.join(env["VS120COMNTOOLS"], + "../../VC/bin")) + if os.path.exists(vc12dir): + env["PATH"] = vc12dir + ";" + env["PATH"] + jarlog = os.getenv("JARLOG_FILE") if jarlog: env["MOZ_JAR_LOG_FILE"] = os.path.abspath(jarlog) diff --git a/content/base/src/nsContentUtils.cpp b/content/base/src/nsContentUtils.cpp index d7e74f7bc77..a0afaf5633d 100644 --- a/content/base/src/nsContentUtils.cpp +++ b/content/base/src/nsContentUtils.cpp @@ -1516,8 +1516,6 @@ nsContentUtils::Shutdown() sModifierSeparator = nullptr; NS_IF_RELEASE(sSameOriginChecker); - - nsTextEditorState::ShutDown(); } /** diff --git a/content/canvas/src/WebGLContext.cpp b/content/canvas/src/WebGLContext.cpp index 5a43f2fa4c3..b416c67a884 100644 --- a/content/canvas/src/WebGLContext.cpp +++ b/content/canvas/src/WebGLContext.cpp @@ -287,6 +287,7 @@ WebGLContext::DestroyResourcesAndContext() if (!IsExtensionEnabled(extension) || (extension == WEBGL_lose_context)) continue; + mExtensions[extension]->MarkLost(); mExtensions[extension] = nullptr; } diff --git a/content/canvas/src/WebGLContextGL.cpp b/content/canvas/src/WebGLContextGL.cpp index a9cbfb564db..2c3a47f8eb4 100644 --- a/content/canvas/src/WebGLContextGL.cpp +++ b/content/canvas/src/WebGLContextGL.cpp @@ -720,6 +720,9 @@ WebGLContext::DeleteRenderbuffer(WebGLRenderbuffer *rbuf) if (mBoundFramebuffer) mBoundFramebuffer->DetachRenderbuffer(rbuf); + // Invalidate framebuffer status cache + rbuf->NotifyFBsStatusChanged(); + if (mBoundRenderbuffer == rbuf) BindRenderbuffer(LOCAL_GL_RENDERBUFFER, static_cast(nullptr)); @@ -742,6 +745,9 @@ WebGLContext::DeleteTexture(WebGLTexture *tex) if (mBoundFramebuffer) mBoundFramebuffer->DetachTexture(tex); + // Invalidate framebuffer status cache + tex->NotifyFBsStatusChanged(); + GLuint activeTexture = mActiveTexture; for (int32_t i = 0; i < mGLMaxTextureUnits; i++) { if ((tex->Target() == LOCAL_GL_TEXTURE_2D && mBound2DTextures[i] == tex) || @@ -2662,6 +2668,8 @@ WebGLContext::RenderbufferStorage(GLenum target, GLenum internalformat, GLsizei height != mBoundRenderbuffer->Height() || internalformat != mBoundRenderbuffer->InternalFormat(); if (sizeChanges) { + // Invalidate framebuffer status cache + mBoundRenderbuffer->NotifyFBsStatusChanged(); GetAndFlushUnderlyingGLErrors(); mBoundRenderbuffer->RenderbufferStorage(internalformatForGL, width, height); GLenum error = GetAndFlushUnderlyingGLErrors(); diff --git a/content/canvas/src/WebGLContextValidate.cpp b/content/canvas/src/WebGLContextValidate.cpp index ea0a7b1b93f..b1c24df0dc8 100644 --- a/content/canvas/src/WebGLContextValidate.cpp +++ b/content/canvas/src/WebGLContextValidate.cpp @@ -1173,6 +1173,7 @@ WebGLContext::ValidateTexImageFormatAndType(GLenum format, GLenum type, WebGLTex case LOCAL_GL_LUMINANCE_ALPHA: validCombo = (type == LOCAL_GL_UNSIGNED_BYTE || type == LOCAL_GL_HALF_FLOAT || + type == LOCAL_GL_HALF_FLOAT_OES || type == LOCAL_GL_FLOAT); break; @@ -1181,6 +1182,7 @@ WebGLContext::ValidateTexImageFormatAndType(GLenum format, GLenum type, WebGLTex validCombo = (type == LOCAL_GL_UNSIGNED_BYTE || type == LOCAL_GL_UNSIGNED_SHORT_5_6_5 || type == LOCAL_GL_HALF_FLOAT || + type == LOCAL_GL_HALF_FLOAT_OES || type == LOCAL_GL_FLOAT); break; @@ -1190,6 +1192,7 @@ WebGLContext::ValidateTexImageFormatAndType(GLenum format, GLenum type, WebGLTex type == LOCAL_GL_UNSIGNED_SHORT_4_4_4_4 || type == LOCAL_GL_UNSIGNED_SHORT_5_5_5_1 || type == LOCAL_GL_HALF_FLOAT || + type == LOCAL_GL_HALF_FLOAT_OES || type == LOCAL_GL_FLOAT); break; @@ -1252,8 +1255,11 @@ WebGLContext::ValidateTexInputData(GLenum type, int jsArrayType, WebGLTexImageFu break; // TODO: WebGL spec doesn't allow half floats to specified as UInt16. - // case LOCAL_GL_HALF_FLOAT: - // case LOCAL_GL_HALF_FLOAT_OES: + case LOCAL_GL_HALF_FLOAT: + case LOCAL_GL_HALF_FLOAT_OES: + validInput = (jsArrayType == -1); + break; + case LOCAL_GL_UNSIGNED_SHORT: case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4: case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1: diff --git a/content/canvas/src/WebGLExtensionBase.cpp b/content/canvas/src/WebGLExtensionBase.cpp index 2b925767dc7..cc1e9f4d9a7 100644 --- a/content/canvas/src/WebGLExtensionBase.cpp +++ b/content/canvas/src/WebGLExtensionBase.cpp @@ -10,6 +10,7 @@ using namespace mozilla; WebGLExtensionBase::WebGLExtensionBase(WebGLContext* context) : WebGLContextBoundObject(context) + , mIsLost(false) { SetIsDOMBinding(); } @@ -18,6 +19,12 @@ WebGLExtensionBase::~WebGLExtensionBase() { } +void +WebGLExtensionBase::MarkLost() +{ + mIsLost = true; +} + NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLExtensionBase) NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLExtensionBase, AddRef) diff --git a/content/canvas/src/WebGLExtensionDebugShaders.cpp b/content/canvas/src/WebGLExtensionDebugShaders.cpp index eb12d34fdb7..e1a4e742a53 100644 --- a/content/canvas/src/WebGLExtensionDebugShaders.cpp +++ b/content/canvas/src/WebGLExtensionDebugShaders.cpp @@ -26,6 +26,11 @@ void WebGLExtensionDebugShaders::GetTranslatedShaderSource(WebGLShader* shader, nsAString& retval) { + if (mIsLost) { + return mContext->ErrorInvalidOperation("getTranslatedShaderSource: " + "Extension is lost."); + } + mContext->GetShaderTranslatedSource(shader, retval); if (retval.IsVoid()) { diff --git a/content/canvas/src/WebGLExtensionDrawBuffers.cpp b/content/canvas/src/WebGLExtensionDrawBuffers.cpp index 941ccec3f4e..ae3fb052901 100644 --- a/content/canvas/src/WebGLExtensionDrawBuffers.cpp +++ b/content/canvas/src/WebGLExtensionDrawBuffers.cpp @@ -1,3 +1,4 @@ +/* -*- 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/. */ @@ -49,6 +50,9 @@ WebGLExtensionDrawBuffers::~WebGLExtensionDrawBuffers() void WebGLExtensionDrawBuffers::DrawBuffersWEBGL(const dom::Sequence& buffers) { + if (mIsLost) + return mContext->ErrorInvalidOperation("drawBuffersWEBGL: Extension is lost."); + mContext->DrawBuffers(buffers); } diff --git a/content/canvas/src/WebGLExtensionInstancedArrays.cpp b/content/canvas/src/WebGLExtensionInstancedArrays.cpp index 2c4ec8e4ba0..3a07abe2be1 100644 --- a/content/canvas/src/WebGLExtensionInstancedArrays.cpp +++ b/content/canvas/src/WebGLExtensionInstancedArrays.cpp @@ -25,6 +25,9 @@ void WebGLExtensionInstancedArrays::DrawArraysInstancedANGLE(GLenum mode, GLint first, GLsizei count, GLsizei primcount) { + if (mIsLost) + return mContext->ErrorInvalidOperation("drawArraysInstancedANGLE: Extension is lost."); + mContext->DrawArraysInstanced(mode, first, count, primcount); } @@ -33,12 +36,18 @@ WebGLExtensionInstancedArrays::DrawElementsInstancedANGLE(GLenum mode, GLsizei c GLenum type, WebGLintptr offset, GLsizei primcount) { + if (mIsLost) + return mContext->ErrorInvalidOperation("drawElementsInstancedANGLE: Extension is lost."); + mContext->DrawElementsInstanced(mode, count, type, offset, primcount); } void WebGLExtensionInstancedArrays::VertexAttribDivisorANGLE(GLuint index, GLuint divisor) { + if (mIsLost) + return mContext->ErrorInvalidOperation("vertexAttribDivisorANGLE: Extension is lost."); + mContext->VertexAttribDivisor(index, divisor); } diff --git a/content/canvas/src/WebGLExtensionVertexArray.cpp b/content/canvas/src/WebGLExtensionVertexArray.cpp index 59c72b6b2f7..3908fb5b269 100644 --- a/content/canvas/src/WebGLExtensionVertexArray.cpp +++ b/content/canvas/src/WebGLExtensionVertexArray.cpp @@ -25,21 +25,37 @@ WebGLExtensionVertexArray::~WebGLExtensionVertexArray() already_AddRefed WebGLExtensionVertexArray::CreateVertexArrayOES() { + if (mIsLost) { + mContext->ErrorInvalidOperation("createVertexArrayOES: Extension is lost. Returning NULL."); + return nullptr; + } + return mContext->CreateVertexArray(); } void WebGLExtensionVertexArray::DeleteVertexArrayOES(WebGLVertexArray* array) { + if (mIsLost) + return mContext->ErrorInvalidOperation("deleteVertexArrayOES: Extension is lost."); + mContext->DeleteVertexArray(array); } bool WebGLExtensionVertexArray::IsVertexArrayOES(WebGLVertexArray* array) { + if (mIsLost) { + mContext->ErrorInvalidOperation("isVertexArrayOES: Extension is lost. Returning false."); + return false; + } + return mContext->IsVertexArray(array); } void WebGLExtensionVertexArray::BindVertexArrayOES(WebGLVertexArray* array) { + if (mIsLost) + return mContext->ErrorInvalidOperation("bindVertexArrayOES: Extension is lost."); + mContext->BindVertexArray(array); } diff --git a/content/canvas/src/WebGLExtensions.h b/content/canvas/src/WebGLExtensions.h index cf85530e9d1..5b13f9ca460 100644 --- a/content/canvas/src/WebGLExtensions.h +++ b/content/canvas/src/WebGLExtensions.h @@ -30,8 +30,13 @@ public: return Context(); } + void MarkLost(); + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLExtensionBase) NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLExtensionBase) + +protected: + bool mIsLost; }; #define DECL_WEBGL_EXTENSION_GOOP \ diff --git a/content/canvas/src/WebGLFramebuffer.cpp b/content/canvas/src/WebGLFramebuffer.cpp index 947b736c768..a9e55f238e6 100644 --- a/content/canvas/src/WebGLFramebuffer.cpp +++ b/content/canvas/src/WebGLFramebuffer.cpp @@ -25,6 +25,7 @@ WebGLFramebuffer::WrapObject(JSContext* cx, JS::Handle scope) WebGLFramebuffer::WebGLFramebuffer(WebGLContext* context) : WebGLContextBoundObject(context) + , mStatus(0) , mHasEverBeenBound(false) , mDepthAttachment(LOCAL_GL_DEPTH_ATTACHMENT) , mStencilAttachment(LOCAL_GL_STENCIL_ATTACHMENT) @@ -391,6 +392,7 @@ WebGLFramebuffer::FramebufferRenderbuffer(GLenum target, WebGLRenderbuffer* wrb) { MOZ_ASSERT(mContext->mBoundFramebuffer == this); + if (!mContext->ValidateObjectAllowNull("framebufferRenderbuffer: renderbuffer", wrb)) return; @@ -400,27 +402,32 @@ WebGLFramebuffer::FramebufferRenderbuffer(GLenum target, if (rbtarget != LOCAL_GL_RENDERBUFFER) return mContext->ErrorInvalidEnumInfo("framebufferRenderbuffer: renderbuffer target:", rbtarget); - switch (attachment) { - case LOCAL_GL_DEPTH_ATTACHMENT: - mDepthAttachment.SetRenderbuffer(wrb); - break; - case LOCAL_GL_STENCIL_ATTACHMENT: - mStencilAttachment.SetRenderbuffer(wrb); - break; - case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT: - mDepthStencilAttachment.SetRenderbuffer(wrb); - break; - default: - // finish checking that the 'attachment' parameter is among the allowed values - if (!CheckColorAttachmentNumber(attachment, "framebufferRenderbuffer")){ - return; - } + /* Get the requested attachment. If result is NULL, attachment is + * invalid and an error is generated. + * + * Don't use GetAttachment(...) here because it opt builds it + * returns mColorAttachment[0] for invalid attachment, which we + * really don't want to mess with. + */ + Attachment* a = GetAttachmentOrNull(attachment); + if (!a) + return; // Error generated internally to GetAttachmentOrNull. - size_t colorAttachmentId = size_t(attachment - LOCAL_GL_COLOR_ATTACHMENT0); - EnsureColorAttachments(colorAttachmentId); - mColorAttachments[colorAttachmentId].SetRenderbuffer(wrb); - break; - } + /* Invalidate cached framebuffer status and inform texture of it's + * new attachment + */ + mStatus = 0; + // Detach current + if (a->Texture()) + a->Texture()->DetachFrom(this, attachment); + else if (a->Renderbuffer()) + a->Renderbuffer()->DetachFrom(this, attachment); + + // Attach new + if (wrb) + wrb->AttachTo(this, attachment); + + a->SetRenderbuffer(wrb); } void @@ -431,11 +438,9 @@ WebGLFramebuffer::FramebufferTexture2D(GLenum target, GLint level) { MOZ_ASSERT(mContext->mBoundFramebuffer == this); - if (!mContext->ValidateObjectAllowNull("framebufferTexture2D: texture", - wtex)) - { + + if (!mContext->ValidateObjectAllowNull("framebufferTexture2D: texture", wtex)) return; - } if (target != LOCAL_GL_FRAMEBUFFER) return mContext->ErrorInvalidEnumInfo("framebufferTexture2D: target", target); @@ -458,25 +463,53 @@ WebGLFramebuffer::FramebufferTexture2D(GLenum target, if (level != 0) return mContext->ErrorInvalidValue("framebufferTexture2D: level must be 0"); - switch (attachment) { - case LOCAL_GL_DEPTH_ATTACHMENT: - mDepthAttachment.SetTexImage(wtex, textarget, level); - break; - case LOCAL_GL_STENCIL_ATTACHMENT: - mStencilAttachment.SetTexImage(wtex, textarget, level); - break; - case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT: - mDepthStencilAttachment.SetTexImage(wtex, textarget, level); - break; - default: - if (!CheckColorAttachmentNumber(attachment, "framebufferTexture2D")) - return; + /* Get the requested attachment. If result is NULL, attachment is + * invalid and an error is generated. + * + * Don't use GetAttachment(...) here because it opt builds it + * returns mColorAttachment[0] for invalid attachment, which we + * really don't want to mess with. + */ + Attachment* a = GetAttachmentOrNull(attachment); + if (!a) + return; // Error generated internally to GetAttachmentOrNull. - size_t colorAttachmentId = size_t(attachment - LOCAL_GL_COLOR_ATTACHMENT0); - EnsureColorAttachments(colorAttachmentId); - mColorAttachments[colorAttachmentId].SetTexImage(wtex, textarget, level); - break; - } + /* Invalidate cached framebuffer status and inform texture of it's + * new attachment + */ + mStatus = 0; + // Detach current + if (a->Texture()) + a->Texture()->DetachFrom(this, attachment); + else if (a->Renderbuffer()) + a->Renderbuffer()->DetachFrom(this, attachment); + + // Attach new + if (wtex) + wtex->AttachTo(this, attachment); + + a->SetTexImage(wtex, textarget, level); +} + +WebGLFramebuffer::Attachment* +WebGLFramebuffer::GetAttachmentOrNull(GLenum attachment) +{ + if (attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) + return &mDepthStencilAttachment; + + if (attachment == LOCAL_GL_DEPTH_ATTACHMENT) + return &mDepthAttachment; + + if (attachment == LOCAL_GL_STENCIL_ATTACHMENT) + return &mStencilAttachment; + + if (!CheckColorAttachmentNumber(attachment, "getAttachmentOrNull")) + return nullptr; + + size_t colorAttachmentId = attachment - LOCAL_GL_COLOR_ATTACHMENT0; + EnsureColorAttachments(colorAttachmentId); + + return &mColorAttachments[colorAttachmentId]; } const WebGLFramebuffer::Attachment& @@ -676,9 +709,12 @@ WebGLFramebuffer::PrecheckFramebufferStatus() const GLenum WebGLFramebuffer::CheckFramebufferStatus() const { - GLenum precheckStatus = PrecheckFramebufferStatus(); - if (precheckStatus != LOCAL_GL_FRAMEBUFFER_COMPLETE) - return precheckStatus; + if (mStatus != 0) + return mStatus; + + mStatus = PrecheckFramebufferStatus(); + if (mStatus != LOCAL_GL_FRAMEBUFFER_COMPLETE) + return mStatus; // Looks good on our end. Let's ask the driver. mContext->MakeContextCurrent(); @@ -686,7 +722,8 @@ WebGLFramebuffer::CheckFramebufferStatus() const // Ok, attach our chosen flavor of {DEPTH, STENCIL, DEPTH_STENCIL}. FinalizeAttachments(); - return mContext->gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); + mStatus = mContext->gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); + return mStatus; } bool @@ -831,6 +868,13 @@ void WebGLFramebuffer::EnsureColorAttachments(size_t colorAttachmentId) } } +void +WebGLFramebuffer::NotifyAttachableChanged() const +{ + // Attachment has changed, so invalidate cached status + mStatus = 0; +} + static void FinalizeDrawAndReadBuffers(GLContext* aGL, bool aColorBufferDefined) { diff --git a/content/canvas/src/WebGLFramebuffer.h b/content/canvas/src/WebGLFramebuffer.h index 351e796f5cd..f8cdb96ffd3 100644 --- a/content/canvas/src/WebGLFramebuffer.h +++ b/content/canvas/src/WebGLFramebuffer.h @@ -14,6 +14,7 @@ namespace mozilla { +class WebGLFramebufferAttachable; class WebGLTexture; class WebGLRenderbuffer; namespace gl { @@ -114,6 +115,7 @@ public: private: const WebGLRectangleObject& GetAnyRectObject() const; + Attachment* GetAttachmentOrNull(GLenum attachment); public: bool HasDefinedAttachments() const; @@ -174,11 +176,17 @@ public: bool CheckColorAttachmentNumber(GLenum attachment, const char* functionName) const; + void EnsureColorAttachments(size_t colorAttachmentId); + + Attachment* AttachmentFor(GLenum attachment); + void NotifyAttachableChanged() const; + +private: + mutable GLenum mStatus; + GLuint mGLName; bool mHasEverBeenBound; - void EnsureColorAttachments(size_t colorAttachmentId); - // we only store pointers to attached renderbuffers, not to attached textures, because // we will only need to initialize renderbuffers. Textures are already initialized. nsTArray mColorAttachments; diff --git a/content/canvas/src/WebGLFramebufferAttachable.cpp b/content/canvas/src/WebGLFramebufferAttachable.cpp new file mode 100644 index 00000000000..4c8f3dadc58 --- /dev/null +++ b/content/canvas/src/WebGLFramebufferAttachable.cpp @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 20; 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 "WebGLContext.h" +#include "WebGLFramebufferAttachable.h" +#include "WebGLFramebuffer.h" +#include "WebGLRenderbuffer.h" +#include "WebGLTexture.h" + +using namespace mozilla; + +WebGLFramebufferAttachable::AttachmentPoint* +WebGLFramebufferAttachable::Contains(const WebGLFramebuffer* fb, GLenum attachment) +{ + AttachmentPoint* first = mAttachmentPoints.begin(); + AttachmentPoint* last = mAttachmentPoints.end(); + + for (; first != last; ++first) { + if (first->mFB == fb && first->mAttachment == attachment) + return first; + } + + return nullptr; +} + +void +WebGLFramebufferAttachable::AttachTo(WebGLFramebuffer* fb, GLenum attachment) +{ + MOZ_ASSERT(fb); + if (!fb) + return; + + if (Contains(fb, attachment)) + return; // Already attached. Ignore. + + mAttachmentPoints.append(AttachmentPoint(fb, attachment)); +} + +void +WebGLFramebufferAttachable::DetachFrom(WebGLFramebuffer* fb, GLenum attachment) +{ + MOZ_ASSERT(fb); + if (!fb) + return; + + AttachmentPoint* point = Contains(fb, attachment); + if (!point) { + MOZ_ASSERT(false, "Is not attached to FB"); + return; + } + + mAttachmentPoints.erase(point); +} + +void +WebGLFramebufferAttachable::NotifyFBsStatusChanged() +{ + AttachmentPoint* first = mAttachmentPoints.begin(); + AttachmentPoint* last = mAttachmentPoints.end(); + for ( ; first != last; ++first) + first->mFB->NotifyAttachableChanged(); +} diff --git a/content/canvas/src/WebGLFramebufferAttachable.h b/content/canvas/src/WebGLFramebufferAttachable.h new file mode 100644 index 00000000000..704354beaa0 --- /dev/null +++ b/content/canvas/src/WebGLFramebufferAttachable.h @@ -0,0 +1,43 @@ +/* -*- 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 WEBGLFRAMEBUFFERATTACHABLE_H_ +#define WEBGLFRAMEBUFFERATTACHABLE_H_ + +#include "GLDefs.h" +#include "mozilla/Vector.h" + +namespace mozilla { + +class WebGLFramebuffer; + +class WebGLFramebufferAttachable +{ + struct AttachmentPoint + { + AttachmentPoint(const WebGLFramebuffer* fb, GLenum attachment) + : mFB(fb) + , mAttachment(attachment) + {} + + const WebGLFramebuffer* mFB; + GLenum mAttachment; + }; + + Vector mAttachmentPoints; + + AttachmentPoint* Contains(const WebGLFramebuffer* fb, GLenum attachment); + +public: + + // Track FBO/Attachment combinations + void AttachTo(WebGLFramebuffer* fb, GLenum attachment); + void DetachFrom(WebGLFramebuffer* fb, GLenum attachment); + void NotifyFBsStatusChanged(); +}; + +} // namespace mozilla + +#endif // !WEBGLFRAMEBUFFERATTACHABLE_H_ diff --git a/content/canvas/src/WebGLObjectModel.h b/content/canvas/src/WebGLObjectModel.h index c1bfde2d403..51e9b6ca47a 100644 --- a/content/canvas/src/WebGLObjectModel.h +++ b/content/canvas/src/WebGLObjectModel.h @@ -305,7 +305,7 @@ public: } bool HasSameDimensionsAs(const WebGLRectangleObject& other) const { - return Width() == other.Width() && Height() == other.Height(); + return Width() == other.Width() && Height() == other.Height(); } protected: diff --git a/content/canvas/src/WebGLRenderbuffer.h b/content/canvas/src/WebGLRenderbuffer.h index 4473253b7b3..0cf560d3f2b 100644 --- a/content/canvas/src/WebGLRenderbuffer.h +++ b/content/canvas/src/WebGLRenderbuffer.h @@ -7,6 +7,7 @@ #define WEBGLRENDERBUFFER_H_ #include "WebGLObjectModel.h" +#include "WebGLFramebufferAttachable.h" #include "nsWrapperCache.h" @@ -20,6 +21,7 @@ class WebGLRenderbuffer MOZ_FINAL , public LinkedListElement , public WebGLRectangleObject , public WebGLContextBoundObject + , public WebGLFramebufferAttachable { public: WebGLRenderbuffer(WebGLContext *context); diff --git a/content/canvas/src/WebGLTexture.cpp b/content/canvas/src/WebGLTexture.cpp index 2211335e952..1974fc48352 100644 --- a/content/canvas/src/WebGLTexture.cpp +++ b/content/canvas/src/WebGLTexture.cpp @@ -147,6 +147,9 @@ WebGLTexture::SetImageInfo(GLenum aTarget, GLint aLevel, if (aLevel > 0) SetCustomMipmap(); + // Invalidate framebuffer status cache + NotifyFBsStatusChanged(); + SetFakeBlackStatus(WebGLTextureFakeBlackStatus::Unknown); } diff --git a/content/canvas/src/WebGLTexture.h b/content/canvas/src/WebGLTexture.h index 7db852764f4..681c5333c26 100644 --- a/content/canvas/src/WebGLTexture.h +++ b/content/canvas/src/WebGLTexture.h @@ -7,6 +7,7 @@ #define WEBGLTEXTURE_H_ #include "WebGLObjectModel.h" +#include "WebGLFramebufferAttachable.h" #include "nsWrapperCache.h" @@ -37,6 +38,7 @@ class WebGLTexture MOZ_FINAL , public WebGLRefCountedObject , public LinkedListElement , public WebGLContextBoundObject + , public WebGLFramebufferAttachable { public: WebGLTexture(WebGLContext *context); diff --git a/content/canvas/src/moz.build b/content/canvas/src/moz.build index 5c4f95ffaf8..b5b5489b14c 100644 --- a/content/canvas/src/moz.build +++ b/content/canvas/src/moz.build @@ -67,6 +67,7 @@ if CONFIG['MOZ_WEBGL']: 'WebGLExtensionTextureHalfFloatLinear.cpp', 'WebGLExtensionVertexArray.cpp', 'WebGLFramebuffer.cpp', + 'WebGLFramebufferAttachable.cpp', 'WebGLObjectModel.cpp', 'WebGLProgram.cpp', 'WebGLQuery.cpp', @@ -104,4 +105,3 @@ LOCAL_INCLUDES += [ '/layout/style', '/layout/xul', ] - diff --git a/content/canvas/test/reftest/reftest.list b/content/canvas/test/reftest/reftest.list index ff2533bb7b3..671a7d76b78 100644 --- a/content/canvas/test/reftest/reftest.list +++ b/content/canvas/test/reftest/reftest.list @@ -148,12 +148,15 @@ random-if(Android&&AndroidVersion<15) == webgl-color-alpha-test.html?colorVal=1 fuzzy(1,65536) fuzzy-if(B2G,256,83) fuzzy-if(Android||B2G,9,65536) random-if(Android&&AndroidVersion<15) == webgl-color-alpha-test.html?colorVal=0.5&alphaVal=1.0 wrapper.html?half-colors.png # Test premult: -fuzzy(1,65536) fails-if(B2G) fuzzy-if(Android,9,65536) random-if(Android&&AndroidVersion<15) == webgl-color-alpha-test.html?colorVal=1.0&alphaVal=0.5&alpha wrapper.html?colors-half-alpha.png +# random-if(B2G) from bug 983650 +fuzzy(1,65536) random-if(B2G) fuzzy-if(Android,9,65536) random-if(Android&&AndroidVersion<15) == webgl-color-alpha-test.html?colorVal=1.0&alphaVal=0.5&alpha wrapper.html?colors-half-alpha.png fuzzy(1,65536) fails-if(B2G) fuzzy-if(Android,9,65536) fails-if(cocoaWidget||Android) random-if(Android&&AndroidVersion<15) == webgl-color-alpha-test.html?colorVal=0.5&alphaVal=0.5&alpha wrapper.html?half-colors-half-alpha.png -fuzzy(1,65536) fails-if(B2G) fuzzy-if(Android,9,65536) random-if(Android&&AndroidVersion<15) == webgl-color-alpha-test.html?colorVal=0.5&alphaVal=0.5&alpha&premult wrapper.html?colors-half-alpha.png +# random-if(B2G) from bug 983650 +fuzzy(1,65536) random-if(B2G) fuzzy-if(Android,9,65536) random-if(Android&&AndroidVersion<15) == webgl-color-alpha-test.html?colorVal=0.5&alphaVal=0.5&alpha&premult wrapper.html?colors-half-alpha.png # Test over-bright premult: -fuzzy(1,65536) fails-if(B2G) fuzzy-if(Android,9,65536) random-if(Android&&AndroidVersion<15) == webgl-color-alpha-test.html?colorVal=1.0&alphaVal=0.5&alpha&premult wrapper.html?colors-half-alpha.png +# random-if(B2G) from bug 983650 +fuzzy(1,65536) random-if(B2G) fuzzy-if(Android,9,65536) random-if(Android&&AndroidVersion<15) == webgl-color-alpha-test.html?colorVal=1.0&alphaVal=0.5&alpha&premult wrapper.html?colors-half-alpha.png # Check for hanging framebuffer bindings: diff --git a/content/html/content/src/nsTextEditorState.cpp b/content/html/content/src/nsTextEditorState.cpp index daf92d49da0..267a3a3961c 100644 --- a/content/html/content/src/nsTextEditorState.cpp +++ b/content/html/content/src/nsTextEditorState.cpp @@ -28,7 +28,7 @@ #include "nsGenericHTMLElement.h" #include "nsIDOMEventListener.h" #include "nsIEditorObserver.h" -#include "nsINativeKeyBindings.h" +#include "nsIWidget.h" #include "nsIDocumentEncoder.h" #include "nsISelectionPrivate.h" #include "nsPIDOMWindow.h" @@ -49,9 +49,6 @@ using namespace mozilla::dom; static NS_DEFINE_CID(kTextEditorCID, NS_TEXTEDITOR_CID); -static nsINativeKeyBindings *sNativeInputBindings = nullptr; -static nsINativeKeyBindings *sNativeTextAreaBindings = nullptr; - class MOZ_STACK_CLASS ValueSetter { public: @@ -679,8 +676,6 @@ protected: nsresult UpdateTextInputCommands(const nsAString& commandsToUpdate); - NS_HIDDEN_(nsINativeKeyBindings*) GetKeyBindings(); - protected: nsIFrame* mFrame; @@ -798,7 +793,7 @@ nsTextInputListener::NotifySelectionChanged(nsIDOMDocument* aDoc, nsISelection* // END nsIDOMSelectionListener static void -DoCommandCallback(const char *aCommand, void *aData) +DoCommandCallback(Command aCommand, void* aData) { nsTextControlFrame *frame = static_cast(aData); nsIContent *content = frame->GetContent(); @@ -821,17 +816,19 @@ DoCommandCallback(const char *aCommand, void *aData) return; } + const char* commandStr = WidgetKeyboardEvent::GetCommandStr(aCommand); + nsCOMPtr controller; - controllers->GetControllerForCommand(aCommand, getter_AddRefs(controller)); + controllers->GetControllerForCommand(commandStr, getter_AddRefs(controller)); if (!controller) { return; } bool commandEnabled; - nsresult rv = controller->IsCommandEnabled(aCommand, &commandEnabled); + nsresult rv = controller->IsCommandEnabled(commandStr, &commandEnabled); NS_ENSURE_SUCCESS_VOID(rv); if (commandEnabled) { - controller->DoCommand(aCommand); + controller->DoCommand(commandStr); } } @@ -858,27 +855,25 @@ nsTextInputListener::HandleEvent(nsIDOMEvent* aEvent) return NS_ERROR_UNEXPECTED; } - nsINativeKeyBindings *bindings = GetKeyBindings(); - if (bindings) { - bool handled = false; - switch (keyEvent->message) { - case NS_KEY_DOWN: - handled = bindings->KeyDown(*keyEvent, DoCommandCallback, mFrame); - break; - case NS_KEY_UP: - handled = bindings->KeyUp(*keyEvent, DoCommandCallback, mFrame); - break; - case NS_KEY_PRESS: - handled = bindings->KeyPress(*keyEvent, DoCommandCallback, mFrame); - break; - default: - MOZ_CRASH("Unknown key message"); - } - if (handled) { - aEvent->PreventDefault(); - } + if (keyEvent->message != NS_KEY_PRESS) { + return NS_OK; } + nsIWidget::NativeKeyBindingsType nativeKeyBindingsType = + mTxtCtrlElement->IsTextArea() ? + nsIWidget::NativeKeyBindingsForMultiLineEditor : + nsIWidget::NativeKeyBindingsForSingleLineEditor; + nsIWidget* widget = keyEvent->widget; + // If the event is created by chrome script, the widget is nullptr. + if (!widget) { + widget = mFrame->GetNearestWidget(); + NS_ENSURE_TRUE(widget, NS_OK); + } + + if (widget->ExecuteNativeKeyBinding(nativeKeyBindingsType, + *keyEvent, DoCommandCallback, mFrame)) { + aEvent->PreventDefault(); + } return NS_OK; } @@ -947,37 +942,6 @@ nsTextInputListener::UpdateTextInputCommands(const nsAString& commandsToUpdate) return domWindow->UpdateCommands(commandsToUpdate); } -nsINativeKeyBindings* -nsTextInputListener::GetKeyBindings() -{ - if (mTxtCtrlElement->IsTextArea()) { - static bool sNoTextAreaBindings = false; - - if (!sNativeTextAreaBindings && !sNoTextAreaBindings) { - CallGetService(NS_NATIVEKEYBINDINGS_CONTRACTID_PREFIX "textarea", - &sNativeTextAreaBindings); - - if (!sNativeTextAreaBindings) { - sNoTextAreaBindings = true; - } - } - - return sNativeTextAreaBindings; - } - - static bool sNoInputBindings = false; - if (!sNativeInputBindings && !sNoInputBindings) { - CallGetService(NS_NATIVEKEYBINDINGS_CONTRACTID_PREFIX "input", - &sNativeInputBindings); - - if (!sNativeInputBindings) { - sNoInputBindings = true; - } - } - - return sNativeInputBindings; -} - // END nsTextInputListener // nsTextEditorState @@ -1982,13 +1946,6 @@ nsTextEditorState::InitializeKeyboardEventListeners() mSelCon->SetScrollableFrame(do_QueryFrame(mBoundFrame->GetFirstPrincipalChild())); } -/* static */ void -nsTextEditorState::ShutDown() -{ - NS_IF_RELEASE(sNativeTextAreaBindings); - NS_IF_RELEASE(sNativeInputBindings); -} - void nsTextEditorState::ValueWasChanged(bool aNotify) { diff --git a/content/html/content/src/nsTextEditorState.h b/content/html/content/src/nsTextEditorState.h index b9b7d8ff841..9a2fcc58080 100644 --- a/content/html/content/src/nsTextEditorState.h +++ b/content/html/content/src/nsTextEditorState.h @@ -188,9 +188,6 @@ public: */ bool GetMaxLength(int32_t* aMaxLength); - /* called to free up native keybinding services */ - static NS_HIDDEN_(void) ShutDown(); - void ClearValueCache() { mCachedValue.Truncate(); } void HideSelectionIfBlurred(); diff --git a/dom/bindings/BindingDeclarations.h b/dom/bindings/BindingDeclarations.h index 91ed3ba97e6..e2fa580d045 100644 --- a/dom/bindings/BindingDeclarations.h +++ b/dom/bindings/BindingDeclarations.h @@ -47,6 +47,12 @@ protected: struct AllTypedArraysBase { }; +// Struct that serves as a base class for all owning unions. +// Particularly useful so we can use IsBaseOf to detect owning union +// template arguments. +struct AllOwningUnionBase { +}; + struct EnumEntry { const char* value; diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h index 24a4d0488e5..ade25f78cb1 100644 --- a/dom/bindings/BindingUtils.h +++ b/dom/bindings/BindingUtils.h @@ -1657,7 +1657,8 @@ public: // sequence types. template::value, - bool isTypedArray=IsBaseOf::value> + bool isTypedArray=IsBaseOf::value, + bool isOwningUnion=IsBaseOf::value> class SequenceTracer { explicit SequenceTracer() MOZ_DELETE; // Should never be instantiated @@ -1665,7 +1666,7 @@ class SequenceTracer // sequence or sequence template<> -class SequenceTracer +class SequenceTracer { explicit SequenceTracer() MOZ_DELETE; // Should never be instantiated @@ -1679,7 +1680,7 @@ public: // sequence template<> -class SequenceTracer +class SequenceTracer { explicit SequenceTracer() MOZ_DELETE; // Should never be instantiated @@ -1693,7 +1694,7 @@ public: // sequence> template -class SequenceTracer, false, false> +class SequenceTracer, false, false, false> { explicit SequenceTracer() MOZ_DELETE; // Should never be instantiated @@ -1707,7 +1708,7 @@ public: // sequence> as return value template -class SequenceTracer, false, false> +class SequenceTracer, false, false, false> { explicit SequenceTracer() MOZ_DELETE; // Should never be instantiated @@ -1721,7 +1722,7 @@ public: // sequence template -class SequenceTracer +class SequenceTracer { explicit SequenceTracer() MOZ_DELETE; // Should never be instantiated @@ -1735,7 +1736,7 @@ public: // sequence template -class SequenceTracer +class SequenceTracer { explicit SequenceTracer() MOZ_DELETE; // Should never be instantiated @@ -1747,9 +1748,23 @@ public: } }; +// sequence +template +class SequenceTracer +{ + explicit SequenceTracer() MOZ_DELETE; // Should never be instantiated + +public: + static void TraceSequence(JSTracer* trc, T* arrayp, T* end) { + for (; arrayp != end; ++arrayp) { + arrayp->TraceUnion(trc); + } + } +}; + // sequence with T? being a Nullable template -class SequenceTracer, false, false> +class SequenceTracer, false, false, false> { explicit SequenceTracer() MOZ_DELETE; // Should never be instantiated diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index ab0963ce6fb..fb6f4ad135c 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -912,8 +912,14 @@ def UnionTypes(descriptors, dictionaries, callbacks, config): typeDesc = p.getDescriptor(f.inner.identifier.name) except NoSuchDescriptorError: continue - declarations.add((typeDesc.nativeType, False)) - implheaders.add(typeDesc.headerFile) + if typeDesc.interface.isCallback(): + # Callback interfaces always use strong refs, so + # we need to include the right header to be able + # to Release() in our inlined code. + headers.add(typeDesc.headerFile) + else: + declarations.add((typeDesc.nativeType, False)) + implheaders.add(typeDesc.headerFile) elif f.isDictionary(): # For a dictionary, we need to see its declaration in # UnionTypes.h so we have its sizeof and know how big to @@ -926,6 +932,11 @@ def UnionTypes(descriptors, dictionaries, callbacks, config): # Need to see the actual definition of the enum, # unfortunately. headers.add(CGHeaders.getDeclarationFilename(f.inner)) + elif f.isCallback(): + # Callbacks always use strong refs, so we need to include + # the right header to be able to Release() in our inlined + # code. + headers.add(CGHeaders.getDeclarationFilename(f)) map(addInfoForType, getAllTypes(descriptors, dictionaries, callbacks)) @@ -3276,7 +3287,10 @@ while (true) { objectMemberTypes = filter(lambda t: t.isObject(), memberTypes) if len(objectMemberTypes) > 0: assert len(objectMemberTypes) == 1 - object = CGGeneric("%s.SetToObject(cx, argObj);\n" + # Very important to NOT construct a temporary Rooted here, since the + # SetToObject call can call a Rooted constructor and we need to keep + # stack discipline for Rooted. + object = CGGeneric("%s.SetToObject(cx, &${val}.toObject());\n" "done = true;" % unionArgumentObj) names.append(objectMemberTypes[0].name) else: @@ -3303,7 +3317,7 @@ while (true) { else: templateBody = CGList([templateBody, object], "\n") - if any([arrayObject, dateObject, callbackObject, object]): + if dateObject: templateBody.prepend(CGGeneric("JS::Rooted argObj(cx, &${val}.toObject());")) templateBody = CGIfWrapper(templateBody, "${val}.isObject()") else: @@ -7187,7 +7201,9 @@ class CGUnionStruct(CGThing): disallowCopyConstruction = True friend=" friend class %sArgument;\n" % str(self.type) if not self.ownsMembers else "" + bases = [ClassBase("AllOwningUnionBase")] if self.ownsMembers else [] return CGClass(selfName, + bases=bases, members=members, constructors=constructors, methods=methods, diff --git a/dom/bindings/test/TestBindingHeader.h b/dom/bindings/test/TestBindingHeader.h index 9c15763c8b5..f6cf3d7988e 100644 --- a/dom/bindings/test/TestBindingHeader.h +++ b/dom/bindings/test/TestBindingHeader.h @@ -524,6 +524,7 @@ public: void PassUnion12(const EventInitOrLong& arg); void PassUnion13(JSContext*, const ObjectOrLongOrNull& arg); void PassUnion14(JSContext*, const ObjectOrLongOrNull& arg); + void PassUnionWithCallback(const EventHandlerNonNullOrNullOrLong& arg); #endif void PassNullableUnion(JSContext*, const Nullable&); void PassOptionalUnion(JSContext*, const Optional&); @@ -566,6 +567,7 @@ public: void PassNullableUnionWithDefaultValue12(const Nullable& arg); void PassSequenceOfUnions(const Sequence&); + void PassSequenceOfUnions2(JSContext*, const Sequence&); void PassVariadicUnion(const Sequence&); void PassSequenceOfNullableUnions(const Sequence>&); diff --git a/dom/bindings/test/TestCodeGen.webidl b/dom/bindings/test/TestCodeGen.webidl index 1a63d75a18f..5729d6c10d6 100644 --- a/dom/bindings/test/TestCodeGen.webidl +++ b/dom/bindings/test/TestCodeGen.webidl @@ -475,6 +475,7 @@ interface TestInterface { void passUnion12(optional (EventInit or long) arg = 5); void passUnion13(optional (object or long?) arg = null); void passUnion14(optional (object or long?) arg = 5); + void passUnionWithCallback((EventHandler or long) arg); #endif void passUnionWithNullable((object? or long) arg); void passNullableUnion((object or long)? arg); @@ -521,6 +522,7 @@ interface TestInterface { void passNullableUnionWithDefaultValue12(optional (unrestricted float or DOMString)? arg = null); void passSequenceOfUnions(sequence<(CanvasPattern or CanvasGradient)> arg); + void passSequenceOfUnions2(sequence<(object or long)> arg); void passVariadicUnion((CanvasPattern or CanvasGradient)... arg); void passSequenceOfNullableUnions(sequence<(CanvasPattern or CanvasGradient)?> arg); diff --git a/dom/bindings/test/TestExampleGen.webidl b/dom/bindings/test/TestExampleGen.webidl index 2b80d165d8e..2f2b122f401 100644 --- a/dom/bindings/test/TestExampleGen.webidl +++ b/dom/bindings/test/TestExampleGen.webidl @@ -368,6 +368,7 @@ interface TestExampleInterface { void passUnion12(optional (EventInit or long) arg = 5); void passUnion13(optional (object or long?) arg = null); void passUnion14(optional (object or long?) arg = 5); + void passUnionWithCallback((EventHandler or long) arg); #endif void passUnionWithNullable((object? or long) arg); void passNullableUnion((object or long)? arg); @@ -414,6 +415,7 @@ interface TestExampleInterface { void passNullableUnionWithDefaultValue12(optional (unrestricted float or DOMString)? arg = null); void passSequenceOfUnions(sequence<(CanvasPattern or CanvasGradient)> arg); + void passSequenceOfUnions2(sequence<(object or long)> arg); void passVariadicUnion((CanvasPattern or CanvasGradient)... arg); void passSequenceOfNullableUnions(sequence<(CanvasPattern or CanvasGradient)?> arg); diff --git a/dom/bindings/test/TestJSImplGen.webidl b/dom/bindings/test/TestJSImplGen.webidl index e59dd88163a..ad92d30bc16 100644 --- a/dom/bindings/test/TestJSImplGen.webidl +++ b/dom/bindings/test/TestJSImplGen.webidl @@ -389,6 +389,7 @@ interface TestJSImplInterface { void passUnion12(optional (EventInit or long) arg = 5); void passUnion13(optional (object or long?) arg = null); void passUnion14(optional (object or long?) arg = 5); + void passUnionWithCallback((EventHandler or long) arg); #endif void passUnionWithNullable((object? or long) arg); void passNullableUnion((object or long)? arg); @@ -435,6 +436,7 @@ interface TestJSImplInterface { void passNullableUnionWithDefaultValue12(optional (unrestricted float or DOMString)? arg = null); void passSequenceOfUnions(sequence<(CanvasPattern or CanvasGradient)> arg); + void passSequenceOfUnions2(sequence<(object or long)> arg); void passVariadicUnion((CanvasPattern or CanvasGradient)... arg); void passSequenceOfNullableUnions(sequence<(CanvasPattern or CanvasGradient)?> arg); diff --git a/dom/identity/DOMIdentity.jsm b/dom/identity/DOMIdentity.jsm index 3bdf6a09db7..c30d5d03b77 100644 --- a/dom/identity/DOMIdentity.jsm +++ b/dom/identity/DOMIdentity.jsm @@ -335,6 +335,7 @@ this.DOMIdentity = { }, _unwatch: function DOMIdentity_unwatch(message, targetMM) { + log("DOMIDentity__unwatch: " + message.id); this.getService(message).RP.unwatch(message.id, targetMM); }, diff --git a/dom/identity/nsDOMIdentity.js b/dom/identity/nsDOMIdentity.js index aeaf247d16a..af75473f40f 100644 --- a/dom/identity/nsDOMIdentity.js +++ b/dom/identity/nsDOMIdentity.js @@ -670,7 +670,7 @@ nsDOMIdentity.prototype = { }, uninit: function DOMIdentity_uninit() { - this._log("nsDOMIdentity uninit()"); + this._log("nsDOMIdentity uninit() " + this._id); this._identityInternal._mm.sendAsyncMessage( "Identity:RP:Unwatch", { id: this._id } diff --git a/dom/identity/tests/mochitest/file_declareAudience.html b/dom/identity/tests/mochitest/file_declareAudience.html index d96ab6b4327..3313ca3ca83 100644 --- a/dom/identity/tests/mochitest/file_declareAudience.html +++ b/dom/identity/tests/mochitest/file_declareAudience.html @@ -41,7 +41,11 @@ onready: onready, onlogin: onlogin, onerror: onerror, - onlogout: function() {}, + + // onlogout will actually be called every time watch() is invoked, + // because fxa will find no signed-in user and so trigger logout. + // For this test, though, we don't care and just ignore logout. + onlogout: function () {}, }); }; diff --git a/dom/identity/tests/mochitest/test_declareAudience.html b/dom/identity/tests/mochitest/test_declareAudience.html index 59e49b013be..4ef9daf5f4c 100644 --- a/dom/identity/tests/mochitest/test_declareAudience.html +++ b/dom/identity/tests/mochitest/test_declareAudience.html @@ -34,7 +34,13 @@ is("appStatus" in document.nodePrincipal, true, function MockFXAManager() {} MockFXAManager.prototype = { - getAssertion: function(audience) { + getAssertion: function(audience, options) { + // Always reject a request for a silent assertion, simulating the + // scenario in which there is no signed-in user to begin with. + if (options.silent) { + return Promise.resolve(null); + } + let deferred = Promise.defer(); jwcrypto.generateKeyPair("DS160", (err, kp) => { if (err) { @@ -137,7 +143,7 @@ function receiveMessage(event) { let expected = app.expected; is(result.success, expected.success, - "Assertion request " + (expected.success ? "succeeds" : "fails")); + "Assertion request succeeds"); if (expected.success) { // Confirm that the assertion audience and origin are as expected @@ -180,7 +186,6 @@ window.addEventListener("message", receiveMessage, false, true); function runTest() { for (let app of apps) { dump("** Testing " + app.title + "\n"); - // Set up state for message handler expectedErrors = 0; receivedErrors = []; diff --git a/dom/identity/tests/mochitest/test_syntheticEvents.html b/dom/identity/tests/mochitest/test_syntheticEvents.html index 194c8533587..2e01deac6bd 100644 --- a/dom/identity/tests/mochitest/test_syntheticEvents.html +++ b/dom/identity/tests/mochitest/test_syntheticEvents.html @@ -31,7 +31,10 @@ Components.utils.import("resource://gre/modules/identity/FirefoxAccounts.jsm"); // plumbing. function MockFXAManager() {} MockFXAManager.prototype = { - getAssertion: function() { + getAssertion: function(audience, options) { + if (options.silent) { + return Promise.resolve(null); + } return Promise.resolve("here~you.go.dude"); } }; diff --git a/dom/media/tests/mochitest/pc.js b/dom/media/tests/mochitest/pc.js index 943b1abedb2..17eb1b591f0 100644 --- a/dom/media/tests/mochitest/pc.js +++ b/dom/media/tests/mochitest/pc.js @@ -1594,8 +1594,8 @@ PeerConnectionWrapper.prototype = { var res = stats[key]; // validate stats ok(res.id == key, "Coherent stats id"); - var nowish = Date.now() + 10000; // TODO: severe drift observed - var minimum = this.whenCreated - 10000; // on Windows XP (Bug 979649) + var nowish = Date.now() + 1000; // TODO: clock drift observed + var minimum = this.whenCreated - 1000; // on Windows XP (Bug 979649) ok(res.timestamp >= minimum, "Valid " + (res.isRemote? "rtcp" : "rtp") + " timestamp " + res.timestamp + " >= " + minimum + " (" + @@ -1631,12 +1631,10 @@ PeerConnectionWrapper.prototype = { if(res.type == "outboundrtp") { ok(rem.type == "inboundrtp", "Rtcp is inbound"); ok(rem.packetsReceived !== undefined, "Rtcp packetsReceived"); - // TODO: Re-enable once Bug 980497 is fixed - // ok(rem.packetsReceived <= res.packetsSent, "No more than sent"); + ok(rem.packetsReceived <= res.packetsSent, "No more than sent"); ok(rem.packetsLost !== undefined, "Rtcp packetsLost"); ok(rem.bytesReceived >= rem.packetsReceived * 8, "Rtcp bytesReceived"); - // TODO: Re-enable once Bug 980497 is fixed - // ok(rem.bytesReceived <= res.bytesSent, "No more than sent bytes"); + ok(rem.bytesReceived <= res.bytesSent, "No more than sent bytes"); ok(rem.jitter !== undefined, "Rtcp jitter"); } else { ok(rem.type == "outboundrtp", "Rtcp is outbound"); diff --git a/dom/network/src/NetworkStatsService.jsm b/dom/network/src/NetworkStatsService.jsm index e5302589040..390e4607e87 100644 --- a/dom/network/src/NetworkStatsService.jsm +++ b/dom/network/src/NetworkStatsService.jsm @@ -41,6 +41,10 @@ const NETWORK_STATUS_AWAY = 2; // The maximum traffic amount can be saved in the |cachedStats|. const MAX_CACHED_TRAFFIC = 500 * 1000 * 1000; // 500 MB +const QUEUE_TYPE_UPDATE_STATS = 0; +const QUEUE_TYPE_UPDATE_CACHE = 1; +const QUEUE_TYPE_WRITE_CACHE = 2; + XPCOMUtils.defineLazyServiceGetter(this, "ppmm", "@mozilla.org/parentprocessmessagemanager;1", "nsIMessageListenerManager"); @@ -398,7 +402,6 @@ this.NetworkStatsService = { * it retrieve them from database and return to the manager. */ getSamples: function getSamples(mm, msg) { - let self = this; let network = msg.network; let netId = this.getNetworkId(network.id, network.type); @@ -420,6 +423,13 @@ this.NetworkStatsService = { let start = new Date(msg.start); let end = new Date(msg.end); + let callback = (function (aError, aResult) { + this._db.find(function onStatsFound(aError, aResult) { + mm.sendAsyncMessage("NetworkStats:Get:Return", + { id: msg.id, error: aError, result: aResult }); + }, appId, serviceType, network, start, end, appManifestURL); + }).bind(this); + this.validateNetwork(network, function onValidateNetwork(aNetId) { if (!aNetId) { mm.sendAsyncMessage("NetworkStats:Get:Return", @@ -429,27 +439,28 @@ this.NetworkStatsService = { // If network is currently active we need to update the cached stats first before // retrieving stats from the DB. - if (self._networks[aNetId].status == NETWORK_STATUS_READY) { - self.updateStats(aNetId, function onStatsUpdated(aResult, aMessage) { - debug("getstats for network " + network.id + " of type " + network.type); - debug("appId: " + appId + " from appManifestURL: " + appManifestURL); + if (this._networks[aNetId].status == NETWORK_STATUS_READY) { + debug("getstats for network " + network.id + " of type " + network.type); + debug("appId: " + appId + " from appManifestURL: " + appManifestURL); + debug("serviceType: " + serviceType); - self.updateCachedStats(function onStatsUpdated(aResult, aMessage) { - self._db.find(function onStatsFound(aError, aResult) { - mm.sendAsyncMessage("NetworkStats:Get:Return", - { id: msg.id, error: aError, result: aResult }); - }, appId, serviceType, network, start, end, appManifestURL); - }); - }); + if (appId || serviceType) { + this.updateCachedStats(callback); + return; + } + + this.updateStats(aNetId, function onStatsUpdated(aResult, aMessage) { + this.updateCachedStats(callback); + }.bind(this)); return; } // Network not active, so no need to update - self._db.find(function onStatsFound(aError, aResult) { + this._db.find(function onStatsFound(aError, aResult) { mm.sendAsyncMessage("NetworkStats:Get:Return", { id: msg.id, error: aError, result: aResult }); }, appId, serviceType, network, start, end, appManifestURL); - }); + }.bind(this)); }, clearInterfaceStats: function clearInterfaceStats(mm, msg) { @@ -515,11 +526,11 @@ this.NetworkStatsService = { }, updateAllStats: function updateAllStats(aCallback) { - // Update |cachedStats|. - this.updateCachedStats(); - let elements = []; let lastElement = null; + let callback = (function (success, message) { + this.updateCachedStats(aCallback); + }).bind(this); // For each connectionType create an object containning the type // and the 'queueIndex', the 'queueIndex' is an integer representing @@ -533,10 +544,12 @@ this.NetworkStatsService = { } lastElement = { netId: netId, - queueIndex: this.updateQueueIndex(netId)}; + queueIndex: this.updateQueueIndex(netId) }; if (lastElement.queueIndex == -1) { - elements.push({ netId: lastElement.netId, callbacks: [] }); + elements.push({ netId: lastElement.netId, + callbacks: [], + queueType: QUEUE_TYPE_UPDATE_STATS }); } } @@ -552,7 +565,7 @@ this.NetworkStatsService = { if (elements.length > 0) { // If length of elements is greater than 0, callback is set to // the last element. - elements[elements.length - 1].callbacks.push(aCallback); + elements[elements.length - 1].callbacks.push(callback); this.updateQueue = this.updateQueue.concat(elements); } else { // Else, it means that all connection types are already in the queue to @@ -567,7 +580,7 @@ this.NetworkStatsService = { return; } - this.updateQueue[lastElement.queueIndex].callbacks.push(aCallback); + this.updateQueue[lastElement.queueIndex].callbacks.push(callback); } // Call the function that process the elements of the queue. @@ -583,7 +596,9 @@ this.NetworkStatsService = { // if it is not being processed or add a callback if it is. let index = this.updateQueueIndex(aNetId); if (index == -1) { - this.updateQueue.push({netId: aNetId, callbacks: [aCallback]}); + this.updateQueue.push({ netId: aNetId, + callbacks: [aCallback], + queueType: QUEUE_TYPE_UPDATE_STATS }); } else { this.updateQueue[index].callbacks.push(aCallback); return; @@ -611,7 +626,7 @@ this.NetworkStatsService = { if (aResult != undefined) { let item = this.updateQueue.shift(); for (let callback of item.callbacks) { - if(callback) { + if (callback) { callback(aResult, aMessage); } } @@ -620,9 +635,7 @@ this.NetworkStatsService = { // if isQueueRunning is false it means there is no processing currently // being done, so start. if (this.isQueueRunning) { - if(this.updateQueue.length > 1) { - return; - } + return; } else { this.isQueueRunning = true; } @@ -635,7 +648,17 @@ this.NetworkStatsService = { } // Call the update function for the next element. - this.update(this.updateQueue[0].netId, this.processQueue.bind(this)); + switch (this.updateQueue[0].queueType) { + case QUEUE_TYPE_UPDATE_STATS: + this.update(this.updateQueue[0].netId, this.processQueue.bind(this)); + break; + case QUEUE_TYPE_UPDATE_CACHE: + this.updateCache(this.processQueue.bind(this)); + break; + case QUEUE_TYPE_WRITE_CACHE: + this.writeCache(this.updateQueue[0].stats, this.processQueue.bind(this)); + break; + } }, update: function update(aNetId, aCallback) { @@ -708,14 +731,11 @@ this.NetworkStatsService = { let netId = this.convertNetworkInterface(aNetwork); if (!netId) { if (aCallback) { - aCallback.notify(false, "Invalid network type"); + aCallback(false, "Invalid network type"); } return; } - debug("saveStats: " + aAppId + " " + aServiceType + " " + netId + " " + - aTimeStamp + " " + aRxBytes + " " + aTxBytes); - // Check if |aConnectionType|, |aAppId| and |aServiceType| are valid. // There are two invalid cases for the combination of |aAppId| and // |aServiceType|: @@ -736,33 +756,41 @@ this.NetworkStatsService = { txBytes: aTxBytes, isAccumulative: aIsAccumulative }; + this.updateQueue.push({ stats: stats, + callbacks: [aCallback], + queueType: QUEUE_TYPE_WRITE_CACHE }); + + this.processQueue(); + }, + + /* + * + */ + writeCache: function writeCache(aStats, aCallback) { + debug("saveStats: " + aStats.appId + " " + aStats.serviceType + " " + + aStats.networkId + " " + aStats.networkType + " " + aStats.date + " " + + aStats.date + " " + aStats.rxBytes + " " + aStats.txBytes); + // Generate an unique key from |appId|, |serviceType| and |netId|, // which is used to retrieve data in |cachedStats|. - let key = stats.appId + "" + stats.serviceType + "" + netId; + let netId = this.getNetworkId(aStats.networkId, aStats.networkType); + let key = aStats.appId + "" + aStats.serviceType + "" + netId; // |cachedStats| only keeps the data with the same date. // If the incoming date is different from |cachedStatsDate|, // both |cachedStats| and |cachedStatsDate| will get updated. - let diff = (this._db.normalizeDate(stats.date) - + let diff = (this._db.normalizeDate(aStats.date) - this._db.normalizeDate(this.cachedStatsDate)) / this._db.sampleRate; if (diff != 0) { - this.updateCachedStats(function onUpdated(success, message) { - this.cachedStatsDate = stats.date; - this.cachedStats[key] = stats; + this.updateCache(function onUpdated(success, message) { + this.cachedStatsDate = aStats.date; + this.cachedStats[key] = aStats; - if (!aCallback) { - return; + if (aCallback) { + aCallback(true, "ok"); } - - if (!success) { - aCallback.notify(false, message); - return; - } - - aCallback.notify(true, "ok"); }.bind(this)); - return; } @@ -770,30 +798,46 @@ this.NetworkStatsService = { // If not found, save the incoming data into the cached. let cachedStats = this.cachedStats[key]; if (!cachedStats) { - this.cachedStats[key] = stats; + this.cachedStats[key] = aStats; + if (aCallback) { + aCallback(true, "ok"); + } return; } // Find matched row, accumulate the traffic amount. - cachedStats.rxBytes += stats.rxBytes; - cachedStats.txBytes += stats.txBytes; + cachedStats.rxBytes += aStats.rxBytes; + cachedStats.txBytes += aStats.txBytes; // If new rxBytes or txBytes exceeds MAX_CACHED_TRAFFIC // the corresponding row will be saved to indexedDB. // Then, the row will be removed from the cached. if (cachedStats.rxBytes > MAX_CACHED_TRAFFIC || cachedStats.txBytes > MAX_CACHED_TRAFFIC) { - this._db.saveStats(cachedStats, - function (error, result) { - debug("Application stats inserted in indexedDB"); + this._db.saveStats(cachedStats, function (error, result) { + debug("Application stats inserted in indexedDB"); + if (aCallback) { + aCallback(true, "ok"); } - ); + }); delete this.cachedStats[key]; + return; + } + + if (aCallback) { + aCallback(true, "ok"); } }, updateCachedStats: function updateCachedStats(aCallback) { - debug("updateCachedStats: " + this.cachedStatsDate); + this.updateQueue.push({ callbacks: [aCallback], + queueType: QUEUE_TYPE_UPDATE_CACHE }); + + this.processQueue(); + }, + + updateCache: function updateCache(aCallback) { + debug("updateCache: " + this.cachedStatsDate); let stats = Object.keys(this.cachedStats); if (stats.length == 0) { @@ -801,39 +845,29 @@ this.NetworkStatsService = { if (aCallback) { aCallback(true, "no need to update"); } - return; } let index = 0; this._db.saveStats(this.cachedStats[stats[index]], - function onSavedStats(error, result) { - if (DEBUG) { - debug("Application stats inserted in indexedDB"); - } + function onSavedStats(error, result) { + debug("Application stats inserted in indexedDB"); - // Clean up the |cachedStats| after updating. - if (index == stats.length - 1) { - this.cachedStats = Object.create(null); - - if (!aCallback) { - return; - } - - if (error) { - aCallback(false, error); - return; - } + // Clean up the |cachedStats| after updating. + if (index == stats.length - 1) { + this.cachedStats = Object.create(null); + if (aCallback) { aCallback(true, "ok"); - return; } + return; + } - // Update is not finished, keep updating. - index += 1; - this._db.saveStats(this.cachedStats[stats[index]], - onSavedStats.bind(this, error, result)); - }.bind(this)); + // Update is not finished, keep updating. + index += 1; + this._db.saveStats(this.cachedStats[stats[index]], + onSavedStats.bind(this, error, result)); + }.bind(this)); }, get maxCachedTraffic () { diff --git a/dom/network/src/NetworkStatsServiceProxy.js b/dom/network/src/NetworkStatsServiceProxy.js index 072bce2a7dc..8586cd4bad9 100644 --- a/dom/network/src/NetworkStatsServiceProxy.js +++ b/dom/network/src/NetworkStatsServiceProxy.js @@ -44,6 +44,10 @@ NetworkStatsServiceProxy.prototype = { " " + aRxBytes + " " + aTxBytes + " " + aIsAccumulative); } + if (aCallback) { + aCallback = aCallback.notify; + } + NetworkStatsService.saveStats(aAppId, "", aNetwork, aTimeStamp, aRxBytes, aTxBytes, aIsAccumulative, aCallback); @@ -69,6 +73,10 @@ NetworkStatsServiceProxy.prototype = { aIsAccumulative); } + if (aCallback) { + aCallback = aCallback.notify; + } + NetworkStatsService.saveStats(0, aServiceType ,aNetwork, aTimeStamp, aRxBytes, aTxBytes, aIsAccumulative, aCallback); diff --git a/dom/network/tests/unit_stats/test_networkstats_service.js b/dom/network/tests/unit_stats/test_networkstats_service.js index 9c72dc53225..2140d2c5b0a 100644 --- a/dom/network/tests/unit_stats/test_networkstats_service.js +++ b/dom/network/tests/unit_stats/test_networkstats_service.js @@ -7,6 +7,8 @@ const NETWORK_STATUS_READY = 0; const NETWORK_STATUS_STANDBY = 1; const NETWORK_STATUS_AWAY = 2; +const QUEUE_TYPE_UPDATE_STATS = 0; + var wifiId = '00'; function getNetworks(callback) { @@ -80,11 +82,11 @@ add_test(function test_update() { }); add_test(function test_updateQueueIndex() { - NetworkStatsService.updateQueue = [{netId: 0, callbacks: null}, - {netId: 1, callbacks: null}, - {netId: 2, callbacks: null}, - {netId: 3, callbacks: null}, - {netId: 4, callbacks: null}]; + NetworkStatsService.updateQueue = [{netId: 0, callbacks: null, queueType: QUEUE_TYPE_UPDATE_STATS}, + {netId: 1, callbacks: null, queueType: QUEUE_TYPE_UPDATE_STATS}, + {netId: 2, callbacks: null, queueType: QUEUE_TYPE_UPDATE_STATS}, + {netId: 3, callbacks: null, queueType: QUEUE_TYPE_UPDATE_STATS}, + {netId: 4, callbacks: null, queueType: QUEUE_TYPE_UPDATE_STATS}]; var index = NetworkStatsService.updateQueueIndex(3); do_check_eq(index, 3); index = NetworkStatsService.updateQueueIndex(10); diff --git a/dom/network/tests/unit_stats/test_networkstats_service_proxy.js b/dom/network/tests/unit_stats/test_networkstats_service_proxy.js index 6b5e3d8a39c..0214936f60b 100644 --- a/dom/network/tests/unit_stats/test_networkstats_service_proxy.js +++ b/dom/network/tests/unit_stats/test_networkstats_service_proxy.js @@ -36,7 +36,6 @@ function mokConvertNetworkInterface() { add_test(function test_saveAppStats() { var cachedStats = NetworkStatsService.cachedStats; var timestamp = NetworkStatsService.cachedStatsDate.getTime(); - var samples = 5; // Create to fake nsINetworkInterfaces. As nsINetworkInterface can not // be instantiated, these two vars will emulate it by filling the properties @@ -49,39 +48,39 @@ add_test(function test_saveAppStats() { do_check_eq(Object.keys(cachedStats).length, 0); - for (var i = 0; i < samples; i++) { - nssProxy.saveAppStats(1, wifi, timestamp, 10, 20, false); + nssProxy.saveAppStats(1, wifi, timestamp, 10, 20, false, + function (success, message) { + do_check_eq(success, true); + nssProxy.saveAppStats(1, mobile, timestamp, 10, 20, false, + function (success, message) { + var key1 = 1 + "" + NetworkStatsService.getNetworkId(wifi.id, wifi.type); + var key2 = 1 + "" + mobileNetId + ""; - nssProxy.saveAppStats(1, mobile, timestamp, 10, 20, false); - } + do_check_eq(Object.keys(cachedStats).length, 2); + do_check_eq(cachedStats[key1].appId, 1); + do_check_eq(cachedStats[key1].serviceType.length, 0); + do_check_eq(cachedStats[key1].networkId, wifi.id); + do_check_eq(cachedStats[key1].networkType, wifi.type); + do_check_eq(new Date(cachedStats[key1].date).getTime() / 1000, + Math.floor(timestamp / 1000)); + do_check_eq(cachedStats[key1].rxBytes, 10); + do_check_eq(cachedStats[key1].txBytes, 20); + do_check_eq(cachedStats[key2].appId, 1); + do_check_eq(cachedStats[key1].serviceType.length, 0); + do_check_eq(cachedStats[key2].networkId, mobile.id); + do_check_eq(cachedStats[key2].networkType, mobile.type); + do_check_eq(new Date(cachedStats[key2].date).getTime() / 1000, + Math.floor(timestamp / 1000)); + do_check_eq(cachedStats[key2].rxBytes, 10); + do_check_eq(cachedStats[key2].txBytes, 20); - var key1 = 1 + "" + NetworkStatsService.getNetworkId(wifi.id, wifi.type); - var key2 = 1 + "" + mobileNetId + ""; - - do_check_eq(Object.keys(cachedStats).length, 2); - do_check_eq(cachedStats[key1].appId, 1); - do_check_eq(cachedStats[key1].serviceType.length, 0); - do_check_eq(cachedStats[key1].networkId, wifi.id); - do_check_eq(cachedStats[key1].networkType, wifi.type); - do_check_eq(new Date(cachedStats[key1].date).getTime() / 1000, - Math.floor(timestamp / 1000)); - do_check_eq(cachedStats[key1].rxBytes, 50); - do_check_eq(cachedStats[key1].txBytes, 100); - do_check_eq(cachedStats[key2].appId, 1); - do_check_eq(cachedStats[key1].serviceType.length, 0); - do_check_eq(cachedStats[key2].networkId, mobile.id); - do_check_eq(cachedStats[key2].networkType, mobile.type); - do_check_eq(new Date(cachedStats[key2].date).getTime() / 1000, - Math.floor(timestamp / 1000)); - do_check_eq(cachedStats[key2].rxBytes, 50); - do_check_eq(cachedStats[key2].txBytes, 100); - - run_next_test(); + run_next_test(); + }); + }); }); add_test(function test_saveServiceStats() { var timestamp = NetworkStatsService.cachedStatsDate.getTime(); - var samples = 5; // Create to fake nsINetworkInterfaces. As nsINetworkInterface can not // be instantiated, these two vars will emulate it by filling the properties @@ -92,90 +91,80 @@ add_test(function test_saveServiceStats() { // Insert fake mobile network interface in NetworkStatsService var mobileNetId = NetworkStatsService.getNetworkId(mobile.id, mobile.type); - NetworkStatsService.updateCachedStats( - function (success, msg) { + NetworkStatsService.updateCachedStats(function (success, msg) { + do_check_eq(success, true); + + var cachedStats = NetworkStatsService.cachedStats; + do_check_eq(Object.keys(cachedStats).length, 0); + + var serviceType = 'FakeType'; + nssProxy.saveServiceStats(serviceType, wifi, timestamp, 10, 20, false, + function (success, message) { do_check_eq(success, true); + nssProxy.saveServiceStats(serviceType, mobile, timestamp, 10, 20, false, + function (success, message) { + do_check_eq(success, true); + var key1 = 0 + "" + serviceType + + NetworkStatsService.getNetworkId(wifi.id, wifi.type); + var key2 = 0 + "" + serviceType + mobileNetId + ""; - var cachedStats = NetworkStatsService.cachedStats; - do_check_eq(Object.keys(cachedStats).length, 0); + do_check_eq(Object.keys(cachedStats).length, 2); + do_check_eq(cachedStats[key1].appId, 0); + do_check_eq(cachedStats[key1].serviceType, serviceType); + do_check_eq(cachedStats[key1].networkId, wifi.id); + do_check_eq(cachedStats[key1].networkType, wifi.type); + do_check_eq(new Date(cachedStats[key1].date).getTime() / 1000, + Math.floor(timestamp / 1000)); + do_check_eq(cachedStats[key1].rxBytes, 10); + do_check_eq(cachedStats[key1].txBytes, 20); + do_check_eq(cachedStats[key2].appId, 0); + do_check_eq(cachedStats[key1].serviceType, serviceType); + do_check_eq(cachedStats[key2].networkId, mobile.id); + do_check_eq(cachedStats[key2].networkType, mobile.type); + do_check_eq(new Date(cachedStats[key2].date).getTime() / 1000, + Math.floor(timestamp / 1000)); + do_check_eq(cachedStats[key2].rxBytes, 10); + do_check_eq(cachedStats[key2].txBytes, 20); - var serviceType = 'FakeType'; - for (var i = 0; i < samples; i++) { - nssProxy.saveServiceStats(serviceType, wifi, timestamp, 10, 20, false); - - nssProxy.saveServiceStats(serviceType, mobile, timestamp, 10, 20, false); - } - - var key1 = 0 + "" + serviceType + - NetworkStatsService.getNetworkId(wifi.id, wifi.type); - var key2 = 0 + "" + serviceType + mobileNetId + ""; - - do_check_eq(Object.keys(cachedStats).length, 2); - do_check_eq(cachedStats[key1].appId, 0); - do_check_eq(cachedStats[key1].serviceType, serviceType); - do_check_eq(cachedStats[key1].networkId, wifi.id); - do_check_eq(cachedStats[key1].networkType, wifi.type); - do_check_eq(new Date(cachedStats[key1].date).getTime() / 1000, - Math.floor(timestamp / 1000)); - do_check_eq(cachedStats[key1].rxBytes, 50); - do_check_eq(cachedStats[key1].txBytes, 100); - do_check_eq(cachedStats[key2].appId, 0); - do_check_eq(cachedStats[key1].serviceType, serviceType); - do_check_eq(cachedStats[key2].networkId, mobile.id); - do_check_eq(cachedStats[key2].networkType, mobile.type); - do_check_eq(new Date(cachedStats[key2].date).getTime() / 1000, - Math.floor(timestamp / 1000)); - do_check_eq(cachedStats[key2].rxBytes, 50); - do_check_eq(cachedStats[key2].txBytes, 100); - - run_next_test(); - } - ); + run_next_test(); + }); + }); + }); }); add_test(function test_saveStatsWithDifferentDates() { var today = NetworkStatsService.cachedStatsDate; var tomorrow = new Date(today.getTime() + (24 * 60 * 60 * 1000)); - var wifi = {type: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, id: "0"}; var mobile = {type: Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE, id: "1234"}; - var key = 1 + "" + NetworkStatsService.getNetworkId(wifi.id, wifi.type); + NetworkStatsService.updateCachedStats(function (success, message) { + do_check_eq(success, true); - NetworkStatsService.updateCachedStats( - function (success, msg) { + do_check_eq(Object.keys(NetworkStatsService.cachedStats).length, 0); + nssProxy.saveAppStats(1, mobile, today.getTime(), 10, 20, false, + function (success, message) { do_check_eq(success, true); - - do_check_eq(Object.keys(NetworkStatsService.cachedStats).length, 0); - - nssProxy.saveAppStats(1, wifi, today.getTime(), 10, 20, false); - - nssProxy.saveAppStats(1, mobile, today.getTime(), 10, 20, false); - - var saveStatsCb = { - notify: function notify(success, message) { - do_check_eq(success, true); - - var cachedStats = NetworkStatsService.cachedStats; - var key = 2 + "" + - NetworkStatsService.getNetworkId(mobile.id, mobile.type); - do_check_eq(Object.keys(cachedStats).length, 1); - do_check_eq(cachedStats[key].appId, 2); - do_check_eq(cachedStats[key].networkId, mobile.id); - do_check_eq(cachedStats[key].networkType, mobile.type); - do_check_eq(new Date(cachedStats[key].date).getTime() / 1000, - Math.floor(tomorrow.getTime() / 1000)); - do_check_eq(cachedStats[key].rxBytes, 30); - do_check_eq(cachedStats[key].txBytes, 40); - - run_next_test(); - } - }; - nssProxy.saveAppStats(2, mobile, tomorrow.getTime(), 30, 40, false, - saveStatsCb); - } - ); + function (success, message) { + do_check_eq(success, true); + + var cachedStats = NetworkStatsService.cachedStats; + var key = 2 + "" + + NetworkStatsService.getNetworkId(mobile.id, mobile.type); + do_check_eq(Object.keys(cachedStats).length, 1); + do_check_eq(cachedStats[key].appId, 2); + do_check_eq(cachedStats[key].networkId, mobile.id); + do_check_eq(cachedStats[key].networkType, mobile.type); + do_check_eq(new Date(cachedStats[key].date).getTime() / 1000, + Math.floor(tomorrow.getTime() / 1000)); + do_check_eq(cachedStats[key].rxBytes, 30); + do_check_eq(cachedStats[key].txBytes, 40); + + run_next_test(); + }); + }); + }); }); add_test(function test_saveStatsWithMaxCachedTraffic() { @@ -183,24 +172,24 @@ add_test(function test_saveStatsWithMaxCachedTraffic() { var maxtraffic = NetworkStatsService.maxCachedTraffic; var wifi = {type: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI, id: "0"}; - NetworkStatsService.updateCachedStats( - function (success, msg) { + NetworkStatsService.updateCachedStats(function (success, message) { + do_check_eq(success, true); + + var cachedStats = NetworkStatsService.cachedStats; + do_check_eq(Object.keys(cachedStats).length, 0); + nssProxy.saveAppStats(1, wifi, timestamp, 10, 20, false, + function (success, message) { do_check_eq(success, true); - - var cachedStats = NetworkStatsService.cachedStats; - do_check_eq(Object.keys(cachedStats).length, 0); - - nssProxy.saveAppStats(1, wifi, timestamp, 10, 20, false); - do_check_eq(Object.keys(cachedStats).length, 1); + nssProxy.saveAppStats(1, wifi, timestamp, maxtraffic, 20, false, + function (success, message) { + do_check_eq(success, true); + do_check_eq(Object.keys(cachedStats).length, 0); - nssProxy.saveAppStats(1, wifi, timestamp, maxtraffic, 20, false); - - do_check_eq(Object.keys(cachedStats).length, 0); - - run_next_test(); - } - ); + run_next_test(); + }); + }); + }); }); function run_test() { diff --git a/dom/system/OSFileConstants.cpp b/dom/system/OSFileConstants.cpp index 489d7eedaae..4b213610e15 100644 --- a/dom/system/OSFileConstants.cpp +++ b/dom/system/OSFileConstants.cpp @@ -709,6 +709,8 @@ static const dom::ConstantSpec gWinProperties[] = INT_CONSTANT(ERROR_FILE_NOT_FOUND), INT_CONSTANT(ERROR_NO_MORE_FILES), INT_CONSTANT(ERROR_PATH_NOT_FOUND), + INT_CONSTANT(ERROR_BAD_ARGUMENTS), + INT_CONSTANT(ERROR_NOT_SUPPORTED), PROP_END }; diff --git a/dom/webidl/NativeOSFileInternals.webidl b/dom/webidl/NativeOSFileInternals.webidl new file mode 100644 index 00000000000..36cc1a3a18c --- /dev/null +++ b/dom/webidl/NativeOSFileInternals.webidl @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtaone at http://mozilla.org/MPL/2.0/. */ + +/** + * Options for nsINativeOSFileInternals::Read + */ +dictionary NativeOSFileReadOptions +{ + /** + * If specified, convert the raw bytes to a String + * with the specified encoding. Otherwise, return + * the raw bytes as a TypedArray. + */ + DOMString? encoding; + + /** + * If specified, limit the number of bytes to read. + */ + unsigned long long? bytes; +}; diff --git a/dom/webidl/RTCStatsReport.webidl b/dom/webidl/RTCStatsReport.webidl index f6bab2182de..3f118d30638 100644 --- a/dom/webidl/RTCStatsReport.webidl +++ b/dom/webidl/RTCStatsReport.webidl @@ -40,6 +40,7 @@ dictionary RTCInboundRTPStreamStats : RTCRTPStreamStats { unsigned long packetsLost; long mozAvSyncDelay; long mozJitterBufferDelay; + long mozRtt; }; dictionary RTCOutboundRTPStreamStats : RTCRTPStreamStats { @@ -58,7 +59,6 @@ dictionary RTCMediaStreamTrackStats : RTCStats { unsigned long framesSent; unsigned long framesReceived; // Only for remoteSource=true unsigned long framesDecoded; - unsigned long first; }; dictionary RTCMediaStreamStats : RTCStats { diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index eac7dda7716..41c7566e2d7 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -245,6 +245,7 @@ WEBIDL_FILES = [ 'MozWakeLock.webidl', 'MutationEvent.webidl', 'MutationObserver.webidl', + 'NativeOSFileInternals.webidl', 'NetDashboard.webidl', 'NetworkOptions.webidl', 'Node.webidl', diff --git a/editor/libeditor/base/nsEditorEventListener.cpp b/editor/libeditor/base/nsEditorEventListener.cpp index 6fbfee4cf4c..7f2aef2bcaa 100644 --- a/editor/libeditor/base/nsEditorEventListener.cpp +++ b/editor/libeditor/base/nsEditorEventListener.cpp @@ -39,7 +39,6 @@ #include "nsIFocusManager.h" // for nsIFocusManager #include "nsIFormControl.h" // for nsIFormControl, etc #include "nsIHTMLEditor.h" // for nsIHTMLEditor -#include "nsINativeKeyBindings.h" // for nsINativeKeyBindings #include "nsINode.h" // for nsINode, ::NODE_IS_EDITABLE, etc #include "nsIPlaintextEditor.h" // for nsIPlaintextEditor, etc #include "nsIPresShell.h" // for nsIPresShell @@ -47,6 +46,7 @@ #include "nsISelectionController.h" // for nsISelectionController, etc #include "nsISelectionPrivate.h" // for nsISelectionPrivate #include "nsITransferable.h" // for kFileMime, kHTMLMime, etc +#include "nsIWidget.h" // for nsIWidget #include "nsLiteralString.h" // for NS_LITERAL_STRING #include "nsPIWindowRoot.h" // for nsPIWindowRoot #include "nsServiceManagerUtils.h" // for do_GetService @@ -61,26 +61,8 @@ class nsPresContext; using namespace mozilla; using namespace mozilla::dom; -static nsINativeKeyBindings *sNativeEditorBindings = nullptr; - -static nsINativeKeyBindings* -GetEditorKeyBindings() -{ - static bool noBindings = false; - if (!sNativeEditorBindings && !noBindings) { - CallGetService(NS_NATIVEKEYBINDINGS_CONTRACTID_PREFIX "editor", - &sNativeEditorBindings); - - if (!sNativeEditorBindings) { - noBindings = true; - } - } - - return sNativeEditorBindings; -} - static void -DoCommandCallback(const char *aCommand, void *aData) +DoCommandCallback(Command aCommand, void* aData) { nsIDocument* doc = static_cast(aData); nsPIDOMWindow* win = doc->GetWindow(); @@ -92,17 +74,19 @@ DoCommandCallback(const char *aCommand, void *aData) return; } + const char* commandStr = WidgetKeyboardEvent::GetCommandStr(aCommand); + nsCOMPtr controller; - root->GetControllerForCommand(aCommand, getter_AddRefs(controller)); + root->GetControllerForCommand(commandStr, getter_AddRefs(controller)); if (!controller) { return; } bool commandEnabled; - nsresult rv = controller->IsCommandEnabled(aCommand, &commandEnabled); + nsresult rv = controller->IsCommandEnabled(commandStr, &commandEnabled); NS_ENSURE_SUCCESS_VOID(rv); if (commandEnabled) { - controller->DoCommand(aCommand); + controller->DoCommand(commandStr); } } @@ -125,12 +109,6 @@ nsEditorEventListener::~nsEditorEventListener() } } -/* static */ void -nsEditorEventListener::ShutDown() -{ - NS_IF_RELEASE(sNativeEditorBindings); -} - nsresult nsEditorEventListener::Connect(nsEditor* aEditor) { @@ -526,19 +504,25 @@ nsEditorEventListener::KeyPress(nsIDOMEvent* aKeyEvent) return NS_OK; } - if (GetEditorKeyBindings() && ShouldHandleNativeKeyBindings(aKeyEvent)) { + if (ShouldHandleNativeKeyBindings(aKeyEvent)) { // Now, ask the native key bindings to handle the event. - // XXX Note that we're not passing the keydown/keyup events to the native - // key bindings, which should be OK since those events are only handled on - // Windows for now, where we don't have native key bindings. WidgetKeyboardEvent* keyEvent = aKeyEvent->GetInternalNSEvent()->AsKeyboardEvent(); MOZ_ASSERT(keyEvent, "DOM key event's internal event must be WidgetKeyboardEvent"); + nsIWidget* widget = keyEvent->widget; + // If the event is created by chrome script, the widget is always nullptr. + if (!widget) { + nsCOMPtr ps = GetPresShell(); + nsPresContext* pc = ps ? ps->GetPresContext() : nullptr; + widget = pc ? pc->GetNearestWidget() : nullptr; + NS_ENSURE_TRUE(widget, NS_OK); + } + nsCOMPtr doc = mEditor->GetDocument(); - bool handled = sNativeEditorBindings->KeyPress(*keyEvent, - DoCommandCallback, - doc); + bool handled = widget->ExecuteNativeKeyBinding( + nsIWidget::NativeKeyBindingsForRichTextEditor, + *keyEvent, DoCommandCallback, doc); if (handled) { aKeyEvent->PreventDefault(); } diff --git a/editor/libeditor/base/nsEditorEventListener.h b/editor/libeditor/base/nsEditorEventListener.h index 7be5f1b74aa..5e01ff7dbed 100644 --- a/editor/libeditor/base/nsEditorEventListener.h +++ b/editor/libeditor/base/nsEditorEventListener.h @@ -59,8 +59,6 @@ public: void SpellCheckIfNeeded(); - static NS_HIDDEN_(void) ShutDown(); - protected: nsresult InstallToEditor(); void UninstallFromEditor(); diff --git a/gfx/2d/Logging.h b/gfx/2d/Logging.h index bc7c19486ff..85e788ce96b 100644 --- a/gfx/2d/Logging.h +++ b/gfx/2d/Logging.h @@ -110,10 +110,12 @@ public: Log &operator <<(const std::string &aLogText) { mMessage << aLogText; return *this; } Log &operator <<(const char aStr[]) { mMessage << static_cast(aStr); return *this; } Log &operator <<(bool aBool) { mMessage << (aBool ? "true" : "false"); return *this; } - Log &operator <<(int32_t aInt) { mMessage << aInt; return *this; } - Log &operator <<(uint32_t aInt) { mMessage << aInt; return *this; } - Log &operator <<(int64_t aLong) { mMessage << aLong; return *this; } - Log &operator <<(uint64_t aLong) { mMessage << aLong; return *this; } + Log &operator <<(int aInt) { mMessage << aInt; return *this; } + Log &operator <<(unsigned int aInt) { mMessage << aInt; return *this; } + Log &operator <<(long aLong) { mMessage << aLong; return *this; } + Log &operator <<(unsigned long aLong) { mMessage << aLong; return *this; } + Log &operator <<(long long aLong) { mMessage << aLong; return *this; } + Log &operator <<(unsigned long long aLong) { mMessage << aLong; return *this; } Log &operator <<(Float aFloat) { mMessage << aFloat; return *this; } Log &operator <<(double aDouble) { mMessage << aDouble; return *this; } template diff --git a/gfx/layers/client/SimpleTiledContentClient.cpp b/gfx/layers/client/SimpleTiledContentClient.cpp index c6bb587e5fc..493115c2917 100644 --- a/gfx/layers/client/SimpleTiledContentClient.cpp +++ b/gfx/layers/client/SimpleTiledContentClient.cpp @@ -203,7 +203,7 @@ SimpleTiledLayerBuffer::GetSurfaceDescriptorTiles() return SurfaceDescriptorTiles(mValidRegion, mPaintedRegion, tiles, mRetainedWidth, mRetainedHeight, - mResolution); + mResolution, mFrameResolution.scale); } bool diff --git a/gfx/layers/client/TiledContentClient.cpp b/gfx/layers/client/TiledContentClient.cpp index 926c9ff0066..12ab766765e 100644 --- a/gfx/layers/client/TiledContentClient.cpp +++ b/gfx/layers/client/TiledContentClient.cpp @@ -608,7 +608,7 @@ ClientTiledLayerBuffer::GetSurfaceDescriptorTiles() } return SurfaceDescriptorTiles(mValidRegion, mPaintedRegion, tiles, mRetainedWidth, mRetainedHeight, - mResolution); + mResolution, mFrameResolution.scale); } void diff --git a/gfx/layers/composite/TiledContentHost.cpp b/gfx/layers/composite/TiledContentHost.cpp index 87f5b097008..dc0ed2c60a6 100644 --- a/gfx/layers/composite/TiledContentHost.cpp +++ b/gfx/layers/composite/TiledContentHost.cpp @@ -34,7 +34,6 @@ TiledLayerBufferComposite::TiledLayerBufferComposite() TiledLayerBufferComposite::TiledLayerBufferComposite(ISurfaceAllocator* aAllocator, const SurfaceDescriptorTiles& aDescriptor, const nsIntRegion& aOldPaintedRegion) - : mFrameResolution(1.0) { mUninitialized = false; mHasDoubleBufferedTiles = false; @@ -43,6 +42,7 @@ TiledLayerBufferComposite::TiledLayerBufferComposite(ISurfaceAllocator* aAllocat mRetainedWidth = aDescriptor.retainedWidth(); mRetainedHeight = aDescriptor.retainedHeight(); mResolution = aDescriptor.resolution(); + mFrameResolution = CSSToScreenScale(aDescriptor.frameResolution()); // Combine any valid content that wasn't already uploaded nsIntRegion oldPaintedRegion(aOldPaintedRegion); diff --git a/gfx/layers/ipc/Axis.cpp b/gfx/layers/ipc/Axis.cpp index 3d08ae2faf1..0d63f151e84 100644 --- a/gfx/layers/ipc/Axis.cpp +++ b/gfx/layers/ipc/Axis.cpp @@ -8,6 +8,7 @@ #include // for fabsf, pow, powf #include // for max #include "AsyncPanZoomController.h" // for AsyncPanZoomController +#include "mozilla/layers/APZCTreeManager.h" // for APZCTreeManager #include "FrameMetrics.h" // for FrameMetrics #include "mozilla/Attributes.h" // for MOZ_FINAL #include "mozilla/Preferences.h" // for Preferences @@ -65,9 +66,9 @@ namespace layers { */ /** - * "apz.max_velocity_pixels_per_ms" + * "apz.max_velocity_inches_per_ms" * - * Maximum velocity in pixels per millisecond. Velocity will be capped at this + * Maximum velocity in inches per millisecond. Velocity will be capped at this * value if a faster fling occurs. Negative values indicate unlimited velocity. * * The default value is -1.0f, set in gfxPrefs.h @@ -84,7 +85,7 @@ Axis::Axis(AsyncPanZoomController* aAsyncPanZoomController) void Axis::UpdateWithTouchAtDevicePoint(int32_t aPos, const TimeDuration& aTimeDelta) { float newVelocity = mAxisLocked ? 0 : (mPos - aPos) / aTimeDelta.ToMilliseconds(); if (gfxPrefs::APZMaxVelocity() > 0.0f) { - newVelocity = std::min(newVelocity, gfxPrefs::APZMaxVelocity()); + newVelocity = std::min(newVelocity, gfxPrefs::APZMaxVelocity() * APZCTreeManager::GetDPI()); } mVelocity = newVelocity; diff --git a/gfx/layers/ipc/LayersMessages.ipdlh b/gfx/layers/ipc/LayersMessages.ipdlh index 38a54b435d4..01379541662 100644 --- a/gfx/layers/ipc/LayersMessages.ipdlh +++ b/gfx/layers/ipc/LayersMessages.ipdlh @@ -286,6 +286,7 @@ struct SurfaceDescriptorTiles { int retainedWidth; int retainedHeight; float resolution; + float frameResolution; }; struct OpUseTiledLayerBuffer { diff --git a/gfx/thebes/gfxFT2Utils.cpp b/gfx/thebes/gfxFT2Utils.cpp index 53b5e577b9c..fac635f0f06 100644 --- a/gfx/thebes/gfxFT2Utils.cpp +++ b/gfx/thebes/gfxFT2Utils.cpp @@ -53,27 +53,29 @@ gfxFT2LockedFace::GetMetrics(gfxFont::Metrics* aMetrics, if (MOZ_UNLIKELY(!mFace)) { // No face. This unfortunate situation might happen if the font // file is (re)moved at the wrong time. - aMetrics->emHeight = mGfxFont->GetStyle()->size; - aMetrics->emAscent = 0.8 * aMetrics->emHeight; - aMetrics->emDescent = 0.2 * aMetrics->emHeight; - aMetrics->maxAscent = aMetrics->emAscent; - aMetrics->maxDescent = aMetrics->maxDescent; - aMetrics->maxHeight = aMetrics->emHeight; + const gfxFloat emHeight = mGfxFont->GetStyle()->size; + aMetrics->emHeight = emHeight; + aMetrics->maxAscent = aMetrics->emAscent = 0.8 * emHeight; + aMetrics->maxDescent = aMetrics->emDescent = 0.2 * emHeight; + aMetrics->maxHeight = emHeight; aMetrics->internalLeading = 0.0; - aMetrics->externalLeading = 0.2 * aMetrics->emHeight; - aSpaceGlyph = 0; - aMetrics->spaceWidth = 0.5 * aMetrics->emHeight; - aMetrics->maxAdvance = aMetrics->spaceWidth; - aMetrics->aveCharWidth = aMetrics->spaceWidth; - aMetrics->zeroOrAveCharWidth = aMetrics->spaceWidth; - aMetrics->xHeight = 0.5 * aMetrics->emHeight; - aMetrics->underlineSize = aMetrics->emHeight / 14.0; - aMetrics->underlineOffset = -aMetrics->underlineSize; - aMetrics->strikeoutOffset = 0.25 * aMetrics->emHeight; - aMetrics->strikeoutSize = aMetrics->underlineSize; - aMetrics->superscriptOffset = aMetrics->xHeight; - aMetrics->subscriptOffset = aMetrics->xHeight; + aMetrics->externalLeading = 0.2 * emHeight; + const gfxFloat spaceWidth = 0.5 * emHeight; + aMetrics->spaceWidth = spaceWidth; + aMetrics->maxAdvance = spaceWidth; + aMetrics->aveCharWidth = spaceWidth; + aMetrics->zeroOrAveCharWidth = spaceWidth; + const gfxFloat xHeight = 0.5 * emHeight; + aMetrics->xHeight = xHeight; + aMetrics->superscriptOffset = xHeight; + aMetrics->subscriptOffset = xHeight; + const gfxFloat underlineSize = emHeight / 14.0; + aMetrics->underlineSize = underlineSize; + aMetrics->underlineOffset = -underlineSize; + aMetrics->strikeoutOffset = 0.25 * emHeight; + aMetrics->strikeoutSize = underlineSize; + *aSpaceGlyph = 0; return; } diff --git a/gfx/thebes/gfxPrefs.h b/gfx/thebes/gfxPrefs.h index 8564ef9b3c6..deb9d6adbb0 100644 --- a/gfx/thebes/gfxPrefs.h +++ b/gfx/thebes/gfxPrefs.h @@ -106,7 +106,7 @@ private: DECL_GFX_PREF(Once, "apz.fling_friction", APZFlingFriction, float, 0.002f); DECL_GFX_PREF(Once, "apz.fling_stopped_threshold", APZFlingStoppedThreshold, float, 0.01f); DECL_GFX_PREF(Once, "apz.max_event_acceleration", APZMaxEventAcceleration, float, 999.0f); - DECL_GFX_PREF(Once, "apz.max_velocity_pixels_per_ms", APZMaxVelocity, float, -1.0f); + DECL_GFX_PREF(Once, "apz.max_velocity_inches_per_ms", APZMaxVelocity, float, -1.0f); DECL_GFX_PREF(Once, "apz.max_velocity_queue_size", APZMaxVelocityQueueSize, uint32_t, 5); DECL_GFX_PREF(Once, "gfx.android.rgb16.force", AndroidRGB16Force, bool, false); diff --git a/js/public/MemoryMetrics.h b/js/public/MemoryMetrics.h index 67566d6a31c..aa428eb1858 100644 --- a/js/public/MemoryMetrics.h +++ b/js/public/MemoryMetrics.h @@ -123,7 +123,6 @@ struct ObjectsExtraSizes macro(Objects, NotLiveGCThing, mallocHeapElementsNonAsmJS) \ macro(Objects, NotLiveGCThing, mallocHeapElementsAsmJS) \ macro(Objects, NotLiveGCThing, nonHeapElementsAsmJS) \ - macro(Objects, NotLiveGCThing, nonHeapElementsMapped) \ macro(Objects, NotLiveGCThing, nonHeapCodeAsmJS) \ macro(Objects, NotLiveGCThing, mallocHeapAsmJSModuleData) \ macro(Objects, NotLiveGCThing, mallocHeapArgumentsData) \ @@ -406,6 +405,7 @@ struct ZoneStats macro(Other, IsLiveGCThing, typeObjectsGCHeap) \ macro(Other, NotLiveGCThing, typeObjectsMallocHeap) \ macro(Other, NotLiveGCThing, typePool) \ + macro(Other, NotLiveGCThing, baselineStubsOptimized) \ ZoneStats() : FOR_EACH_SIZE(ZERO_SIZE) @@ -501,7 +501,6 @@ struct CompartmentStats macro(Other, NotLiveGCThing, scriptsMallocHeapData) \ macro(Other, NotLiveGCThing, baselineData) \ macro(Other, NotLiveGCThing, baselineStubsFallback) \ - macro(Other, NotLiveGCThing, baselineStubsOptimized) \ macro(Other, NotLiveGCThing, ionData) \ macro(Other, NotLiveGCThing, typeInferenceTypeScripts) \ macro(Other, NotLiveGCThing, typeInferenceAllocationSiteTables) \ diff --git a/js/src/builtin/TypedObject.cpp b/js/src/builtin/TypedObject.cpp index 38d2e9ab676..2d07bcfc510 100644 --- a/js/src/builtin/TypedObject.cpp +++ b/js/src/builtin/TypedObject.cpp @@ -733,6 +733,15 @@ UnsizedArrayTypeDescr::dimension(JSContext *cx, unsigned int argc, jsval *vp) return true; } +bool +js::IsTypedObjectArray(JSObject &obj) +{ + if (!obj.is()) + return false; + TypeDescr& d = obj.as().typeDescr(); + return d.is() || d.is(); +} + /********************************* * StructType class */ diff --git a/js/src/builtin/TypedObject.h b/js/src/builtin/TypedObject.h index 25472f68c6e..910e865ac8c 100644 --- a/js/src/builtin/TypedObject.h +++ b/js/src/builtin/TypedObject.h @@ -295,7 +295,8 @@ class X4TypeDescr : public SizedTypeDescr macro_(X4TypeDescr::TYPE_INT32, int32_t, int32) \ macro_(X4TypeDescr::TYPE_FLOAT32, float, float32) -bool IsTypedObjectClass(const Class *clasp); // Defined in TypedArrayObject.h +bool IsTypedObjectClass(const Class *clasp); // Defined below +bool IsTypedObjectArray(JSObject& obj); bool InitializeCommonTypeDescriptorProperties(JSContext *cx, HandleTypeDescr obj, diff --git a/js/src/builtin/TypedObject.js b/js/src/builtin/TypedObject.js index 4bb4e4d3532..981648a57e2 100644 --- a/js/src/builtin/TypedObject.js +++ b/js/src/builtin/TypedObject.js @@ -1510,7 +1510,7 @@ function MapTypedParImplDepth1(inArray, inArrayType, outArrayType, func) { if (outGrainTypeIsComplex) SetTypedObjectValue(outGrainType, outArray, outOffset, r); else - outArray[i] = r; + UnsafePutElements(outArray, i, r); } inOffset += inGrainTypeSize; outOffset += outGrainTypeSize; diff --git a/js/src/gc/Memory.cpp b/js/src/gc/Memory.cpp index b25f4fc13c0..c4a1f8703cc 100644 --- a/js/src/gc/Memory.cpp +++ b/js/src/gc/Memory.cpp @@ -107,21 +107,6 @@ gc::GetPageFaultCount() return pmc.PageFaultCount; } -void * -gc::AllocateMappedObject(int fd, int *new_fd, size_t offset, size_t length, - size_t alignment, size_t header) -{ - // TODO: to be implemented. - return nullptr; -} - -// Deallocate mapped memory for object. -void -gc::DeallocateMappedObject(int fd, void *p, size_t length) -{ - // TODO: to be implemented. -} - #elif defined(SOLARIS) #include @@ -180,28 +165,10 @@ gc::GetPageFaultCount() return 0; } -void * -gc::AllocateMappedObject(int fd, int *new_fd, size_t offset, size_t length, - size_t alignment, size_t header) -{ - // TODO: to be implemented. - return nullptr; -} - -// Deallocate mapped memory for object. -void -gc::DeallocateMappedObject(int fd, void *p, size_t length) -{ - // TODO: to be implemented. -} - #elif defined(XP_UNIX) -#include #include #include -#include -#include #include void @@ -318,90 +285,6 @@ gc::GetPageFaultCount() return usage.ru_majflt; } -void * -gc::AllocateMappedObject(int fd, int *new_fd, size_t offset, size_t length, - size_t alignment, size_t header) -{ -#define NEED_PAGE_ALIGNED 0 - size_t pa_start; // Page aligned starting - size_t pa_end; // Page aligned ending - size_t pa_size; // Total page aligned size - size_t page_size = sysconf(_SC_PAGESIZE); // Page size - bool page_for_header = false; // Do we need an additional page for header? - struct stat st; - uint8_t *buf; - - // Make sure file exists and do sanity check for offset and size. - if (fstat(fd, &st) < 0 || offset >= (size_t) st.st_size || - length == 0 || length > (size_t) st.st_size - offset) - return nullptr; - - // Check for minimal alignment requirement. -#if NEED_PAGE_ALIGNED - alignment = std::max(alignment, page_size); -#endif - if (offset & (alignment - 1)) - return nullptr; - - // Page aligned starting of the offset. - pa_start = offset & ~(page_size - 1); - // Calculate page aligned ending by adding one page to the page aligned - // starting of data end position(offset + length - 1). - pa_end = ((offset + length - 1) & ~(page_size - 1)) + page_size; - pa_size = pa_end - pa_start; - - // Do we need one more page for header? - if (offset - pa_start < header) { - page_for_header = true; - pa_size += page_size; - } - - // Ask for a continuous memory location. - buf = (uint8_t *) MapMemory(pa_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); - if (buf == MAP_FAILED) - return nullptr; - - // Duplicate a new fd for mapping, so each cloned object uses a different fd. - *new_fd = dup(fd); - - // If there's an additional page for header, don't map that page to file. - if (page_for_header) { - buf = (uint8_t *) mmap(buf + page_size, pa_size - page_size, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_FIXED, *new_fd, pa_start); - } else { - buf = (uint8_t *) mmap(buf, pa_size, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_FIXED, *new_fd, pa_start); - } - if (buf == MAP_FAILED) { - close(*new_fd); - return nullptr; - } - - // Reset the data before target file, which we don't need to see. - memset(buf, 0, offset - pa_start); - - // Reset the data after target file, which we don't need to see. - memset(buf + (offset - pa_start) + length, 0, pa_end - (offset + length)); - - return buf + (offset - pa_start) - header; -} - -void -gc::DeallocateMappedObject(int fd, void *p, size_t length) -{ - void *pa_start; // Page aligned starting - size_t page_size = sysconf(_SC_PAGESIZE); // Page size - size_t total_size; // Total allocated size - - // The fd is not needed anymore. - close(fd); - - pa_start = (void *)(uintptr_t(p) & ~(page_size - 1)); - total_size = ((uintptr_t(p) + length) & ~(page_size - 1)) + page_size - uintptr_t(pa_start); - munmap(pa_start, total_size); -} - #else #error "Memory mapping functions are not defined for your OS." #endif diff --git a/js/src/gc/Memory.h b/js/src/gc/Memory.h index c8d2c21e5b9..406888b1f08 100644 --- a/js/src/gc/Memory.h +++ b/js/src/gc/Memory.h @@ -41,19 +41,6 @@ MarkPagesInUse(JSRuntime *rt, void *p, size_t size); size_t GetPageFaultCount(); -// Allocate mapped memory for object from file descriptor, offset and length -// of the file. -// The new_fd is duplicated from original fd, for the purpose of cloned object. -// The offset must be aligned according to alignment requirement. -// An additional page might be allocated depending on offset and header size given. -void * -AllocateMappedObject(int fd, int *new_fd, size_t offset, size_t length, - size_t alignment, size_t header); - -// Deallocate mapped memory of the object. -void -DeallocateMappedObject(int fd, void *p, size_t length); - } // namespace gc } // namespace js diff --git a/js/src/gc/Zone.cpp b/js/src/gc/Zone.cpp index 52bda5c6578..7718cceb633 100644 --- a/js/src/gc/Zone.cpp +++ b/js/src/gc/Zone.cpp @@ -42,6 +42,9 @@ JS::Zone::Zone(JSRuntime *rt) gcGrayRoots(), data(nullptr), types(this) +#ifdef JS_ION + , jitZone_(nullptr) +#endif { /* Ensure that there are no vtables to mess us up here. */ JS_ASSERT(reinterpret_cast(this) == @@ -54,6 +57,10 @@ Zone::~Zone() { if (this == runtimeFromMainThread()->systemZone) runtimeFromMainThread()->systemZone = nullptr; + +#ifdef JS_ION + js_delete(jitZone_); +#endif } bool @@ -169,6 +176,9 @@ void Zone::discardJitCode(FreeOp *fop) { #ifdef JS_ION + if (!jitZone()) + return; + if (isPreservingCode()) { PurgeJITCaches(this); } else { @@ -205,8 +215,7 @@ Zone::discardJitCode(FreeOp *fop) script->resetUseCount(); } - for (CompartmentsInZoneIter comp(this); !comp.done(); comp.next()) - jit::FinishDiscardJitCode(fop, comp); + jitZone()->optimizedStubSpace()->free(); } #endif } @@ -219,6 +228,20 @@ Zone::gcNumber() return usedByExclusiveThread ? 0 : runtimeFromMainThread()->gcNumber; } +#ifdef JS_ION +js::jit::JitZone * +Zone::createJitZone(JSContext *cx) +{ + MOZ_ASSERT(!jitZone_); + + if (!cx->runtime()->getJitRuntime(cx)) + return nullptr; + + jitZone_ = cx->new_(); + return jitZone_; +} +#endif + JS::Zone * js::ZoneOfObject(const JSObject &obj) { diff --git a/js/src/gc/Zone.h b/js/src/gc/Zone.h index 20bd1beb529..e5230fd2573 100644 --- a/js/src/gc/Zone.h +++ b/js/src/gc/Zone.h @@ -18,6 +18,10 @@ namespace js { +namespace jit { +class JitZone; +} + /* * Encapsulates the data needed to perform allocation. Typically there is * precisely one of these per zone (|cx->zone().allocator|). However, in @@ -280,7 +284,9 @@ struct Zone : public JS::shadow::Zone, void discardJitCode(js::FreeOp *fop); - void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, size_t *typePool); + void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, + size_t *typePool, + size_t *baselineStubsOptimized); void setGCLastBytes(size_t lastBytes, js::JSGCInvocationKind gckind); void reduceGCTriggerBytes(size_t amount); @@ -316,6 +322,19 @@ struct Zone : public JS::shadow::Zone, private: void sweepBreakpoints(js::FreeOp *fop); + +#ifdef JS_ION + js::jit::JitZone *jitZone_; + js::jit::JitZone *createJitZone(JSContext *cx); + + public: + js::jit::JitZone *getJitZone(JSContext *cx) { + return jitZone_ ? jitZone_ : createJitZone(cx); + } + js::jit::JitZone *jitZone() { + return jitZone_; + } +#endif }; } /* namespace JS */ diff --git a/js/src/jit-test/tests/ion/bug953164.js b/js/src/jit-test/tests/ion/bug953164.js new file mode 100644 index 00000000000..41868cda8d4 --- /dev/null +++ b/js/src/jit-test/tests/ion/bug953164.js @@ -0,0 +1,20 @@ +function test(a) { + var total = 0; + for (var i=0; i<100; i++) { + + var j = 1; + var b = a.a + if (b) { + j += b.test; + } + total += j; + } + print(total) +} + +var a1 = {"a": {"test":1}}; +var a2 = {"a": undefined}; +test(a1) +test(a2) +test(a1) +test(a2) diff --git a/js/src/jit-test/tests/parallel/bug977853-convert-doubles.js b/js/src/jit-test/tests/parallel/bug977853-convert-doubles.js new file mode 100644 index 00000000000..aeca79f2199 --- /dev/null +++ b/js/src/jit-test/tests/parallel/bug977853-convert-doubles.js @@ -0,0 +1,63 @@ +// Bug 977853 -- Pared down version of script exhibiting negative +// interaction with convert to doubles optimization. See bug for gory +// details. + +if (!getBuildConfiguration().parallelJS) + quit(); + +load(libdir + "parallelarray-helpers.js") + +var numIters = 5; +var golden_output; + +function PJS_div4(v, s) +{ + return [ v[0]/s, v[1]/s, v[2]/s, v[3]/s ]; +} + +function PJS_normalized(v) +{ + var d = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); + d = d > 0.0 ? d : 1.0; + var result = [ v[0]/d, v[1]/d, v[2]/d, 1.0 ]; + return result; +} + +// This is the elemental function passed to mapPar +function PJS_displace(p) +{ + var position = [p[0], p[1], p[2], 1.0]; + var normal = position; + var roughness = 0.025 / 0.35; + normal = PJS_normalized(PJS_div4(normal, roughness)); + return null; +} +var NUM_VERTEX_COMPONENTS = 3; +var initPos, nVertices; +var userData = { + nVertices : 25, //2880, + initPos : [], +}; +function setup() { + for(var k = 0; k < NUM_VERTEX_COMPONENTS*userData.nVertices; k++) { + userData.initPos[k] = k/1000; + } + nVertices = userData.nVertices; + initPos = new Array(nVertices); + for(var i=0, j=0; iruntime()->spsProfiler.enabled() && ionScript->hasSPSInstrumentation()) { + if (ionScript->hasSPSInstrumentation()) { IonSpew(IonSpew_BaselineBailouts, " Setting SPS flag on frame!"); flags |= BaselineFrame::HAS_PUSHED_SPS_FRAME; } diff --git a/js/src/jit/BaselineIC.h b/js/src/jit/BaselineIC.h index e8aff5f1a99..db70926a5a8 100644 --- a/js/src/jit/BaselineIC.h +++ b/js/src/jit/BaselineIC.h @@ -1087,7 +1087,7 @@ class ICStubCompiler ICStubSpace *getStubSpace(JSScript *script) { if (ICStub::CanMakeCalls(kind)) return script->baselineScript()->fallbackStubSpace(); - return script->compartment()->jitCompartment()->optimizedStubSpace(); + return script->zone()->jitZone()->optimizedStubSpace(); } }; diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index a62ee82debc..e494aa29e49 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -972,8 +972,7 @@ CodeGenerator::visitLambda(LLambda *lir) JS_ASSERT(!info.singletonType); - masm.newGCThing(output, tempReg, info.fun, ool->entry(), gc::DefaultHeap); - masm.initGCThing(output, tempReg, info.fun); + masm.createGCObject(output, tempReg, info.fun, gc::DefaultHeap, ool->entry()); emitLambdaInit(output, scopeChain, info); @@ -3337,29 +3336,6 @@ CodeGenerator::visitNewDerivedTypedObject(LNewDerivedTypedObject *lir) return callVM(CreateDerivedTypedObjInfo, lir); } -bool -CodeGenerator::visitNewSlots(LNewSlots *lir) -{ - Register temp1 = ToRegister(lir->temp1()); - Register temp2 = ToRegister(lir->temp2()); - Register temp3 = ToRegister(lir->temp3()); - Register output = ToRegister(lir->output()); - - masm.mov(ImmPtr(GetIonContext()->runtime), temp1); - masm.mov(ImmWord(lir->mir()->nslots()), temp2); - - masm.setupUnalignedABICall(2, temp3); - masm.passABIArg(temp1); - masm.passABIArg(temp2); - masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, NewSlots)); - - masm.testPtr(output, output); - if (!bailoutIf(Assembler::Zero, lir->snapshot())) - return false; - - return true; -} - bool CodeGenerator::visitAtan2D(LAtan2D *lir) { Register temp = ToRegister(lir->temp()); @@ -3408,8 +3384,7 @@ CodeGenerator::visitNewArray(LNewArray *lir) if (!addOutOfLineCode(ool)) return false; - masm.newGCThing(objReg, tempReg, templateObject, ool->entry(), lir->mir()->initialHeap()); - masm.initGCThing(objReg, tempReg, templateObject); + masm.createGCObject(objReg, tempReg, templateObject, lir->mir()->initialHeap(), ool->entry()); masm.bind(ool->rejoin()); return true; @@ -3495,8 +3470,7 @@ CodeGenerator::visitNewObject(LNewObject *lir) if (!addOutOfLineCode(ool)) return false; - masm.newGCThing(objReg, tempReg, templateObject, ool->entry(), lir->mir()->initialHeap()); - masm.initGCThing(objReg, tempReg, templateObject); + masm.createGCObject(objReg, tempReg, templateObject, lir->mir()->initialHeap(), ool->entry()); masm.bind(ool->rejoin()); return true; @@ -3531,14 +3505,13 @@ CodeGenerator::visitNewDeclEnvObject(LNewDeclEnvObject *lir) if (!ool) return false; - masm.newGCThing(objReg, tempReg, templateObj, ool->entry(), gc::DefaultHeap); - masm.initGCThing(objReg, tempReg, templateObj); + masm.createGCObject(objReg, tempReg, templateObj, gc::DefaultHeap, ool->entry()); + masm.bind(ool->rejoin()); return true; } -typedef JSObject *(*NewCallObjectFn)(JSContext *, HandleScript, HandleShape, - HandleTypeObject, HeapSlot *); +typedef JSObject *(*NewCallObjectFn)(JSContext *, HandleScript, HandleShape, HandleTypeObject); static const VMFunction NewCallObjectInfo = FunctionInfo(NewCallObject); @@ -3551,22 +3524,11 @@ CodeGenerator::visitNewCallObject(LNewCallObject *lir) JSObject *templateObj = lir->mir()->templateObject(); // If we have a template object, we can inline call object creation. - OutOfLineCode *ool; - if (lir->slots()->isRegister()) { - ool = oolCallVM(NewCallObjectInfo, lir, - (ArgList(), ImmGCPtr(lir->mir()->block()->info().script()), - ImmGCPtr(templateObj->lastProperty()), - ImmGCPtr(templateObj->hasSingletonType() ? nullptr : templateObj->type()), - ToRegister(lir->slots())), - StoreRegisterTo(objReg)); - } else { - ool = oolCallVM(NewCallObjectInfo, lir, - (ArgList(), ImmGCPtr(lir->mir()->block()->info().script()), - ImmGCPtr(templateObj->lastProperty()), - ImmGCPtr(templateObj->hasSingletonType() ? nullptr : templateObj->type()), - ImmPtr(nullptr)), - StoreRegisterTo(objReg)); - } + OutOfLineCode *ool = oolCallVM(NewCallObjectInfo, lir, + (ArgList(), ImmGCPtr(lir->mir()->block()->info().script()), + ImmGCPtr(templateObj->lastProperty()), + ImmGCPtr(templateObj->hasSingletonType() ? nullptr : templateObj->type())), + StoreRegisterTo(objReg)); if (!ool) return false; @@ -3574,11 +3536,7 @@ CodeGenerator::visitNewCallObject(LNewCallObject *lir) // Objects can only be given singleton types in VM calls. masm.jump(ool->entry()); } else { - masm.newGCThing(objReg, tempReg, templateObj, ool->entry(), gc::DefaultHeap); - masm.initGCThing(objReg, tempReg, templateObj); - - if (lir->slots()->isRegister()) - masm.storePtr(ToRegister(lir->slots()), Address(objReg, JSObject::offsetOfSlots())); + masm.createGCObject(objReg, tempReg, templateObj, gc::DefaultHeap, ool->entry()); } masm.bind(ool->rejoin()); @@ -3595,17 +3553,6 @@ CodeGenerator::visitNewCallObjectPar(LNewCallObjectPar *lir) JSObject *templateObj = lir->mir()->templateObj(); emitAllocateGCThingPar(lir, resultReg, cxReg, tempReg1, tempReg2, templateObj); - - // NB: !lir->slots()->isRegister() implies that there is no slots - // array at all, and the memory is already zeroed when copying - // from the template object - - if (lir->slots()->isRegister()) { - Register slotsReg = ToRegister(lir->slots()); - JS_ASSERT(slotsReg != resultReg); - masm.storePtr(slotsReg, Address(resultReg, JSObject::offsetOfSlots())); - } - return true; } @@ -3664,8 +3611,7 @@ CodeGenerator::visitNewStringObject(LNewStringObject *lir) if (!ool) return false; - masm.newGCThing(output, temp, templateObj, ool->entry(), gc::DefaultHeap); - masm.initGCThing(output, temp, templateObj); + masm.createGCObject(output, temp, templateObj, gc::DefaultHeap, ool->entry()); masm.loadStringLength(input, temp); @@ -3910,7 +3856,7 @@ CodeGenerator::visitCreateThisWithTemplate(LCreateThisWithTemplate *lir) return false; // Allocate. If the FreeList is empty, call to VM, which may GC. - masm.newGCThing(objReg, tempReg, templateObject, ool->entry(), lir->mir()->initialHeap()); + masm.newGCThing(objReg, tempReg, templateObject, lir->mir()->initialHeap(), ool->entry()); // Initialize based on the templateObject. masm.bind(ool->rejoin()); @@ -4979,6 +4925,70 @@ JitCompartment::generateStringConcatStub(JSContext *cx, ExecutionMode mode) return code; } +JitCode * +JitRuntime::generateMallocStub(JSContext *cx) +{ + const Register regReturn = CallTempReg0; + const Register regNBytes = CallTempReg0; + const Register regRuntime = CallTempReg1; + const Register regTemp = CallTempReg1; + + MacroAssembler masm(cx); + + RegisterSet regs = RegisterSet::Volatile(); + regs.takeUnchecked(regNBytes); + masm.PushRegsInMask(regs); + + masm.setupUnalignedABICall(2, regTemp); + masm.movePtr(ImmPtr(cx->runtime()), regRuntime); + masm.passABIArg(regRuntime); + masm.passABIArg(regNBytes); + masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, MallocWrapper)); + masm.storeCallResult(regReturn); + + masm.PopRegsInMask(regs); + masm.ret(); + + Linker linker(masm); + JitCode *code = linker.newCode(cx, JSC::OTHER_CODE); + +#ifdef JS_ION_PERF + writePerfSpewerJitCodeProfile(code, "MallocStub"); +#endif + + return code; +} + +JitCode * +JitRuntime::generateFreeStub(JSContext *cx) +{ + const Register regSlots = CallTempReg0; + const Register regTemp = CallTempReg1; + + MacroAssembler masm(cx); + + RegisterSet regs = RegisterSet::Volatile(); + regs.takeUnchecked(regSlots); + masm.PushRegsInMask(regs); + + masm.setupUnalignedABICall(1, regTemp); + masm.passABIArg(regSlots); + masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, js_free)); + + masm.PopRegsInMask(regs); + + masm.ret(); + + Linker linker(masm); + JitCode *code = linker.newCode(cx, JSC::OTHER_CODE); + +#ifdef JS_ION_PERF + writePerfSpewerJitCodeProfile(code, "FreeStub"); +#endif + + return code; +} + typedef bool (*CharCodeAtFn)(JSContext *, HandleString, int32_t, uint32_t *); static const VMFunction CharCodeAtInfo = FunctionInfo(jit::CharCodeAt); @@ -5632,9 +5642,7 @@ CodeGenerator::visitArrayConcat(LArrayConcat *lir) masm.branch32(Assembler::NotEqual, Address(temp1, ObjectElements::offsetOfLength()), temp2, &fail); // Try to allocate an object. - JSObject *templateObj = lir->mir()->templateObj(); - masm.newGCThing(temp1, temp2, templateObj, &fail, lir->mir()->initialHeap()); - masm.initGCThing(temp1, temp2, templateObj); + masm.createGCObject(temp1, temp2, lir->mir()->templateObj(), lir->mir()->initialHeap(), &fail); masm.jump(&call); { masm.bind(&fail); @@ -6005,8 +6013,7 @@ CodeGenerator::visitRest(LRest *lir) JSObject *templateObject = lir->mir()->templateObject(); Label joinAlloc, failAlloc; - masm.newGCThing(temp2, temp0, templateObject, &failAlloc, gc::DefaultHeap); - masm.initGCThing(temp2, temp0, templateObject); + masm.createGCObject(temp2, temp0, templateObject, gc::DefaultHeap, &failAlloc); masm.jump(&joinAlloc); { masm.bind(&failAlloc); diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index e675d7cd1fe..244bc66b6a5 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -131,7 +131,6 @@ class CodeGenerator : public CodeGeneratorSpecific bool visitCallDirectEvalV(LCallDirectEvalV *lir); bool visitDoubleToInt32(LDoubleToInt32 *lir); bool visitFloat32ToInt32(LFloat32ToInt32 *lir); - bool visitNewSlots(LNewSlots *lir); bool visitNewArrayCallVM(LNewArray *lir); bool visitNewArray(LNewArray *lir); bool visitOutOfLineNewArray(OutOfLineNewArray *ool); diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index 777729448e1..1cded87568d 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -268,6 +268,16 @@ JitRuntime::initialize(JSContext *cx) if (!shapePreBarrier_) return false; + IonSpew(IonSpew_Codegen, "# Emitting malloc stub"); + mallocStub_ = generateMallocStub(cx); + if (!mallocStub_) + return false; + + IonSpew(IonSpew_Codegen, "# Emitting free stub"); + freeStub_ = generateFreeStub(cx); + if (!freeStub_) + return false; + IonSpew(IonSpew_Codegen, "# Emitting VM function wrappers"); for (VMFunction *fun = VMFunction::functions; fun; fun = fun->next) { if (!generateVMWrapper(cx, *fun)) @@ -455,9 +465,8 @@ jit::RequestInterruptForIonCode(JSRuntime *rt, JSRuntime::InterruptMode mode) } } -JitCompartment::JitCompartment(JitRuntime *rt) - : rt(rt), - stubCodes_(nullptr), +JitCompartment::JitCompartment() + : stubCodes_(nullptr), baselineCallReturnAddr_(nullptr), baselineGetPropReturnAddr_(nullptr), baselineSetPropReturnAddr_(nullptr), @@ -562,7 +571,7 @@ JitCompartment::mark(JSTracer *trc, JSCompartment *compartment) FinishAllOffThreadCompilations(compartment); // Free temporary OSR buffer. - rt->freeOsrTempData(); + trc->runtime->jitRuntime()->freeOsrTempData(); } void @@ -2701,14 +2710,6 @@ jit::FinishInvalidation(FreeOp *fop, JSScript *script) FinishInvalidationOf(fop, script, script->parallelIonScript(), true); } -void -jit::FinishDiscardJitCode(FreeOp *fop, JSCompartment *comp) -{ - // Free optimized baseline stubs. - if (comp->jitCompartment()) - comp->jitCompartment()->optimizedStubSpace()->free(); -} - void jit::MarkValueFromIon(JSRuntime *rt, Value *vp) { @@ -2918,13 +2919,4 @@ AutoDebugModeInvalidation::~AutoDebugModeInvalidation() script->baselineScript()->resetActive(); } } - - if (comp_) { - FinishDiscardJitCode(fop, comp_); - } else { - for (CompartmentsInZoneIter comp(zone_); !comp.done(); comp.next()) { - if (comp->principals) - FinishDiscardJitCode(fop, comp); - } - } } diff --git a/js/src/jit/IonAnalysis.cpp b/js/src/jit/IonAnalysis.cpp index 08f8e8fd38c..facc1fa65af 100644 --- a/js/src/jit/IonAnalysis.cpp +++ b/js/src/jit/IonAnalysis.cpp @@ -1647,42 +1647,31 @@ TryEliminateTypeBarrierFromTest(MTypeBarrier *barrier, bool filtersNull, bool fi input = inputUnbox->input(); } - if (test->getOperand(0) == input && direction == TRUE_BRANCH) { - *eliminated = true; - if (inputUnbox) - inputUnbox->makeInfallible(); - barrier->replaceAllUsesWith(barrier->input()); - return; - } + MDefinition *subject = nullptr; + bool removeUndefined; + bool removeNull; + test->filtersUndefinedOrNull(direction == TRUE_BRANCH, &subject, &removeUndefined, &removeNull); - if (!test->getOperand(0)->isCompare()) + // The Test doesn't filter undefined nor null. + if (!subject) return; - MCompare *compare = test->getOperand(0)->toCompare(); - MCompare::CompareType compareType = compare->compareType(); - - if (compareType != MCompare::Compare_Undefined && compareType != MCompare::Compare_Null) - return; - if (compare->getOperand(0) != input) + // Make sure the subject equals the input to the TypeBarrier. + if (subject != input) return; - JSOp op = compare->jsop(); - JS_ASSERT(op == JSOP_EQ || op == JSOP_STRICTEQ || - op == JSOP_NE || op == JSOP_STRICTNE); - - if ((direction == TRUE_BRANCH) != (op == JSOP_NE || op == JSOP_STRICTNE)) + // When the TypeBarrier filters undefined, the test must at least also do, + // this, before the TypeBarrier can get removed. + if (!removeUndefined && filtersUndefined) return; - // A test 'if (x.f != null)' or 'if (x.f != undefined)' filters both null - // and undefined. If strict equality is used, only the specified rhs is - // tested for. - if (op == JSOP_STRICTEQ || op == JSOP_STRICTNE) { - if (compareType == MCompare::Compare_Undefined && !filtersUndefined) - return; - if (compareType == MCompare::Compare_Null && !filtersNull) - return; - } + // When the TypeBarrier filters null, the test must at least also do, + // this, before the TypeBarrier can get removed. + if (!removeNull && filtersNull) + return; + // Eliminate the TypeBarrier. The possible TypeBarrier unboxing is kept, + // but made infallible. *eliminated = true; if (inputUnbox) inputUnbox->makeInfallible(); diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 2a94b4c7595..377c12e5da6 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -189,18 +189,21 @@ GetJumpOffset(jsbytecode *pc) } IonBuilder::CFGState -IonBuilder::CFGState::If(jsbytecode *join, MBasicBlock *ifFalse) +IonBuilder::CFGState::If(jsbytecode *join, MTest *test) { CFGState state; state.state = IF_TRUE; state.stopAt = join; - state.branch.ifFalse = ifFalse; + state.branch.ifFalse = test->ifFalse(); + state.branch.test = test; return state; } IonBuilder::CFGState -IonBuilder::CFGState::IfElse(jsbytecode *trueEnd, jsbytecode *falseEnd, MBasicBlock *ifFalse) +IonBuilder::CFGState::IfElse(jsbytecode *trueEnd, jsbytecode *falseEnd, MTest *test) { + MBasicBlock *ifFalse = test->ifFalse(); + CFGState state; // If the end of the false path is the same as the start of the // false path, then the "else" block is empty and we can devolve @@ -213,6 +216,7 @@ IonBuilder::CFGState::IfElse(jsbytecode *trueEnd, jsbytecode *falseEnd, MBasicBl state.stopAt = trueEnd; state.branch.falseEnd = falseEnd; state.branch.ifFalse = ifFalse; + state.branch.test = test; return state; } @@ -223,6 +227,7 @@ IonBuilder::CFGState::AndOr(jsbytecode *join, MBasicBlock *joinStart) state.state = AND_OR; state.stopAt = join; state.branch.ifFalse = joinStart; + state.branch.test = nullptr; return state; } @@ -1885,6 +1890,10 @@ IonBuilder::processIfElseTrueEnd(CFGState &state) pc = state.branch.ifFalse->pc(); setCurrentAndSpecializePhis(state.branch.ifFalse); graph().moveBlockToEnd(current); + + if (state.branch.test) + filterTypesAtTest(state.branch.test); + return ControlStatus_Jumped; } @@ -3008,6 +3017,64 @@ IonBuilder::tableSwitch(JSOp op, jssrcnote *sn) return ControlStatus_Jumped; } +bool +IonBuilder::filterTypesAtTest(MTest *test) +{ + JS_ASSERT(test->ifTrue() == current || test->ifFalse() == current); + + bool trueBranch = test->ifTrue() == current; + + MDefinition *subject = nullptr; + bool removeUndefined; + bool removeNull; + + test->filtersUndefinedOrNull(trueBranch, &subject, &removeUndefined, &removeNull); + + // The test filters no undefined or null. + if (!subject) + return true; + + // There is no TypeSet that can get filtered. + if (!subject->resultTypeSet()) + return true; + + // Only do this optimization if the typeset does contains null or undefined. + if ((!(removeUndefined && subject->resultTypeSet()->hasType(types::Type::UndefinedType())) && + !(removeNull && subject->resultTypeSet()->hasType(types::Type::NullType())))) + { + return true; + } + + // Find all values on the stack that correspond to the subject + // and replace it with a MIR with filtered TypeSet information. + // Create the replacement MIR lazily upon first occurence. + MDefinition *replace = nullptr; + for (uint32_t i = 0; i < current->stackDepth(); i++) { + if (current->getSlot(i) != subject) + continue; + + // Create replacement MIR with filtered TypesSet. + if (!replace) { + types::TemporaryTypeSet *type = + subject->resultTypeSet()->filter(alloc_->lifoAlloc(), removeUndefined, + removeNull); + if (!type) + return false; + + replace = ensureDefiniteTypeSet(subject, type); + // Make sure we don't hoist it above the MTest, we can use the + // 'dependency' of an MInstruction. This is normally used by + // Alias Analysis, but won't get overwritten, since this + // instruction doesn't have an AliasSet. + replace->setDependency(test); + } + + current->setSlot(i, replace); + } + + return true; +} + bool IonBuilder::jsop_label() { @@ -3426,7 +3493,7 @@ IonBuilder::jsop_ifeq(JSOp op) // IF case, the IFEQ offset is the join point. switch (SN_TYPE(sn)) { case SRC_IF: - if (!cfgStack_.append(CFGState::If(falseStart, ifFalse))) + if (!cfgStack_.append(CFGState::If(falseStart, test))) return false; break; @@ -3445,7 +3512,7 @@ IonBuilder::jsop_ifeq(JSOp op) JS_ASSERT(falseEnd > trueEnd); JS_ASSERT(falseEnd >= falseStart); - if (!cfgStack_.append(CFGState::IfElse(trueEnd, falseEnd, ifFalse))) + if (!cfgStack_.append(CFGState::IfElse(trueEnd, falseEnd, test))) return false; break; } @@ -3458,6 +3525,9 @@ IonBuilder::jsop_ifeq(JSOp op) // it's the next instruction. setCurrentAndSpecializePhis(ifTrue); + // Filter the types in the true branch. + filterTypesAtTest(test); + return true; } @@ -4597,23 +4667,9 @@ IonBuilder::createCallObject(MDefinition *callee, MDefinition *scope) // creation. CallObject *templateObj = inspector->templateCallObject(); - // If the CallObject needs dynamic slots, allocate those now. - MInstruction *slots; - if (templateObj->hasDynamicSlots()) { - size_t nslots = JSObject::dynamicSlotsCount(templateObj->numFixedSlots(), - templateObj->lastProperty()->slotSpan(templateObj->getClass()), - templateObj->getClass()); - slots = MNewSlots::New(alloc(), nslots); - } else { - slots = MConstant::New(alloc(), NullValue()); - } - current->add(slots); - - // Allocate the actual object. It is important that no intervening - // instructions could potentially bailout, thus leaking the dynamic slots - // pointer. Run-once scripts need a singleton type, so always do a VM call - // in such cases. - MNewCallObject *callObj = MNewCallObject::New(alloc(), templateObj, script()->treatAsRunOnce(), slots); + // Allocate the object. Run-once scripts need a singleton type, so always do + // a VM call in such cases. + MNewCallObject *callObj = MNewCallObject::New(alloc(), templateObj, script()->treatAsRunOnce()); current->add(callObj); // Initialize the object's reserved slots. No post barrier is needed here, @@ -4622,14 +4678,20 @@ IonBuilder::createCallObject(MDefinition *callee, MDefinition *scope) current->add(MStoreFixedSlot::New(alloc(), callObj, CallObject::calleeSlot(), callee)); // Initialize argument slots. + MSlots *slots = nullptr; for (AliasedFormalIter i(script()); i; i++) { unsigned slot = i.scopeSlot(); unsigned formal = i.frameIndex(); MDefinition *param = current->getSlot(info().argSlotUnchecked(formal)); - if (slot >= templateObj->numFixedSlots()) + if (slot >= templateObj->numFixedSlots()) { + if (!slots) { + slots = MSlots::New(alloc(), callObj); + current->add(slots); + } current->add(MStoreSlot::New(alloc(), slots, slot - templateObj->numFixedSlots(), param)); - else + } else { current->add(MStoreFixedSlot::New(alloc(), callObj, slot, param)); + } } return callObj; @@ -6242,6 +6304,26 @@ IonBuilder::ensureDefiniteType(MDefinition *def, JSValueType definiteType) return replace; } +MDefinition * +IonBuilder::ensureDefiniteTypeSet(MDefinition *def, types::TemporaryTypeSet *types) +{ + // We cannot arbitrarily add a typeset to a definition. It can be shared + // in another path. So we always need to create a new MIR. + + // Use ensureDefiniteType to do unboxing. If that happened the type can + // be added on the newly created unbox operation. + MDefinition *replace = ensureDefiniteType(def, types->getKnownTypeTag()); + if (replace != def) { + replace->setResultTypeSet(types); + return replace; + } + + // Create a NOP mir instruction to filter the typeset. + MFilterTypeSet *filter = MFilterTypeSet::New(alloc(), def, types); + current->add(filter); + return filter; +} + static size_t NumFixedSlots(JSObject *object) { @@ -7159,6 +7241,36 @@ IonBuilder::jsop_getelem_dense(MDefinition *obj, MDefinition *index) JS_ASSERT(knownType == JSVAL_TYPE_UNKNOWN); } + // If the array is being converted to doubles, but we've observed + // just int, substitute a type set of int+double into the observed + // type set. The reason for this is that, in the + // interpreter+baseline, such arrays may consist of mixed + // ints/doubles, but when we enter ion code, we will be coercing + // all inputs to doubles. Therefore, the type barrier checking for + // just int is highly likely (*almost* guaranteed) to fail sooner + // or later. Essentially, by eagerly coercing to double, ion is + // making the observed types outdated. To compensate for this, we + // substitute a broader observed type set consisting of both ints + // and doubles. There is perhaps a tradeoff here, so we limit this + // optimization to parallel code, where it is needed to prevent + // perpetual bailouts in some extreme cases. (Bug 977853) + // + // NB: we have not added a MConvertElementsToDoubles MIR, so we + // cannot *assume* the result is a double. + if (executionMode == ParallelExecution && + barrier && + types->getKnownTypeTag() == JSVAL_TYPE_INT32 && + objTypes && + objTypes->convertDoubleElements(constraints()) == types::TemporaryTypeSet::AlwaysConvertToDoubles) + { + // Note: double implies int32 as well for typesets + types = alloc_->lifoAlloc()->new_(types::Type::DoubleType()); + if (!types) + return false; + + barrier = false; // Don't need a barrier anymore + } + if (knownType != JSVAL_TYPE_UNKNOWN) load->setResultType(MIRTypeFromValueType(knownType)); @@ -7460,7 +7572,7 @@ IonBuilder::setElemTryScalarElemOfTypedObject(bool *emitted, } // Store the element - if (!storeScalarTypedObjectValue(obj, indexAsByteOffset, elemType, canBeNeutered, value)) + if (!storeScalarTypedObjectValue(obj, indexAsByteOffset, elemType, canBeNeutered, false, value)) return false; current->push(value); @@ -7492,6 +7604,10 @@ IonBuilder::setElemTryTypedStatic(bool *emitted, MDefinition *object, return true; TypedArrayObject *tarr = &tarrObj->as(); + + if (gc::IsInsideNursery(tarr->runtimeFromMainThread(), tarr->viewData())) + return true; + ArrayBufferView::ViewType viewType = (ArrayBufferView::ViewType) tarr->type(); MDefinition *ptr = convertShiftToMaskForStaticTypedArray(index, viewType); @@ -7806,6 +7922,28 @@ IonBuilder::jsop_setelem_typed(ScalarTypeDescr::Type arrayType, return resumeAfter(ins); } +bool +IonBuilder::jsop_setelem_typed_object(ScalarTypeDescr::Type arrayType, + SetElemSafety safety, + bool racy, + MDefinition *object, MDefinition *index, MDefinition *value) +{ + JS_ASSERT(safety == SetElem_Unsafe); // Can be fixed, but there's been no reason to as of yet + + MInstruction *int_index = MToInt32::New(alloc(), index); + current->add(int_index); + + size_t elemSize = ScalarTypeDescr::alignment(arrayType); + MMul *byteOffset = MMul::New(alloc(), int_index, constantInt(elemSize), + MIRType_Int32, MMul::Integer); + current->add(byteOffset); + + if (!storeScalarTypedObjectValue(object, byteOffset, arrayType, false, racy, value)) + return false; + + return true; +} + bool IonBuilder::jsop_length() { @@ -8983,7 +9121,7 @@ IonBuilder::setPropTryScalarPropOfTypedObject(bool *emitted, // OK! Perform the optimization. - if (!storeScalarTypedObjectValue(obj, constantInt(fieldOffset), fieldType, true, value)) + if (!storeScalarTypedObjectValue(obj, constantInt(fieldOffset), fieldType, true, false, value)) return false; current->push(value); @@ -9999,15 +10137,16 @@ IonBuilder::typeObjectForFieldFromStructType(MDefinition *typeObj, bool IonBuilder::storeScalarTypedObjectValue(MDefinition *typedObj, - MDefinition *offset, + MDefinition *byteOffset, ScalarTypeDescr::Type type, bool canBeNeutered, + bool racy, MDefinition *value) { // Find location within the owner object. MDefinition *elements, *scaledOffset; size_t alignment = ScalarTypeDescr::alignment(type); - loadTypedObjectElements(typedObj, offset, alignment, canBeNeutered, + loadTypedObjectElements(typedObj, byteOffset, alignment, canBeNeutered, &elements, &scaledOffset); // Clamp value to [0, 255] when type is Uint8Clamped @@ -10020,6 +10159,8 @@ IonBuilder::storeScalarTypedObjectValue(MDefinition *typedObj, MStoreTypedArrayElement *store = MStoreTypedArrayElement::New(alloc(), elements, scaledOffset, toWrite, type); + if (racy) + store->setRacy(); current->add(store); return true; diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 0c5dc96bfe2..6b7b2b4d116 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -110,6 +110,7 @@ class IonBuilder : public MIRGenerator MBasicBlock *ifFalse; jsbytecode *falseEnd; MBasicBlock *ifTrue; // Set when the end of the true path is reached. + MTest *test; } branch; struct { // Common entry point. @@ -200,8 +201,8 @@ class IonBuilder : public MIRGenerator } } - static CFGState If(jsbytecode *join, MBasicBlock *ifFalse); - static CFGState IfElse(jsbytecode *trueEnd, jsbytecode *falseEnd, MBasicBlock *ifFalse); + static CFGState If(jsbytecode *join, MTest *test); + static CFGState IfElse(jsbytecode *trueEnd, jsbytecode *falseEnd, MTest *test); static CFGState AndOr(jsbytecode *join, MBasicBlock *joinStart); static CFGState TableSwitch(jsbytecode *exitpc, MTableSwitch *ins); static CFGState CondSwitch(IonBuilder *builder, jsbytecode *exitpc, jsbytecode *defaultTarget); @@ -337,6 +338,9 @@ class IonBuilder : public MIRGenerator MConstant *constant(const Value &v); MConstant *constantInt(int32_t i); + // Filter the type information at tests + bool filterTypesAtTest(MTest *test); + // Add a guard which ensure that the set of type which goes through this // generated code correspond to the observed types for the bytecode. bool pushTypeBarrier(MDefinition *def, types::TemporaryTypeSet *observed, bool needBarrier); @@ -352,6 +356,9 @@ class IonBuilder : public MIRGenerator // added to |current| in this case. MDefinition *ensureDefiniteType(MDefinition* def, JSValueType definiteType); + // Creates a MDefinition based on the given def improved with type as TypeSet. + MDefinition *ensureDefiniteTypeSet(MDefinition* def, types::TemporaryTypeSet *types); + JSObject *getSingletonPrototype(JSFunction *target); MDefinition *createThisScripted(MDefinition *callee); @@ -459,6 +466,7 @@ class IonBuilder : public MIRGenerator MDefinition *offset, ScalarTypeDescr::Type type, bool canBeNeutered, + bool racy, MDefinition *value); bool checkTypedObjectIndexInBounds(size_t elemSize, MDefinition *obj, @@ -565,6 +573,9 @@ class IonBuilder : public MIRGenerator bool jsop_setelem_typed(ScalarTypeDescr::Type arrayType, SetElemSafety safety, MDefinition *object, MDefinition *index, MDefinition *value); + bool jsop_setelem_typed_object(ScalarTypeDescr::Type arrayType, + SetElemSafety safety, bool racy, + MDefinition *object, MDefinition *index, MDefinition *value); bool jsop_length(); bool jsop_length_fastPath(); bool jsop_arguments(); @@ -667,6 +678,8 @@ class IonBuilder : public MIRGenerator bool inlineUnsafeSetDenseArrayElement(CallInfo &callInfo, uint32_t base); bool inlineUnsafeSetTypedArrayElement(CallInfo &callInfo, uint32_t base, ScalarTypeDescr::Type arrayType); + bool inlineUnsafeSetTypedObjectArrayElement(CallInfo &callInfo, uint32_t base, + ScalarTypeDescr::Type arrayType); InliningStatus inlineNewDenseArray(CallInfo &callInfo); InliningStatus inlineNewDenseArrayForSequentialExecution(CallInfo &callInfo); InliningStatus inlineNewDenseArrayForParallelExecution(CallInfo &callInfo); @@ -680,6 +693,8 @@ class IonBuilder : public MIRGenerator // TypedObject intrinsics. InliningStatus inlineObjectIsTypeDescr(CallInfo &callInfo); + bool elementAccessIsTypedObjectArrayOfScalarType(MDefinition* obj, MDefinition* id, + ScalarTypeDescr::Type *arrayType); // Utility intrinsics. InliningStatus inlineIsCallable(CallInfo &callInfo); diff --git a/js/src/jit/IonLinker.h b/js/src/jit/IonLinker.h index 4caf86b4645..4af0d01dbb5 100644 --- a/js/src/jit/IonLinker.h +++ b/js/src/jit/IonLinker.h @@ -78,7 +78,7 @@ class Linker template JitCode *newCode(JSContext *cx, JSC::CodeKind kind) { - return newCode(cx, cx->compartment()->jitCompartment()->execAlloc(), kind); + return newCode(cx, cx->runtime()->jitRuntime()->execAlloc(), kind); } JitCode *newCodeForIonScript(JSContext *cx) { diff --git a/js/src/jit/IonMacroAssembler.cpp b/js/src/jit/IonMacroAssembler.cpp index a201e15eb1c..2988a666177 100644 --- a/js/src/jit/IonMacroAssembler.cpp +++ b/js/src/jit/IonMacroAssembler.cpp @@ -633,14 +633,11 @@ MacroAssembler::clampDoubleToUint8(FloatRegister input, Register output) #endif } +// Inlined version of gc::CheckAllocatorState that checks the bare essentials +// and bails for anything that cannot be handled with our jit allocators. void -MacroAssembler::newGCThing(Register result, Register temp, gc::AllocKind allocKind, Label *fail, - gc::InitialHeap initialHeap /* = gc::DefaultHeap */) +MacroAssembler::checkAllocatorState(Label *fail) { - // Inlined equivalent of js::gc::NewGCThing() without failure case handling. - - int thingSize = int(gc::Arena::thingSize(allocKind)); - #ifdef JS_GC_ZEAL // Don't execute the inline path if gcZeal is active. branch32(Assembler::NotEqual, @@ -652,29 +649,66 @@ MacroAssembler::newGCThing(Register result, Register temp, gc::AllocKind allocKi // as the metadata to use for the object may vary between executions of the op. if (GetIonContext()->compartment->hasObjectMetadataCallback()) jump(fail); +} +// Inline version of ShouldNurseryAllocate. +bool +MacroAssembler::shouldNurseryAllocate(gc::AllocKind allocKind, gc::InitialHeap initialHeap) +{ #ifdef JSGC_GENERATIONAL - // Always use nursery allocation if it is possible to do so. The jit - // assumes a nursery pointer is returned to avoid barriers. - if (allocKind <= gc::FINALIZE_OBJECT_LAST && initialHeap != gc::TenuredHeap) { - // Inline Nursery::allocate. No explicit check for nursery.isEnabled() - // is needed, as the comparison with the nursery's end will always fail - // in such cases. - const Nursery &nursery = GetIonContext()->runtime->gcNursery(); - loadPtr(AbsoluteAddress(nursery.addressOfPosition()), result); - computeEffectiveAddress(Address(result, thingSize), temp); - branchPtr(Assembler::BelowOrEqual, AbsoluteAddress(nursery.addressOfCurrentEnd()), temp, fail); - storePtr(temp, AbsoluteAddress(nursery.addressOfPosition())); - return; - } + // Note that Ion elides barriers on writes to objects know to be in the + // nursery, so any allocation that can be made into the nursery must be made + // into the nursery, even if the nursery is disabled. At runtime these will + // take the out-of-line path, which is required to insert a barrier for the + // initializing writes. + return IsNurseryAllocable(allocKind) && initialHeap != gc::TenuredHeap; +#else + return false; +#endif +} + +// Inline version of Nursery::allocateObject. +void +MacroAssembler::nurseryAllocate(Register result, Register slots, gc::AllocKind allocKind, + size_t nDynamicSlots, gc::InitialHeap initialHeap, Label *fail) +{ +#ifdef JSGC_GENERATIONAL + JS_ASSERT(IsNurseryAllocable(allocKind)); + JS_ASSERT(initialHeap != gc::TenuredHeap); + + // This allocation site is requesting too many dynamic slots. Several + // allocation sites assume that nursery allocation will succeed to + // avoid needing barriers later. Ensure these sites limit their slot + // requests appropriately. + JS_ASSERT(nDynamicSlots < Nursery::MaxNurserySlots); + + // No explicit check for nursery.isEnabled() is needed, as the comparison + // with the nursery's end will always fail in such cases. + const Nursery &nursery = GetIonContext()->runtime->gcNursery(); + Register temp = slots; + int thingSize = int(gc::Arena::thingSize(allocKind)); + int totalSize = thingSize + nDynamicSlots * sizeof(HeapSlot); + loadPtr(AbsoluteAddress(nursery.addressOfPosition()), result); + computeEffectiveAddress(Address(result, totalSize), temp); + branchPtr(Assembler::BelowOrEqual, AbsoluteAddress(nursery.addressOfCurrentEnd()), temp, fail); + storePtr(temp, AbsoluteAddress(nursery.addressOfPosition())); + + if (nDynamicSlots) + computeEffectiveAddress(Address(result, thingSize), slots); #endif // JSGC_GENERATIONAL +} +// Inlined version of FreeSpan::allocate. +void +MacroAssembler::freeSpanAllocate(Register result, Register temp, gc::AllocKind allocKind, Label *fail) +{ CompileZone *zone = GetIonContext()->compartment->zone(); + int thingSize = int(gc::Arena::thingSize(allocKind)); - // Inline FreeSpan::allocate. - // There is always exactly one FreeSpan per allocKind per JSCompartment. - // If a FreeSpan is replaced, its members are updated in the freeLists table, - // which the code below always re-reads. + // Load FreeSpan::first of |zone|'s freeLists for |allocKind|. If there is + // no room remaining in the span, we bail to finish the allocation. The + // interpreter will call |refillFreeLists|, setting up a new FreeSpan so + // that we can continue allocating in the jit. loadPtr(AbsoluteAddress(zone->addressOfFreeListFirst(allocKind)), result); branchPtr(Assembler::BelowOrEqual, AbsoluteAddress(zone->addressOfFreeListLast(allocKind)), result, fail); computeEffectiveAddress(Address(result, thingSize), temp); @@ -682,25 +716,118 @@ MacroAssembler::newGCThing(Register result, Register temp, gc::AllocKind allocKi } void -MacroAssembler::newGCThing(Register result, Register temp, JSObject *templateObject, Label *fail, - gc::InitialHeap initialHeap) +MacroAssembler::callMallocStub(size_t nbytes, Register result, Label *fail) +{ + // This register must match the one in JitRuntime::generateMallocStub. + const Register regNBytes = CallTempReg0; + + JS_ASSERT(nbytes > 0); + JS_ASSERT(nbytes <= INT32_MAX); + + if (regNBytes != result) + push(regNBytes); + move32(Imm32(nbytes), regNBytes); + call(GetIonContext()->runtime->jitRuntime()->mallocStub()); + if (regNBytes != result) { + movePtr(regNBytes, result); + pop(regNBytes); + } + branchTest32(Assembler::Zero, result, result, fail); +} + +void +MacroAssembler::callFreeStub(Register slots) +{ + // This register must match the one in JitRuntime::generateFreeStub. + const Register regSlots = CallTempReg0; + + push(regSlots); + movePtr(slots, regSlots); + call(GetIonContext()->runtime->jitRuntime()->freeStub()); + pop(regSlots); +} + +// Inlined equivalent of gc::AllocateObject, without failure case handling. +void +MacroAssembler::allocateObject(Register result, Register slots, gc::AllocKind allocKind, + uint32_t nDynamicSlots, gc::InitialHeap initialHeap, Label *fail) { - gc::AllocKind allocKind = templateObject->tenuredGetAllocKind(); JS_ASSERT(allocKind >= gc::FINALIZE_OBJECT0 && allocKind <= gc::FINALIZE_OBJECT_LAST); - newGCThing(result, temp, allocKind, fail, initialHeap); + checkAllocatorState(fail); + + if (shouldNurseryAllocate(allocKind, initialHeap)) + return nurseryAllocate(result, slots, allocKind, nDynamicSlots, initialHeap, fail); + + if (!nDynamicSlots) + return freeSpanAllocate(result, slots, allocKind, fail); + + callMallocStub(nDynamicSlots * sizeof(HeapValue), slots, fail); + + Label failAlloc; + Label success; + + push(slots); + freeSpanAllocate(result, slots, allocKind, &failAlloc); + pop(slots); + jump(&success); + + bind(&failAlloc); + pop(slots); + callFreeStub(slots); + jump(fail); + + bind(&success); +} + +void +MacroAssembler::newGCThing(Register result, Register temp, JSObject *templateObj, + gc::InitialHeap initialHeap, Label *fail) +{ + // This method does not initialize the object: if external slots get + // allocated into |temp|, there is no easy way for us to ensure the caller + // frees them. Instead just assert this case does not happen. + JS_ASSERT(!templateObj->numDynamicSlots()); + + gc::AllocKind allocKind = templateObj->tenuredGetAllocKind(); + JS_ASSERT(allocKind >= gc::FINALIZE_OBJECT0 && allocKind <= gc::FINALIZE_OBJECT_LAST); + + allocateObject(result, temp, allocKind, templateObj->numDynamicSlots(), initialHeap, fail); +} + +void +MacroAssembler::createGCObject(Register obj, Register temp, JSObject *templateObj, + gc::InitialHeap initialHeap, Label *fail) +{ + uint32_t nDynamicSlots = templateObj->numDynamicSlots(); + gc::AllocKind allocKind = templateObj->tenuredGetAllocKind(); + JS_ASSERT(allocKind >= gc::FINALIZE_OBJECT0 && allocKind <= gc::FINALIZE_OBJECT_LAST); + + allocateObject(obj, temp, allocKind, nDynamicSlots, initialHeap, fail); + initGCThing(obj, temp, templateObj); +} + + +// Inlined equivalent of gc::AllocateNonObject, without failure case handling. +// Non-object allocation does not need to worry about slots, so can take a +// simpler path. +void +MacroAssembler::allocateNonObject(Register result, Register temp, gc::AllocKind allocKind, Label *fail) +{ + checkAllocatorState(fail); + freeSpanAllocate(result, temp, allocKind, fail); } void MacroAssembler::newGCString(Register result, Register temp, Label *fail) { - newGCThing(result, temp, js::gc::FINALIZE_STRING, fail); + allocateNonObject(result, temp, js::gc::FINALIZE_STRING, fail); } void MacroAssembler::newGCShortString(Register result, Register temp, Label *fail) { - newGCThing(result, temp, js::gc::FINALIZE_SHORT_STRING, fail); + allocateNonObject(result, temp, js::gc::FINALIZE_SHORT_STRING, fail); } void @@ -756,6 +883,7 @@ MacroAssembler::newGCThingPar(Register result, Register cx, Register tempReg1, R { gc::AllocKind allocKind = templateObject->tenuredGetAllocKind(); JS_ASSERT(allocKind >= gc::FINALIZE_OBJECT0 && allocKind <= gc::FINALIZE_OBJECT_LAST); + JS_ASSERT(!templateObject->numDynamicSlots()); newGCThingPar(result, cx, tempReg1, tempReg2, allocKind, fail); } @@ -775,7 +903,7 @@ MacroAssembler::newGCShortStringPar(Register result, Register cx, Register tempR } void -MacroAssembler::copySlotsFromTemplate(Register obj, Register temp, const JSObject *templateObj, +MacroAssembler::copySlotsFromTemplate(Register obj, const JSObject *templateObj, uint32_t start, uint32_t end) { uint32_t nfixed = Min(templateObj->numFixedSlots(), end); @@ -784,27 +912,26 @@ MacroAssembler::copySlotsFromTemplate(Register obj, Register temp, const JSObjec } void -MacroAssembler::fillSlotsWithUndefined(Register obj, Register temp, const JSObject *templateObj, - uint32_t start, uint32_t end) +MacroAssembler::fillSlotsWithUndefined(Address base, Register temp, uint32_t start, uint32_t end) { #ifdef JS_NUNBOX32 // We only have a single spare register, so do the initialization as two // strided writes of the tag and body. jsval_layout jv = JSVAL_TO_IMPL(UndefinedValue()); - uint32_t nfixed = Min(templateObj->numFixedSlots(), end); - mov(ImmWord(jv.s.tag), temp); - for (unsigned i = start; i < nfixed; i++) - store32(temp, ToType(Address(obj, JSObject::getFixedSlotOffset(i)))); + Address addr = base; + move32(Imm32(jv.s.payload.i32), temp); + for (unsigned i = start; i < end; ++i, addr.offset += sizeof(HeapValue)) + store32(temp, ToPayload(addr)); - mov(ImmWord(jv.s.payload.i32), temp); - for (unsigned i = start; i < nfixed; i++) - store32(temp, ToPayload(Address(obj, JSObject::getFixedSlotOffset(i)))); + addr = base; + move32(Imm32(jv.s.tag), temp); + for (unsigned i = start; i < end; ++i, addr.offset += sizeof(HeapValue)) + store32(temp, ToType(addr)); #else moveValue(UndefinedValue(), temp); - uint32_t nfixed = Min(templateObj->numFixedSlots(), end); - for (unsigned i = start; i < nfixed; i++) - storePtr(temp, Address(obj, JSObject::getFixedSlotOffset(i))); + for (uint32_t i = start; i < end; ++i, base.offset += sizeof(HeapValue)) + storePtr(temp, base); #endif } @@ -821,7 +948,7 @@ FindStartOfUndefinedSlots(JSObject *templateObj, uint32_t nslots) } void -MacroAssembler::initGCSlots(Register obj, Register temp, JSObject *templateObj) +MacroAssembler::initGCSlots(Register obj, Register slots, JSObject *templateObj) { // Slots of non-array objects are required to be initialized. // Use the values currently in the template object. @@ -829,6 +956,9 @@ MacroAssembler::initGCSlots(Register obj, Register temp, JSObject *templateObj) if (nslots == 0) return; + uint32_t nfixed = templateObj->numFixedSlots(); + uint32_t ndynamic = templateObj->numDynamicSlots(); + // Attempt to group slot writes such that we minimize the amount of // duplicated data we need to embed in code and load into registers. In // general, most template object slots will be undefined except for any @@ -837,22 +967,41 @@ MacroAssembler::initGCSlots(Register obj, Register temp, JSObject *templateObj) // duplicated writes of UndefinedValue to the tail. For the majority of // objects, the "tail" will be the entire slot range. uint32_t startOfUndefined = FindStartOfUndefinedSlots(templateObj, nslots); - copySlotsFromTemplate(obj, temp, templateObj, 0, startOfUndefined); - fillSlotsWithUndefined(obj, temp, templateObj, startOfUndefined, nslots); + JS_ASSERT(startOfUndefined <= nfixed); // Reserved slots must be fixed. + + // Copy over any preserved reserved slots. + copySlotsFromTemplate(obj, templateObj, 0, startOfUndefined); + + // Fill the rest of the fixed slots with undefined. + fillSlotsWithUndefined(Address(obj, JSObject::getFixedSlotOffset(startOfUndefined)), slots, + startOfUndefined, nfixed); + + if (ndynamic) { + // We are short one register to do this elegantly. Borrow the obj + // register briefly for our slots base address. + push(obj); + loadPtr(Address(obj, JSObject::offsetOfSlots()), obj); + fillSlotsWithUndefined(Address(obj, 0), slots, 0, ndynamic); + pop(obj); + } } void -MacroAssembler::initGCThing(Register obj, Register temp, JSObject *templateObj) +MacroAssembler::initGCThing(Register obj, Register slots, JSObject *templateObj) { - // Fast initialization of an empty object returned by NewGCThing(). + // Fast initialization of an empty object returned by allocateObject(). JS_ASSERT(!templateObj->hasDynamicElements()); storePtr(ImmGCPtr(templateObj->lastProperty()), Address(obj, JSObject::offsetOfShape())); storePtr(ImmGCPtr(templateObj->type()), Address(obj, JSObject::offsetOfType())); - storePtr(ImmPtr(nullptr), Address(obj, JSObject::offsetOfSlots())); + if (templateObj->hasDynamicSlots()) + storePtr(slots, Address(obj, JSObject::offsetOfSlots())); + else + storePtr(ImmPtr(nullptr), Address(obj, JSObject::offsetOfSlots())); if (templateObj->is()) { + Register temp = slots; JS_ASSERT(!templateObj->getDenseInitializedLength()); int elementsOffset = JSObject::offsetOfFixedElements(); @@ -875,7 +1024,7 @@ MacroAssembler::initGCThing(Register obj, Register temp, JSObject *templateObj) } else { storePtr(ImmPtr(emptyObjectElements), Address(obj, JSObject::offsetOfElements())); - initGCSlots(obj, temp, templateObj); + initGCSlots(obj, slots, templateObj); if (templateObj->hasPrivate()) { uint32_t nfixed = templateObj->numFixedSlots(); diff --git a/js/src/jit/IonMacroAssembler.h b/js/src/jit/IonMacroAssembler.h index 7e889d447db..0f7c260de95 100644 --- a/js/src/jit/IonMacroAssembler.h +++ b/js/src/jit/IonMacroAssembler.h @@ -786,10 +786,30 @@ class MacroAssembler : public MacroAssemblerSpecific void branchEqualTypeIfNeeded(MIRType type, MDefinition *maybeDef, Register tag, Label *label); // Inline allocation. - void newGCThing(Register result, Register temp, gc::AllocKind allocKind, Label *fail, - gc::InitialHeap initialHeap = gc::DefaultHeap); - void newGCThing(Register result, Register temp, JSObject *templateObject, Label *fail, - gc::InitialHeap initialHeap); + private: + void checkAllocatorState(Label *fail); + bool shouldNurseryAllocate(gc::AllocKind allocKind, gc::InitialHeap initialHeap); + void nurseryAllocate(Register result, Register slots, gc::AllocKind allocKind, + size_t nDynamicSlots, gc::InitialHeap initialHeap, Label *fail); + void freeSpanAllocate(Register result, Register temp, gc::AllocKind allocKind, Label *fail); + void allocateObject(Register result, Register slots, gc::AllocKind allocKind, + uint32_t nDynamicSlots, gc::InitialHeap initialHeap, Label *fail); + void allocateNonObject(Register result, Register temp, gc::AllocKind allocKind, Label *fail); + void copySlotsFromTemplate(Register obj, const JSObject *templateObj, + uint32_t start, uint32_t end); + void fillSlotsWithUndefined(Address addr, Register temp, uint32_t start, uint32_t end); + void initGCSlots(Register obj, Register temp, JSObject *templateObj); + + public: + void callMallocStub(size_t nbytes, Register result, Label *fail); + void callFreeStub(Register slots); + void createGCObject(Register result, Register temp, JSObject *templateObj, + gc::InitialHeap initialHeap, Label *fail); + + void newGCThing(Register result, Register temp, JSObject *templateObj, + gc::InitialHeap initialHeap, Label *fail); + void initGCThing(Register obj, Register temp, JSObject *templateObj); + void newGCString(Register result, Register temp, Label *fail); void newGCShortString(Register result, Register temp, Label *fail); @@ -802,12 +822,6 @@ class MacroAssembler : public MacroAssemblerSpecific void newGCShortStringPar(Register result, Register cx, Register tempReg1, Register tempReg2, Label *fail); - void copySlotsFromTemplate(Register obj, Register temp, const JSObject *templateObj, - uint32_t start, uint32_t end); - void fillSlotsWithUndefined(Register obj, Register temp, const JSObject *templateObj, - uint32_t start, uint32_t end); - void initGCSlots(Register obj, Register temp, JSObject *templateObj); - void initGCThing(Register obj, Register temp, JSObject *templateObj); // Compares two strings for equality based on the JSOP. // This checks for identical pointers, atoms and length and fails for everything else. diff --git a/js/src/jit/JitCompartment.h b/js/src/jit/JitCompartment.h index da0e09bf892..1a345c260b7 100644 --- a/js/src/jit/JitCompartment.h +++ b/js/src/jit/JitCompartment.h @@ -186,6 +186,10 @@ class JitRuntime JitCode *valuePreBarrier_; JitCode *shapePreBarrier_; + // Thunk to call malloc/free. + JitCode *mallocStub_; + JitCode *freeStub_; + // Thunk used by the debugger for breakpoint and step mode. JitCode *debugTrapHandler_; @@ -221,6 +225,8 @@ class JitRuntime JitCode *generateBailoutHandler(JSContext *cx); JitCode *generateInvalidator(JSContext *cx); JitCode *generatePreBarrier(JSContext *cx, MIRType type); + JitCode *generateMallocStub(JSContext *cx); + JitCode *generateFreeStub(JSContext *cx); JitCode *generateDebugTrapHandler(JSContext *cx); JitCode *generateForkJoinGetSliceStub(JSContext *cx); JitCode *generateVMWrapper(JSContext *cx, const VMFunction &f); @@ -245,6 +251,10 @@ class JitRuntime flusher_ = fl; } + JSC::ExecutableAllocator *execAlloc() const { + return execAlloc_; + } + JSC::ExecutableAllocator *getIonAlloc(JSContext *cx) { JS_ASSERT(cx->runtime()->currentThreadOwnsInterruptLock()); return ionAlloc_ ? ionAlloc_ : createIonAlloc(cx); @@ -326,19 +336,35 @@ class JitRuntime return shapePreBarrier_; } + JitCode *mallocStub() const { + return mallocStub_; + } + + JitCode *freeStub() const { + return freeStub_; + } + bool ensureForkJoinGetSliceStubExists(JSContext *cx); JitCode *forkJoinGetSliceStub() const { return forkJoinGetSliceStub_; } }; +class JitZone +{ + // Allocated space for optimized baseline stubs. + OptimizedICStubSpace optimizedStubSpace_; + + public: + OptimizedICStubSpace *optimizedStubSpace() { + return &optimizedStubSpace_; + } +}; + class JitCompartment { friend class JitActivation; - // Ion state for the compartment's runtime. - JitRuntime *rt; - // Map ICStub keys to ICStub shared code objects. typedef WeakValueCache > ICStubCodeMap; ICStubCodeMap *stubCodes_; @@ -349,9 +375,6 @@ class JitCompartment void *baselineGetPropReturnAddr_; void *baselineSetPropReturnAddr_; - // Allocated space for optimized baseline stubs. - OptimizedICStubSpace optimizedStubSpace_; - // Stub to concatenate two strings inline. Note that it can't be // stored in JitRuntime because masm.newGCString bakes in zone-specific // pointers. This has to be a weak pointer to avoid keeping the whole @@ -406,7 +429,7 @@ class JitCompartment JSC::ExecutableAllocator *createIonAlloc(); public: - JitCompartment(JitRuntime *rt); + JitCompartment(); ~JitCompartment(); bool initialize(JSContext *cx); @@ -417,10 +440,6 @@ class JitCompartment void mark(JSTracer *trc, JSCompartment *compartment); void sweep(FreeOp *fop); - JSC::ExecutableAllocator *execAlloc() { - return rt->execAlloc_; - } - JitCode *stringConcatStub(ExecutionMode mode) const { switch (mode) { case SequentialExecution: return stringConcatStub_; @@ -428,16 +447,11 @@ class JitCompartment default: MOZ_ASSUME_UNREACHABLE("No such execution mode"); } } - - OptimizedICStubSpace *optimizedStubSpace() { - return &optimizedStubSpace_; - } }; // Called from JSCompartment::discardJitCode(). void InvalidateAll(FreeOp *fop, JS::Zone *zone); void FinishInvalidation(FreeOp *fop, JSScript *script); -void FinishDiscardJitCode(FreeOp *fop, JSCompartment *comp); // On windows systems, really large frames need to be incrementally touched. // The following constant defines the minimum increment of the touch. diff --git a/js/src/jit/LIR-Common.h b/js/src/jit/LIR-Common.h index 1b8d8a6590f..22eb4c14342 100644 --- a/js/src/jit/LIR-Common.h +++ b/js/src/jit/LIR-Common.h @@ -302,32 +302,6 @@ class LGoto : public LControlInstructionHelper<1, 0, 0> } }; -class LNewSlots : public LCallInstructionHelper<1, 0, 3> -{ - public: - LIR_HEADER(NewSlots) - - LNewSlots(const LDefinition &temp1, const LDefinition &temp2, const LDefinition &temp3) { - setTemp(0, temp1); - setTemp(1, temp2); - setTemp(2, temp3); - } - - const LDefinition *temp1() { - return getTemp(0); - } - const LDefinition *temp2() { - return getTemp(1); - } - const LDefinition *temp3() { - return getTemp(2); - } - - MNewSlots *mir() const { - return mir_->toNewSlots(); - } -}; - class LNewArray : public LInstructionHelper<1, 0, 1> { public: @@ -464,22 +438,19 @@ class LNewDeclEnvObject : public LInstructionHelper<1, 0, 1> } }; -// Allocates a new CallObject. The inputs are: -// slots: either a reg representing a HeapSlot *, or a placeholder -// meaning that no slots pointer is needed. +// Allocates a new CallObject. // // This instruction generates two possible instruction sets: // (1) If the call object is extensible, this is a callVM to create the // call object. // (2) Otherwise, an inline allocation of the call object is attempted. // -class LNewCallObject : public LInstructionHelper<1, 1, 1> +class LNewCallObject : public LInstructionHelper<1, 0, 1> { public: LIR_HEADER(NewCallObject) - LNewCallObject(const LAllocation &slots, const LDefinition &temp) { - setOperand(0, slots); + LNewCallObject(const LDefinition &temp) { setTemp(0, temp); } @@ -487,21 +458,15 @@ class LNewCallObject : public LInstructionHelper<1, 1, 1> return getTemp(0); } - const LAllocation *slots() { - return getOperand(0); - } MNewCallObject *mir() const { return mir_->toNewCallObject(); } }; -class LNewCallObjectPar : public LInstructionHelper<1, 2, 2> +class LNewCallObjectPar : public LInstructionHelper<1, 1, 2> { - LNewCallObjectPar(const LAllocation &cx, const LAllocation &slots, - const LDefinition &temp1, const LDefinition &temp2) - { + LNewCallObjectPar(const LAllocation &cx, const LDefinition &temp1, const LDefinition &temp2) { setOperand(0, cx); - setOperand(1, slots); setTemp(0, temp1); setTemp(1, temp2); } @@ -509,37 +474,16 @@ class LNewCallObjectPar : public LInstructionHelper<1, 2, 2> public: LIR_HEADER(NewCallObjectPar); - static LNewCallObjectPar *NewWithSlots(TempAllocator &alloc, - const LAllocation &cx, const LAllocation &slots, - const LDefinition &temp1, const LDefinition &temp2) + static LNewCallObjectPar *New(TempAllocator &alloc, const LAllocation &cx, + const LDefinition &temp1, const LDefinition &temp2) { - return new(alloc) LNewCallObjectPar(cx, slots, temp1, temp2); - } - - static LNewCallObjectPar *NewSansSlots(TempAllocator &alloc, - const LAllocation &cx, - const LDefinition &temp1, const LDefinition &temp2) - { - LAllocation slots = LConstantIndex::Bogus(); - return new(alloc) LNewCallObjectPar(cx, slots, temp1, temp2); + return new(alloc) LNewCallObjectPar(cx, temp1, temp2); } const LAllocation *forkJoinContext() { return getOperand(0); } - const LAllocation *slots() { - return getOperand(1); - } - - const bool hasDynamicSlots() { - // TO INVESTIGATE: Felix tried using isRegister() method here, - // but for useFixed(_, CallTempN), isRegister() is false (and - // isUse() is true). So for now ignore that and try to match - // the LConstantIndex::Bogus() generated above instead. - return slots() && ! slots()->isConstant(); - } - const MNewCallObjectPar *mir() const { return mir_->toNewCallObjectPar(); } diff --git a/js/src/jit/LOpcodes.h b/js/src/jit/LOpcodes.h index d5fa461b0ef..fde736b40e8 100644 --- a/js/src/jit/LOpcodes.h +++ b/js/src/jit/LOpcodes.h @@ -25,7 +25,6 @@ _(Goto) \ _(NewArray) \ _(NewObject) \ - _(NewSlots) \ _(NewDeclEnvObject) \ _(NewCallObject) \ _(NewStringObject) \ diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index bff96c4ddec..05a705b9cc6 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -160,17 +160,6 @@ LIRGenerator::visitDefFun(MDefFun *ins) return add(lir, ins) && assignSafepoint(lir, ins); } -bool -LIRGenerator::visitNewSlots(MNewSlots *ins) -{ - // No safepoint needed, since we don't pass a cx. - LNewSlots *lir = new(alloc()) LNewSlots(tempFixed(CallTempReg0), tempFixed(CallTempReg1), - tempFixed(CallTempReg2)); - if (!assignSnapshot(lir)) - return false; - return defineReturn(lir, ins); -} - bool LIRGenerator::visitNewArray(MNewArray *ins) { @@ -195,13 +184,7 @@ LIRGenerator::visitNewDeclEnvObject(MNewDeclEnvObject *ins) bool LIRGenerator::visitNewCallObject(MNewCallObject *ins) { - LAllocation slots; - if (ins->slots()->type() == MIRType_Slots) - slots = useRegister(ins->slots()); - else - slots = LConstantIndex::Bogus(); - - LNewCallObject *lir = new(alloc()) LNewCallObject(slots, temp()); + LNewCallObject *lir = new(alloc()) LNewCallObject(temp()); if (!define(lir, ins)) return false; @@ -225,17 +208,7 @@ bool LIRGenerator::visitNewCallObjectPar(MNewCallObjectPar *ins) { const LAllocation &parThreadContext = useRegister(ins->forkJoinContext()); - const LDefinition &temp1 = temp(); - const LDefinition &temp2 = temp(); - - LNewCallObjectPar *lir; - if (ins->slots()->type() == MIRType_Slots) { - const LAllocation &slots = useRegister(ins->slots()); - lir = LNewCallObjectPar::NewWithSlots(alloc(), parThreadContext, slots, temp1, temp2); - } else { - lir = LNewCallObjectPar::NewSansSlots(alloc(), parThreadContext, temp1, temp2); - } - + LNewCallObjectPar *lir = LNewCallObjectPar::New(alloc(), parThreadContext, temp(), temp()); return define(lir, ins); } @@ -2205,6 +2178,12 @@ LIRGenerator::visitStoreSlot(MStoreSlot *ins) return true; } +bool +LIRGenerator::visitFilterTypeSet(MFilterTypeSet *ins) +{ + return redefine(ins, ins->input()); +} + bool LIRGenerator::visitTypeBarrier(MTypeBarrier *ins) { diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h index 494c03ba830..6c0603e10e5 100644 --- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -66,7 +66,6 @@ class LIRGenerator : public LIRGeneratorSpecific bool visitCallee(MCallee *callee); bool visitGoto(MGoto *ins); bool visitTableSwitch(MTableSwitch *tableswitch); - bool visitNewSlots(MNewSlots *ins); bool visitNewArray(MNewArray *ins); bool visitNewObject(MNewObject *ins); bool visitNewDeclEnvObject(MNewDeclEnvObject *ins); @@ -166,6 +165,7 @@ class LIRGenerator : public LIRGeneratorSpecific bool visitInterruptCheck(MInterruptCheck *ins); bool visitInterruptCheckPar(MInterruptCheckPar *ins); bool visitStoreSlot(MStoreSlot *ins); + bool visitFilterTypeSet(MFilterTypeSet *ins); bool visitTypeBarrier(MTypeBarrier *ins); bool visitMonitorTypes(MMonitorTypes *ins); bool visitPostWriteBarrier(MPostWriteBarrier *ins); diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp index fcfd1f5f354..a2cb8d4ab48 100644 --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -1283,10 +1283,11 @@ IonBuilder::inlineUnsafePutElements(CallInfo &callInfo) } // We can only inline setelem on dense arrays that do not need type - // barriers and on typed arrays. + // barriers and on typed arrays and on typed object arrays. ScalarTypeDescr::Type arrayType; if ((!isDenseNative || writeNeedsBarrier) && - !ElementAccessIsTypedArray(obj, id, &arrayType)) + !ElementAccessIsTypedArray(obj, id, &arrayType) && + !elementAccessIsTypedObjectArrayOfScalarType(obj, id, &arrayType)) { return InliningStatus_NotInlined; } @@ -1320,12 +1321,47 @@ IonBuilder::inlineUnsafePutElements(CallInfo &callInfo) continue; } + if (elementAccessIsTypedObjectArrayOfScalarType(obj, id, &arrayType)) { + if (!inlineUnsafeSetTypedObjectArrayElement(callInfo, base, arrayType)) + return InliningStatus_Error; + continue; + } + MOZ_ASSUME_UNREACHABLE("Element access not dense array nor typed array"); } return InliningStatus_Inlined; } +bool +IonBuilder::elementAccessIsTypedObjectArrayOfScalarType(MDefinition* obj, MDefinition* id, + ScalarTypeDescr::Type *arrayType) +{ + if (obj->type() != MIRType_Object) // lookupTypeDescrSet() tests for TypedObject + return false; + + if (id->type() != MIRType_Int32 && id->type() != MIRType_Double) + return false; + + TypeDescrSet objDescrs; + if (!lookupTypeDescrSet(obj, &objDescrs)) + return false; + + if (!objDescrs.allOfArrayKind()) + return false; + + TypeDescrSet elemDescrs; + if (!objDescrs.arrayElementType(*this, &elemDescrs)) + return false; + + if (elemDescrs.empty() || elemDescrs.kind() != TypeDescr::Scalar) + return false; + + JS_ASSERT(TypeDescr::isSized(elemDescrs.kind())); + + return elemDescrs.scalarType(arrayType); +} + bool IonBuilder::inlineUnsafeSetDenseArrayElement(CallInfo &callInfo, uint32_t base) { @@ -1367,6 +1403,26 @@ IonBuilder::inlineUnsafeSetTypedArrayElement(CallInfo &callInfo, return true; } +bool +IonBuilder::inlineUnsafeSetTypedObjectArrayElement(CallInfo &callInfo, + uint32_t base, + ScalarTypeDescr::Type arrayType) +{ + // Note: we do not check the conditions that are asserted as true + // in intrinsic_UnsafePutElements(): + // - arr is a typed array + // - idx < length + + MDefinition *obj = callInfo.getArg(base + 0); + MDefinition *id = callInfo.getArg(base + 1); + MDefinition *elem = callInfo.getArg(base + 2); + + if (!jsop_setelem_typed_object(arrayType, SetElem_Unsafe, true, obj, id, elem)) + return false; + + return true; +} + IonBuilder::InliningStatus IonBuilder::inlineForceSequentialOrInParallelSection(CallInfo &callInfo) { diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp index bee5c2cb954..beeb044e499 100644 --- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -225,6 +225,12 @@ MaybeCallable(MDefinition *op) return types->maybeCallable(); } +MTest * +MTest::New(TempAllocator &alloc, MDefinition *ins, MBasicBlock *ifTrue, MBasicBlock *ifFalse) +{ + return new(alloc) MTest(ins, ifTrue, ifFalse); +} + void MTest::infer() { @@ -245,6 +251,32 @@ MTest::foldsTo(TempAllocator &alloc, bool useValueNumbers) return this; } +void +MTest::filtersUndefinedOrNull(bool trueBranch, MDefinition **subject, bool *filtersUndefined, + bool *filtersNull) +{ + MDefinition *ins = getOperand(0); + if (ins->isCompare()) { + ins->toCompare()->filtersUndefinedOrNull(trueBranch, subject, filtersUndefined, filtersNull); + return; + } + + if (!trueBranch && ins->isNot()) { + *subject = ins->getOperand(0); + *filtersUndefined = *filtersNull = true; + return; + } + + if (trueBranch) { + *subject = ins; + *filtersUndefined = *filtersNull = true; + return; + } + + *filtersUndefined = *filtersNull = false; + *subject = nullptr; +} + void MDefinition::printOpcode(FILE *fp) const { @@ -826,12 +858,6 @@ MRound::trySpecializeFloat32(TempAllocator &alloc) setPolicyType(MIRType_Float32); } -MTest * -MTest::New(TempAllocator &alloc, MDefinition *ins, MBasicBlock *ifTrue, MBasicBlock *ifFalse) -{ - return new(alloc) MTest(ins, ifTrue, ifFalse); -} - MCompare * MCompare::New(TempAllocator &alloc, MDefinition *left, MDefinition *right, JSOp op) { @@ -2567,6 +2593,37 @@ MCompare::trySpecializeFloat32(TempAllocator &alloc) } } +void +MCompare::filtersUndefinedOrNull(bool trueBranch, MDefinition **subject, bool *filtersUndefined, + bool *filtersNull) +{ + *filtersNull = *filtersUndefined = false; + *subject = nullptr; + + if (compareType() != Compare_Undefined && compareType() != Compare_Null) + return; + + JS_ASSERT(jsop() == JSOP_STRICTNE || jsop() == JSOP_NE || + jsop() == JSOP_STRICTEQ || jsop() == JSOP_EQ); + + // JSOP_*NE only removes undefined/null from if/true branch + if (!trueBranch && (jsop() == JSOP_STRICTNE || jsop() == JSOP_NE)) + return; + + // JSOP_*EQ only removes undefined/null from else/false branch + if (trueBranch && (jsop() == JSOP_STRICTEQ || jsop() == JSOP_EQ)) + return; + + if (jsop() == JSOP_STRICTEQ || jsop() == JSOP_STRICTNE) { + *filtersUndefined = compareType() == Compare_Undefined; + *filtersNull = compareType() == Compare_Null; + } else { + *filtersUndefined = *filtersNull = true; + } + + *subject = lhs(); +} + void MNot::infer() { diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 35927a836a8..7249e017931 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -1321,6 +1321,8 @@ class MTest } void infer(); MDefinition *foldsTo(TempAllocator &alloc, bool useValueNumbers); + void filtersUndefinedOrNull(bool trueBranch, MDefinition **subject, bool *filtersUndefined, + bool *filtersNull); void markOperandCantEmulateUndefined() { operandMightEmulateUndefined_ = false; @@ -2278,6 +2280,8 @@ class MCompare bool tryFold(bool *result); bool evaluateConstantOperands(bool *result); MDefinition *foldsTo(TempAllocator &alloc, bool useValueNumbers); + void filtersUndefinedOrNull(bool trueBranch, MDefinition **subject, bool *filtersUndefined, + bool *filtersNull); void infer(BaselineInspector *inspector, jsbytecode *pc); CompareType compareType() const { @@ -8806,6 +8810,37 @@ class MGuardThreadExclusive } }; +class MFilterTypeSet + : public MUnaryInstruction +{ + MFilterTypeSet(MDefinition *def, types::TemporaryTypeSet *types) + : MUnaryInstruction(def) + { + JS_ASSERT(!types->unknown()); + + MIRType type = MIRTypeFromValueType(types->getKnownTypeTag()); + setResultType(type); + setResultTypeSet(types); + } + + public: + INSTRUCTION_HEADER(FilterTypeSet) + + static MFilterTypeSet *New(TempAllocator &alloc, MDefinition *def, types::TemporaryTypeSet *types) { + return new(alloc) MFilterTypeSet(def, types); + } + + bool congruentTo(MDefinition *def) const { + return false; + } + AliasSet getAliasSet() const { + return AliasSet::None(); + } + virtual bool neverHoist() const { + return resultTypeSet()->empty(); + } +}; + // Given a value, guard that the value is in a particular TypeSet, then returns // that value. class MTypeBarrier @@ -8932,33 +8967,6 @@ class MPostWriteBarrier : public MBinaryInstruction, public ObjectPolicy<0> #endif }; -class MNewSlots : public MNullaryInstruction -{ - unsigned nslots_; - - MNewSlots(unsigned nslots) - : nslots_(nslots) - { - setResultType(MIRType_Slots); - } - - public: - INSTRUCTION_HEADER(NewSlots) - - static MNewSlots *New(TempAllocator &alloc, unsigned nslots) { - return new(alloc) MNewSlots(nslots); - } - unsigned nslots() const { - return nslots_; - } - AliasSet getAliasSet() const { - return AliasSet::None(); - } - bool possiblyCalls() const { - return true; - } -}; - class MNewDeclEnvObject : public MNullaryInstruction { CompilerRootObject templateObj_; @@ -8985,13 +8993,13 @@ class MNewDeclEnvObject : public MNullaryInstruction } }; -class MNewCallObject : public MUnaryInstruction +class MNewCallObject : public MNullaryInstruction { CompilerRootObject templateObj_; bool needsSingletonType_; - MNewCallObject(JSObject *templateObj, bool needsSingletonType, MDefinition *slots) - : MUnaryInstruction(slots), + MNewCallObject(JSObject *templateObj, bool needsSingletonType) + : MNullaryInstruction(), templateObj_(templateObj), needsSingletonType_(needsSingletonType) { @@ -9001,15 +9009,11 @@ class MNewCallObject : public MUnaryInstruction public: INSTRUCTION_HEADER(NewCallObject) - static MNewCallObject *New(TempAllocator &alloc, JSObject *templateObj, bool needsSingletonType, - MDefinition *slots) + static MNewCallObject *New(TempAllocator &alloc, JSObject *templateObj, bool needsSingletonType) { - return new(alloc) MNewCallObject(templateObj, needsSingletonType, slots); + return new(alloc) MNewCallObject(templateObj, needsSingletonType); } - MDefinition *slots() { - return getOperand(0); - } JSObject *templateObject() { return templateObj_; } @@ -9021,12 +9025,12 @@ class MNewCallObject : public MUnaryInstruction } }; -class MNewCallObjectPar : public MBinaryInstruction +class MNewCallObjectPar : public MUnaryInstruction { CompilerRootObject templateObj_; - MNewCallObjectPar(MDefinition *cx, JSObject *templateObj, MDefinition *slots) - : MBinaryInstruction(cx, slots), + MNewCallObjectPar(MDefinition *cx, JSObject *templateObj) + : MUnaryInstruction(cx), templateObj_(templateObj) { setResultType(MIRType_Object); @@ -9036,17 +9040,13 @@ class MNewCallObjectPar : public MBinaryInstruction INSTRUCTION_HEADER(NewCallObjectPar); static MNewCallObjectPar *New(TempAllocator &alloc, MDefinition *cx, MNewCallObject *callObj) { - return new(alloc) MNewCallObjectPar(cx, callObj->templateObject(), callObj->slots()); + return new(alloc) MNewCallObjectPar(cx, callObj->templateObject()); } MDefinition *forkJoinContext() const { return getOperand(0); } - MDefinition *slots() const { - return getOperand(1); - } - JSObject *templateObj() const { return templateObj_; } diff --git a/js/src/jit/MOpcodes.h b/js/src/jit/MOpcodes.h index e598a711a5e..c630211c2ec 100644 --- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -85,7 +85,6 @@ namespace jit { _(ToInt32) \ _(TruncateToInt32) \ _(ToString) \ - _(NewSlots) \ _(NewArray) \ _(NewObject) \ _(NewDeclEnvObject) \ @@ -114,6 +113,7 @@ namespace jit { _(LoadSlot) \ _(StoreSlot) \ _(FunctionEnvironment) \ + _(FilterTypeSet) \ _(TypeBarrier) \ _(MonitorTypes) \ _(PostWriteBarrier) \ diff --git a/js/src/jit/ParallelSafetyAnalysis.cpp b/js/src/jit/ParallelSafetyAnalysis.cpp index 791b38614cf..676752c09a1 100644 --- a/js/src/jit/ParallelSafetyAnalysis.cpp +++ b/js/src/jit/ParallelSafetyAnalysis.cpp @@ -179,7 +179,6 @@ class ParallelSafetyVisitor : public MInstructionVisitor SAFE_OP(TruncateToInt32) SAFE_OP(MaybeToDoubleElement) CUSTOM_OP(ToString) - SAFE_OP(NewSlots) CUSTOM_OP(NewArray) CUSTOM_OP(NewObject) CUSTOM_OP(NewCallObject) @@ -201,6 +200,7 @@ class ParallelSafetyVisitor : public MInstructionVisitor SAFE_OP(LoadSlot) WRITE_GUARDED_OP(StoreSlot, slots) SAFE_OP(FunctionEnvironment) // just a load of func env ptr + SAFE_OP(FilterTypeSet) SAFE_OP(TypeBarrier) // causes a bailout if the type is not found: a-ok with us SAFE_OP(MonitorTypes) // causes a bailout if the type is not found: a-ok with us UNSAFE_OP(PostWriteBarrier) @@ -526,6 +526,10 @@ ParallelSafetyVisitor::visitCreateThisWithTemplate(MCreateThisWithTemplate *ins) bool ParallelSafetyVisitor::visitNewCallObject(MNewCallObject *ins) { + if (ins->templateObject()->hasDynamicSlots()) { + SpewMIR(ins, "call with dynamic slots"); + return markUnsafe(); + } replace(ins, MNewCallObjectPar::New(alloc(), ForkJoinContext(), ins)); return true; } @@ -640,11 +644,6 @@ ParallelSafetyVisitor::insertWriteGuard(MInstruction *writeInstruction, object = valueBeingWritten->toSlots()->object(); break; - case MDefinition::Op_NewSlots: - // Values produced by new slots will ALWAYS be - // thread-local. - return true; - default: SpewMIR(writeInstruction, "cannot insert write guard for %s", valueBeingWritten->opName()); diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index 71bdbc296f3..daac4d0cc74 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -509,26 +509,16 @@ InterruptCheck(JSContext *cx) return CheckForInterrupt(cx); } -HeapSlot * -NewSlots(JSRuntime *rt, unsigned nslots) +void * +MallocWrapper(JSRuntime *rt, size_t nbytes) { - JS_STATIC_ASSERT(sizeof(Value) == sizeof(HeapSlot)); - - Value *slots = reinterpret_cast(rt->malloc_(nslots * sizeof(Value))); - if (!slots) - return nullptr; - - for (unsigned i = 0; i < nslots; i++) - slots[i] = UndefinedValue(); - - return reinterpret_cast(slots); + return rt->pod_malloc(nbytes); } JSObject * -NewCallObject(JSContext *cx, HandleScript script, - HandleShape shape, HandleTypeObject type, HeapSlot *slots) +NewCallObject(JSContext *cx, HandleScript script, HandleShape shape, HandleTypeObject type) { - JSObject *obj = CallObject::create(cx, script, shape, type, slots); + JSObject *obj = CallObject::create(cx, script, shape, type); if (!obj) return nullptr; diff --git a/js/src/jit/VMFunctions.h b/js/src/jit/VMFunctions.h index 86e0a03ad4c..b1d3c11390d 100644 --- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -617,9 +617,9 @@ bool SetProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, Handl bool InterruptCheck(JSContext *cx); -HeapSlot *NewSlots(JSRuntime *rt, unsigned nslots); +void *MallocWrapper(JSRuntime *rt, size_t nbytes); JSObject *NewCallObject(JSContext *cx, HandleScript script, - HandleShape shape, HandleTypeObject type, HeapSlot *slots); + HandleShape shape, HandleTypeObject type); JSObject *NewStringObject(JSContext *cx, HandleString str); bool SPSEnter(JSContext *cx, HandleScript script); diff --git a/js/src/jit/mips/Assembler-mips.h b/js/src/jit/mips/Assembler-mips.h index 0d84f465b97..448b0b1e60b 100644 --- a/js/src/jit/mips/Assembler-mips.h +++ b/js/src/jit/mips/Assembler-mips.h @@ -55,6 +55,7 @@ static MOZ_CONSTEXPR_VAR Register fp = { Registers::fp }; static MOZ_CONSTEXPR_VAR Register ra = { Registers::ra }; static MOZ_CONSTEXPR_VAR Register ScratchRegister = at; +static MOZ_CONSTEXPR_VAR Register SecondScratchReg = t8; // Use arg reg from EnterJIT function as OsrFrameReg. static MOZ_CONSTEXPR_VAR Register OsrFrameReg = a3; diff --git a/js/src/jit/mips/BaselineCompiler-mips.cpp b/js/src/jit/mips/BaselineCompiler-mips.cpp new file mode 100644 index 00000000000..69c935b5f21 --- /dev/null +++ b/js/src/jit/mips/BaselineCompiler-mips.cpp @@ -0,0 +1,16 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 "jit/mips/BaselineCompiler-mips.h" + +using namespace js; +using namespace js::jit; + +BaselineCompilerMIPS::BaselineCompilerMIPS(JSContext *cx, TempAllocator &alloc, + HandleScript script) + : BaselineCompilerShared(cx, alloc, script) +{ +} diff --git a/js/src/jit/mips/BaselineCompiler-mips.h b/js/src/jit/mips/BaselineCompiler-mips.h new file mode 100644 index 00000000000..7db49b30d83 --- /dev/null +++ b/js/src/jit/mips/BaselineCompiler-mips.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 jit_mips_BaselineCompiler_mips_h +#define jit_mips_BaselineCompiler_mips_h + +#include "jit/shared/BaselineCompiler-shared.h" + +namespace js { +namespace jit { + +class BaselineCompilerMIPS : public BaselineCompilerShared +{ + protected: + BaselineCompilerMIPS(JSContext *cx, TempAllocator &alloc, HandleScript script); +}; + +typedef BaselineCompilerMIPS BaselineCompilerSpecific; + +} // namespace jit +} // namespace js + +#endif /* jit_mips_BaselineCompiler_mips_h */ diff --git a/js/src/jit/mips/BaselineHelpers-mips.h b/js/src/jit/mips/BaselineHelpers-mips.h new file mode 100644 index 00000000000..6c810e94de9 --- /dev/null +++ b/js/src/jit/mips/BaselineHelpers-mips.h @@ -0,0 +1,333 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 jit_mips_BaselineHelpers_mips_h +#define jit_mips_BaselineHelpers_mips_h + +#ifdef JS_ION +#include "jit/BaselineFrame.h" +#include "jit/BaselineIC.h" +#include "jit/BaselineRegisters.h" +#include "jit/IonMacroAssembler.h" + +namespace js { +namespace jit { + +// Distance from sp to the top Value inside an IC stub (no return address on +// the stack on MIPS). +static const size_t ICStackValueOffset = 0; + +inline void +EmitRestoreTailCallReg(MacroAssembler &masm) +{ + // No-op on MIPS because ra register is always holding the return address. +} + +inline void +EmitRepushTailCallReg(MacroAssembler &masm) +{ + // No-op on MIPS because ra register is always holding the return address. +} + +inline void +EmitCallIC(CodeOffsetLabel *patchOffset, MacroAssembler &masm) +{ + // Move ICEntry offset into BaselineStubReg. + CodeOffsetLabel offset = masm.movWithPatch(ImmWord(-1), BaselineStubReg); + *patchOffset = offset; + + // Load stub pointer into BaselineStubReg. + masm.loadPtr(Address(BaselineStubReg, ICEntry::offsetOfFirstStub()), BaselineStubReg); + + // Load stubcode pointer from BaselineStubEntry. + // R2 won't be active when we call ICs, so we can use it as scratch. + masm.loadPtr(Address(BaselineStubReg, ICStub::offsetOfStubCode()), R2.scratchReg()); + + // Call the stubcode via a direct jump-and-link + masm.call(R2.scratchReg()); +} + +inline void +EmitEnterTypeMonitorIC(MacroAssembler &masm, + size_t monitorStubOffset = ICMonitoredStub::offsetOfFirstMonitorStub()) +{ + // This is expected to be called from within an IC, when BaselineStubReg + // is properly initialized to point to the stub. + masm.loadPtr(Address(BaselineStubReg, (uint32_t) monitorStubOffset), BaselineStubReg); + + // Load stubcode pointer from BaselineStubEntry. + // R2 won't be active when we call ICs, so we can use it. + masm.loadPtr(Address(BaselineStubReg, ICStub::offsetOfStubCode()), R2.scratchReg()); + + // Jump to the stubcode. + masm.branch(R2.scratchReg()); +} + +inline void +EmitReturnFromIC(MacroAssembler &masm) +{ + masm.branch(ra); +} + +inline void +EmitChangeICReturnAddress(MacroAssembler &masm, Register reg) +{ + masm.movePtr(reg, ra); +} + +inline void +EmitTailCallVM(JitCode *target, MacroAssembler &masm, uint32_t argSize) +{ + // We assume during this that R0 and R1 have been pushed, and that R2 is + // unused. + MOZ_ASSERT(R2 == ValueOperand(t7, t6)); + + // Compute frame size. + masm.movePtr(BaselineFrameReg, t6); + masm.addPtr(Imm32(BaselineFrame::FramePointerOffset), t6); + masm.subPtr(BaselineStackReg, t6); + + // Store frame size without VMFunction arguments for GC marking. + masm.ma_subu(t7, t6, Imm32(argSize)); + masm.storePtr(t7, Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfFrameSize())); + + // Push frame descriptor and perform the tail call. + // BaselineTailCallReg (ra) already contains the return address (as we + // keep it there through the stub calls), but the VMWrapper code being + // called expects the return address to also be pushed on the stack. + MOZ_ASSERT(BaselineTailCallReg == ra); + masm.makeFrameDescriptor(t6, IonFrame_BaselineJS); + masm.subPtr(Imm32(sizeof(IonCommonFrameLayout)), StackPointer); + masm.storePtr(t6, Address(StackPointer, IonCommonFrameLayout::offsetOfDescriptor())); + masm.storePtr(ra, Address(StackPointer, IonCommonFrameLayout::offsetOfReturnAddress())); + + masm.branch(target); +} + +inline void +EmitCreateStubFrameDescriptor(MacroAssembler &masm, Register reg) +{ + // Compute stub frame size. We have to add two pointers: the stub reg and + // previous frame pointer pushed by EmitEnterStubFrame. + masm.movePtr(BaselineFrameReg, reg); + masm.addPtr(Imm32(sizeof(intptr_t) * 2), reg); + masm.subPtr(BaselineStackReg, reg); + + masm.makeFrameDescriptor(reg, IonFrame_BaselineStub); +} + +inline void +EmitCallVM(JitCode *target, MacroAssembler &masm) +{ + EmitCreateStubFrameDescriptor(masm, t6); + masm.push(t6); + masm.call(target); +} + +struct BaselineStubFrame { + uintptr_t savedFrame; + uintptr_t savedStub; + uintptr_t returnAddress; + uintptr_t descriptor; +}; + +static const uint32_t STUB_FRAME_SIZE = sizeof(BaselineStubFrame); +static const uint32_t STUB_FRAME_SAVED_STUB_OFFSET = offsetof(BaselineStubFrame, savedStub); + +inline void +EmitEnterStubFrame(MacroAssembler &masm, Register scratch) +{ + MOZ_ASSERT(scratch != BaselineTailCallReg); + + // Compute frame size. + masm.movePtr(BaselineFrameReg, scratch); + masm.addPtr(Imm32(BaselineFrame::FramePointerOffset), scratch); + masm.subPtr(BaselineStackReg, scratch); + + masm.storePtr(scratch, Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfFrameSize())); + + // Note: when making changes here, don't forget to update + // BaselineStubFrame if needed. + + // Push frame descriptor and return address. + masm.makeFrameDescriptor(scratch, IonFrame_BaselineJS); + masm.subPtr(Imm32(STUB_FRAME_SIZE), StackPointer); + masm.storePtr(scratch, Address(StackPointer, offsetof(BaselineStubFrame, descriptor))); + masm.storePtr(BaselineTailCallReg, Address(StackPointer, + offsetof(BaselineStubFrame, returnAddress))); + + // Save old frame pointer, stack pointer and stub reg. + masm.storePtr(BaselineStubReg, Address(StackPointer, + offsetof(BaselineStubFrame, savedStub))); + masm.storePtr(BaselineFrameReg, Address(StackPointer, + offsetof(BaselineStubFrame, savedFrame))); + masm.movePtr(BaselineStackReg, BaselineFrameReg); + + // We pushed 4 words, so the stack is still aligned to 8 bytes. + masm.checkStackAlignment(); +} + +inline void +EmitLeaveStubFrame(MacroAssembler &masm, bool calledIntoIon = false) +{ + // Ion frames do not save and restore the frame pointer. If we called + // into Ion, we have to restore the stack pointer from the frame descriptor. + // If we performed a VM call, the descriptor has been popped already so + // in that case we use the frame pointer. + if (calledIntoIon) { + masm.pop(ScratchRegister); + masm.rshiftPtr(Imm32(FRAMESIZE_SHIFT), ScratchRegister); + masm.addPtr(ScratchRegister, BaselineStackReg); + } else { + masm.movePtr(BaselineFrameReg, BaselineStackReg); + } + + masm.loadPtr(Address(StackPointer, offsetof(BaselineStubFrame, savedFrame)), + BaselineFrameReg); + masm.loadPtr(Address(StackPointer, offsetof(BaselineStubFrame, savedStub)), + BaselineStubReg); + + // Load the return address. + masm.loadPtr(Address(StackPointer, offsetof(BaselineStubFrame, returnAddress)), + BaselineTailCallReg); + + // Discard the frame descriptor. + masm.loadPtr(Address(StackPointer, offsetof(BaselineStubFrame, descriptor)), ScratchRegister); + masm.addPtr(Imm32(STUB_FRAME_SIZE), StackPointer); +} + +inline void +EmitStowICValues(MacroAssembler &masm, int values) +{ + MOZ_ASSERT(values >= 0 && values <= 2); + switch(values) { + case 1: + // Stow R0 + masm.pushValue(R0); + break; + case 2: + // Stow R0 and R1 + masm.pushValue(R0); + masm.pushValue(R1); + break; + } +} + +inline void +EmitUnstowICValues(MacroAssembler &masm, int values, bool discard = false) +{ + MOZ_ASSERT(values >= 0 && values <= 2); + switch(values) { + case 1: + // Unstow R0. + if (discard) + masm.addPtr(Imm32(sizeof(Value)), BaselineStackReg); + else + masm.popValue(R0); + break; + case 2: + // Unstow R0 and R1. + if (discard) { + masm.addPtr(Imm32(sizeof(Value) * 2), BaselineStackReg); + } else { + masm.popValue(R1); + masm.popValue(R0); + } + break; + } +} + +inline void +EmitCallTypeUpdateIC(MacroAssembler &masm, JitCode *code, uint32_t objectOffset) +{ + // R0 contains the value that needs to be typechecked. + // The object we're updating is a boxed Value on the stack, at offset + // objectOffset from $sp, excluding the return address. + + // Save the current BaselineStubReg to stack, as well as the TailCallReg, + // since on mips, the $ra is live. + masm.subPtr(Imm32(2 * sizeof(intptr_t)), StackPointer); + masm.storePtr(BaselineStubReg, Address(StackPointer, sizeof(intptr_t))); + masm.storePtr(BaselineTailCallReg, Address(StackPointer, 0)); + + // This is expected to be called from within an IC, when BaselineStubReg + // is properly initialized to point to the stub. + masm.loadPtr(Address(BaselineStubReg, ICUpdatedStub::offsetOfFirstUpdateStub()), + BaselineStubReg); + + // Load stubcode pointer from BaselineStubReg into BaselineTailCallReg. + masm.loadPtr(Address(BaselineStubReg, ICStub::offsetOfStubCode()), R2.scratchReg()); + + // Call the stubcode. + masm.call(R2.scratchReg()); + + // Restore the old stub reg and tailcall reg. + masm.loadPtr(Address(StackPointer, 0), BaselineTailCallReg); + masm.loadPtr(Address(StackPointer, sizeof(intptr_t)), BaselineStubReg); + masm.addPtr(Imm32(2 * sizeof(intptr_t)), StackPointer); + + // The update IC will store 0 or 1 in R1.scratchReg() reflecting if the + // value in R0 type-checked properly or not. + Label success; + masm.ma_b(R1.scratchReg(), Imm32(1), &success, Assembler::Equal, ShortJump); + + // If the IC failed, then call the update fallback function. + EmitEnterStubFrame(masm, R1.scratchReg()); + + masm.loadValue(Address(BaselineStackReg, STUB_FRAME_SIZE + objectOffset), R1); + + masm.pushValue(R0); + masm.pushValue(R1); + masm.push(BaselineStubReg); + + // Load previous frame pointer, push BaselineFrame *. + masm.loadPtr(Address(BaselineFrameReg, 0), R0.scratchReg()); + masm.pushBaselineFramePtr(R0.scratchReg(), R0.scratchReg()); + + EmitCallVM(code, masm); + EmitLeaveStubFrame(masm); + + // Success at end. + masm.bind(&success); +} + +template +inline void +EmitPreBarrier(MacroAssembler &masm, const AddrType &addr, MIRType type) +{ + // On MIPS, $ra is clobbered by patchableCallPreBarrier. Save it first. + masm.push(ra); + masm.patchableCallPreBarrier(addr, type); + masm.pop(ra); +} + +inline void +EmitStubGuardFailure(MacroAssembler &masm) +{ + // NOTE: This routine assumes that the stub guard code left the stack in + // the same state it was in when it was entered. + + // BaselineStubEntry points to the current stub. + + // Load next stub into BaselineStubReg + masm.loadPtr(Address(BaselineStubReg, ICStub::offsetOfNext()), BaselineStubReg); + + // Load stubcode pointer from BaselineStubEntry into scratch register. + masm.loadPtr(Address(BaselineStubReg, ICStub::offsetOfStubCode()), R2.scratchReg()); + + // Return address is already loaded, just jump to the next stubcode. + MOZ_ASSERT(BaselineTailCallReg == ra); + masm.branch(R2.scratchReg()); +} + + +} // namespace jit +} // namespace js + +#endif // JS_ION + +#endif /* jit_mips_BaselineHelpers_mips_h */ + diff --git a/js/src/jit/mips/BaselineIC-mips.cpp b/js/src/jit/mips/BaselineIC-mips.cpp new file mode 100644 index 00000000000..777f5a60f79 --- /dev/null +++ b/js/src/jit/mips/BaselineIC-mips.cpp @@ -0,0 +1,223 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 "jsiter.h" + +#include "jit/BaselineCompiler.h" +#include "jit/BaselineHelpers.h" +#include "jit/BaselineIC.h" +#include "jit/BaselineJIT.h" +#include "jit/IonLinker.h" + +#include "jsboolinlines.h" + +using namespace js; +using namespace js::jit; + +namespace js { +namespace jit { + +// ICCompare_Int32 + +bool +ICCompare_Int32::Compiler::generateStubCode(MacroAssembler &masm) +{ + // Guard that R0 is an integer and R1 is an integer. + Label failure; + Label conditionTrue; + masm.branchTestInt32(Assembler::NotEqual, R0, &failure); + masm.branchTestInt32(Assembler::NotEqual, R1, &failure); + + // Compare payload regs of R0 and R1. + Assembler::Condition cond = JSOpToCondition(op, /* signed = */true); + masm.ma_cmp_set(R0.payloadReg(), R0.payloadReg(), R1.payloadReg(), cond); + + masm.tagValue(JSVAL_TYPE_BOOLEAN, R0.payloadReg(), R0); + EmitReturnFromIC(masm); + + // Failure case - jump to next stub + masm.bind(&failure); + EmitStubGuardFailure(masm); + + return true; +} + +bool +ICCompare_Double::Compiler::generateStubCode(MacroAssembler &masm) +{ + Label failure, isNaN; + masm.ensureDouble(R0, FloatReg0, &failure); + masm.ensureDouble(R1, FloatReg1, &failure); + + Register dest = R0.scratchReg(); + + Assembler::DoubleCondition doubleCond = JSOpToDoubleCondition(op); + + masm.ma_cmp_set_double(dest, FloatReg0, FloatReg1, doubleCond); + + masm.tagValue(JSVAL_TYPE_BOOLEAN, dest, R0); + EmitReturnFromIC(masm); + + // Failure case - jump to next stub + masm.bind(&failure); + EmitStubGuardFailure(masm); + return true; +} + +// ICBinaryArith_Int32 + +bool +ICBinaryArith_Int32::Compiler::generateStubCode(MacroAssembler &masm) +{ + // Guard that R0 is an integer and R1 is an integer. + Label failure; + masm.branchTestInt32(Assembler::NotEqual, R0, &failure); + masm.branchTestInt32(Assembler::NotEqual, R1, &failure); + + // Add R0 and R1. Don't need to explicitly unbox, just use R2's payloadReg. + Register scratchReg = R2.payloadReg(); + + // DIV and MOD need an extra non-volatile ValueOperand to hold R0. + GeneralRegisterSet savedRegs = availableGeneralRegs(2); + savedRegs = GeneralRegisterSet::Intersect(GeneralRegisterSet::NonVolatile(), savedRegs); + ValueOperand savedValue = savedRegs.takeAnyValue(); + + Label goodMul, divTest1, divTest2; + switch(op_) { + case JSOP_ADD: + // We know R0.typeReg() already contains the integer tag. No boxing + // required. + masm.ma_addTestOverflow(R0.payloadReg(), R0.payloadReg(), R1.payloadReg(), &failure); + break; + case JSOP_SUB: + masm.ma_subTestOverflow(R0.payloadReg(), R0.payloadReg(), R1.payloadReg(), &failure); + break; + case JSOP_MUL: { + masm.ma_mul_branch_overflow(scratchReg, R0.payloadReg(), R1.payloadReg(), &failure); + + masm.ma_b(scratchReg, Imm32(0), &goodMul, Assembler::NotEqual, ShortJump); + + // Result is -0 if operands have different signs. + masm.as_xor(t8, R0.payloadReg(), R1.payloadReg()); + masm.ma_b(t8, Imm32(0), &failure, Assembler::LessThan, ShortJump); + + masm.bind(&goodMul); + masm.move32(scratchReg, R0.payloadReg()); + break; + } + case JSOP_DIV: + case JSOP_MOD: { + // Check for INT_MIN / -1, it results in a double. + masm.ma_b(R0.payloadReg(), Imm32(INT_MIN), &divTest1, Assembler::NotEqual, ShortJump); + masm.ma_b(R1.payloadReg(), Imm32(-1), &failure, Assembler::Equal, ShortJump); + masm.bind(&divTest1); + + // Check for division by zero + masm.ma_b(R1.payloadReg(), Imm32(0), &failure, Assembler::Equal, ShortJump); + + // Check for 0 / X with X < 0 (results in -0). + masm.ma_b(R0.payloadReg(), Imm32(0), &divTest2, Assembler::NotEqual, ShortJump); + masm.ma_b(R1.payloadReg(), Imm32(0), &failure, Assembler::LessThan, ShortJump); + masm.bind(&divTest2); + + masm.as_div(R0.payloadReg(), R1.payloadReg()); + + if (op_ == JSOP_DIV) { + // Result is a double if the remainder != 0. + masm.as_mfhi(scratchReg); + masm.ma_b(scratchReg, Imm32(0), &failure, Assembler::NotEqual, ShortJump); + masm.as_mflo(scratchReg); + masm.tagValue(JSVAL_TYPE_INT32, scratchReg, R0); + } else { + Label done; + // If X % Y == 0 and X < 0, the result is -0. + masm.as_mfhi(scratchReg); + masm.ma_b(scratchReg, Imm32(0), &done, Assembler::NotEqual, ShortJump); + masm.ma_b(R0.payloadReg(), Imm32(0), &failure, Assembler::LessThan, ShortJump); + masm.bind(&done); + masm.tagValue(JSVAL_TYPE_INT32, scratchReg, R0); + } + break; + } + case JSOP_BITOR: + masm.ma_or(R0.payloadReg() , R0.payloadReg(), R1.payloadReg()); + break; + case JSOP_BITXOR: + masm.ma_xor(R0.payloadReg() , R0.payloadReg(), R1.payloadReg()); + break; + case JSOP_BITAND: + masm.ma_and(R0.payloadReg() , R0.payloadReg(), R1.payloadReg()); + break; + case JSOP_LSH: + // MIPS will only use 5 lowest bits in R1 as shift offset. + masm.ma_sll(R0.payloadReg(), R0.payloadReg(), R1.payloadReg()); + break; + case JSOP_RSH: + masm.ma_sra(R0.payloadReg(), R0.payloadReg(), R1.payloadReg()); + break; + case JSOP_URSH: + masm.ma_srl(scratchReg, R0.payloadReg(), R1.payloadReg()); + if (allowDouble_) { + Label toUint; + masm.ma_b(scratchReg, Imm32(0), &toUint, Assembler::LessThan, ShortJump); + + // Move result and box for return. + masm.move32(scratchReg, R0.payloadReg()); + EmitReturnFromIC(masm); + + masm.bind(&toUint); + masm.convertUInt32ToDouble(scratchReg, FloatReg1); + masm.boxDouble(FloatReg1, R0); + } else { + masm.ma_b(scratchReg, Imm32(0), &failure, Assembler::LessThan, ShortJump); + // Move result for return. + masm.move32(scratchReg, R0.payloadReg()); + } + break; + default: + MOZ_ASSUME_UNREACHABLE("Unhandled op for BinaryArith_Int32."); + } + + EmitReturnFromIC(masm); + + // Failure case - jump to next stub + masm.bind(&failure); + EmitStubGuardFailure(masm); + + return true; +} + +bool +ICUnaryArith_Int32::Compiler::generateStubCode(MacroAssembler &masm) +{ + Label failure; + masm.branchTestInt32(Assembler::NotEqual, R0, &failure); + + switch (op) { + case JSOP_BITNOT: + masm.not32(R0.payloadReg()); + break; + case JSOP_NEG: + // Guard against 0 and MIN_INT, both result in a double. + masm.branchTest32(Assembler::Zero, R0.payloadReg(), Imm32(INT32_MAX), &failure); + + masm.neg32(R0.payloadReg()); + break; + default: + MOZ_ASSUME_UNREACHABLE("Unexpected op"); + return false; + } + + EmitReturnFromIC(masm); + + masm.bind(&failure); + EmitStubGuardFailure(masm); + return true; +} + + +} // namespace jit +} // namespace js diff --git a/js/src/jit/mips/BaselineRegisters-mips.h b/js/src/jit/mips/BaselineRegisters-mips.h new file mode 100644 index 00000000000..6ecdfeafc93 --- /dev/null +++ b/js/src/jit/mips/BaselineRegisters-mips.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 jit_mips_BaselineRegisters_mips_h +#define jit_mips_BaselineRegisters_mips_h + +#ifdef JS_ION + +#include "jit/IonMacroAssembler.h" + +namespace js { +namespace jit { + +static MOZ_CONSTEXPR_VAR Register BaselineFrameReg = s5; +static MOZ_CONSTEXPR_VAR Register BaselineStackReg = sp; + +static MOZ_CONSTEXPR_VAR ValueOperand R0(v1, v0); +static MOZ_CONSTEXPR_VAR ValueOperand R1(s7, s6); +static MOZ_CONSTEXPR_VAR ValueOperand R2(t7, t6); + +// BaselineTailCallReg and BaselineStubReg +// These use registers that are not preserved across calls. +static MOZ_CONSTEXPR_VAR Register BaselineTailCallReg = ra; +static MOZ_CONSTEXPR_VAR Register BaselineStubReg = t5; + +static MOZ_CONSTEXPR_VAR Register ExtractTemp0 = InvalidReg; +static MOZ_CONSTEXPR_VAR Register ExtractTemp1 = InvalidReg; + +// Register used internally by MacroAssemblerMIPS. +static MOZ_CONSTEXPR_VAR Register BaselineSecondScratchReg = SecondScratchReg; + +// Note that BaselineTailCallReg is actually just the link register. +// In MIPS code emission, we do not clobber BaselineTailCallReg since we keep +// the return address for calls there. + +// FloatReg0 must be equal to ReturnFloatReg. +static MOZ_CONSTEXPR_VAR FloatRegister FloatReg0 = f0; +static MOZ_CONSTEXPR_VAR FloatRegister FloatReg1 = f2; + +} // namespace jit +} // namespace js + +#endif // JS_ION + +#endif /* jit_mips_BaselineRegisters_mips_h */ + diff --git a/js/src/jit/mips/MacroAssembler-mips.cpp b/js/src/jit/mips/MacroAssembler-mips.cpp index cb594950659..13b311a7f34 100644 --- a/js/src/jit/mips/MacroAssembler-mips.cpp +++ b/js/src/jit/mips/MacroAssembler-mips.cpp @@ -212,18 +212,18 @@ void MacroAssemblerMIPS::inc64(AbsoluteAddress dest) { ma_li(ScratchRegister, Imm32((int32_t)dest.addr)); - as_lw(secondScratchReg_, ScratchRegister, 0); + as_lw(SecondScratchReg, ScratchRegister, 0); - as_addiu(secondScratchReg_, secondScratchReg_, 1); - as_sw(secondScratchReg_, ScratchRegister, 0); + as_addiu(SecondScratchReg, SecondScratchReg, 1); + as_sw(SecondScratchReg, ScratchRegister, 0); - as_sltiu(secondScratchReg_, secondScratchReg_, 1); + as_sltiu(SecondScratchReg, SecondScratchReg, 1); as_lw(ScratchRegister, ScratchRegister, 4); - as_addu(secondScratchReg_, ScratchRegister, secondScratchReg_); + as_addu(SecondScratchReg, ScratchRegister, SecondScratchReg); ma_li(ScratchRegister, Imm32((int32_t)dest.addr)); - as_sw(secondScratchReg_, ScratchRegister, 4); + as_sw(SecondScratchReg, ScratchRegister, 4); } void @@ -408,7 +408,7 @@ MacroAssemblerMIPS::ma_or(Register rd, Imm32 imm) void MacroAssemblerMIPS::ma_or(Register rd, Register rs, Imm32 imm) { - if (Imm16::isInSignedRange(imm.value)) { + if (Imm16::isInUnsignedRange(imm.value)) { as_ori(rd, rs, imm.value); } else { ma_li(ScratchRegister, imm); @@ -438,7 +438,7 @@ MacroAssemblerMIPS::ma_xor(Register rd, Imm32 imm) void MacroAssemblerMIPS::ma_xor(Register rd, Register rs, Imm32 imm) { - if (Imm16::isInSignedRange(imm.value)) { + if (Imm16::isInUnsignedRange(imm.value)) { as_xori(rd, rs, imm.value); } else { ma_li(ScratchRegister, imm); @@ -476,17 +476,17 @@ void MacroAssemblerMIPS::ma_addTestOverflow(Register rd, Register rs, Register rt, Label *overflow) { Label goodAddition; - as_addu(secondScratchReg_, rs, rt); + as_addu(SecondScratchReg, rs, rt); as_xor(ScratchRegister, rs, rt); // If different sign, no overflow ma_b(ScratchRegister, Imm32(0), &goodAddition, Assembler::LessThan, ShortJump); // If different sign, then overflow - as_xor(ScratchRegister, rs, secondScratchReg_); + as_xor(ScratchRegister, rs, SecondScratchReg); ma_b(ScratchRegister, Imm32(0), overflow, Assembler::LessThan); bind(&goodAddition); - ma_move(rd, secondScratchReg_); + ma_move(rd, SecondScratchReg); } void @@ -496,18 +496,18 @@ MacroAssemblerMIPS::ma_addTestOverflow(Register rd, Register rs, Imm32 imm, Labe // Check for unsigned range because of as_xori if (Imm16::isInSignedRange(imm.value) && Imm16::isInUnsignedRange(imm.value)) { Label goodAddition; - as_addiu(secondScratchReg_, rs, imm.value); + as_addiu(SecondScratchReg, rs, imm.value); // If different sign, no overflow as_xori(ScratchRegister, rs, imm.value); ma_b(ScratchRegister, Imm32(0), &goodAddition, Assembler::LessThan, ShortJump); // If different sign, then overflow - as_xor(ScratchRegister, rs, secondScratchReg_); + as_xor(ScratchRegister, rs, SecondScratchReg); ma_b(ScratchRegister, Imm32(0), overflow, Assembler::LessThan); bind(&goodAddition); - ma_move(rd, secondScratchReg_); + ma_move(rd, SecondScratchReg); } else { ma_li(ScratchRegister, imm); ma_addTestOverflow(rd, rs, ScratchRegister, overflow); @@ -544,17 +544,17 @@ MacroAssemblerMIPS::ma_subTestOverflow(Register rd, Register rs, Register rt, La Label goodSubtraction; // Use second scratch. The instructions generated by ma_b don't use the // second scratch register. - ma_subu(secondScratchReg_, rs, rt); + ma_subu(SecondScratchReg, rs, rt); as_xor(ScratchRegister, rs, rt); // If same sign, no overflow ma_b(ScratchRegister, Imm32(0), &goodSubtraction, Assembler::GreaterThanOrEqual, ShortJump); // If different sign, then overflow - as_xor(ScratchRegister, rs, secondScratchReg_); + as_xor(ScratchRegister, rs, SecondScratchReg); ma_b(ScratchRegister, Imm32(0), overflow, Assembler::LessThan); bind(&goodSubtraction); - ma_move(rd, secondScratchReg_); + ma_move(rd, SecondScratchReg); } void @@ -581,8 +581,8 @@ MacroAssemblerMIPS::ma_mul_branch_overflow(Register rd, Register rs, Register rt as_mult(rs, rt); as_mflo(rd); as_sra(ScratchRegister, rd, 31); - as_mfhi(secondScratchReg_); - ma_b(ScratchRegister, secondScratchReg_, overflow, Assembler::NotEqual); + as_mfhi(SecondScratchReg); + ma_b(ScratchRegister, SecondScratchReg, overflow, Assembler::NotEqual); } void @@ -653,16 +653,16 @@ MacroAssemblerMIPS::ma_mod_mask(Register src, Register dest, Register hold, int3 bind(&head); // Extract the bottom bits into lr. - ma_and(secondScratchReg_, ScratchRegister, Imm32(mask)); + ma_and(SecondScratchReg, ScratchRegister, Imm32(mask)); // Add those bits to the accumulator. - as_addu(dest, dest, secondScratchReg_); + as_addu(dest, dest, SecondScratchReg); // Do a trial subtraction, this is the same operation as cmp, but we // store the dest - ma_subu(secondScratchReg_, dest, Imm32(mask)); + ma_subu(SecondScratchReg, dest, Imm32(mask)); // If (sum - C) > 0, store sum - C back into sum, thus performing a // modulus. - ma_b(secondScratchReg_, secondScratchReg_, &sumSigned, Signed, ShortJump); - ma_move(dest, secondScratchReg_); + ma_b(SecondScratchReg, SecondScratchReg, &sumSigned, Signed, ShortJump); + ma_move(dest, SecondScratchReg); bind(&sumSigned); // Get rid of the bits that we extracted before. as_srl(ScratchRegister, ScratchRegister, shift); @@ -728,8 +728,8 @@ void MacroAssemblerMIPS::ma_load(const Register &dest, const BaseIndex &src, LoadStoreSize size, LoadStoreExtension extension) { - computeScaledAddress(src, secondScratchReg_); - ma_load(dest, Address(secondScratchReg_, src.offset), size, extension); + computeScaledAddress(src, SecondScratchReg); + ma_load(dest, Address(SecondScratchReg, src.offset), size, extension); } void @@ -768,24 +768,24 @@ void MacroAssemblerMIPS::ma_store(const Register &data, const BaseIndex &dest, LoadStoreSize size, LoadStoreExtension extension) { - computeScaledAddress(dest, secondScratchReg_); - ma_store(data, Address(secondScratchReg_, dest.offset), size, extension); + computeScaledAddress(dest, SecondScratchReg); + ma_store(data, Address(SecondScratchReg, dest.offset), size, extension); } void MacroAssemblerMIPS::ma_store(const Imm32 &imm, const BaseIndex &dest, LoadStoreSize size, LoadStoreExtension extension) { - // Make sure that secondScratchReg_ contains absolute address so that + // Make sure that SecondScratchReg contains absolute address so that // offset is 0. - computeEffectiveAddress(dest, secondScratchReg_); + computeEffectiveAddress(dest, SecondScratchReg); // Scrach register is free now, use it for loading imm value ma_li(ScratchRegister, imm); // with offset=0 ScratchRegister will not be used in ma_store() // so we can use it as a parameter here - ma_store(ScratchRegister, Address(secondScratchReg_, 0), size, extension); + ma_store(ScratchRegister, Address(SecondScratchReg, 0), size, extension); } void @@ -822,11 +822,11 @@ MacroAssemblerMIPS::ma_sw(Imm32 imm, Address address) if (Imm16::isInSignedRange(address.offset)) { as_sw(ScratchRegister, address.base, Imm16(address.offset).encode()); } else { - MOZ_ASSERT(address.base != secondScratchReg_); + MOZ_ASSERT(address.base != SecondScratchReg); - ma_li(secondScratchReg_, Imm32(address.offset)); - as_addu(secondScratchReg_, address.base, secondScratchReg_); - as_sw(ScratchRegister, secondScratchReg_, 0); + ma_li(SecondScratchReg, Imm32(address.offset)); + as_addu(SecondScratchReg, address.base, SecondScratchReg); + as_sw(ScratchRegister, SecondScratchReg, 0); } } @@ -905,8 +905,8 @@ MacroAssemblerMIPS::ma_b(Register lhs, Address addr, Label *label, Condition c, void MacroAssemblerMIPS::ma_b(Address addr, Imm32 imm, Label *label, Condition c, JumpKind jumpKind) { - ma_lw(secondScratchReg_, addr); - ma_b(secondScratchReg_, imm, label, c, jumpKind); + ma_lw(SecondScratchReg, addr); + ma_b(SecondScratchReg, imm, label, c, jumpKind); } void @@ -1395,8 +1395,8 @@ MacroAssemblerMIPS::ma_sd(FloatRegister ft, Address address) void MacroAssemblerMIPS::ma_sd(FloatRegister ft, BaseIndex address) { - computeScaledAddress(address, secondScratchReg_); - ma_sd(ft, Address(secondScratchReg_, address.offset)); + computeScaledAddress(address, SecondScratchReg); + ma_sd(ft, Address(SecondScratchReg, address.offset)); } void @@ -1414,8 +1414,8 @@ MacroAssemblerMIPS::ma_ss(FloatRegister ft, Address address) void MacroAssemblerMIPS::ma_ss(FloatRegister ft, BaseIndex address) { - computeScaledAddress(address, secondScratchReg_); - ma_ss(ft, Address(secondScratchReg_, address.offset)); + computeScaledAddress(address, SecondScratchReg); + ma_ss(ft, Address(SecondScratchReg, address.offset)); } void @@ -1555,9 +1555,9 @@ void MacroAssemblerMIPSCompat::add32(Imm32 imm, const Address &dest) { - load32(dest, secondScratchReg_); - ma_addu(secondScratchReg_, imm); - store32(secondScratchReg_, dest); + load32(dest, SecondScratchReg); + ma_addu(SecondScratchReg, imm); + store32(SecondScratchReg, dest); } void @@ -1585,6 +1585,12 @@ MacroAssemblerMIPSCompat::addPtr(const Address &src, Register dest) ma_addu(dest, ScratchRegister); } +void +MacroAssemblerMIPSCompat::subPtr(Register src, Register dest) +{ + ma_subu(dest, dest, src); +} + void MacroAssemblerMIPSCompat::not32(Register reg) { @@ -1601,17 +1607,17 @@ MacroAssemblerMIPSCompat::and32(Imm32 imm, Register dest) void MacroAssemblerMIPSCompat::and32(Imm32 imm, const Address &dest) { - load32(dest, secondScratchReg_); - ma_and(secondScratchReg_, imm); - store32(secondScratchReg_, dest); + load32(dest, SecondScratchReg); + ma_and(SecondScratchReg, imm); + store32(SecondScratchReg, dest); } void MacroAssemblerMIPSCompat::or32(Imm32 imm, const Address &dest) { - load32(dest, secondScratchReg_); - ma_or(secondScratchReg_, imm); - store32(secondScratchReg_, dest); + load32(dest, SecondScratchReg); + ma_or(SecondScratchReg, imm); + store32(SecondScratchReg, dest); } void @@ -1802,8 +1808,8 @@ MacroAssemblerMIPSCompat::loadDouble(const Address &address, const FloatRegister void MacroAssemblerMIPSCompat::loadDouble(const BaseIndex &src, const FloatRegister &dest) { - computeScaledAddress(src, secondScratchReg_); - ma_ld(dest, Address(secondScratchReg_, src.offset)); + computeScaledAddress(src, SecondScratchReg); + ma_ld(dest, Address(SecondScratchReg, src.offset)); } void @@ -1829,15 +1835,15 @@ MacroAssemblerMIPSCompat::loadFloat32(const Address &address, const FloatRegiste void MacroAssemblerMIPSCompat::loadFloat32(const BaseIndex &src, const FloatRegister &dest) { - computeScaledAddress(src, secondScratchReg_); - ma_ls(dest, Address(secondScratchReg_, src.offset)); + computeScaledAddress(src, SecondScratchReg); + ma_ls(dest, Address(SecondScratchReg, src.offset)); } void MacroAssemblerMIPSCompat::store8(const Imm32 &imm, const Address &address) { - ma_li(secondScratchReg_, imm); - ma_store(secondScratchReg_, address, SizeByte); + ma_li(SecondScratchReg, imm); + ma_store(SecondScratchReg, address, SizeByte); } void @@ -1861,8 +1867,8 @@ MacroAssemblerMIPSCompat::store8(const Register &src, const BaseIndex &dest) void MacroAssemblerMIPSCompat::store16(const Imm32 &imm, const Address &address) { - ma_li(secondScratchReg_, imm); - ma_store(secondScratchReg_, address, SizeHalfWord); + ma_li(SecondScratchReg, imm); + ma_store(SecondScratchReg, address, SizeHalfWord); } void @@ -1998,16 +2004,16 @@ void MacroAssemblerMIPSCompat::branchTestGCThing(Condition cond, const Address &address, Label *label) { MOZ_ASSERT(cond == Equal || cond == NotEqual); - extractTag(address, secondScratchReg_); - ma_b(secondScratchReg_, ImmTag(JSVAL_LOWER_INCL_TAG_OF_GCTHING_SET), label, + extractTag(address, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_LOWER_INCL_TAG_OF_GCTHING_SET), label, (cond == Equal) ? AboveOrEqual : Below); } void MacroAssemblerMIPSCompat::branchTestGCThing(Condition cond, const BaseIndex &src, Label *label) { MOZ_ASSERT(cond == Equal || cond == NotEqual); - extractTag(src, secondScratchReg_); - ma_b(secondScratchReg_, ImmTag(JSVAL_LOWER_INCL_TAG_OF_GCTHING_SET), label, + extractTag(src, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_LOWER_INCL_TAG_OF_GCTHING_SET), label, (cond == Equal) ? AboveOrEqual : Below); } @@ -2043,16 +2049,16 @@ void MacroAssemblerMIPSCompat::branchTestInt32(Condition cond, const Address &address, Label *label) { MOZ_ASSERT(cond == Equal || cond == NotEqual); - extractTag(address, secondScratchReg_); - ma_b(secondScratchReg_, ImmTag(JSVAL_TAG_INT32), label, cond); + extractTag(address, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_INT32), label, cond); } void MacroAssemblerMIPSCompat::branchTestInt32(Condition cond, const BaseIndex &src, Label *label) { MOZ_ASSERT(cond == Equal || cond == NotEqual); - extractTag(src, secondScratchReg_); - ma_b(secondScratchReg_, ImmTag(JSVAL_TAG_INT32), label, cond); + extractTag(src, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_INT32), label, cond); } void @@ -2074,8 +2080,8 @@ void MacroAssemblerMIPSCompat::branchTestBoolean(Condition cond, const BaseIndex &src, Label *label) { MOZ_ASSERT(cond == Equal || cond == NotEqual); - extractTag(src, secondScratchReg_); - ma_b(secondScratchReg_, ImmType(JSVAL_TYPE_BOOLEAN), label, cond); + extractTag(src, SecondScratchReg); + ma_b(SecondScratchReg, ImmType(JSVAL_TYPE_BOOLEAN), label, cond); } void @@ -2098,8 +2104,8 @@ void MacroAssemblerMIPSCompat::branchTestDouble(Condition cond, const Address &address, Label *label) { MOZ_ASSERT(cond == Equal || cond == NotEqual); - extractTag(address, secondScratchReg_); - ma_b(secondScratchReg_, ImmTag(JSVAL_TAG_CLEAR), label, cond); + extractTag(address, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_CLEAR), label, cond); } void @@ -2107,8 +2113,8 @@ MacroAssemblerMIPSCompat::branchTestDouble(Condition cond, const BaseIndex &src, { MOZ_ASSERT(cond == Equal || cond == NotEqual); Condition actual = (cond == Equal) ? Below : AboveOrEqual; - extractTag(src, secondScratchReg_); - ma_b(secondScratchReg_, ImmTag(JSVAL_TAG_CLEAR), label, actual); + extractTag(src, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_CLEAR), label, actual); } void @@ -2129,8 +2135,8 @@ void MacroAssemblerMIPSCompat::branchTestNull(Condition cond, const BaseIndex &src, Label *label) { MOZ_ASSERT(cond == Equal || cond == NotEqual); - extractTag(src, secondScratchReg_); - ma_b(secondScratchReg_, ImmTag(JSVAL_TAG_NULL), label, cond); + extractTag(src, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_NULL), label, cond); } @@ -2151,8 +2157,8 @@ void MacroAssemblerMIPSCompat::branchTestObject(Condition cond, const BaseIndex &src, Label *label) { MOZ_ASSERT(cond == Equal || cond == NotEqual); - extractTag(src, secondScratchReg_); - ma_b(secondScratchReg_, ImmTag(JSVAL_TAG_OBJECT), label, cond); + extractTag(src, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_OBJECT), label, cond); } @@ -2173,8 +2179,8 @@ void MacroAssemblerMIPSCompat::branchTestString(Condition cond, const BaseIndex &src, Label *label) { MOZ_ASSERT(cond == Equal || cond == NotEqual); - extractTag(src, secondScratchReg_); - ma_b(secondScratchReg_, ImmTag(JSVAL_TAG_STRING), label, cond); + extractTag(src, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_STRING), label, cond); } void @@ -2196,16 +2202,16 @@ void MacroAssemblerMIPSCompat::branchTestUndefined(Condition cond, const BaseIndex &src, Label *label) { MOZ_ASSERT(cond == Equal || cond == NotEqual); - extractTag(src, secondScratchReg_); - ma_b(secondScratchReg_, ImmTag(JSVAL_TAG_UNDEFINED), label, cond); + extractTag(src, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_UNDEFINED), label, cond); } void MacroAssemblerMIPSCompat::branchTestUndefined(Condition cond, const Address &address, Label *label) { MOZ_ASSERT(cond == Equal || cond == NotEqual); - extractTag(address, secondScratchReg_); - ma_b(secondScratchReg_, ImmTag(JSVAL_TAG_UNDEFINED), label, cond); + extractTag(address, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_UNDEFINED), label, cond); } @@ -2240,16 +2246,16 @@ void MacroAssemblerMIPSCompat::branchTestMagic(Condition cond, const Address &address, Label *label) { MOZ_ASSERT(cond == Equal || cond == NotEqual); - extractTag(address, secondScratchReg_); - ma_b(secondScratchReg_, ImmTag(JSVAL_TAG_MAGIC), label, cond); + extractTag(address, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_MAGIC), label, cond); } void MacroAssemblerMIPSCompat::branchTestMagic(Condition cond, const BaseIndex &src, Label *label) { MOZ_ASSERT(cond == Equal || cond == NotEqual); - extractTag(src, secondScratchReg_); - ma_b(secondScratchReg_, ImmTag(JSVAL_TAG_MAGIC), label, cond); + extractTag(src, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_MAGIC), label, cond); } void @@ -2427,10 +2433,10 @@ MacroAssemblerMIPSCompat::loadInt32OrDouble(const Address &src, const FloatRegis { Label notInt32, end; // If it's an int, convert it to double. - ma_lw(secondScratchReg_, Address(src.base, src.offset + TAG_OFFSET)); - branchTestInt32(Assembler::NotEqual, secondScratchReg_, ¬Int32); - ma_lw(secondScratchReg_, Address(src.base, src.offset + PAYLOAD_OFFSET)); - convertInt32ToDouble(secondScratchReg_, dest); + ma_lw(SecondScratchReg, Address(src.base, src.offset + TAG_OFFSET)); + branchTestInt32(Assembler::NotEqual, SecondScratchReg, ¬Int32); + ma_lw(SecondScratchReg, Address(src.base, src.offset + PAYLOAD_OFFSET)); + convertInt32ToDouble(SecondScratchReg, dest); ma_b(&end, ShortJump); // Not an int, just load as double. @@ -2447,22 +2453,22 @@ MacroAssemblerMIPSCompat::loadInt32OrDouble(Register base, Register index, // If it's an int, convert it to double. - computeScaledAddress(BaseIndex(base, index, ShiftToScale(shift)), secondScratchReg_); + computeScaledAddress(BaseIndex(base, index, ShiftToScale(shift)), SecondScratchReg); // Since we only have one scratch, we need to stomp over it with the tag. - load32(Address(secondScratchReg_, TAG_OFFSET), secondScratchReg_); - branchTestInt32(Assembler::NotEqual, secondScratchReg_, ¬Int32); + load32(Address(SecondScratchReg, TAG_OFFSET), SecondScratchReg); + branchTestInt32(Assembler::NotEqual, SecondScratchReg, ¬Int32); - computeScaledAddress(BaseIndex(base, index, ShiftToScale(shift)), secondScratchReg_); - load32(Address(secondScratchReg_, PAYLOAD_OFFSET), secondScratchReg_); - convertInt32ToDouble(secondScratchReg_, dest); + computeScaledAddress(BaseIndex(base, index, ShiftToScale(shift)), SecondScratchReg); + load32(Address(SecondScratchReg, PAYLOAD_OFFSET), SecondScratchReg); + convertInt32ToDouble(SecondScratchReg, dest); ma_b(&end, ShortJump); // Not an int, just load as double. bind(¬Int32); // First, recompute the offset that had been stored in the scratch register // since the scratch register was overwritten loading in the type. - computeScaledAddress(BaseIndex(base, index, ShiftToScale(shift)), secondScratchReg_); - loadDouble(Address(secondScratchReg_, 0), dest); + computeScaledAddress(BaseIndex(base, index, ShiftToScale(shift)), SecondScratchReg); + loadDouble(Address(SecondScratchReg, 0), dest); bind(&end); } @@ -2484,10 +2490,10 @@ MacroAssemblerMIPSCompat::branchTestStringTruthy(bool b, const ValueOperand &val { Register string = value.payloadReg(); size_t mask = (0xFFFFFFFF << JSString::LENGTH_SHIFT); - ma_lw(secondScratchReg_, Address(string, JSString::offsetOfLengthAndFlags())); + ma_lw(SecondScratchReg, Address(string, JSString::offsetOfLengthAndFlags())); - // Use secondScratchReg_ because ma_and will clobber ScratchRegister - ma_and(ScratchRegister, secondScratchReg_, Imm32(mask)); + // Use SecondScratchReg because ma_and will clobber ScratchRegister + ma_and(ScratchRegister, SecondScratchReg, Imm32(mask)); ma_b(ScratchRegister, ScratchRegister, label, b ? NonZero : Zero); } @@ -2587,8 +2593,8 @@ MacroAssemblerMIPSCompat::storeValue(ValueOperand val, Operand dst) void MacroAssemblerMIPSCompat::storeValue(ValueOperand val, const BaseIndex &dest) { - computeScaledAddress(dest, secondScratchReg_); - storeValue(val, Address(secondScratchReg_, dest.offset)); + computeScaledAddress(dest, SecondScratchReg); + storeValue(val, Address(SecondScratchReg, dest.offset)); } void @@ -2599,8 +2605,8 @@ MacroAssemblerMIPSCompat::storeValue(JSValueType type, Register reg, BaseIndex d // Make sure that ma_sw doesn't clobber ScratchRegister int32_t offset = dest.offset; if (!Imm16::isInSignedRange(offset)) { - ma_li(secondScratchReg_, Imm32(offset)); - as_addu(ScratchRegister, ScratchRegister, secondScratchReg_); + ma_li(SecondScratchReg, Imm32(offset)); + as_addu(ScratchRegister, ScratchRegister, SecondScratchReg); offset = 0; } @@ -2617,22 +2623,22 @@ MacroAssemblerMIPSCompat::storeValue(ValueOperand val, const Address &dest) void MacroAssemblerMIPSCompat::storeValue(JSValueType type, Register reg, Address dest) { - MOZ_ASSERT(dest.base != secondScratchReg_); + MOZ_ASSERT(dest.base != SecondScratchReg); ma_sw(reg, Address(dest.base, dest.offset + PAYLOAD_OFFSET)); - ma_li(secondScratchReg_, ImmTag(JSVAL_TYPE_TO_TAG(type))); - ma_sw(secondScratchReg_, Address(dest.base, dest.offset + TAG_OFFSET)); + ma_li(SecondScratchReg, ImmTag(JSVAL_TYPE_TO_TAG(type))); + ma_sw(SecondScratchReg, Address(dest.base, dest.offset + TAG_OFFSET)); } void MacroAssemblerMIPSCompat::storeValue(const Value &val, Address dest) { - MOZ_ASSERT(dest.base != secondScratchReg_); + MOZ_ASSERT(dest.base != SecondScratchReg); - ma_li(secondScratchReg_, Imm32(getType(val))); - ma_sw(secondScratchReg_, Address(dest.base, dest.offset + TAG_OFFSET)); - moveData(val, secondScratchReg_); - ma_sw(secondScratchReg_, Address(dest.base, dest.offset + PAYLOAD_OFFSET)); + ma_li(SecondScratchReg, Imm32(getType(val))); + ma_sw(SecondScratchReg, Address(dest.base, dest.offset + TAG_OFFSET)); + moveData(val, SecondScratchReg); + ma_sw(SecondScratchReg, Address(dest.base, dest.offset + PAYLOAD_OFFSET)); } void @@ -2643,8 +2649,8 @@ MacroAssemblerMIPSCompat::storeValue(const Value &val, BaseIndex dest) // Make sure that ma_sw doesn't clobber ScratchRegister int32_t offset = dest.offset; if (!Imm16::isInSignedRange(offset)) { - ma_li(secondScratchReg_, Imm32(offset)); - as_addu(ScratchRegister, ScratchRegister, secondScratchReg_); + ma_li(SecondScratchReg, Imm32(offset)); + as_addu(ScratchRegister, ScratchRegister, SecondScratchReg); offset = 0; } storeValue(val, Address(ScratchRegister, offset)); @@ -2653,8 +2659,8 @@ MacroAssemblerMIPSCompat::storeValue(const Value &val, BaseIndex dest) void MacroAssemblerMIPSCompat::loadValue(const BaseIndex &addr, ValueOperand val) { - computeScaledAddress(addr, secondScratchReg_); - loadValue(Address(secondScratchReg_, addr.offset), val); + computeScaledAddress(addr, SecondScratchReg); + loadValue(Address(SecondScratchReg, addr.offset), val); } void @@ -2714,8 +2720,8 @@ MacroAssemblerMIPSCompat::popValue(ValueOperand val) void MacroAssemblerMIPSCompat::storePayload(const Value &val, Address dest) { - moveData(val, secondScratchReg_); - ma_sw(secondScratchReg_, Address(dest.base, dest.offset + PAYLOAD_OFFSET)); + moveData(val, SecondScratchReg); + ma_sw(SecondScratchReg, Address(dest.base, dest.offset + PAYLOAD_OFFSET)); } void @@ -2729,33 +2735,33 @@ void MacroAssemblerMIPSCompat::storePayload(const Value &val, Register base, Register index, int32_t shift) { - computeScaledAddress(BaseIndex(base, index, ShiftToScale(shift)), secondScratchReg_); + computeScaledAddress(BaseIndex(base, index, ShiftToScale(shift)), SecondScratchReg); moveData(val, ScratchRegister); - as_sw(ScratchRegister, secondScratchReg_, NUNBOX32_PAYLOAD_OFFSET); + as_sw(ScratchRegister, SecondScratchReg, NUNBOX32_PAYLOAD_OFFSET); } void MacroAssemblerMIPSCompat::storePayload(Register src, Register base, Register index, int32_t shift) { - computeScaledAddress(BaseIndex(base, index, ShiftToScale(shift)), secondScratchReg_); - as_sw(src, secondScratchReg_, NUNBOX32_PAYLOAD_OFFSET); + computeScaledAddress(BaseIndex(base, index, ShiftToScale(shift)), SecondScratchReg); + as_sw(src, SecondScratchReg, NUNBOX32_PAYLOAD_OFFSET); } void MacroAssemblerMIPSCompat::storeTypeTag(ImmTag tag, Address dest) { - ma_li(secondScratchReg_, tag); - ma_sw(secondScratchReg_, Address(dest.base, dest.offset + TAG_OFFSET)); + ma_li(SecondScratchReg, tag); + ma_sw(SecondScratchReg, Address(dest.base, dest.offset + TAG_OFFSET)); } void MacroAssemblerMIPSCompat::storeTypeTag(ImmTag tag, Register base, Register index, int32_t shift) { - computeScaledAddress(BaseIndex(base, index, ShiftToScale(shift)), secondScratchReg_); + computeScaledAddress(BaseIndex(base, index, ShiftToScale(shift)), SecondScratchReg); ma_li(ScratchRegister, tag); - as_sw(ScratchRegister, secondScratchReg_, TAG_OFFSET); + as_sw(ScratchRegister, SecondScratchReg, TAG_OFFSET); } void diff --git a/js/src/jit/mips/MacroAssembler-mips.h b/js/src/jit/mips/MacroAssembler-mips.h index 420846ce253..afff3b06847 100644 --- a/js/src/jit/mips/MacroAssembler-mips.h +++ b/js/src/jit/mips/MacroAssembler-mips.h @@ -65,16 +65,7 @@ static_assert(1 << defaultShift == sizeof(jsval), "The defaultShift is wrong"); class MacroAssemblerMIPS : public Assembler { - protected: - Register secondScratchReg_; - public: - MacroAssemblerMIPS() : secondScratchReg_(t8) - { } - - Register secondScratch() { - return secondScratchReg_; - } void convertBoolToInt32(Register source, Register dest); void convertInt32ToDouble(const Register &src, const FloatRegister &dest); @@ -576,8 +567,8 @@ class MacroAssemblerMIPSCompat : public MacroAssemblerMIPS ma_b(ScratchRegister, rhs, label, cond); } void branch32(Condition cond, const Address &lhs, Imm32 rhs, Label *label) { - ma_lw(secondScratchReg_, lhs); - ma_b(secondScratchReg_, rhs, label, cond); + ma_lw(SecondScratchReg, lhs); + ma_b(SecondScratchReg, rhs, label, cond); } void branchPtr(Condition cond, const Address &lhs, Register rhs, Label *label) { branch32(cond, lhs, rhs, label); @@ -658,8 +649,8 @@ class MacroAssemblerMIPSCompat : public MacroAssemblerMIPS branchTest32(cond, lhs, ScratchRegister, label); } void branchTest32(Condition cond, const Address &address, Imm32 imm, Label *label) { - ma_lw(secondScratchReg_, address); - branchTest32(cond, secondScratchReg_, imm, label); + ma_lw(SecondScratchReg, address); + branchTest32(cond, SecondScratchReg, imm, label); } void branchTestPtr(Condition cond, const Register &lhs, const Register &rhs, Label *label) { branchTest32(cond, lhs, rhs, label); @@ -712,22 +703,22 @@ public: template CodeOffsetJump branchPtrWithPatch(Condition cond, Address addr, T ptr, RepatchLabel *label) { - loadPtr(addr, secondScratchReg_); + loadPtr(addr, SecondScratchReg); movePtr(ptr, ScratchRegister); Label skipJump; - ma_b(secondScratchReg_, ScratchRegister, &skipJump, InvertCondition(cond), ShortJump); + ma_b(SecondScratchReg, ScratchRegister, &skipJump, InvertCondition(cond), ShortJump); CodeOffsetJump off = jumpWithPatch(label); bind(&skipJump); return off; } void branchPtr(Condition cond, Address addr, ImmGCPtr ptr, Label *label) { - ma_lw(secondScratchReg_, addr); + ma_lw(SecondScratchReg, addr); ma_li(ScratchRegister, ptr); - ma_b(secondScratchReg_, ScratchRegister, label, cond); + ma_b(SecondScratchReg, ScratchRegister, label, cond); } void branchPtr(Condition cond, Address addr, ImmWord ptr, Label *label) { - ma_lw(secondScratchReg_, addr); - ma_b(secondScratchReg_, Imm32(ptr.value), label, cond); + ma_lw(SecondScratchReg, addr); + ma_b(SecondScratchReg, Imm32(ptr.value), label, cond); } void branchPtr(Condition cond, Address addr, ImmPtr ptr, Label *label) { branchPtr(cond, addr, ImmWord(uintptr_t(ptr.value)), label); @@ -742,8 +733,8 @@ public: ma_b(ScratchRegister, ptr, label, cond); } void branch32(Condition cond, const AbsoluteAddress &lhs, Imm32 rhs, Label *label) { - loadPtr(lhs, secondScratchReg_); // ma_b might use scratch - ma_b(secondScratchReg_, rhs, label, cond); + loadPtr(lhs, SecondScratchReg); // ma_b might use scratch + ma_b(SecondScratchReg, rhs, label, cond); } void branch32(Condition cond, const AbsoluteAddress &lhs, const Register &rhs, Label *label) { loadPtr(lhs, ScratchRegister); @@ -917,6 +908,7 @@ public: void andPtr(Imm32 imm, Register dest); void andPtr(Register src, Register dest); void addPtr(Register src, Register dest); + void subPtr(Register src, Register dest); void addPtr(const Address &src, Register dest); void not32(Register reg); diff --git a/js/src/jit/mips/MoveEmitter-mips.cpp b/js/src/jit/mips/MoveEmitter-mips.cpp new file mode 100644 index 00000000000..884408e0599 --- /dev/null +++ b/js/src/jit/mips/MoveEmitter-mips.cpp @@ -0,0 +1,330 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 "jit/mips/MoveEmitter-mips.h" + +using namespace js; +using namespace js::jit; + +MoveEmitterMIPS::MoveEmitterMIPS(MacroAssemblerMIPSCompat &masm) + : inCycle_(false), + masm(masm), + pushedAtCycle_(-1), + pushedAtSpill_(-1), + spilledReg_(InvalidReg), + spilledFloatReg_(InvalidFloatReg) +{ + pushedAtStart_ = masm.framePushed(); +} + +void +MoveEmitterMIPS::emit(const MoveResolver &moves) +{ + if (moves.hasCycles()) { + // Reserve stack for cycle resolution + masm.reserveStack(sizeof(double)); + pushedAtCycle_ = masm.framePushed(); + } + + for (size_t i = 0; i < moves.numMoves(); i++) + emit(moves.getMove(i)); +} + +MoveEmitterMIPS::~MoveEmitterMIPS() +{ + assertDone(); +} + +Address +MoveEmitterMIPS::cycleSlot() const +{ + int offset = masm.framePushed() - pushedAtCycle_; + MOZ_ASSERT(Imm16::isInSignedRange(offset)); + return Address(StackPointer, offset); +} + +int32_t +MoveEmitterMIPS::getAdjustedOffset(const MoveOperand &operand) +{ + MOZ_ASSERT(operand.isMemoryOrEffectiveAddress()); + if (operand.base() != StackPointer) + return operand.disp(); + + // Adjust offset if stack pointer has been moved. + return operand.disp() + masm.framePushed() - pushedAtStart_; +} + +Address +MoveEmitterMIPS::getAdjustedAddress(const MoveOperand &operand) +{ + return Address(operand.base(), getAdjustedOffset(operand)); +} + + +Register +MoveEmitterMIPS::tempReg() +{ + spilledReg_ = SecondScratchReg; + return SecondScratchReg; +} + +void +MoveEmitterMIPS::breakCycle(const MoveOperand &from, const MoveOperand &to, MoveOp::Type type) +{ + // There is some pattern: + // (A -> B) + // (B -> A) + // + // This case handles (A -> B), which we reach first. We save B, then allow + // the original move to continue. + switch (type) { + case MoveOp::FLOAT32: + if (to.isMemory()) { + FloatRegister temp = ScratchFloatReg; + masm.loadFloat32(getAdjustedAddress(to), temp); + masm.storeFloat32(temp, cycleSlot()); + } else { + masm.storeFloat32(to.floatReg(), cycleSlot()); + } + break; + case MoveOp::DOUBLE: + if (to.isMemory()) { + FloatRegister temp = ScratchFloatReg; + masm.loadDouble(getAdjustedAddress(to), temp); + masm.storeDouble(temp, cycleSlot()); + } else { + masm.storeDouble(to.floatReg(), cycleSlot()); + } + break; + case MoveOp::INT32: + MOZ_ASSERT(sizeof(uintptr_t) == sizeof(int32_t)); + case MoveOp::GENERAL: + if (to.isMemory()) { + Register temp = tempReg(); + masm.loadPtr(getAdjustedAddress(to), temp); + masm.storePtr(temp, cycleSlot()); + } else { + // Second scratch register should not be moved by MoveEmitter. + MOZ_ASSERT(to.reg() != spilledReg_); + masm.storePtr(to.reg(), cycleSlot()); + } + break; + default: + MOZ_ASSUME_UNREACHABLE("Unexpected move type"); + } +} + +void +MoveEmitterMIPS::completeCycle(const MoveOperand &from, const MoveOperand &to, MoveOp::Type type) +{ + // There is some pattern: + // (A -> B) + // (B -> A) + // + // This case handles (B -> A), which we reach last. We emit a move from the + // saved value of B, to A. + switch (type) { + case MoveOp::FLOAT32: + if (to.isMemory()) { + FloatRegister temp = ScratchFloatReg; + masm.loadFloat32(cycleSlot(), temp); + masm.storeFloat32(temp, getAdjustedAddress(to)); + } else { + masm.loadFloat32(cycleSlot(), to.floatReg()); + } + break; + case MoveOp::DOUBLE: + if (to.isMemory()) { + FloatRegister temp = ScratchFloatReg; + masm.loadDouble(cycleSlot(), temp); + masm.storeDouble(temp, getAdjustedAddress(to)); + } else { + masm.loadDouble(cycleSlot(), to.floatReg()); + } + break; + case MoveOp::INT32: + MOZ_ASSERT(sizeof(uintptr_t) == sizeof(int32_t)); + case MoveOp::GENERAL: + if (to.isMemory()) { + Register temp = tempReg(); + masm.loadPtr(cycleSlot(), temp); + masm.storePtr(temp, getAdjustedAddress(to)); + } else { + // Second scratch register should not be moved by MoveEmitter. + MOZ_ASSERT(to.reg() != spilledReg_); + masm.loadPtr(cycleSlot(), to.reg()); + } + break; + default: + MOZ_ASSUME_UNREACHABLE("Unexpected move type"); + } +} + +void +MoveEmitterMIPS::emitMove(const MoveOperand &from, const MoveOperand &to) +{ + if (from.isGeneralReg()) { + // Second scratch register should not be moved by MoveEmitter. + MOZ_ASSERT(from.reg() != spilledReg_); + + if (to.isGeneralReg()) + masm.movePtr(from.reg(), to.reg()); + else if (to.isMemory()) + masm.storePtr(from.reg(), getAdjustedAddress(to)); + else + MOZ_ASSUME_UNREACHABLE("Invalid emitMove arguments."); + } else if (from.isMemory()) { + if (to.isGeneralReg()) { + masm.loadPtr(getAdjustedAddress(from), to.reg()); + } else if (to.isMemory()) { + masm.loadPtr(getAdjustedAddress(from), tempReg()); + masm.storePtr(tempReg(), getAdjustedAddress(to)); + } else { + MOZ_ASSUME_UNREACHABLE("Invalid emitMove arguments."); + } + } else if (from.isEffectiveAddress()) { + if (to.isGeneralReg()) { + masm.computeEffectiveAddress(getAdjustedAddress(from), to.reg()); + } else if (to.isMemory()) { + masm.computeEffectiveAddress(getAdjustedAddress(from), tempReg()); + masm.storePtr(tempReg(), getAdjustedAddress(to)); + } else { + MOZ_ASSUME_UNREACHABLE("Invalid emitMove arguments."); + } + } else { + MOZ_ASSUME_UNREACHABLE("Invalid emitMove arguments."); + } +} + +void +MoveEmitterMIPS::emitFloat32Move(const MoveOperand &from, const MoveOperand &to) +{ + // Ensure that we can use ScratchFloatReg in memory move. + MOZ_ASSERT_IF(from.isFloatReg(), from.floatReg() != ScratchFloatReg); + MOZ_ASSERT_IF(to.isFloatReg(), to.floatReg() != ScratchFloatReg); + + if (from.isFloatReg()) { + if (to.isFloatReg()) { + masm.moveFloat32(from.floatReg(), to.floatReg()); + } else if (to.isGeneralReg()) { + // This should only be used when passing float parameter in a1,a2,a3 + MOZ_ASSERT(to.reg() == a1 || to.reg() == a2 || to.reg() == a3); + masm.as_mfc1(to.reg(), from.floatReg()); + } else { + MOZ_ASSERT(to.isMemory()); + masm.storeFloat32(from.floatReg(), getAdjustedAddress(to)); + } + } else if (to.isFloatReg()) { + MOZ_ASSERT(from.isMemory()); + masm.loadFloat32(getAdjustedAddress(from), to.floatReg()); + } else if (to.isGeneralReg()) { + MOZ_ASSERT(from.isMemory()); + // This should only be used when passing float parameter in a1,a2,a3 + MOZ_ASSERT(to.reg() == a1 || to.reg() == a2 || to.reg() == a3); + masm.loadPtr(getAdjustedAddress(from), to.reg()); + } else { + MOZ_ASSERT(from.isMemory()); + MOZ_ASSERT(to.isMemory()); + masm.loadFloat32(getAdjustedAddress(from), ScratchFloatReg); + masm.storeFloat32(ScratchFloatReg, getAdjustedAddress(to)); + } +} + +void +MoveEmitterMIPS::emitDoubleMove(const MoveOperand &from, const MoveOperand &to) +{ + // Ensure that we can use ScratchFloatReg in memory move. + MOZ_ASSERT_IF(from.isFloatReg(), from.floatReg() != ScratchFloatReg); + MOZ_ASSERT_IF(to.isFloatReg(), to.floatReg() != ScratchFloatReg); + + if (from.isFloatReg()) { + if (to.isFloatReg()) { + masm.moveDouble(from.floatReg(), to.floatReg()); + } else if (to.isGeneralReg()) { + // Used for passing double parameter in a2,a3 register pair. + // Two moves are added for one double parameter by + // MacroAssemblerMIPSCompat::passABIArg + if(to.reg() == a2) + masm.as_mfc1(a2, from.floatReg()); + else if(to.reg() == a3) + masm.as_mfc1_Odd(a3, from.floatReg()); + else + MOZ_ASSUME_UNREACHABLE("Invalid emitDoubleMove arguments."); + } else { + MOZ_ASSERT(to.isMemory()); + masm.storeDouble(from.floatReg(), getAdjustedAddress(to)); + } + } else if (to.isFloatReg()) { + MOZ_ASSERT(from.isMemory()); + masm.loadDouble(getAdjustedAddress(from), to.floatReg()); + } else if (to.isGeneralReg()) { + MOZ_ASSERT(from.isMemory()); + // Used for passing double parameter in a2,a3 register pair. + // Two moves are added for one double parameter by + // MacroAssemblerMIPSCompat::passABIArg + if(to.reg() == a2) + masm.loadPtr(getAdjustedAddress(from), a2); + else if(to.reg() == a3) + masm.loadPtr(Address(from.base(), getAdjustedOffset(from) + sizeof(uint32_t)), a3); + else + MOZ_ASSUME_UNREACHABLE("Invalid emitDoubleMove arguments."); + } else { + MOZ_ASSERT(from.isMemory()); + MOZ_ASSERT(to.isMemory()); + masm.loadDouble(getAdjustedAddress(from), ScratchFloatReg); + masm.storeDouble(ScratchFloatReg, getAdjustedAddress(to)); + } +} + +void +MoveEmitterMIPS::emit(const MoveOp &move) +{ + const MoveOperand &from = move.from(); + const MoveOperand &to = move.to(); + + if (move.isCycleEnd()) { + MOZ_ASSERT(inCycle_); + completeCycle(from, to, move.type()); + inCycle_ = false; + return; + } + + if (move.isCycleBegin()) { + MOZ_ASSERT(!inCycle_); + breakCycle(from, to, move.endCycleType()); + inCycle_ = true; + } + + switch (move.type()) { + case MoveOp::FLOAT32: + emitFloat32Move(from, to); + break; + case MoveOp::DOUBLE: + emitDoubleMove(from, to); + break; + case MoveOp::INT32: + MOZ_ASSERT(sizeof(uintptr_t) == sizeof(int32_t)); + case MoveOp::GENERAL: + emitMove(from, to); + break; + default: + MOZ_ASSUME_UNREACHABLE("Unexpected move type"); + } +} + +void +MoveEmitterMIPS::assertDone() +{ + MOZ_ASSERT(!inCycle_); +} + +void +MoveEmitterMIPS::finish() +{ + assertDone(); + + masm.freeStack(masm.framePushed() - pushedAtStart_); +} diff --git a/js/src/jit/mips/MoveEmitter-mips.h b/js/src/jit/mips/MoveEmitter-mips.h new file mode 100644 index 00000000000..d17820a099a --- /dev/null +++ b/js/src/jit/mips/MoveEmitter-mips.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 jit_mips_MoveEmitter_mips_h +#define jit_mips_MoveEmitter_mips_h + +#include "jit/IonMacroAssembler.h" +#include "jit/MoveResolver.h" + +namespace js { +namespace jit { + +class CodeGenerator; + +class MoveEmitterMIPS +{ + bool inCycle_; + MacroAssemblerMIPSCompat &masm; + + // Original stack push value. + uint32_t pushedAtStart_; + + // These store stack offsets to spill locations, snapshotting + // codegen->framePushed_ at the time they were allocated. They are -1 if no + // stack space has been allocated for that particular spill. + int32_t pushedAtCycle_; + int32_t pushedAtSpill_; + + // These are registers that are available for temporary use. They may be + // assigned InvalidReg. If no corresponding spill space has been assigned, + // then these registers do not need to be spilled. + Register spilledReg_; + FloatRegister spilledFloatReg_; + + void assertDone(); + Register tempReg(); + FloatRegister tempFloatReg(); + Address cycleSlot() const; + int32_t getAdjustedOffset(const MoveOperand &operand); + Address getAdjustedAddress(const MoveOperand &operand); + + void emitMove(const MoveOperand &from, const MoveOperand &to); + void emitFloat32Move(const MoveOperand &from, const MoveOperand &to); + void emitDoubleMove(const MoveOperand &from, const MoveOperand &to); + void breakCycle(const MoveOperand &from, const MoveOperand &to, MoveOp::Type type); + void completeCycle(const MoveOperand &from, const MoveOperand &to, MoveOp::Type type); + void emit(const MoveOp &move); + + public: + MoveEmitterMIPS(MacroAssemblerMIPSCompat &masm); + ~MoveEmitterMIPS(); + void emit(const MoveResolver &moves); + void finish(); +}; + +typedef MoveEmitterMIPS MoveEmitter; + +} // namespace jit +} // namespace js + +#endif /* jit_mips_MoveEmitter_mips_h */ diff --git a/js/src/jsapi-tests/moz.build b/js/src/jsapi-tests/moz.build index 64f9d56ef54..a166525daf1 100644 --- a/js/src/jsapi-tests/moz.build +++ b/js/src/jsapi-tests/moz.build @@ -45,7 +45,6 @@ UNIFIED_SOURCES += [ 'testJSEvaluateScript.cpp', 'testLookup.cpp', 'testLooselyEqual.cpp', - 'testMappedArrayBuffer.cpp', 'testNewObject.cpp', 'testNullRoot.cpp', 'testObjectEmulatingUndefined.cpp', diff --git a/js/src/jsapi-tests/testMappedArrayBuffer.cpp b/js/src/jsapi-tests/testMappedArrayBuffer.cpp deleted file mode 100644 index a0387e252f4..00000000000 --- a/js/src/jsapi-tests/testMappedArrayBuffer.cpp +++ /dev/null @@ -1,179 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- - * vim: set ts=8 sts=4 et sw=4 tw=99: - */ - -#ifdef XP_UNIX -#include -#include -#include -#include -#include -#include - -#include "jsfriendapi.h" -#include "js/StructuredClone.h" -#include "jsapi-tests/tests.h" -#include "vm/ArrayBufferObject.h" - -const char test_data[] = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; -const char test_filename[] = "temp-bug945152_MappedArrayBuffer"; - -BEGIN_TEST(testMappedArrayBuffer_bug945152) -{ - TempFile test_file; - FILE *test_stream = test_file.open(test_filename); - CHECK(fputs(test_data, test_stream) != EOF); - test_file.close(); - - // Offset 0. - CHECK(TestCreateObject(0, 12)); - - // Aligned offset. - CHECK(TestCreateObject(8, 12)); - - // Unaligned offset. - CHECK(CreateNewObject(11, 12) == nullptr); - - // Offset + length greater than file size. - CHECK(CreateNewObject(8, sizeof(test_data) - 7) == nullptr); - - // Release the mapped content. - CHECK(TestReleaseContents()); - -#ifdef JSGC_USE_EXACT_ROOTING - // Ensure that fd is closed after object been GCed. - // Check the fd returned from object created in a function, - // then do the GC, in order to guarantee the object is freed when - // exact rooting is not on. - int fd = GetNewObjectFD(); - GC(cx); - CHECK(!fd_is_valid(fd)); -#endif - - // Neuter mapped array buffer. - CHECK(TestNeuterObject()); - - // Clone mapped array buffer. - CHECK(TestCloneObject()); - - test_file.remove(); - - return true; -} - -JSObject *CreateNewObject(const int offset, const int length) -{ - int fd = open(test_filename, O_RDONLY); - void *ptr; - int new_fd; - if (!JS_CreateMappedArrayBufferContents(fd, &new_fd, offset, length, &ptr)) - return nullptr; - JSObject *obj = JS_NewArrayBufferWithContents(cx, ptr); - close(fd); - - return obj; -} - -// Return the fd from object created in the stack. -int GetNewObjectFD() -{ - JS::RootedObject obj(cx, CreateNewObject(0, 12)); - int fd = getFD(obj); - CHECK(fd_is_valid(fd)); - - return fd; -} - -bool VerifyObject(JS::HandleObject obj, const int offset, const int length) -{ - CHECK(obj != nullptr); - CHECK(JS_IsArrayBufferObject(obj)); - CHECK_EQUAL(JS_GetArrayBufferByteLength(obj), length); - js::ArrayBufferObject *buf = &obj->as(); - CHECK(buf->isMappedArrayBuffer()); - const char *data = reinterpret_cast(JS_GetArrayBufferData(obj)); - CHECK(data != nullptr); - CHECK(memcmp(data, test_data + offset, length) == 0); - - return true; -} - -bool TestCreateObject(const int offset, const int length) -{ - JS::RootedObject obj(cx, CreateNewObject(offset, length)); - CHECK(VerifyObject(obj, offset, length)); - - return true; -} - -bool TestReleaseContents() -{ - int fd = open(test_filename, O_RDONLY); - void *ptr; - int new_fd; - if (!JS_CreateMappedArrayBufferContents(fd, &new_fd, 0, 12, &ptr)) - return false; - CHECK(fd_is_valid(new_fd)); - JS_ReleaseMappedArrayBufferContents(new_fd, ptr, 12); - CHECK(!fd_is_valid(new_fd)); - close(fd); - - return true; -} - -bool TestNeuterObject() -{ - JS::RootedObject obj(cx, CreateNewObject(8, 12)); - CHECK(obj != nullptr); - int fd = getFD(obj); - CHECK(fd_is_valid(fd)); - JS_NeuterArrayBuffer(cx, obj); - CHECK(isNeutered(obj)); - CHECK(!fd_is_valid(fd)); - - return true; -} - -bool TestCloneObject() -{ - JS::RootedObject obj1(cx, CreateNewObject(8, 12)); - CHECK(obj1 != nullptr); - JSAutoStructuredCloneBuffer cloned_buffer; - JS::RootedValue v1(cx, OBJECT_TO_JSVAL(obj1)); - const JSStructuredCloneCallbacks *callbacks = js::GetContextStructuredCloneCallbacks(cx); - CHECK(cloned_buffer.write(cx, v1, callbacks, nullptr)); - JS::RootedValue v2(cx); - CHECK(cloned_buffer.read(cx, &v2, callbacks, nullptr)); - JS::RootedObject obj2(cx, JSVAL_TO_OBJECT(v2)); - CHECK(VerifyObject(obj2, 8, 12)); - - return true; -} - -bool isNeutered(JS::HandleObject obj) -{ - JS::RootedValue v(cx); - return JS_GetProperty(cx, obj, "byteLength", &v) && v.toInt32() == 0; -} - -int getFD(JS::HandleObject obj) -{ - CHECK(obj != nullptr); - js::ArrayBufferObject *buf = &obj->as(); - return buf->getMappingFD(); -} - -static bool fd_is_valid(int fd) -{ - return fcntl(fd, F_GETFD) != -1 || errno != EBADF; -} - -static void GC(JSContext *cx) -{ - JS_GC(JS_GetRuntime(cx)); - // Trigger another to wait for background finalization to end. - JS_GC(JS_GetRuntime(cx)); -} - -END_TEST(testMappedArrayBuffer_bug945152) -#endif diff --git a/js/src/jsapi.h b/js/src/jsapi.h index d37cc9f78b2..e2b4ec4456c 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -3161,26 +3161,6 @@ JS_AllocateArrayBufferContents(JSContext *maybecx, uint32_t nbytes, void **conte extern JS_PUBLIC_API(bool) JS_ReallocateArrayBufferContents(JSContext *cx, uint32_t nbytes, void **contents, uint8_t **data); -/* - * Create memory mapped array buffer contents. - * For cloning, the fd will not be closed after mapping, and the caller must - * take care of closing fd after calling this function. - * A new duplicated fd used by the mapping is returned in new_fd. - */ -extern JS_PUBLIC_API(bool) -JS_CreateMappedArrayBufferContents(int fd, int *new_fd, size_t offset, - size_t length, void **contents); - -/* - * Release the allocated resource of mapped array buffer contents before the - * object is created. - * If a new object has been created by JS_NewArrayBufferWithContents() with - * this content, then JS_NeuterArrayBuffer() should be used instead to release - * the resource used by the object. - */ -extern JS_PUBLIC_API(void) -JS_ReleaseMappedArrayBufferContents(int fd, void *contents, size_t length); - extern JS_PUBLIC_API(JSIdArray *) JS_Enumerate(JSContext *cx, JSObject *obj); diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index c6ecfe4ff72..7101cda7bc1 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -164,12 +164,11 @@ JSCompartment::ensureJitCompartmentExists(JSContext *cx) if (jitCompartment_) return true; - JitRuntime *jitRuntime = cx->runtime()->getJitRuntime(cx); - if (!jitRuntime) + if (!zone()->getJitZone(cx)) return false; /* Set the compartment early, so linking works. */ - jitCompartment_ = cx->new_(jitRuntime); + jitCompartment_ = cx->new_(); if (!jitCompartment_) return false; @@ -916,8 +915,7 @@ JSCompartment::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, size_t *shapesCompartmentTables, size_t *crossCompartmentWrappersArg, size_t *regexpCompartment, - size_t *debuggeesSet, - size_t *baselineStubsOptimized) + size_t *debuggeesSet) { *compartmentObject += mallocSizeOf(this); types.addSizeOfExcludingThis(mallocSizeOf, tiAllocationSiteTables, @@ -929,12 +927,6 @@ JSCompartment::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, *crossCompartmentWrappersArg += crossCompartmentWrappers.sizeOfExcludingThis(mallocSizeOf); *regexpCompartment += regExps.sizeOfExcludingThis(mallocSizeOf); *debuggeesSet += debuggees.sizeOfExcludingThis(mallocSizeOf); -#ifdef JS_ION - if (jitCompartment()) { - *baselineStubsOptimized += - jitCompartment()->optimizedStubSpace()->sizeOfExcludingThis(mallocSizeOf); - } -#endif } void diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index ba570e6915e..91865a7b094 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -229,8 +229,7 @@ struct JSCompartment size_t *shapesCompartmentTables, size_t *crossCompartmentWrappers, size_t *regexpCompartment, - size_t *debuggeesSet, - size_t *baselineStubsOptimized); + size_t *debuggeesSet); /* * Shared scope property tree, and arena-pool for allocating its nodes. diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index dd385ec1fb3..bb52429930f 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -5299,6 +5299,9 @@ js::ReleaseAllJITCode(FreeOp *fop) # endif for (ZonesIter zone(fop->runtime(), SkipAtoms); !zone.done(); zone.next()) { + if (!zone->jitZone()) + continue; + # ifdef DEBUG /* Assert no baseline scripts are marked as active. */ for (CellIter i(zone, FINALIZE_SCRIPT); !i.done(); i.next()) { @@ -5322,6 +5325,8 @@ js::ReleaseAllJITCode(FreeOp *fop) */ jit::FinishDiscardBaselineScript(fop, script); } + + zone->jitZone()->optimizedStubSpace()->free(); } #endif } diff --git a/js/src/jsinfer.cpp b/js/src/jsinfer.cpp index 50ea75c7672..a567c52fb63 100644 --- a/js/src/jsinfer.cpp +++ b/js/src/jsinfer.cpp @@ -494,6 +494,22 @@ TypeSet::clone(LifoAlloc *alloc) const return res; } +TemporaryTypeSet * +TypeSet::filter(LifoAlloc *alloc, bool filterUndefined, bool filterNull) const +{ + TemporaryTypeSet *res = clone(alloc); + if (!res) + return nullptr; + + if (filterUndefined) + res->flags = res->flags & ~TYPE_FLAG_UNDEFINED; + + if (filterNull) + res->flags = res->flags & ~TYPE_FLAG_NULL; + + return res; +} + /* static */ TemporaryTypeSet * TypeSet::unionSets(TypeSet *a, TypeSet *b, LifoAlloc *alloc) { @@ -4265,9 +4281,17 @@ TypeScript::destroy() } void -Zone::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, size_t *typePool) +Zone::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, + size_t *typePool, + size_t *baselineStubsOptimized) { *typePool += types.typeLifoAlloc.sizeOfExcludingThis(mallocSizeOf); +#ifdef JS_ION + if (jitZone()) { + *baselineStubsOptimized += + jitZone()->optimizedStubSpace()->sizeOfExcludingThis(mallocSizeOf); + } +#endif } void diff --git a/js/src/jsinfer.h b/js/src/jsinfer.h index ae11d43cc2c..1102bdb44c8 100644 --- a/js/src/jsinfer.h +++ b/js/src/jsinfer.h @@ -577,6 +577,9 @@ class TypeSet TemporaryTypeSet *clone(LifoAlloc *alloc) const; bool clone(LifoAlloc *alloc, TemporaryTypeSet *result) const; + // Create a new TemporaryTypeSet where undefined and/or null has been filtered out. + TemporaryTypeSet *filter(LifoAlloc *alloc, bool filterUndefined, bool filterNull) const; + protected: uint32_t baseObjectCount() const { return (flags & TYPE_FLAG_OBJECT_COUNT_MASK) >> TYPE_FLAG_OBJECT_COUNT_SHIFT; diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 00ac52a1fb4..24a5b2629c7 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -5908,8 +5908,6 @@ JSObject::addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::Objects #else sizes->mallocHeapElementsAsmJS += mallocSizeOf(elements); #endif - } else if (MOZ_UNLIKELY(elements->isMappedArrayBuffer())) { - sizes->nonHeapElementsMapped += as().byteLength(); } else { sizes->mallocHeapElementsNonAsmJS += mallocSizeOf(elements); } diff --git a/js/src/jsobj.h b/js/src/jsobj.h index 5d546ac6d15..7420ac09e95 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -252,8 +252,7 @@ class JSObject : public js::ObjectImpl js::gc::AllocKind kind, js::gc::InitialHeap heap, js::HandleShape shape, - js::HandleTypeObject type, - js::HeapSlot *extantSlots = nullptr); + js::HandleTypeObject type); /* Make an array object with the specified initial state. */ static inline js::ArrayObject *createArray(js::ExclusiveContext *cx, diff --git a/js/src/jsobjinlines.h b/js/src/jsobjinlines.h index 2b28e53c26d..d5976da4002 100644 --- a/js/src/jsobjinlines.h +++ b/js/src/jsobjinlines.h @@ -497,8 +497,7 @@ inline bool JSObject::isVarObj() /* static */ inline JSObject * JSObject::create(js::ExclusiveContext *cx, js::gc::AllocKind kind, js::gc::InitialHeap heap, - js::HandleShape shape, js::HandleTypeObject type, - js::HeapSlot *extantSlots /* = nullptr */) + js::HandleShape shape, js::HandleTypeObject type) { /* * Callers must use dynamicSlotsCount to size the initial slot array of the @@ -511,13 +510,9 @@ JSObject::create(js::ExclusiveContext *cx, js::gc::AllocKind kind, js::gc::Initi JS_ASSERT(js::gc::GetGCKindSlots(kind, type->clasp()) == shape->numFixedSlots()); JS_ASSERT_IF(type->clasp()->flags & JSCLASS_BACKGROUND_FINALIZE, IsBackgroundFinalized(kind)); JS_ASSERT_IF(type->clasp()->finalize, heap == js::gc::TenuredHeap); - JS_ASSERT_IF(extantSlots, dynamicSlotsCount(shape->numFixedSlots(), shape->slotSpan(), - type->clasp())); const js::Class *clasp = type->clasp(); - size_t nDynamicSlots = 0; - if (!extantSlots) - nDynamicSlots = dynamicSlotsCount(shape->numFixedSlots(), shape->slotSpan(), clasp); + size_t nDynamicSlots = dynamicSlotsCount(shape->numFixedSlots(), shape->slotSpan(), clasp); JSObject *obj = js::NewGCObject(cx, kind, nDynamicSlots, heap); if (!obj) @@ -525,13 +520,7 @@ JSObject::create(js::ExclusiveContext *cx, js::gc::AllocKind kind, js::gc::Initi obj->shape_.init(shape); obj->type_.init(type); - if (extantSlots) { -#ifdef JSGC_GENERATIONAL - if (cx->isJSContext()) - cx->asJSContext()->runtime()->gcNursery.notifyInitialSlots(obj, extantSlots); -#endif - obj->slots = extantSlots; - } + // Note: slots are created and assigned internally by NewGCObject. obj->elements = js::emptyObjectElements; if (clasp->hasPrivate()) @@ -588,8 +577,6 @@ JSObject::finish(js::FreeOp *fop) js::ObjectElements *elements = getElementsHeader(); if (MOZ_UNLIKELY(elements->isAsmJSArrayBuffer())) js::ArrayBufferObject::releaseAsmJSArrayBuffer(fop, this); - else if (MOZ_UNLIKELY(elements->isMappedArrayBuffer())) - js::ArrayBufferObject::releaseMappedArrayBuffer(fop, this); else fop->free_(elements); } diff --git a/js/src/vm/ArrayBufferObject.cpp b/js/src/vm/ArrayBufferObject.cpp index 696ad54a47b..f4105131523 100644 --- a/js/src/vm/ArrayBufferObject.cpp +++ b/js/src/vm/ArrayBufferObject.cpp @@ -30,7 +30,6 @@ #include "gc/Barrier.h" #include "gc/Marking.h" -#include "gc/Memory.h" #include "jit/AsmJS.h" #include "jit/AsmJSModule.h" #include "vm/GlobalObject.h" @@ -474,26 +473,28 @@ ArrayBufferObject::changeContents(JSContext *cx, ObjectElements *newHeader) } void -ArrayBufferObject::neuter(JSContext *cx) +ArrayBufferObject::neuter(ObjectElements *newHeader, JSContext *cx) { - JS_ASSERT(!isSharedArrayBuffer()); + MOZ_ASSERT(!isSharedArrayBuffer()); + + if (hasStealableContents()) { + MOZ_ASSERT(newHeader); - JS_ASSERT(cx); - if (isMappedArrayBuffer()) { - releaseMappedArrayBuffer(nullptr, this); - setFixedElements(); - } else if (hasDynamicElements() && !isAsmJSArrayBuffer()) { ObjectElements *oldHeader = getElementsHeader(); - changeContents(cx, ObjectElements::fromElements(fixedElements())); + MOZ_ASSERT(newHeader != oldHeader); + + changeContents(cx, newHeader); FreeOp fop(cx->runtime(), false); fop.free_(oldHeader); + } else { + elements = newHeader->elements(); } uint32_t byteLen = 0; - updateElementsHeader(getElementsHeader(), byteLen); + updateElementsHeader(newHeader, byteLen); - getElementsHeader()->setIsNeuteredBuffer(); + newHeader->setIsNeuteredBuffer(); } /* static */ bool @@ -634,33 +635,6 @@ ArrayBufferObject::neuterAsmJSArrayBuffer(JSContext *cx, ArrayBufferObject &buff #endif } -void * -ArrayBufferObject::createMappedArrayBuffer(int fd, int *new_fd, size_t offset, size_t length) -{ - void *ptr = AllocateMappedObject(fd, new_fd, offset, length, 8, - sizeof(MappingInfoHeader) + sizeof(ObjectElements)); - if (!ptr) - return nullptr; - - ptr = reinterpret_cast(uintptr_t(ptr) + sizeof(MappingInfoHeader)); - ObjectElements *header = reinterpret_cast(ptr); - initMappedElementsHeader(header, *new_fd, offset, length); - - return ptr; -} - -void -ArrayBufferObject::releaseMappedArrayBuffer(FreeOp *fop, JSObject *obj) -{ - ArrayBufferObject &buffer = obj->as(); - if(!buffer.isMappedArrayBuffer() || buffer.isNeutered()) - return; - - ObjectElements *header = buffer.getElementsHeader(); - if (header) - DeallocateMappedObject(buffer.getMappingFD(), header, header->initializedLength); -} - void ArrayBufferObject::addView(ArrayBufferViewObject *view) { @@ -787,19 +761,20 @@ ArrayBufferObject::createDataViewForThis(JSContext *cx, unsigned argc, Value *vp ArrayBufferObject::stealContents(JSContext *cx, Handle buffer, void **contents, uint8_t **data) { - // If the ArrayBuffer's elements are dynamically allocated and nothing else - // prevents us from stealing them, transfer ownership directly. Otherwise, - // the elements are small and allocated inside the ArrayBuffer object's GC - // header so we must make a copy. - ObjectElements *transferableHeader; - bool stolen; - if (buffer->hasDynamicElements() && !buffer->isAsmJSArrayBuffer()) { - stolen = true; - transferableHeader = buffer->getElementsHeader(); - } else { - stolen = false; + uint32_t byteLen = buffer->byteLength(); - uint32_t byteLen = buffer->byteLength(); + // If the ArrayBuffer's elements are transferrable, transfer ownership + // directly. Otherwise we have to copy the data into new elements. + ObjectElements *transferableHeader; + ObjectElements *newHeader; + bool stolen = buffer->hasStealableContents(); + if (stolen) { + transferableHeader = buffer->getElementsHeader(); + + newHeader = AllocateArrayBufferContents(cx, byteLen); + if (!newHeader) + return false; + } else { transferableHeader = AllocateArrayBufferContents(cx, byteLen); if (!transferableHeader) return false; @@ -807,6 +782,9 @@ ArrayBufferObject::stealContents(JSContext *cx, Handle buffe initElementsHeader(transferableHeader, byteLen); void *headerDataPointer = reinterpret_cast(transferableHeader->elements()); memcpy(headerDataPointer, buffer->dataPointer(), byteLen); + + // Keep using the current elements. + newHeader = buffer->getElementsHeader(); } JS_ASSERT(!IsInsideNursery(cx->runtime(), transferableHeader)); @@ -818,13 +796,13 @@ ArrayBufferObject::stealContents(JSContext *cx, Handle buffe if (!ArrayBufferObject::neuterViews(cx, buffer)) return false; - // If the elements were taken from the neutered buffer, revert it back to - // using inline storage so it doesn't attempt to free the stolen elements - // when finalized. + // If the elements were transferrable, revert the buffer back to using + // inline storage so it doesn't attempt to free the stolen elements when + // finalized. if (stolen) buffer->changeContents(cx, ObjectElements::fromElements(buffer->fixedElements())); - buffer->neuter(cx); + buffer->neuter(newHeader, cx); return true; } @@ -1301,9 +1279,33 @@ JS_NeuterArrayBuffer(JSContext *cx, HandleObject obj) } Rooted buffer(cx, &obj->as()); - if (!ArrayBufferObject::neuterViews(cx, buffer)) + + ObjectElements *newHeader; + if (buffer->hasStealableContents()) { + // If we're "disposing" with the buffer contents, allocate zeroed + // memory of equal size and swap that in as contents. This ensures + // that stale indexes that assume the original length, won't index out + // of bounds. This is a temporary hack: when we're confident we've + // eradicated all stale accesses, we'll stop doing this. + newHeader = AllocateArrayBufferContents(cx, buffer->byteLength()); + if (!newHeader) + return false; + } else { + // This case neuters out the existing elements in-place, so use the + // old header as new. + newHeader = buffer->getElementsHeader(); + } + + // Mark all views of the ArrayBuffer as neutered. + if (!ArrayBufferObject::neuterViews(cx, buffer)) { + if (buffer->hasStealableContents()) { + FreeOp fop(cx->runtime(), false); + fop.free_(newHeader); + } return false; - buffer->neuter(cx); + } + + buffer->neuter(newHeader, cx); return true; } @@ -1390,21 +1392,6 @@ JS_StealArrayBufferContents(JSContext *cx, HandleObject objArg, void **contents, return true; } -JS_PUBLIC_API(bool) -JS_CreateMappedArrayBufferContents(int fd, int *new_fd, size_t offset, - size_t length, void **contents) -{ - *contents = ArrayBufferObject::createMappedArrayBuffer(fd, new_fd, offset, length); - - return *contents; -} - -JS_PUBLIC_API(void) -JS_ReleaseMappedArrayBufferContents(int fd, void *contents, size_t length) -{ - DeallocateMappedObject(fd, contents, length); -} - JS_FRIEND_API(void *) JS_GetArrayBufferViewData(JSObject *obj) { diff --git a/js/src/vm/ArrayBufferObject.h b/js/src/vm/ArrayBufferObject.h index dc30fb8f952..f59f9de9cb5 100644 --- a/js/src/vm/ArrayBufferObject.h +++ b/js/src/vm/ArrayBufferObject.h @@ -18,13 +18,6 @@ namespace js { class ArrayBufferViewObject; -// Header for mapped array buffer -struct MappingInfoHeader -{ - uint32_t fd; - uint32_t offset; -}; - // The inheritance hierarchy for the various classes relating to typed arrays // is as follows. // @@ -139,6 +132,24 @@ class ArrayBufferObject : public JSObject static bool saveArrayBufferList(JSCompartment *c, ArrayBufferVector &vector); static void restoreArrayBufferLists(ArrayBufferVector &vector); + bool hasStealableContents() const { + // Inline elements strictly adhere to the corresponding buffer. + if (!hasDynamicElements()) + return false; + + // asm.js buffer contents are transferred by copying, just like inline + // elements. + if (isAsmJSArrayBuffer()) + return false; + + // Neutered contents aren't transferrable because we want a neutered + // array's contents to be backed by zeroed memory equal in length to + // the original buffer contents. Transferring these contents would + // allocate new ones based on the current byteLength, which is 0 for a + // neutered array -- not the original byteLength. + return !isNeutered(); + } + static bool stealContents(JSContext *cx, Handle buffer, void **contents, uint8_t **data); @@ -157,33 +168,6 @@ class ArrayBufferObject : public JSObject updateElementsHeader(header, bytes); } - static void initMappedElementsHeader(js::ObjectElements *header, uint32_t fd, - uint32_t offset, uint32_t bytes) { - initElementsHeader(header, bytes); - header->setIsMappedArrayBuffer(); - MappingInfoHeader *mh = getMappingInfoHeader(header); - mh->fd = fd; - mh->offset = offset; - } - - static MappingInfoHeader *getMappingInfoHeader(js::ObjectElements *header) { - MOZ_ASSERT(header->isMappedArrayBuffer()); - return reinterpret_cast(uintptr_t(header) - - sizeof(MappingInfoHeader)); - } - - uint32_t getMappingFD() { - MOZ_ASSERT(getElementsHeader()->isMappedArrayBuffer()); - MappingInfoHeader *mh = getMappingInfoHeader(getElementsHeader()); - return mh->fd; - } - - uint32_t getMappingOffset() const { - MOZ_ASSERT(getElementsHeader()->isMappedArrayBuffer()); - MappingInfoHeader *mh = getMappingInfoHeader(getElementsHeader()); - return mh->offset; - } - static uint32_t headerInitializedLength(const js::ObjectElements *header) { return header->initializedLength; } @@ -210,10 +194,16 @@ class ArrayBufferObject : public JSObject uint8_t * dataPointer() const; /* - * Discard the ArrayBuffer contents. For asm.js buffers, at least, should + * Discard the ArrayBuffer contents, and use |newHeader| for the buffer's + * new contents. (These new contents are zeroed, of identical size in + * memory as the current contents, but appear to be neutered and of zero + * length. This is purely precautionary against stale indexes that were + * in-bounds with respect to the initial length but would not be after + * neutering. This precaution will be removed once we're sure such stale + * indexing no longer happens.) For asm.js buffers, at least, should * be called after neuterViews(). */ - void neuter(JSContext *cx); + void neuter(ObjectElements *newHeader, JSContext *cx); /* * Check if the arrayBuffer contains any data. This will return false for @@ -236,15 +226,6 @@ class ArrayBufferObject : public JSObject static bool prepareForAsmJS(JSContext *cx, Handle buffer); static bool neuterAsmJSArrayBuffer(JSContext *cx, ArrayBufferObject &buffer); static void releaseAsmJSArrayBuffer(FreeOp *fop, JSObject *obj); - - bool isMappedArrayBuffer() const { - return getElementsHeader()->isMappedArrayBuffer(); - } - void setIsMappedArrayBuffer() { - getElementsHeader()->setIsMappedArrayBuffer(); - } - static void *createMappedArrayBuffer(int fd, int *new_fd, size_t offset, size_t length); - static void releaseMappedArrayBuffer(FreeOp *fop, JSObject *obj); }; /* diff --git a/js/src/vm/MemoryMetrics.cpp b/js/src/vm/MemoryMetrics.cpp index 0300dd43926..197f43f5b04 100644 --- a/js/src/vm/MemoryMetrics.cpp +++ b/js/src/vm/MemoryMetrics.cpp @@ -232,7 +232,9 @@ StatsZoneCallback(JSRuntime *rt, void *data, Zone *zone) rtStats->initExtraZoneStats(zone, &zStats); rtStats->currZoneStats = &zStats; - zone->addSizeOfIncludingThis(rtStats->mallocSizeOf_, &zStats.typePool); + zone->addSizeOfIncludingThis(rtStats->mallocSizeOf_, + &zStats.typePool, + &zStats.baselineStubsOptimized); } static void @@ -257,8 +259,7 @@ StatsCompartmentCallback(JSRuntime *rt, void *data, JSCompartment *compartment) &cStats.shapesMallocHeapCompartmentTables, &cStats.crossCompartmentWrappersTable, &cStats.regexpCompartment, - &cStats.debuggeesSet, - &cStats.baselineStubsOptimized); + &cStats.debuggeesSet); } static void diff --git a/js/src/vm/ObjectImpl.h b/js/src/vm/ObjectImpl.h index 6ca3ecbe8a9..7093a1e8637 100644 --- a/js/src/vm/ObjectImpl.h +++ b/js/src/vm/ObjectImpl.h @@ -171,11 +171,10 @@ class ObjectElements ASMJS_ARRAY_BUFFER = 0x2, NEUTERED_BUFFER = 0x4, SHARED_ARRAY_BUFFER = 0x8, - MAPPED_ARRAY_BUFFER = 0x10, // Present only if these elements correspond to an array with // non-writable length; never present for non-arrays. - NONWRITABLE_ARRAY_LENGTH = 0x20, + NONWRITABLE_ARRAY_LENGTH = 0x10 }; private: @@ -250,12 +249,6 @@ class ObjectElements void setIsSharedArrayBuffer() { flags |= SHARED_ARRAY_BUFFER; } - bool isMappedArrayBuffer() const { - return flags & MAPPED_ARRAY_BUFFER; - } - void setIsMappedArrayBuffer() { - flags |= MAPPED_ARRAY_BUFFER; - } bool hasNonwritableArrayLength() const { return flags & NONWRITABLE_ARRAY_LENGTH; } diff --git a/js/src/vm/ScopeObject.cpp b/js/src/vm/ScopeObject.cpp index fc85588c5dd..b2127a07084 100644 --- a/js/src/vm/ScopeObject.cpp +++ b/js/src/vm/ScopeObject.cpp @@ -141,14 +141,14 @@ ScopeObject::setEnclosingScope(HandleObject obj) * The call object must be further initialized to be usable. */ CallObject * -CallObject::create(JSContext *cx, HandleScript script, HandleShape shape, HandleTypeObject type, HeapSlot *slots) +CallObject::create(JSContext *cx, HandleScript script, HandleShape shape, HandleTypeObject type) { gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots()); JS_ASSERT(CanBeFinalizedInBackground(kind, &CallObject::class_)); kind = gc::GetBackgroundAllocKind(kind); gc::InitialHeap heap = script->treatAsRunOnce() ? gc::TenuredHeap : gc::DefaultHeap; - JSObject *obj = JSObject::create(cx, kind, heap, shape, type, slots); + JSObject *obj = JSObject::create(cx, kind, heap, shape, type); if (!obj) return nullptr; diff --git a/js/src/vm/ScopeObject.h b/js/src/vm/ScopeObject.h index f3a83d2ed29..cb056f121b6 100644 --- a/js/src/vm/ScopeObject.h +++ b/js/src/vm/ScopeObject.h @@ -237,7 +237,7 @@ class CallObject : public ScopeObject /* These functions are internal and are exposed only for JITs. */ static CallObject * - create(JSContext *cx, HandleScript script, HandleShape shape, HandleTypeObject type, HeapSlot *slots); + create(JSContext *cx, HandleScript script, HandleShape shape, HandleTypeObject type); static CallObject * createTemplateObject(JSContext *cx, HandleScript script, gc::InitialHeap heap); diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index 5da68ef948d..6c71239054a 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -414,14 +414,15 @@ js::intrinsic_UnsafePutElements(JSContext *cx, unsigned argc, Value *vp) uint32_t elemi = base+2; JS_ASSERT(args[arri].isObject()); - JS_ASSERT(args[arri].toObject().isNative()); + JS_ASSERT(args[arri].toObject().isNative() || IsTypedObjectArray(args[arri].toObject())); JS_ASSERT(args[idxi].isInt32()); RootedObject arrobj(cx, &args[arri].toObject()); uint32_t idx = args[idxi].toInt32(); - if (arrobj->is()) { - JS_ASSERT(idx < arrobj->as().length()); + if (arrobj->is() || arrobj->is()) { + JS_ASSERT(!arrobj->is() || idx < arrobj->as().length()); + JS_ASSERT(!arrobj->is() || idx < arrobj->as().length()); RootedValue tmp(cx, args[elemi]); // XXX: Always non-strict. if (!JSObject::setElement(cx, arrobj, arrobj, idx, &tmp, false)) diff --git a/js/src/vm/StructuredClone.cpp b/js/src/vm/StructuredClone.cpp index 8d3ff51e93c..8c9a75b5cf9 100644 --- a/js/src/vm/StructuredClone.cpp +++ b/js/src/vm/StructuredClone.cpp @@ -66,7 +66,6 @@ enum StructuredDataType { SCTAG_ARRAY_OBJECT, SCTAG_OBJECT_OBJECT, SCTAG_ARRAY_BUFFER_OBJECT, - SCTAG_MAPPED_ARRAY_BUFFER_OBJECT, SCTAG_BOOLEAN_OBJECT, SCTAG_STRING_OBJECT, SCTAG_NUMBER_OBJECT, @@ -213,7 +212,6 @@ struct JSStructuredCloneReader { JSString *readString(uint32_t nchars); bool readTypedArray(uint32_t arrayType, uint32_t nelems, js::Value *vp, bool v1Read = false); bool readArrayBuffer(uint32_t nbytes, js::Value *vp); - bool readMappedArrayBuffer(Value *vp, uint32_t fd, uint32_t offset, uint32_t length); bool readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems, js::Value *vp); bool readId(jsid *idp); bool startRead(js::Value *vp); @@ -833,12 +831,6 @@ bool JSStructuredCloneWriter::writeArrayBuffer(HandleObject obj) { ArrayBufferObject &buffer = obj->as(); - - if (buffer.isMappedArrayBuffer()) { - return out.writePair(SCTAG_MAPPED_ARRAY_BUFFER_OBJECT, buffer.byteLength()) && - out.writePair(buffer.getMappingFD(), buffer.getMappingOffset()); - } - return out.writePair(SCTAG_ARRAY_BUFFER_OBJECT, buffer.byteLength()) && out.writeBytes(buffer.dataPointer(), buffer.byteLength()); } @@ -1236,26 +1228,6 @@ JSStructuredCloneReader::readArrayBuffer(uint32_t nbytes, Value *vp) return in.readArray(buffer.dataPointer(), nbytes); } -bool -JSStructuredCloneReader::readMappedArrayBuffer(Value *vp, uint32_t fd, - uint32_t offset, uint32_t length) -{ - void *ptr; - int new_fd; - if(!JS_CreateMappedArrayBufferContents(fd, &new_fd, offset, length, &ptr)) { - JS_ReportError(context(), "Failed to create mapped array buffer contents"); - return false; - } - JSObject *obj = JS_NewArrayBufferWithContents(context(), ptr); - if (!obj) { - JS_ReleaseMappedArrayBufferContents(new_fd, ptr, length); - return false; - } - vp->setObject(*obj); - - return true; -} - static size_t bytesPerTypedArrayElement(uint32_t arrayType) { @@ -1440,14 +1412,6 @@ JSStructuredCloneReader::startRead(Value *vp) return false; break; - case SCTAG_MAPPED_ARRAY_BUFFER_OBJECT: - uint32_t fd, offset; - if (!in.readPair(&fd, &offset)) - return false; - if (!readMappedArrayBuffer(vp, fd, offset, data)) - return false; - break; - case SCTAG_TYPED_ARRAY_OBJECT: // readTypedArray adds the array to allObjs uint64_t arrayType; diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp index 6b8c708eda6..12850ab44e3 100644 --- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -1839,6 +1839,10 @@ ReportZoneStats(const JS::ZoneStats &zStats, zStats.typePool, "Type sets and related data."); + ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("baseline/optimized-stubs"), + zStats.baselineStubsOptimized, + "The Baseline JIT's optimized IC stubs (excluding code)."); + size_t stringsNotableAboutMemoryGCHeap = 0; size_t stringsNotableAboutMemoryMallocHeap = 0; @@ -2067,14 +2071,10 @@ ReportCompartmentStats(const JS::CompartmentStats &cStats, cStats.baselineData, "The Baseline JIT's compilation data (BaselineScripts)."); - ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("baseline/stubs/fallback"), + ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("baseline/fallback-stubs"), cStats.baselineStubsFallback, "The Baseline JIT's fallback IC stubs (excluding code)."); - ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("baseline/stubs/optimized"), - cStats.baselineStubsOptimized, - "The Baseline JIT's optimized IC stubs (excluding code)."); - ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("ion-data"), cStats.ionData, "The IonMonkey JIT's compilation data (IonScripts)."); @@ -2139,12 +2139,6 @@ ReportCompartmentStats(const JS::CompartmentStats &cStats, "the GC heap."); } - if (cStats.objectsExtra.nonHeapElementsMapped > 0) { - REPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/non-heap/elements/mapped"), - KIND_NONHEAP, cStats.objectsExtra.nonHeapElementsMapped, - "Memory-mapped array buffer elements."); - } - REPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/non-heap/code/asm.js"), KIND_NONHEAP, cStats.objectsExtra.nonHeapCodeAsmJS, "AOT-compiled asm.js code."); diff --git a/layout/build/nsLayoutStatics.cpp b/layout/build/nsLayoutStatics.cpp index 74953363daa..12640bb302d 100644 --- a/layout/build/nsLayoutStatics.cpp +++ b/layout/build/nsLayoutStatics.cpp @@ -352,7 +352,6 @@ nsLayoutStatics::Shutdown() nsGlobalWindow::ShutDown(); nsDOMClassInfo::ShutDown(); nsListControlFrame::Shutdown(); - nsEditorEventListener::ShutDown(); nsXBLService::Shutdown(); nsAutoCopyListener::Shutdown(); FrameLayerBuilder::Shutdown(); diff --git a/layout/generic/nsHTMLReflowState.cpp b/layout/generic/nsHTMLReflowState.cpp index 23fe476d0a3..96f78ef40a1 100644 --- a/layout/generic/nsHTMLReflowState.cpp +++ b/layout/generic/nsHTMLReflowState.cpp @@ -28,6 +28,7 @@ #include "StickyScrollContainer.h" #include "nsIFrameInlines.h" #include +#include "mozilla/dom/HTMLInputElement.h" #ifdef DEBUG #undef NOISY_VERTICAL_ALIGN @@ -37,6 +38,7 @@ using namespace mozilla; using namespace mozilla::css; +using namespace mozilla::dom; using namespace mozilla::layout; enum eNormalLineHeightControl { @@ -2485,9 +2487,10 @@ nsHTMLReflowState::CalcLineHeight(nsIContent* aContent, NS_ASSERTION(lineHeight >= 0, "ComputeLineHeight screwed up"); - if (aContent && aContent->IsHTML(nsGkAtoms::input)) { - // For Web-compatibility, input elements cannot have a line-height - // smaller than one. + HTMLInputElement* input = HTMLInputElement::FromContentOrNull(aContent); + if (input && input->IsSingleLineTextControl()) { + // For Web-compatibility, single-line text input elements cannot + // have a line-height smaller than one. nscoord lineHeightOne = aFontSizeInflation * aStyleContext->StyleFont()->mFont.size; if (lineHeight < lineHeightOne) { diff --git a/layout/generic/nsSelection.cpp b/layout/generic/nsSelection.cpp index 230afc766f2..f7f19d5046d 100644 --- a/layout/generic/nsSelection.cpp +++ b/layout/generic/nsSelection.cpp @@ -5104,8 +5104,7 @@ Selection::Extend(nsINode& aParentNode, uint32_t aOffset, ErrorResult& aRv) #endif SetDirection(dir); #ifdef DEBUG_SELECTION - nsCOMPtrcontent; - content = do_QueryInterface(aParentNode); + nsCOMPtr content = do_QueryInterface(&aParentNode); printf ("Sel. Extend to %p %s %d\n", content.get(), nsAtomCString(content->Tag()).get(), aOffset); diff --git a/layout/media/symbols.def.in b/layout/media/symbols.def.in index a8adc61e10e..c43406716d6 100644 --- a/layout/media/symbols.def.in +++ b/layout/media/symbols.def.in @@ -61,6 +61,7 @@ vpx_codec_get_cx_data vpx_codec_enc_config_default vpx_img_alloc vpx_codec_encode +vpx_mem_set_functions #endif #endif #ifdef MOZ_VORBIS diff --git a/layout/reftests/forms/button/reftest.list b/layout/reftests/forms/button/reftest.list index 9511350fe13..4a85f05d079 100644 --- a/layout/reftests/forms/button/reftest.list +++ b/layout/reftests/forms/button/reftest.list @@ -15,5 +15,5 @@ fuzzy-if(B2G,125,80) == percent-width-child-2.html percent-width-child-2-ref.ht != line-height-button-0.5.html line-height-button-1.0.html != line-height-button-1.5.html line-height-button-1.0.html -== line-height-input-0.5.html line-height-input-1.0.html +!= line-height-input-0.5.html line-height-input-1.0.html != line-height-input-1.5.html line-height-input-1.0.html diff --git a/layout/reftests/svg/smil/transform/reftest.list b/layout/reftests/svg/smil/transform/reftest.list index 8f82539d650..06cebbe7398 100644 --- a/layout/reftests/svg/smil/transform/reftest.list +++ b/layout/reftests/svg/smil/transform/reftest.list @@ -11,7 +11,7 @@ fuzzy-if(cocoaWidget,1,32) == paced-1.svg paced-1-ref.svg # bug 981640 == rotate-angle-5.svg rotate-angle-ref.svg fuzzy(12,27) == scale-1.svg scale-1-ref.svg # bug 981004 == set-transform-1.svg lime.svg -== skew-1.svg skew-1-ref.svg +fuzzy-if(winWidget,1,3) == skew-1.svg skew-1-ref.svg # bug 983671 == translate-clipPath-1.svg lime.svg fails-if(OSX==10.6) == translate-gradient-1.svg lime.svg == translate-pattern-1.svg lime.svg diff --git a/layout/xul/nsSliderFrame.cpp b/layout/xul/nsSliderFrame.cpp index 2b5956a941d..b37010423e1 100644 --- a/layout/xul/nsSliderFrame.cpp +++ b/layout/xul/nsSliderFrame.cpp @@ -665,6 +665,11 @@ nsSliderFrame::CurrentPositionChanged() else newThumbRect.y = clientRect.y + NSToCoordRound(pos * mRatio); +#ifdef MOZ_WIDGET_GONK + // avoid putting the scroll thumb at subpixel positions which cause needless invalidations + nscoord appUnitsPerPixel = PresContext()->AppUnitsPerDevPixel(); + newThumbRect = newThumbRect.ToNearestPixels(appUnitsPerPixel).ToAppUnits(appUnitsPerPixel); +#endif // set the rect thumbFrame->SetRect(newThumbRect); diff --git a/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp b/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp index 3081d7bf908..ff8837e9b4b 100644 --- a/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp +++ b/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp @@ -153,26 +153,22 @@ NTPtoDOMHighResTimeStamp(uint32_t ntpHigh, uint32_t ntpLow) { } bool WebrtcAudioConduit::GetRTCPReceiverReport(DOMHighResTimeStamp* timestamp, - unsigned int* jitterMs, - unsigned int* packetsReceived, + uint32_t* jitterMs, + uint32_t* packetsReceived, uint64_t* bytesReceived, - unsigned int *cumulativeLost) { - unsigned int ntpHigh, ntpLow; - unsigned int rtpTimestamp, playoutTimestamp; - unsigned int packetsSent; - unsigned int bytesSent32; - unsigned short fractionLost; - bool result = !mPtrRTP->GetRemoteRTCPData(mChannel, ntpHigh, ntpLow, - rtpTimestamp, playoutTimestamp, - packetsSent, bytesSent32, - jitterMs, - &fractionLost, cumulativeLost); + uint32_t* cumulativeLost, + int32_t* rttMs) { + uint32_t ntpHigh, ntpLow; + uint16_t fractionLost; + bool result = !mPtrRTP->GetRemoteRTCPReceiverInfo(mChannel, ntpHigh, ntpLow, + *packetsReceived, + *bytesReceived, + *jitterMs, + fractionLost, + *cumulativeLost, + *rttMs); if (result) { *timestamp = NTPtoDOMHighResTimeStamp(ntpHigh, ntpLow); - *packetsReceived = (packetsSent >= *cumulativeLost) ? - (packetsSent - *cumulativeLost) : 0; - *bytesReceived = (packetsSent ? - (bytesSent32 / packetsSent) : 0) * (*packetsReceived); } return result; } @@ -180,18 +176,13 @@ bool WebrtcAudioConduit::GetRTCPReceiverReport(DOMHighResTimeStamp* timestamp, bool WebrtcAudioConduit::GetRTCPSenderReport(DOMHighResTimeStamp* timestamp, unsigned int* packetsSent, uint64_t* bytesSent) { - unsigned int ntpHigh, ntpLow; - unsigned int rtpTimestamp, playoutTimestamp; - unsigned int bytesSent32; - unsigned int jitterMs; - unsigned short fractionLost; - bool result = !mPtrRTP->GetRemoteRTCPData(mChannel, ntpHigh, ntpLow, - rtpTimestamp, playoutTimestamp, - *packetsSent, bytesSent32, - &jitterMs, &fractionLost); + struct webrtc::SenderInfo senderInfo; + bool result = !mPtrRTP->GetRemoteRTCPSenderInfo(mChannel, &senderInfo); if (result) { - *timestamp = NTPtoDOMHighResTimeStamp(ntpHigh, ntpLow); - *bytesSent = bytesSent32; + *timestamp = NTPtoDOMHighResTimeStamp(senderInfo.NTP_timestamp_high, + senderInfo.NTP_timestamp_low); + *packetsSent = senderInfo.sender_packet_count; + *bytesSent = senderInfo.sender_octet_count; } return result; } diff --git a/media/webrtc/signaling/src/media-conduit/AudioConduit.h b/media/webrtc/signaling/src/media-conduit/AudioConduit.h index 8101d95745d..5211c734a93 100755 --- a/media/webrtc/signaling/src/media-conduit/AudioConduit.h +++ b/media/webrtc/signaling/src/media-conduit/AudioConduit.h @@ -185,10 +185,11 @@ public: int32_t* avSyncOffsetMs); bool GetRTPStats(unsigned int* jitterMs, unsigned int* cumulativeLost); bool GetRTCPReceiverReport(DOMHighResTimeStamp* timestamp, - unsigned int* jitterMs, - unsigned int* packetsReceived, + uint32_t* jitterMs, + uint32_t* packetsReceived, uint64_t* bytesReceived, - unsigned int *cumulativeLost); + uint32_t *cumulativeLost, + int32_t* rttMs); bool GetRTCPSenderReport(DOMHighResTimeStamp* timestamp, unsigned int* packetsSent, uint64_t* bytesSent); diff --git a/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h b/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h index 9267b94c97d..6651117f595 100755 --- a/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h +++ b/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h @@ -149,10 +149,11 @@ public: virtual bool GetRTPStats(unsigned int* jitterMs, unsigned int* cumulativeLost) = 0; virtual bool GetRTCPReceiverReport(DOMHighResTimeStamp* timestamp, - unsigned int* jitterMs, - unsigned int* packetsReceived, + uint32_t* jitterMs, + uint32_t* packetsReceived, uint64_t* bytesReceived, - unsigned int* cumulativeLost) = 0; + uint32_t* cumulativeLost, + int32_t* rttMs) = 0; virtual bool GetRTCPSenderReport(DOMHighResTimeStamp* timestamp, unsigned int* packetsSent, uint64_t* bytesSent) = 0; diff --git a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp index 481dcf79bb1..4db89a855a2 100644 --- a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp +++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp @@ -141,15 +141,11 @@ bool WebrtcVideoConduit::GetAVStats(int32_t* jitterBufferDelayMs, bool WebrtcVideoConduit::GetRTPStats(unsigned int* jitterMs, unsigned int* cumulativeLost) { - unsigned int ntpHigh, ntpLow; - unsigned int packetsSent, bytesSent; unsigned short fractionLost; unsigned extendedMax; int rttMs; // GetReceivedRTCPStatistics is a poorly named GetRTPStatistics variant - return !mPtrRTP->GetReceivedRTCPStatistics(mChannel, ntpHigh, ntpLow, - packetsSent, bytesSent, - fractionLost, + return !mPtrRTP->GetReceivedRTCPStatistics(mChannel, fractionLost, *cumulativeLost, extendedMax, *jitterMs, @@ -157,29 +153,22 @@ bool WebrtcVideoConduit::GetRTPStats(unsigned int* jitterMs, } bool WebrtcVideoConduit::GetRTCPReceiverReport(DOMHighResTimeStamp* timestamp, - unsigned int* jitterMs, - unsigned int* packetsReceived, + uint32_t* jitterMs, + uint32_t* packetsReceived, uint64_t* bytesReceived, - unsigned int* cumulativeLost) { - unsigned int ntpHigh, ntpLow; - unsigned int packetsSent; - unsigned int bytesSent32; - unsigned short fractionLost; - unsigned extendedMax; - int rttMs; - bool result = !mPtrRTP->GetSentRTCPStatistics(mChannel, ntpHigh, ntpLow, - bytesSent32, packetsSent, - fractionLost, - *cumulativeLost, - extendedMax, - *jitterMs, - rttMs); + uint32_t* cumulativeLost, + int32_t* rttMs) { + uint32_t ntpHigh, ntpLow; + uint16_t fractionLost; + bool result = !mPtrRTP->GetRemoteRTCPReceiverInfo(mChannel, ntpHigh, ntpLow, + *packetsReceived, + *bytesReceived, + jitterMs, + &fractionLost, + cumulativeLost, + rttMs); if (result) { *timestamp = NTPtoDOMHighResTimeStamp(ntpHigh, ntpLow); - *packetsReceived = (packetsSent >= *cumulativeLost) ? - (packetsSent - *cumulativeLost) : 0; - *bytesReceived = (packetsSent ? - (bytesSent32 / packetsSent) : 0) * (*packetsReceived); } return result; } @@ -187,22 +176,13 @@ bool WebrtcVideoConduit::GetRTCPReceiverReport(DOMHighResTimeStamp* timestamp, bool WebrtcVideoConduit::GetRTCPSenderReport(DOMHighResTimeStamp* timestamp, unsigned int* packetsSent, uint64_t* bytesSent) { - unsigned int ntpHigh, ntpLow; - unsigned int bytesSent32; - unsigned int jitterMs; - unsigned short fractionLost; - unsigned int cumulativeLost; - unsigned extendedMax; - int rttMs; - bool result = !mPtrRTP->GetReceivedRTCPStatistics(mChannel, ntpHigh, ntpLow, - bytesSent32, *packetsSent, - fractionLost, - cumulativeLost, - jitterMs, extendedMax, - rttMs); + struct webrtc::SenderInfo senderInfo; + bool result = !mPtrRTP->GetRemoteRTCPSenderInfo(mChannel, &senderInfo); if (result) { - *timestamp = NTPtoDOMHighResTimeStamp(ntpHigh, ntpLow); - *bytesSent = bytesSent32; + *timestamp = NTPtoDOMHighResTimeStamp(senderInfo.NTP_timestamp_high, + senderInfo.NTP_timestamp_low); + *packetsSent = senderInfo.sender_packet_count; + *bytesSent = senderInfo.sender_octet_count; } return result; } diff --git a/media/webrtc/signaling/src/media-conduit/VideoConduit.h b/media/webrtc/signaling/src/media-conduit/VideoConduit.h index 912006418d1..16681eea463 100755 --- a/media/webrtc/signaling/src/media-conduit/VideoConduit.h +++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.h @@ -210,10 +210,11 @@ public: int32_t* avSyncOffsetMs); bool GetRTPStats(unsigned int* jitterMs, unsigned int* cumulativeLost); bool GetRTCPReceiverReport(DOMHighResTimeStamp* timestamp, - unsigned int* jitterMs, - unsigned int* packetsReceived, + uint32_t* jitterMs, + uint32_t* packetsReceived, uint64_t* bytesReceived, - unsigned int* cumulativeLost); + uint32_t* cumulativeLost, + int32_t* rttMs); bool GetRTCPSenderReport(DOMHighResTimeStamp* timestamp, unsigned int* packetsSent, uint64_t* bytesSent); diff --git a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp index 5d5dcccd931..03b84ca63d6 100644 --- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp +++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp @@ -2164,10 +2164,12 @@ PeerConnectionImpl::ExecuteStatsQuery_s(RTCStatsQuery *query) { uint32_t packetsReceived; uint64_t bytesReceived; uint32_t packetsLost; + int32_t rtt; if (mp.Conduit()->GetRTCPReceiverReport(×tamp, &jitterMs, &packetsReceived, &bytesReceived, - &packetsLost)) { + &packetsLost, + &rtt)) { remoteId = NS_LITERAL_STRING("outbound_rtcp_") + idstr; RTCInboundRTPStreamStats s; s.mTimestamp.Construct(timestamp); @@ -2182,6 +2184,7 @@ PeerConnectionImpl::ExecuteStatsQuery_s(RTCStatsQuery *query) { s.mPacketsReceived.Construct(packetsReceived); s.mBytesReceived.Construct(bytesReceived); s.mPacketsLost.Construct(packetsLost); + s.mMozRtt.Construct(rtt); query->report.mInboundRTPStreamStats.Value().AppendElement(s); } } diff --git a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h index 65d57946b9a..0f16c57e871 100644 --- a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h +++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h @@ -424,6 +424,17 @@ class RtpRtcp : public Module { */ virtual void SetRtt(uint32_t rtt) = 0; + /* + * Get time of last rr, as well as packets received remotely + * (derived from rr report + cached sender-side info). + * + * return -1 on failure else 0 + */ + virtual int32_t GetReportBlockInfo(const uint32_t remote_ssrc, + uint32_t* ntp_high, + uint32_t* ntp_low, + uint32_t* packets_received, + uint64_t* octets_received) const = 0; /* * Force a send of a RTCP packet * normal SR and RR are triggered via the process function diff --git a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtcp_receiver.cc b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtcp_receiver.cc index 9ab1fb69b2d..5b459197f3d 100644 --- a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtcp_receiver.cc +++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtcp_receiver.cc @@ -234,6 +234,27 @@ int RTCPReceiver::SetRTT(uint16_t rtt) { return 0; } +int32_t RTCPReceiver::GetReportBlockInfo(uint32_t remoteSSRC, + uint32_t* NTPHigh, + uint32_t* NTPLow, + uint32_t* PacketsReceived, + uint64_t* OctetsReceived) const +{ + CriticalSectionScoped lock(_criticalSectionRTCPReceiver); + + RTCPReportBlockInformation* reportBlock = + GetReportBlockInformation(remoteSSRC); + + if (reportBlock == NULL) { + return -1; + } + *NTPHigh = reportBlock->lastReceivedRRNTPsecs; + *NTPLow = reportBlock->lastReceivedRRNTPfrac; + *PacketsReceived = reportBlock->remotePacketsReceived; + *OctetsReceived = reportBlock->remoteOctetsReceived; + return 0; +} + int32_t RTCPReceiver::NTP(uint32_t *ReceivedNTPsecs, uint32_t *ReceivedNTPfrac, @@ -477,8 +498,11 @@ RTCPReceiver::HandleReportBlock(const RTCPUtility::RTCPPacket& rtcpPacket, // To avoid problem with acquiring _criticalSectionRTCPSender while holding // _criticalSectionRTCPReceiver. _criticalSectionRTCPReceiver->Leave(); - uint32_t sendTimeMS = - _rtpRtcp.SendTimeOfSendReport(rtcpPacket.ReportBlockItem.LastSR); + uint32_t sendTimeMS = 0; + uint32_t sentPackets = 0; + uint64_t sentOctets = 0; + _rtpRtcp.GetSendReportMetadata(rtcpPacket.ReportBlockItem.LastSR, + &sendTimeMS, &sentPackets, &sentOctets); _criticalSectionRTCPReceiver->Enter(); RTCPReportBlockInformation* reportBlock = @@ -496,6 +520,12 @@ RTCPReceiver::HandleReportBlock(const RTCPUtility::RTCPPacket& rtcpPacket, reportBlock->remoteReceiveBlock.fractionLost = rb.FractionLost; reportBlock->remoteReceiveBlock.cumulativeLost = rb.CumulativeNumOfPacketsLost; + if (sentPackets > rb.CumulativeNumOfPacketsLost) { + uint32_t packetsReceived = sentPackets - rb.CumulativeNumOfPacketsLost; + reportBlock->remotePacketsReceived = packetsReceived; + reportBlock->remoteOctetsReceived = (sentOctets / sentPackets) * + packetsReceived; + } if (rb.ExtendedHighestSequenceNumber > reportBlock->remoteReceiveBlock.extendedHighSeqNum) { // We have successfully delivered new RTP packets to the remote side after @@ -516,14 +546,15 @@ RTCPReceiver::HandleReportBlock(const RTCPUtility::RTCPPacket& rtcpPacket, rtcpPacket.ReportBlockItem.DelayLastSR; // local NTP time when we received this - uint32_t lastReceivedRRNTPsecs = 0; - uint32_t lastReceivedRRNTPfrac = 0; + reportBlock->lastReceivedRRNTPsecs = 0; + reportBlock->lastReceivedRRNTPfrac = 0; - _clock->CurrentNtp(lastReceivedRRNTPsecs, lastReceivedRRNTPfrac); + _clock->CurrentNtp(reportBlock->lastReceivedRRNTPsecs, + reportBlock->lastReceivedRRNTPfrac); // time when we received this in MS - uint32_t receiveTimeMS = Clock::NtpToMs(lastReceivedRRNTPsecs, - lastReceivedRRNTPfrac); + uint32_t receiveTimeMS = Clock::NtpToMs(reportBlock->lastReceivedRRNTPsecs, + reportBlock->lastReceivedRRNTPfrac); // Estimate RTT uint32_t d = (delaySinceLastSendReport & 0x0000ffff) * 1000; diff --git a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtcp_receiver.h b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtcp_receiver.h index 01432318871..ca17282066a 100644 --- a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtcp_receiver.h +++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtcp_receiver.h @@ -82,6 +82,12 @@ public: int32_t ResetRTT(const uint32_t remoteSSRC); + int32_t GetReportBlockInfo(uint32_t remoteSSRC, + uint32_t* NTPHigh, + uint32_t* NTPLow, + uint32_t* PacketsReceived, + uint64_t* OctetsReceived) const; + int32_t SenderInfoReceived(RTCPSenderInfo* senderInfo) const; // get statistics diff --git a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtcp_receiver_help.cc b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtcp_receiver_help.cc index 964512c4b94..eab7232e526 100644 --- a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtcp_receiver_help.cc +++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtcp_receiver_help.cc @@ -101,6 +101,10 @@ RTCPPacketInformation::AddReportInfo( RTCPReportBlockInformation::RTCPReportBlockInformation(): remoteReceiveBlock(), remoteMaxJitter(0), + remotePacketsReceived(0), + remoteOctetsReceived(0), + lastReceivedRRNTPsecs(0), + lastReceivedRRNTPfrac(0), RTT(0), minRTT(0), maxRTT(0), diff --git a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtcp_receiver_help.h b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtcp_receiver_help.h index 85d3f53629e..2d53bb736da 100644 --- a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtcp_receiver_help.h +++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtcp_receiver_help.h @@ -32,6 +32,10 @@ public: // Statistics RTCPReportBlock remoteReceiveBlock; uint32_t remoteMaxJitter; + uint32_t remotePacketsReceived; + uint64_t remoteOctetsReceived; + uint32_t lastReceivedRRNTPsecs; + uint32_t lastReceivedRRNTPfrac; // RTT uint16_t RTT; diff --git a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtcp_sender.cc b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtcp_sender.cc index 6d81d95963d..e52c0d5569f 100644 --- a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtcp_sender.cc +++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtcp_sender.cc @@ -127,6 +127,8 @@ RTCPSender::RTCPSender(const int32_t id, _lastSendReport(), _lastRTCPTime(), + _lastSRPacketCount(), + _lastSROctetCount(), _CSRCs(0), _CSRC(), @@ -157,6 +159,8 @@ RTCPSender::RTCPSender(const int32_t id, memset(_CNAME, 0, sizeof(_CNAME)); memset(_lastSendReport, 0, sizeof(_lastSendReport)); memset(_lastRTCPTime, 0, sizeof(_lastRTCPTime)); + memset(_lastSRPacketCount, 0, sizeof(_lastSRPacketCount)); + memset(_lastSROctetCount, 0, sizeof(_lastSROctetCount)); WEBRTC_TRACE(kTraceMemory, kTraceRtpRtcp, id, "%s created", __FUNCTION__); } @@ -228,6 +232,8 @@ RTCPSender::Init() memset(_CNAME, 0, sizeof(_CNAME)); memset(_lastSendReport, 0, sizeof(_lastSendReport)); memset(_lastRTCPTime, 0, sizeof(_lastRTCPTime)); + memset(_lastSRPacketCount, 0, sizeof(_lastSRPacketCount)); + memset(_lastSROctetCount, 0, sizeof(_lastSROctetCount)); _nackCount = 0; _pliCount = 0; @@ -569,26 +575,32 @@ RTCPSender::LastSendReport( uint32_t& lastRTCPTime) return _lastSendReport[0]; } -uint32_t -RTCPSender::SendTimeOfSendReport(const uint32_t sendReport) +bool +RTCPSender::GetSendReportMetadata(const uint32_t sendReport, + uint32_t *timeOfSend, + uint32_t *packetCount, + uint64_t *octetCount) { CriticalSectionScoped lock(_criticalSectionRTCPSender); // This is only saved when we are the sender if((_lastSendReport[0] == 0) || (sendReport == 0)) { - return 0; // will be ignored + return false; } else { for(int i = 0; i < RTCP_NUMBER_OF_SR; ++i) { if( _lastSendReport[i] == sendReport) { - return _lastRTCPTime[i]; + *timeOfSend = _lastRTCPTime[i]; + *packetCount = _lastSRPacketCount[i]; + *octetCount = _lastSROctetCount[i]; + return true; } } } - return 0; + return false; } int32_t RTCPSender::AddExternalReportBlock( @@ -664,10 +676,14 @@ int32_t RTCPSender::BuildSR(const FeedbackState& feedback_state, // shift old _lastSendReport[i+1] = _lastSendReport[i]; _lastRTCPTime[i+1] =_lastRTCPTime[i]; + _lastSRPacketCount[i+1] = _lastSRPacketCount[i]; + _lastSROctetCount[i+1] = _lastSROctetCount[i]; } _lastRTCPTime[0] = Clock::NtpToMs(NTPsec, NTPfrac); _lastSendReport[0] = (NTPsec << 16) + (NTPfrac >> 16); + _lastSRPacketCount[0] = feedback_state.packet_count_sent; + _lastSROctetCount[0] = feedback_state.byte_count_sent; // The timestamp of this RTCP packet should be estimated as the timestamp of // the frame being captured at this moment. We are calculating that diff --git a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtcp_sender.h b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtcp_sender.h index 909c15d231c..48c218b89ff 100644 --- a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtcp_sender.h +++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtcp_sender.h @@ -105,7 +105,10 @@ public: int32_t RemoveMixedCNAME(const uint32_t SSRC); - uint32_t SendTimeOfSendReport(const uint32_t sendReport); + bool GetSendReportMetadata(const uint32_t sendReport, + uint32_t *timeOfSend, + uint32_t *packetCount, + uint64_t *octetCount); bool TimeToSendRTCPReport(const bool sendKeyframeBeforeRTP = false) const; @@ -288,6 +291,8 @@ private: // Sent uint32_t _lastSendReport[RTCP_NUMBER_OF_SR]; // allow packet loss and RTT above 1 sec uint32_t _lastRTCPTime[RTCP_NUMBER_OF_SR]; + uint32_t _lastSRPacketCount[RTCP_NUMBER_OF_SR]; + uint64_t _lastSROctetCount[RTCP_NUMBER_OF_SR]; // send CSRCs uint8_t _CSRCs; diff --git a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc index 7c4cee7e873..9228210af1e 100644 --- a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc +++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.cc @@ -910,6 +910,19 @@ void ModuleRtpRtcpImpl:: SetRtt(uint32_t rtt) { rtcp_receiver_.SetRTT(static_cast(rtt)); } +int32_t +ModuleRtpRtcpImpl::GetReportBlockInfo(const uint32_t remote_ssrc, + uint32_t* ntp_high, + uint32_t* ntp_low, + uint32_t* packets_received, + uint64_t* octets_received) const { + WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, "RemotePacketsReceived()"); + + return rtcp_receiver_.GetReportBlockInfo(remote_ssrc, + ntp_high, ntp_low, + packets_received, octets_received); +} + // Reset RTP data counters for the sending side. int32_t ModuleRtpRtcpImpl::ResetSendDataCountersRTP() { WEBRTC_TRACE(kTraceModuleCall, kTraceRtpRtcp, id_, @@ -1538,9 +1551,14 @@ int32_t ModuleRtpRtcpImpl::SendRTCPReferencePictureSelection( feedback_state, kRtcpRpsi, 0, 0, false, picture_id); } -uint32_t ModuleRtpRtcpImpl::SendTimeOfSendReport( - const uint32_t send_report) { - return rtcp_sender_.SendTimeOfSendReport(send_report); +bool ModuleRtpRtcpImpl::GetSendReportMetadata(const uint32_t send_report, + uint32_t *time_of_send, + uint32_t *packet_count, + uint64_t *octet_count) { + return rtcp_sender_.GetSendReportMetadata(send_report, + time_of_send, + packet_count, + octet_count); } void ModuleRtpRtcpImpl::OnReceivedNACK( diff --git a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h index ada2442fe3a..2e45f321602 100644 --- a/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h +++ b/media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h @@ -173,6 +173,12 @@ class ModuleRtpRtcpImpl : public RtpRtcp { virtual void SetRtt(uint32_t rtt) OVERRIDE; + virtual int32_t GetReportBlockInfo(const uint32_t remote_ssrc, + uint32_t* ntp_high, + uint32_t* ntp_low, + uint32_t* packets_received, + uint64_t* octets_received) const OVERRIDE; + // Force a send of an RTCP packet. // Normal SR and RR are triggered via the process function. virtual int32_t SendRTCP(uint32_t rtcp_packet_type = kRtcpReport) OVERRIDE; @@ -331,7 +337,10 @@ class ModuleRtpRtcpImpl : public RtpRtcp { uint32_t* fec_rate, uint32_t* nackRate) const OVERRIDE; - virtual uint32_t SendTimeOfSendReport(const uint32_t send_report); + virtual bool GetSendReportMetadata(const uint32_t send_report, + uint32_t *time_of_send, + uint32_t *packet_count, + uint64_t *octet_count); // Good state of RTP receiver inform sender. virtual int32_t SendRTCPReferencePictureSelection( diff --git a/media/webrtc/trunk/webrtc/video_engine/include/vie_rtp_rtcp.h b/media/webrtc/trunk/webrtc/video_engine/include/vie_rtp_rtcp.h index c28a804b85e..474cd9987e0 100644 --- a/media/webrtc/trunk/webrtc/video_engine/include/vie_rtp_rtcp.h +++ b/media/webrtc/trunk/webrtc/video_engine/include/vie_rtp_rtcp.h @@ -91,6 +91,8 @@ class WEBRTC_DLLEXPORT ViERTCPObserver { virtual ~ViERTCPObserver() {} }; +struct SenderInfo; + class WEBRTC_DLLEXPORT ViERTP_RTCP { public: enum { KDefaultDeltaTransmitTimeSeconds = 15 }; @@ -171,6 +173,16 @@ class WEBRTC_DLLEXPORT ViERTP_RTCP { const int video_channel, char rtcp_cname[KMaxRTCPCNameLength]) const = 0; + virtual int GetRemoteRTCPReceiverInfo(const int video_channel, + uint32_t& NTPHigh, + uint32_t& NTPLow, + uint32_t& receivedPacketCount, + uint64_t& receivedOctetCount, + uint32_t* jitter, + uint16_t* fractionLost, + uint32_t* cumulativeLost, + int32_t* rttMs) const = 0; + // This function sends an RTCP APP packet on a specific channel. virtual int SendApplicationDefinedRTCPPacket( const int video_channel, @@ -264,10 +276,6 @@ class WEBRTC_DLLEXPORT ViERTP_RTCP { // stream. virtual int GetReceivedRTCPStatistics( const int video_channel, - unsigned int& ntpHigh, - unsigned int& ntpLow, - unsigned int& bytes_sent, - unsigned int& packets_sent, unsigned short& fraction_lost, unsigned int& cumulative_lost, unsigned int& extended_max, @@ -277,10 +285,6 @@ class WEBRTC_DLLEXPORT ViERTP_RTCP { // This function returns statistics reported by the remote client in a RTCP // packet. virtual int GetSentRTCPStatistics(const int video_channel, - unsigned int& ntpHigh, - unsigned int& ntpLow, - unsigned int& bytes_sent, - unsigned int& packets_sent, unsigned short& fraction_lost, unsigned int& cumulative_lost, unsigned int& extended_max, @@ -294,6 +298,10 @@ class WEBRTC_DLLEXPORT ViERTP_RTCP { unsigned int& bytes_received, unsigned int& packets_received) const = 0; + // Gets the sender info part of the last received RTCP Sender Report (SR) + virtual int GetRemoteRTCPSenderInfo(const int video_channel, + SenderInfo* sender_info) const = 0; + // The function gets bandwidth usage statistics from the sent RTP streams in // bits/s. virtual int GetBandwidthUsage(const int video_channel, diff --git a/media/webrtc/trunk/webrtc/video_engine/vie_channel.cc b/media/webrtc/trunk/webrtc/video_engine/vie_channel.cc index 0574eda004b..fff87d81f9a 100644 --- a/media/webrtc/trunk/webrtc/video_engine/vie_channel.cc +++ b/media/webrtc/trunk/webrtc/video_engine/vie_channel.cc @@ -30,6 +30,7 @@ #include "webrtc/video_engine/include/vie_image_process.h" #include "webrtc/video_engine/include/vie_rtp_rtcp.h" #include "webrtc/video_engine/vie_defines.h" +#include "webrtc/voice_engine/include/voe_rtp_rtcp.h" // for webrtc::SenderInfo namespace webrtc { @@ -1177,11 +1178,82 @@ int32_t ViEChannel::SendApplicationDefinedRTCPPacket( return 0; } -int32_t ViEChannel::GetSendRtcpStatistics(uint32_t* ntp_high, - uint32_t* ntp_low, - uint32_t* bytes_sent, - uint32_t* packets_sent, - uint16_t* fraction_lost, +int32_t ViEChannel::GetRemoteRTCPReceiverInfo(uint32_t& NTPHigh, + uint32_t& NTPLow, + uint32_t& receivedPacketCount, + uint64_t& receivedOctetCount, + uint32_t* jitterSamples, + uint16_t* fractionLost, + uint32_t* cumulativeLost, + int32_t* rttMs) { + WEBRTC_TRACE(kTraceInfo, kTraceVideo, ViEId(engine_id_, channel_id_), "%s", + __FUNCTION__); + + // TODO: how do we do this for simulcast ? average for all + // except cumulative_lost that is the sum ? + // CriticalSectionScoped cs(rtp_rtcp_cs_.get()); + + // for (std::list::const_iterator it = simulcast_rtp_rtcp_.begin(); + // it != simulcast_rtp_rtcp_.end(); + // it++) { + // RtpRtcp* rtp_rtcp = *it; + // } + uint32_t remote_ssrc = vie_receiver_.GetRemoteSsrc(); + + // Get all RTCP receiver report blocks that have been received on this + // channel. If we receive RTP packets from a remote source we know the + // remote SSRC and use the report block from him. + // Otherwise use the first report block. + std::vector remote_stats; + if (rtp_rtcp_->RemoteRTCPStat(&remote_stats) != 0 || remote_stats.empty()) { + WEBRTC_TRACE(kTraceWarning, kTraceVideo, ViEId(engine_id_, channel_id_), + "%s: Could not get remote stats", __FUNCTION__); + return -1; + } + std::vector::const_iterator statistics = + remote_stats.begin(); + for (; statistics != remote_stats.end(); ++statistics) { + if (statistics->remoteSSRC == remote_ssrc) + break; + } + + if (statistics == remote_stats.end()) { + // If we have not received any RTCP packets from this SSRC it probably means + // we have not received any RTP packets. + // Use the first received report block instead. + statistics = remote_stats.begin(); + remote_ssrc = statistics->remoteSSRC; + } + + if (rtp_rtcp_->GetReportBlockInfo(remote_ssrc, + &NTPHigh, + &NTPLow, + &receivedPacketCount, + &receivedOctetCount) != 0) { + WEBRTC_TRACE(kTraceWarning, kTraceVideo, ViEId(engine_id_, channel_id_), + "%s: failed to retrieve RTT", __FUNCTION__); + NTPHigh = 0; + NTPLow = 0; + receivedPacketCount = 0; + receivedOctetCount = 0; + } + + *fractionLost = statistics->fractionLost; + *cumulativeLost = statistics->cumulativeLost; + *jitterSamples = statistics->jitter; + + uint16_t dummy; + uint16_t rtt = 0; + if (rtp_rtcp_->RTT(remote_ssrc, &rtt, &dummy, &dummy, &dummy) != 0) { + WEBRTC_TRACE(kTraceWarning, kTraceVideo, ViEId(engine_id_, channel_id_), + "%s: Could not get RTT", __FUNCTION__); + return -1; + } + *rttMs = rtt; + return 0; +} + +int32_t ViEChannel::GetSendRtcpStatistics(uint16_t* fraction_lost, uint32_t* cumulative_lost, uint32_t* extended_max, uint32_t* jitter_samples, @@ -1200,20 +1272,6 @@ int32_t ViEChannel::GetSendRtcpStatistics(uint32_t* ntp_high, // } uint32_t remote_ssrc = vie_receiver_.GetRemoteSsrc(); - // --- Information from sender info in received RTCP Sender Reports - - RTCPSenderInfo sender_info; - if (rtp_rtcp_->RemoteRTCPStat(&sender_info) != 0) { - WEBRTC_TRACE(kTraceWarning, kTraceVideo, ViEId(engine_id_, channel_id_), - "%s: Could not get sender info for remote side", __FUNCTION__); - return -1; - } - - *ntp_high = sender_info.NTPseconds; - *ntp_low = sender_info.NTPfraction; - *bytes_sent = sender_info.sendOctetCount; - *packets_sent = sender_info.sendPacketCount; - // Get all RTCP receiver report blocks that have been received on this // channel. If we receive RTP packets from a remote source we know the // remote SSRC and use the report block from him. @@ -1258,11 +1316,7 @@ int32_t ViEChannel::GetSendRtcpStatistics(uint32_t* ntp_high, // TODO(holmer): This is a bad function name as it implies that it returns the // received RTCP, while it actually returns the statistics which will be sent // in the RTCP. -int32_t ViEChannel::GetReceivedRtcpStatistics(uint32_t* ntp_high, - uint32_t* ntp_low, - uint32_t* bytes_sent, - uint32_t* packets_sent, - uint16_t* fraction_lost, +int32_t ViEChannel::GetReceivedRtcpStatistics(uint16_t* fraction_lost, uint32_t* cumulative_lost, uint32_t* extended_max, uint32_t* jitter_samples, @@ -1270,22 +1324,6 @@ int32_t ViEChannel::GetReceivedRtcpStatistics(uint32_t* ntp_high, WEBRTC_TRACE(kTraceInfo, kTraceVideo, ViEId(engine_id_, channel_id_), "%s", __FUNCTION__); - // --- Information from sender info in received RTCP Sender Reports - - RTCPSenderInfo sender_info; - if (rtp_rtcp_->RemoteRTCPStat(&sender_info) != 0) { - WEBRTC_TRACE(kTraceWarning, kTraceVideo, ViEId(engine_id_, channel_id_), - "%s: Could not get sender info for remote side", __FUNCTION__); - return -1; - } - - *ntp_high = sender_info.NTPseconds; - *ntp_low = sender_info.NTPfraction; - *bytes_sent = sender_info.sendOctetCount; - *packets_sent = sender_info.sendPacketCount; - - // --- Locally derived information - uint32_t remote_ssrc = vie_receiver_.GetRemoteSsrc(); uint8_t frac_lost = 0; StreamStatistician* statistician = @@ -1345,6 +1383,26 @@ int32_t ViEChannel::GetRtpStatistics(uint32_t* bytes_sent, return 0; } +int32_t ViEChannel::GetRemoteRTCPSenderInfo(SenderInfo* sender_info) const { + WEBRTC_TRACE(kTraceInfo, kTraceVideo, ViEId(engine_id_, channel_id_), "%s", + __FUNCTION__); + + // Get the sender info from the latest received RTCP Sender Report. + RTCPSenderInfo rtcp_sender_info; + if (rtp_rtcp_->RemoteRTCPStat(&rtcp_sender_info) != 0) { + WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, channel_id_), + "%s: failed to read RTCP SR sender info", __FUNCTION__); + return -1; + } + + sender_info->NTP_timestamp_high = rtcp_sender_info.NTPseconds; + sender_info->NTP_timestamp_low = rtcp_sender_info.NTPfraction; + sender_info->RTP_timestamp = rtcp_sender_info.RTPtimeStamp; + sender_info->sender_packet_count = rtcp_sender_info.sendPacketCount; + sender_info->sender_octet_count = rtcp_sender_info.sendOctetCount; + return 0; +} + void ViEChannel::GetBandwidthUsage(uint32_t* total_bitrate_sent, uint32_t* video_bitrate_sent, uint32_t* fec_bitrate_sent, diff --git a/media/webrtc/trunk/webrtc/video_engine/vie_channel.h b/media/webrtc/trunk/webrtc/video_engine/vie_channel.h index e806b13349d..b9e622aa049 100644 --- a/media/webrtc/trunk/webrtc/video_engine/vie_channel.h +++ b/media/webrtc/trunk/webrtc/video_engine/vie_channel.h @@ -48,6 +48,8 @@ class VideoDecoder; class VideoRenderCallback; class VoEVideoSync; +struct SenderInfo; + class ViEChannel : public VCMFrameTypeCallback, public VCMReceiveCallback, @@ -155,6 +157,7 @@ class ViEChannel // Gets the CName of the incoming stream. int32_t GetRemoteRTCPCName(char rtcp_cname[]); + int32_t RegisterRtpObserver(ViERTPObserver* observer); int32_t RegisterRtcpObserver(ViERTCPObserver* observer); int32_t SendApplicationDefinedRTCPPacket( @@ -163,23 +166,25 @@ class ViEChannel const uint8_t* data, uint16_t data_length_in_bytes); + // Gets info (including timestamp) from last rr + remote packetcount + // (derived from rr report + cached sender-side info). + int32_t GetRemoteRTCPReceiverInfo(uint32_t& NTPHigh, uint32_t& NTPLow, + uint32_t& receivedPacketCount, + uint64_t& receivedOctetCount, + uint32_t* jitterSamples, + uint16_t* fractionLost, + uint32_t* cumulativeLost, + int32_t* rttMs); + // Returns statistics reported by the remote client in an RTCP packet. - int32_t GetSendRtcpStatistics(uint32_t* ntp_high, - uint32_t* ntp_low, - uint32_t* bytes_sent, - uint32_t* packets_sent, - uint16_t* fraction_lost, + int32_t GetSendRtcpStatistics(uint16_t* fraction_lost, uint32_t* cumulative_lost, uint32_t* extended_max, uint32_t* jitter_samples, int32_t* rtt_ms); - // Returns RTCP sender report + locally created stats of received RTP stream - int32_t GetReceivedRtcpStatistics(uint32_t* ntp_high, - uint32_t* ntp_low, - uint32_t* bytes_sent, - uint32_t* packets_sent, - uint16_t* fraction_lost, + // Returns our localy created statistics of the received RTP stream. + int32_t GetReceivedRtcpStatistics(uint16_t* fraction_lost, uint32_t* cumulative_lost, uint32_t* extended_max, uint32_t* jitter_samples, @@ -190,6 +195,9 @@ class ViEChannel uint32_t* packets_sent, uint32_t* bytes_received, uint32_t* packets_received) const; + + int32_t GetRemoteRTCPSenderInfo(SenderInfo* sender_info) const; + void GetBandwidthUsage(uint32_t* total_bitrate_sent, uint32_t* video_bitrate_sent, uint32_t* fec_bitrate_sent, diff --git a/media/webrtc/trunk/webrtc/video_engine/vie_rtp_rtcp_impl.cc b/media/webrtc/trunk/webrtc/video_engine/vie_rtp_rtcp_impl.cc index 25a3fef51ec..7fdec1c752a 100644 --- a/media/webrtc/trunk/webrtc/video_engine/vie_rtp_rtcp_impl.cc +++ b/media/webrtc/trunk/webrtc/video_engine/vie_rtp_rtcp_impl.cc @@ -424,6 +424,41 @@ int ViERTP_RTCPImpl::GetRemoteRTCPCName( return 0; } +int ViERTP_RTCPImpl::GetRemoteRTCPReceiverInfo(const int video_channel, + uint32_t& NTPHigh, + uint32_t& NTPLow, + uint32_t& receivedPacketCount, + uint64_t& receivedOctetCount, + uint32_t* jitter, + uint16_t* fractionLost, + uint32_t* cumulativeLost, + int32_t* rttMs) const { + WEBRTC_TRACE(kTraceApiCall, kTraceVideo, + ViEId(shared_data_->instance_id(), video_channel), + "%s(channel: %d)", __FUNCTION__, video_channel); + ViEChannelManagerScoped cs(*(shared_data_->channel_manager())); + ViEChannel* vie_channel = cs.Channel(video_channel); + if (!vie_channel) { + WEBRTC_TRACE(kTraceError, kTraceVideo, + ViEId(shared_data_->instance_id(), video_channel), + "%s: Channel %d doesn't exist", __FUNCTION__, video_channel); + shared_data_->SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + if (vie_channel->GetRemoteRTCPReceiverInfo(NTPHigh, + NTPLow, + receivedPacketCount, + receivedOctetCount, + jitter, + fractionLost, + cumulativeLost, + rttMs) != 0) { + shared_data_->SetLastError(kViERtpRtcpUnknownError); + return -1; + } + return 0; +} + int ViERTP_RTCPImpl::SendApplicationDefinedRTCPPacket( const int video_channel, const unsigned char sub_type, @@ -824,10 +859,6 @@ int ViERTP_RTCPImpl::SetTransmissionSmoothingStatus(int video_channel, } int ViERTP_RTCPImpl::GetReceivedRTCPStatistics(const int video_channel, - unsigned int& ntp_high, - unsigned int& ntp_low, - unsigned int& bytes_sent, - unsigned int& packets_sent, uint16_t& fraction_lost, unsigned int& cumulative_lost, unsigned int& extended_max, @@ -845,11 +876,7 @@ int ViERTP_RTCPImpl::GetReceivedRTCPStatistics(const int video_channel, shared_data_->SetLastError(kViERtpRtcpInvalidChannelId); return -1; } - if (vie_channel->GetReceivedRtcpStatistics(&ntp_high, - &ntp_low, - &bytes_sent, - &packets_sent, - &fraction_lost, + if (vie_channel->GetReceivedRtcpStatistics(&fraction_lost, &cumulative_lost, &extended_max, &jitter, @@ -861,10 +888,6 @@ int ViERTP_RTCPImpl::GetReceivedRTCPStatistics(const int video_channel, } int ViERTP_RTCPImpl::GetSentRTCPStatistics(const int video_channel, - unsigned int& ntp_high, - unsigned int& ntp_low, - unsigned int& bytes_sent, - unsigned int& packets_sent, uint16_t& fraction_lost, unsigned int& cumulative_lost, unsigned int& extended_max, @@ -883,9 +906,7 @@ int ViERTP_RTCPImpl::GetSentRTCPStatistics(const int video_channel, return -1; } - if (vie_channel->GetSendRtcpStatistics(&ntp_high, &ntp_low, - &bytes_sent, &packets_sent, - &fraction_lost, &cumulative_lost, + if (vie_channel->GetSendRtcpStatistics(&fraction_lost, &cumulative_lost, &extended_max, &jitter, &rtt_ms) != 0) { shared_data_->SetLastError(kViERtpRtcpUnknownError); @@ -921,6 +942,27 @@ int ViERTP_RTCPImpl::GetRTPStatistics(const int video_channel, return 0; } +int ViERTP_RTCPImpl::GetRemoteRTCPSenderInfo(const int video_channel, + SenderInfo* sender_info) const { + WEBRTC_TRACE(kTraceApiCall, kTraceVideo, + ViEId(shared_data_->instance_id(), video_channel), + "%s(channel: %d)", __FUNCTION__, video_channel); + ViEChannelManagerScoped cs(*(shared_data_->channel_manager())); + ViEChannel* vie_channel = cs.Channel(video_channel); + if (!vie_channel) { + WEBRTC_TRACE(kTraceError, kTraceVideo, + ViEId(shared_data_->instance_id(), video_channel), + "%s: Channel %d doesn't exist", __FUNCTION__, video_channel); + shared_data_->SetLastError(kViERtpRtcpInvalidChannelId); + return -1; + } + if (vie_channel->GetRemoteRTCPSenderInfo(sender_info) != 0) { + shared_data_->SetLastError(kViERtpRtcpUnknownError); + return -1; + } + return 0; +} + int ViERTP_RTCPImpl::GetBandwidthUsage(const int video_channel, unsigned int& total_bitrate_sent, unsigned int& video_bitrate_sent, diff --git a/media/webrtc/trunk/webrtc/video_engine/vie_rtp_rtcp_impl.h b/media/webrtc/trunk/webrtc/video_engine/vie_rtp_rtcp_impl.h index bd2926984ab..ce4f51f9fa8 100644 --- a/media/webrtc/trunk/webrtc/video_engine/vie_rtp_rtcp_impl.h +++ b/media/webrtc/trunk/webrtc/video_engine/vie_rtp_rtcp_impl.h @@ -55,6 +55,15 @@ class ViERTP_RTCPImpl char rtcp_cname[KMaxRTCPCNameLength]) const; virtual int GetRemoteRTCPCName(const int video_channel, char rtcp_cname[KMaxRTCPCNameLength]) const; + virtual int GetRemoteRTCPReceiverInfo(const int video_channel, + uint32_t& NTPHigh, + uint32_t& NTPLow, + uint32_t& receivedPacketCount, + uint64_t& receivedOctetCount, + uint32_t* jitter, + uint16_t* fractionLost, + uint32_t* cumulativeLost, + int32_t* rttMs) const; virtual int SendApplicationDefinedRTCPPacket( const int video_channel, const unsigned char sub_type, @@ -90,20 +99,12 @@ class ViERTP_RTCPImpl int id); virtual int SetTransmissionSmoothingStatus(int video_channel, bool enable); virtual int GetReceivedRTCPStatistics(const int video_channel, - unsigned int& ntpHigh, - unsigned int& ntpLow, - unsigned int& bytes_sent, - unsigned int& packets_sent, uint16_t& fraction_lost, unsigned int& cumulative_lost, unsigned int& extended_max, unsigned int& jitter, int& rtt_ms) const; virtual int GetSentRTCPStatistics(const int video_channel, - unsigned int& ntpHigh, - unsigned int& ntpLow, - unsigned int& bytes_sent, - unsigned int& packets_sent, uint16_t& fraction_lost, unsigned int& cumulative_lost, unsigned int& extended_max, @@ -113,6 +114,8 @@ class ViERTP_RTCPImpl unsigned int& packets_sent, unsigned int& bytes_received, unsigned int& packets_received) const; + virtual int GetRemoteRTCPSenderInfo(const int video_channel, + SenderInfo* sender_info) const; virtual int GetBandwidthUsage(const int video_channel, unsigned int& total_bitrate_sent, unsigned int& video_bitrate_sent, diff --git a/media/webrtc/trunk/webrtc/voice_engine/channel.cc b/media/webrtc/trunk/webrtc/voice_engine/channel.cc index a7a8ef2fd17..b342194213c 100644 --- a/media/webrtc/trunk/webrtc/voice_engine/channel.cc +++ b/media/webrtc/trunk/webrtc/voice_engine/channel.cc @@ -3797,107 +3797,81 @@ Channel::GetRemoteRTCP_CNAME(char cName[256]) } int -Channel::GetRemoteRTCPData( - unsigned int& NTPHigh, - unsigned int& NTPLow, - unsigned int& timestamp, - unsigned int& playoutTimestamp, - unsigned int& sendPacketCount, - unsigned int& sendOctetCount, - unsigned int* jitter, - unsigned short* fractionLost, - unsigned int* cumulativeLost) +Channel::GetRemoteRTCPReceiverInfo( + uint32_t& NTPHigh, + uint32_t& NTPLow, + uint32_t& receivedPacketCount, + uint64_t& receivedOctetCount, + uint32_t& jitter, + uint16_t& fractionLost, + uint32_t& cumulativeLost, + int32_t& rttMs) { - // --- Information from sender info in received Sender Reports + // Get all RTCP receiver report blocks that have been received on this + // channel. If we receive RTP packets from a remote source we know the + // remote SSRC and use the report block from him. + // Otherwise use the first report block. + std::vector remote_stats; + if (_rtpRtcpModule->RemoteRTCPStat(&remote_stats) != 0 || + remote_stats.empty()) { + WEBRTC_TRACE(kTraceWarning, kTraceVoice, + VoEId(_instanceId, _channelId), + "GetRemoteRTCPReceiverInfo() failed to measure statistics due" + " to lack of received RTP and/or RTCP packets"); + return -1; + } - RTCPSenderInfo senderInfo; - if (_rtpRtcpModule->RemoteRTCPStat(&senderInfo) != 0) - { - _engineStatisticsPtr->SetLastError( - VE_RTP_RTCP_MODULE_ERROR, kTraceError, - "GetRemoteRTCPData() failed to retrieve sender info for remote " - "side"); - return -1; - } + uint32_t remoteSSRC = rtp_receiver_->SSRC(); + std::vector::const_iterator it = remote_stats.begin(); + for (; it != remote_stats.end(); ++it) { + if (it->remoteSSRC == remoteSSRC) + break; + } - NTPHigh = senderInfo.NTPseconds; - NTPLow = senderInfo.NTPfraction; - timestamp = senderInfo.RTPtimeStamp; - sendPacketCount = senderInfo.sendPacketCount; - sendOctetCount = senderInfo.sendOctetCount; + if (it == remote_stats.end()) { + // If we have not received any RTCP packets from this SSRC it probably + // means that we have not received any RTP packets. + // Use the first received report block instead. + it = remote_stats.begin(); + remoteSSRC = it->remoteSSRC; + } - WEBRTC_TRACE(kTraceStateInfo, kTraceVoice, + if (_rtpRtcpModule->GetReportBlockInfo(remoteSSRC, + &NTPHigh, + &NTPLow, + &receivedPacketCount, + &receivedOctetCount) != 0) { + WEBRTC_TRACE(kTraceWarning, kTraceVoice, VoEId(_instanceId, _channelId), - "GetRemoteRTCPData() => NTPHigh=%lu, NTPLow=%lu, " - "timestamp=%lu", - NTPHigh, NTPLow, timestamp); + "GetRemoteRTCPReceiverInfo() failed to retrieve RTT from " + "the RTP/RTCP module"); + NTPHigh = 0; + NTPLow = 0; + receivedPacketCount = 0; + receivedOctetCount = 0; + } - // --- Locally derived information + jitter = it->jitter; + fractionLost = it->fractionLost; + cumulativeLost = it->cumulativeLost; + WEBRTC_TRACE(kTraceStateInfo, kTraceVoice, + VoEId(_instanceId, _channelId), + "GetRemoteRTCPReceiverInfo() => jitter = %lu, " + "fractionLost = %lu, cumulativeLost = %lu", + jitter, fractionLost, cumulativeLost); - // This value is updated on each incoming RTCP packet (0 when no packet - // has been received) - playoutTimestamp = playout_timestamp_rtcp_; - - WEBRTC_TRACE(kTraceStateInfo, kTraceVoice, + uint16_t dummy; + uint16_t rtt = 0; + if (_rtpRtcpModule->RTT(remoteSSRC, &rtt, &dummy, &dummy, &dummy) + != 0) + { + WEBRTC_TRACE(kTraceWarning, kTraceVoice, VoEId(_instanceId, _channelId), - "GetRemoteRTCPData() => playoutTimestamp=%lu", - playout_timestamp_rtcp_); - - if (NULL != jitter || NULL != fractionLost || NULL != cumulativeLost) - { - // Get all RTCP receiver report blocks that have been received on this - // channel. If we receive RTP packets from a remote source we know the - // remote SSRC and use the report block from him. - // Otherwise use the first report block. - std::vector remote_stats; - if (_rtpRtcpModule->RemoteRTCPStat(&remote_stats) != 0 || - remote_stats.empty()) { - WEBRTC_TRACE(kTraceWarning, kTraceVoice, - VoEId(_instanceId, _channelId), - "GetRemoteRTCPData() failed to measure statistics due" - " to lack of received RTP and/or RTCP packets"); - return -1; - } - - uint32_t remoteSSRC = rtp_receiver_->SSRC(); - std::vector::const_iterator it = remote_stats.begin(); - for (; it != remote_stats.end(); ++it) { - if (it->remoteSSRC == remoteSSRC) - break; - } - - if (it == remote_stats.end()) { - // If we have not received any RTCP packets from this SSRC it probably - // means that we have not received any RTP packets. - // Use the first received report block instead. - it = remote_stats.begin(); - remoteSSRC = it->remoteSSRC; - } - - if (jitter) { - *jitter = it->jitter; - WEBRTC_TRACE(kTraceStateInfo, kTraceVoice, - VoEId(_instanceId, _channelId), - "GetRemoteRTCPData() => jitter = %lu", *jitter); - } - - if (fractionLost) { - *fractionLost = it->fractionLost; - WEBRTC_TRACE(kTraceStateInfo, kTraceVoice, - VoEId(_instanceId, _channelId), - "GetRemoteRTCPData() => fractionLost = %lu", - *fractionLost); - } - - if (cumulativeLost) { - *cumulativeLost = it->cumulativeLost; - WEBRTC_TRACE(kTraceStateInfo, kTraceVoice, - VoEId(_instanceId, _channelId), - "GetRemoteRTCPData() => cumulativeLost = %lu", - *cumulativeLost); - } - } - return 0; + "GetRTPStatistics() failed to retrieve RTT from " + "the RTP/RTCP module"); + } + rttMs = rtt; + return 0; } int diff --git a/media/webrtc/trunk/webrtc/voice_engine/channel.h b/media/webrtc/trunk/webrtc/voice_engine/channel.h index c70bb31c534..274b6ea7ef7 100644 --- a/media/webrtc/trunk/webrtc/voice_engine/channel.h +++ b/media/webrtc/trunk/webrtc/voice_engine/channel.h @@ -264,14 +264,13 @@ public: int SetRTCP_CNAME(const char cName[256]); int GetRTCP_CNAME(char cName[256]); int GetRemoteRTCP_CNAME(char cName[256]); - int GetRemoteRTCPData(unsigned int& NTPHigh, unsigned int& NTPLow, - unsigned int& timestamp, - unsigned int& playoutTimestamp, - unsigned int& sendPacketCount, - unsigned int& sendOctetCount, - unsigned int* jitter, - unsigned short* fractionLost, - unsigned int* cumulativeLost); + int GetRemoteRTCPReceiverInfo(uint32_t& NTPHigh, uint32_t& NTPLow, + uint32_t& receivedPacketCount, + uint64_t& receivedOctetCount, + uint32_t& jitter, + uint16_t& fractionLost, + uint32_t& cumulativeLost, + int32_t& rttMs); int SendApplicationDefinedRTCPPacket(unsigned char subType, unsigned int name, const char* data, unsigned short dataLengthInBytes); diff --git a/media/webrtc/trunk/webrtc/voice_engine/include/voe_rtp_rtcp.h b/media/webrtc/trunk/webrtc/voice_engine/include/voe_rtp_rtcp.h index 8f7286b190a..94386464785 100644 --- a/media/webrtc/trunk/webrtc/voice_engine/include/voe_rtp_rtcp.h +++ b/media/webrtc/trunk/webrtc/voice_engine/include/voe_rtp_rtcp.h @@ -182,12 +182,12 @@ public: virtual int GetRemoteRTCP_CNAME(int channel, char cName[256]) = 0; // Gets RTCP data from incoming RTCP Sender Reports. - virtual int GetRemoteRTCPData( - int channel, unsigned int& NTPHigh, unsigned int& NTPLow, - unsigned int& timestamp, unsigned int& playoutTimestamp, - unsigned int& sendPacketCount, unsigned int& sendOctetCount, - unsigned int* jitter = NULL, unsigned short* fractionLost = NULL, - unsigned int* cumulativeLost = NULL) = 0; + virtual int GetRemoteRTCPReceiverInfo( + int channel, uint32_t& NTPHigh, uint32_t& NTPLow, + uint32_t& receivedPacketCount, uint64_t& receivedOctetCount, + uint32_t& jitter, uint16_t& fractionLost, + uint32_t& cumulativeLost, + int32_t& rttMs) = 0; // Gets RTP statistics for a specific |channel|. virtual int GetRTPStatistics( diff --git a/media/webrtc/trunk/webrtc/voice_engine/voe_rtp_rtcp_impl.cc b/media/webrtc/trunk/webrtc/voice_engine/voe_rtp_rtcp_impl.cc index 3f251d6f15a..c12ff6d4173 100644 --- a/media/webrtc/trunk/webrtc/voice_engine/voe_rtp_rtcp_impl.cc +++ b/media/webrtc/trunk/webrtc/voice_engine/voe_rtp_rtcp_impl.cc @@ -369,20 +369,19 @@ int VoERTP_RTCPImpl::GetRemoteRTCP_CNAME(int channel, char cName[256]) return channelPtr->GetRemoteRTCP_CNAME(cName); } -int VoERTP_RTCPImpl::GetRemoteRTCPData( +int VoERTP_RTCPImpl::GetRemoteRTCPReceiverInfo( int channel, - unsigned int& NTPHigh, // from sender info in SR - unsigned int& NTPLow, // from sender info in SR - unsigned int& timestamp, // from sender info in SR - unsigned int& playoutTimestamp, // derived locally - unsigned int& sendPacketCount, // from sender info in SR - unsigned int& sendOctetCount, // from sender info in SR - unsigned int* jitter, // from report block 1 in SR/RR - unsigned short* fractionLost, // from report block 1 in SR/RR - unsigned int* cumulativeLost) // from report block 1 in SR/RR + uint32_t& NTPHigh, // when last RR received + uint32_t& NTPLow, // when last RR received + uint32_t& receivedPacketCount, // derived from RR + cached info + uint64_t& receivedOctetCount, // derived from RR + cached info + uint32_t& jitter, // from report block 1 in RR + uint16_t& fractionLost, // from report block 1 in RR + uint32_t& cumulativeLost, // from report block 1 in RR + int32_t& rttMs) { WEBRTC_TRACE(kTraceApiCall, kTraceVoice, VoEId(_shared->instance_id(), -1), - "GetRemoteRTCPData(channel=%d,...)", channel); + "GetRemoteRTCPReceiverInfo(channel=%d,...)", channel); if (!_shared->statistics().Initialized()) { _shared->SetLastError(VE_NOT_INITED, kTraceError); @@ -393,18 +392,17 @@ int VoERTP_RTCPImpl::GetRemoteRTCPData( if (channelPtr == NULL) { _shared->SetLastError(VE_CHANNEL_NOT_VALID, kTraceError, - "GetRemoteRTCP_CNAME() failed to locate channel"); + "GetRemoteRTCPReceiverInfo() failed to locate channel"); return -1; } - return channelPtr->GetRemoteRTCPData(NTPHigh, - NTPLow, - timestamp, - playoutTimestamp, - sendPacketCount, - sendOctetCount, - jitter, - fractionLost, - cumulativeLost); + return channelPtr->GetRemoteRTCPReceiverInfo(NTPHigh, + NTPLow, + receivedPacketCount, + receivedOctetCount, + jitter, + fractionLost, + cumulativeLost, + rttMs); } int VoERTP_RTCPImpl::SendApplicationDefinedRTCPPacket( diff --git a/media/webrtc/trunk/webrtc/voice_engine/voe_rtp_rtcp_impl.h b/media/webrtc/trunk/webrtc/voice_engine/voe_rtp_rtcp_impl.h index 73280967bf4..db14cfc4fcc 100644 --- a/media/webrtc/trunk/webrtc/voice_engine/voe_rtp_rtcp_impl.h +++ b/media/webrtc/trunk/webrtc/voice_engine/voe_rtp_rtcp_impl.h @@ -40,16 +40,15 @@ public: virtual int GetRemoteRTCP_CNAME(int channel, char cName[256]); - virtual int GetRemoteRTCPData(int channel, - unsigned int& NTPHigh, - unsigned int& NTPLow, - unsigned int& timestamp, - unsigned int& playoutTimestamp, - unsigned int& sendPacketCount, - unsigned int& sendOctetCount, - unsigned int* jitter = NULL, - unsigned short* fractionLost = NULL, - unsigned int* cumulativeLost = NULL); + virtual int GetRemoteRTCPReceiverInfo(int channel, + uint32_t& NTPHigh, + uint32_t& NTPLow, + uint32_t& receivedPacketCount, + uint64_t& receivedOctetCount, + uint32_t& jitter, + uint16_t& fractionLost, + uint32_t& cumulativeLost, + int32_t& rttMs); virtual int SendApplicationDefinedRTCPPacket( int channel, diff --git a/mobile/android/base/BrowserApp.java b/mobile/android/base/BrowserApp.java index b98f4200375..16246510e60 100644 --- a/mobile/android/base/BrowserApp.java +++ b/mobile/android/base/BrowserApp.java @@ -20,6 +20,7 @@ import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException; import org.mozilla.gecko.animation.PropertyAnimator; import org.mozilla.gecko.animation.ViewHelper; import org.mozilla.gecko.db.BrowserContract.Combined; +import org.mozilla.gecko.db.BrowserContract.ReadingListItems; import org.mozilla.gecko.db.BrowserDB; import org.mozilla.gecko.favicons.Favicons; import org.mozilla.gecko.favicons.LoadFaviconTask; @@ -59,6 +60,7 @@ import org.mozilla.gecko.widget.GeckoActionProvider; import android.app.Activity; import android.app.AlertDialog; +import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -385,7 +387,7 @@ abstract public class BrowserApp extends GeckoApp }); } - void handleReaderAdded(int result, final String title, final String url) { + private void handleReaderAdded(int result, final ContentValues values) { if (result != READER_ADD_SUCCESS) { if (result == READER_ADD_FAILED) { showToast(R.string.reading_list_failed, Toast.LENGTH_SHORT); @@ -399,7 +401,7 @@ abstract public class BrowserApp extends GeckoApp ThreadUtils.postToBackgroundThread(new Runnable() { @Override public void run() { - BrowserDB.addReadingListItem(getContentResolver(), title, url); + BrowserDB.addReadingListItem(getContentResolver(), values); showToast(R.string.reading_list_added, Toast.LENGTH_SHORT); final int count = BrowserDB.getReadingListCount(getContentResolver()); @@ -408,6 +410,15 @@ abstract public class BrowserApp extends GeckoApp }); } + private ContentValues messageToReadingListContentValues(JSONObject message) { + final ContentValues values = new ContentValues(); + values.put(ReadingListItems.URL, message.optString("url")); + values.put(ReadingListItems.TITLE, message.optString("title")); + values.put(ReadingListItems.LENGTH, message.optInt("length")); + values.put(ReadingListItems.EXCERPT, message.optString("excerpt")); + return values; + } + void handleReaderRemoved(final String url) { ThreadUtils.postToBackgroundThread(new Runnable() { @Override @@ -1127,9 +1138,7 @@ abstract public class BrowserApp extends GeckoApp handleReaderListStatusRequest(message.getString("url")); } else if (event.equals("Reader:Added")) { final int result = message.getInt("result"); - final String title = message.getString("title"); - final String url = message.getString("url"); - handleReaderAdded(result, title, url); + handleReaderAdded(result, messageToReadingListContentValues(message)); } else if (event.equals("Reader:Removed")) { final String url = message.getString("url"); handleReaderRemoved(url); @@ -1645,17 +1654,21 @@ abstract public class BrowserApp extends GeckoApp final ViewStub homePagerStub = (ViewStub) findViewById(R.id.home_pager_stub); mHomePager = (HomePager) homePagerStub.inflate(); - final HomeBanner homeBanner = (HomeBanner) findViewById(R.id.home_banner); - mHomePager.setBanner(homeBanner); + // Don't show the banner in guest mode. + if (!getProfile().inGuestMode()) { + final ViewStub homeBannerStub = (ViewStub) findViewById(R.id.home_banner_stub); + final HomeBanner homeBanner = (HomeBanner) homeBannerStub.inflate(); + mHomePager.setBanner(homeBanner); - // Remove the banner from the view hierarchy if it is dismissed. - homeBanner.setOnDismissListener(new HomeBanner.OnDismissListener() { - @Override - public void onDismiss() { - mHomePager.setBanner(null); - mHomePagerContainer.removeView(homeBanner); - } - }); + // Remove the banner from the view hierarchy if it is dismissed. + homeBanner.setOnDismissListener(new HomeBanner.OnDismissListener() { + @Override + public void onDismiss() { + mHomePager.setBanner(null); + mHomePagerContainer.removeView(homeBanner); + } + }); + } } mHomePagerContainer.setVisibility(View.VISIBLE); diff --git a/mobile/android/base/DynamicToolbar.java b/mobile/android/base/DynamicToolbar.java index 0905eb0339f..2210e7ee7bf 100644 --- a/mobile/android/base/DynamicToolbar.java +++ b/mobile/android/base/DynamicToolbar.java @@ -106,7 +106,7 @@ public class DynamicToolbar { return; } - final boolean immediate = transition.equals(VisibilityTransition.ANIMATE); + final boolean immediate = transition.equals(VisibilityTransition.IMMEDIATE); if (visible) { layerView.getLayerMarginsAnimator().showMargins(immediate); } else { diff --git a/mobile/android/base/GeckoAppShell.java b/mobile/android/base/GeckoAppShell.java index 9ae01f922e8..28b36a7fd2d 100644 --- a/mobile/android/base/GeckoAppShell.java +++ b/mobile/android/base/GeckoAppShell.java @@ -1197,10 +1197,10 @@ public class GeckoAppShell * @return an Intent, or null if none could be * produced. */ - static Intent getShareIntent(final Context context, - final String targetURI, - final String mimeType, - final String title) { + public static Intent getShareIntent(final Context context, + final String targetURI, + final String mimeType, + final String title) { Intent shareIntent = getIntentForActionString(Intent.ACTION_SEND); shareIntent.putExtra(Intent.EXTRA_TEXT, targetURI); shareIntent.putExtra(Intent.EXTRA_SUBJECT, title); diff --git a/mobile/android/base/db/BrowserContract.java b/mobile/android/base/db/BrowserContract.java index 9462473bdeb..0b809bafa01 100644 --- a/mobile/android/base/db/BrowserContract.java +++ b/mobile/android/base/db/BrowserContract.java @@ -396,6 +396,9 @@ public class BrowserContract { public static final String DEFAULT_SORT_ORDER = _ID + " DESC"; public static final String[] DEFAULT_PROJECTION = new String[] { _ID, URL, TITLE, EXCERPT, LENGTH }; + // Minimum fields required to create a reading list item. + public static final String[] REQUIRED_FIELDS = { Bookmarks.URL, Bookmarks.TITLE }; + public static final String TABLE_NAME = "reading_list"; } diff --git a/mobile/android/base/db/BrowserDB.java b/mobile/android/base/db/BrowserDB.java index 1a1c15eed8c..d2558c2fd15 100644 --- a/mobile/android/base/db/BrowserDB.java +++ b/mobile/android/base/db/BrowserDB.java @@ -13,6 +13,7 @@ import org.mozilla.gecko.favicons.decoders.LoadFaviconResult; import org.mozilla.gecko.mozglue.RobocopTarget; import android.content.ContentResolver; +import android.content.ContentValues; import android.database.ContentObserver; import android.database.Cursor; import android.database.CursorWrapper; @@ -98,7 +99,7 @@ public class BrowserDB { @RobocopTarget public void updateBookmark(ContentResolver cr, int id, String uri, String title, String keyword); - public void addReadingListItem(ContentResolver cr, String title, String uri); + public void addReadingListItem(ContentResolver cr, ContentValues values); public void removeReadingListItemWithURL(ContentResolver cr, String uri); @@ -271,8 +272,8 @@ public class BrowserDB { sDb.updateBookmark(cr, id, uri, title, keyword); } - public static void addReadingListItem(ContentResolver cr, String title, String uri) { - sDb.addReadingListItem(cr, title, uri); + public static void addReadingListItem(ContentResolver cr, ContentValues values) { + sDb.addReadingListItem(cr, values); } public static void removeReadingListItemWithURL(ContentResolver cr, String uri) { diff --git a/mobile/android/base/db/LocalBrowserDB.java b/mobile/android/base/db/LocalBrowserDB.java index 6e4cc7af54f..e2c3e5da915 100644 --- a/mobile/android/base/db/LocalBrowserDB.java +++ b/mobile/android/base/db/LocalBrowserDB.java @@ -699,11 +699,16 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { } @Override - public void addReadingListItem(ContentResolver cr, String title, String uri) { - final ContentValues values = new ContentValues(); + public void addReadingListItem(ContentResolver cr, ContentValues values) { + // Check that required fields are present. + for (String field: ReadingListItems.REQUIRED_FIELDS) { + if (!values.containsKey(field)) { + throw new IllegalArgumentException("Missing required field for reading list item: " + field); + } + } + + // Clear delete flag if necessary values.put(ReadingListItems.IS_DELETED, 0); - values.put(ReadingListItems.URL, uri); - values.put(ReadingListItems.TITLE, title); // Restore deleted record if possible final Uri insertUri = mReadingListUriWithProfile @@ -714,7 +719,7 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { final int updated = cr.update(insertUri, values, ReadingListItems.URL + " = ? ", - new String[] { uri }); + new String[] { values.getAsString(ReadingListItems.URL) }); debug("Updated " + updated + " rows to new modified time."); } diff --git a/mobile/android/base/home/HomeBanner.java b/mobile/android/base/home/HomeBanner.java index 63347df42c9..d4717e4d629 100644 --- a/mobile/android/base/home/HomeBanner.java +++ b/mobile/android/base/home/HomeBanner.java @@ -70,7 +70,7 @@ public class HomeBanner extends LinearLayout public HomeBanner(Context context, AttributeSet attrs) { super(context, attrs); - LayoutInflater.from(context).inflate(R.layout.home_banner, this); + LayoutInflater.from(context).inflate(R.layout.home_banner_content, this); mTextView = (TextView) findViewById(R.id.text); mIconView = (ImageView) findViewById(R.id.icon); diff --git a/mobile/android/base/locales/en-US/android_strings.dtd b/mobile/android/base/locales/en-US/android_strings.dtd index 79da8a1839c..0baf1da318e 100644 --- a/mobile/android/base/locales/en-US/android_strings.dtd +++ b/mobile/android/base/locales/en-US/android_strings.dtd @@ -335,6 +335,10 @@ size. --> as alternate text for accessibility. It is not visible in the UI. --> + + + diff --git a/mobile/android/base/menu/MenuItemActionView.java b/mobile/android/base/menu/MenuItemActionView.java index 504e20e130c..c51703ff64d 100644 --- a/mobile/android/base/menu/MenuItemActionView.java +++ b/mobile/android/base/menu/MenuItemActionView.java @@ -102,6 +102,25 @@ public class MenuItemActionView extends LinearLayout mMenuItem.setShowIcon(show); } + public void setIcon(Drawable icon) { + mMenuItem.setIcon(icon); + mMenuButton.setIcon(icon); + } + + public void setIcon(int icon) { + mMenuItem.setIcon(icon); + mMenuButton.setIcon(icon); + } + + public void setTitle(CharSequence title) { + mMenuItem.setTitle(title); + mMenuButton.setContentDescription(title); + } + + public void setSubMenuIndicator(boolean hasSubMenu) { + mMenuItem.setSubMenuIndicator(hasSubMenu); + } + public void addActionButton(Drawable drawable) { // If this is the first icon, retain the text. // If not, make the menu item an icon. diff --git a/mobile/android/base/menu/MenuItemDefault.java b/mobile/android/base/menu/MenuItemDefault.java index 5eda65f266c..2af896b567e 100644 --- a/mobile/android/base/menu/MenuItemDefault.java +++ b/mobile/android/base/menu/MenuItemDefault.java @@ -142,7 +142,7 @@ public class MenuItemDefault extends TextView } } - private void setSubMenuIndicator(boolean hasSubMenu) { + void setSubMenuIndicator(boolean hasSubMenu) { if (mHasSubMenu != hasSubMenu) { mHasSubMenu = hasSubMenu; refreshDrawableState(); diff --git a/mobile/android/base/prompts/IntentChooserPrompt.java b/mobile/android/base/prompts/IntentChooserPrompt.java index 8ca4681be9a..5b78f4ffc23 100644 --- a/mobile/android/base/prompts/IntentChooserPrompt.java +++ b/mobile/android/base/prompts/IntentChooserPrompt.java @@ -61,7 +61,7 @@ public class IntentChooserPrompt { // If there's only one item in the intent list, just return it if (mItems.size() == 1) { - handler.onIntentSelected(mItems.get(0).intent, 0); + handler.onIntentSelected(mItems.get(0).getIntent(), 0); return; } @@ -82,7 +82,7 @@ public class IntentChooserPrompt { if (itemId == -1) { handler.onCancelled(); } else { - handler.onIntentSelected(mItems.get(itemId).intent, itemId); + handler.onIntentSelected(mItems.get(itemId).getIntent(), itemId); } } }); @@ -128,12 +128,14 @@ public class IntentChooserPrompt { private PromptListItem getItemForResolveInfo(ResolveInfo info, PackageManager pm, Intent intent) { PromptListItem item = new PromptListItem(info.loadLabel(pm).toString()); - item.icon = info.loadIcon(pm); - item.intent = new Intent(intent); + item.setIcon(info.loadIcon(pm)); + Intent i = new Intent(intent); // These intents should be implicit. - item.intent.setComponent(new ComponentName(info.activityInfo.applicationInfo.packageName, - info.activityInfo.name)); + i.setComponent(new ComponentName(info.activityInfo.applicationInfo.packageName, + info.activityInfo.name)); + item.setIntent(new Intent(i)); + return item; } diff --git a/mobile/android/base/prompts/PromptListAdapter.java b/mobile/android/base/prompts/PromptListAdapter.java index 43e64175b31..8ee36d9c301 100644 --- a/mobile/android/base/prompts/PromptListAdapter.java +++ b/mobile/android/base/prompts/PromptListAdapter.java @@ -1,12 +1,17 @@ package org.mozilla.gecko.prompts; +import org.mozilla.gecko.menu.MenuItemActionView; import org.mozilla.gecko.R; +import org.mozilla.gecko.GeckoAppShell; +import org.mozilla.gecko.gfx.BitmapUtils; +import org.mozilla.gecko.widget.GeckoActionProvider; import org.json.JSONArray; import org.json.JSONObject; import org.json.JSONException; import android.content.Context; +import android.content.Intent; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.graphics.Bitmap; @@ -14,18 +19,20 @@ import android.graphics.drawable.BitmapDrawable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.AdapterView; import android.widget.CheckedTextView; import android.widget.TextView; import android.widget.ListView; +import android.widget.ArrayAdapter; +import android.widget.AdapterView; +import android.util.TypedValue; import java.util.ArrayList; public class PromptListAdapter extends ArrayAdapter { private static final int VIEW_TYPE_ITEM = 0; private static final int VIEW_TYPE_GROUP = 1; - private static final int VIEW_TYPE_COUNT = 2; + private static final int VIEW_TYPE_ACTIONS = 2; + private static final int VIEW_TYPE_COUNT = 3; private static final String LOGTAG = "GeckoPromptListAdapter"; @@ -38,6 +45,7 @@ public class PromptListAdapter extends ArrayAdapter { private static int mIconSize; private static int mMinRowSize; private static int mIconTextPadding; + private static float mTextSize; private static boolean mInitialized = false; PromptListAdapter(Context context, int textViewResourceId, PromptListItem[] objects) { @@ -55,6 +63,8 @@ public class PromptListAdapter extends ArrayAdapter { mIconTextPadding = (int) (res.getDimension(R.dimen.prompt_service_icon_text_padding)); mIconSize = (int) (res.getDimension(R.dimen.prompt_service_icon_size)); mMinRowSize = (int) (res.getDimension(R.dimen.prompt_service_min_list_item_height)); + mTextSize = res.getDimension(R.dimen.menu_item_textsize); + mInitialized = true; } } @@ -64,6 +74,8 @@ public class PromptListAdapter extends ArrayAdapter { PromptListItem item = getItem(position); if (item.isGroup) { return VIEW_TYPE_GROUP; + } else if (item.showAsActions) { + return VIEW_TYPE_ACTIONS; } else { return VIEW_TYPE_ITEM; } @@ -90,11 +102,11 @@ public class PromptListAdapter extends ArrayAdapter { public void toggleSelected(int position) { PromptListItem item = getItem(position); - item.selected = !item.selected; + item.setSelected(!item.getSelected()); } private void maybeUpdateIcon(PromptListItem item, TextView t) { - if (item.icon == null && !item.inGroup && !item.isParent) { + if (item.getIcon() == null && !item.inGroup && !item.isParent) { t.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null); return; } @@ -103,10 +115,10 @@ public class PromptListAdapter extends ArrayAdapter { Resources res = getContext().getResources(); // Set the padding between the icon and the text. t.setCompoundDrawablePadding(mIconTextPadding); - if (item.icon != null) { + if (item.getIcon() != null) { // We want the icon to be of a specific size. Some do not // follow this rule so we have to resize them. - Bitmap bitmap = ((BitmapDrawable) item.icon).getBitmap(); + Bitmap bitmap = ((BitmapDrawable) item.getIcon()).getBitmap(); d = new BitmapDrawable(res, Bitmap.createScaledBitmap(bitmap, mIconSize, mIconSize, true)); } else if (item.inGroup) { // We don't currently support "indenting" items with icons @@ -130,12 +142,12 @@ public class PromptListAdapter extends ArrayAdapter { // Apparently just using ct.setChecked(true) doesn't work, so this // is stolen from the android source code as a way to set the checked // state of these items - list.setItemChecked(position, item.selected); + list.setItemChecked(position, item.getSelected()); } } boolean isSelected(int position){ - return getItem(position).selected; + return getItem(position).getSelected(); } ArrayList getSelected() { @@ -161,6 +173,41 @@ public class PromptListAdapter extends ArrayAdapter { return -1; } + private View getActionView(PromptListItem item) { + GeckoActionProvider provider = GeckoActionProvider.getForType(item.getIntent().getType(), getContext()); + provider.setIntent(item.getIntent()); + return provider.onCreateActionView(); + } + + private void updateActionView(final PromptListItem item, final MenuItemActionView view, final ListView list, final int position) { + view.setTitle(item.label); + view.setIcon(item.getIcon()); + view.setSubMenuIndicator(item.isParent); + view.setMenuItemClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + ListView.OnItemClickListener listener = list.getOnItemClickListener(); + if (listener != null) { + listener.onItemClick(list, view, position, position); + } + + final GeckoActionProvider provider = GeckoActionProvider.getForType(item.getIntent().getType(), getContext()); + IntentChooserPrompt prompt = new IntentChooserPrompt(getContext(), provider); + prompt.show(item.label, getContext(), new IntentHandler() { + @Override + public void onIntentSelected(final Intent intent, final int p) { + provider.chooseActivity(p); + } + + @Override + public void onCancelled() { + // do nothing + } + }); + } + }); + } + @Override public View getView(int position, View convertView, ViewGroup parent) { PromptListItem item = getItem(position); @@ -168,26 +215,36 @@ public class PromptListAdapter extends ArrayAdapter { ViewHolder viewHolder = null; if (convertView == null) { - int resourceId = mResourceId; - if (item.isGroup) { - resourceId = R.layout.list_item_header; + if (type == VIEW_TYPE_ACTIONS) { + convertView = getActionView(item); + } else { + int resourceId = mResourceId; + if (item.isGroup) { + resourceId = R.layout.list_item_header; + } + + LayoutInflater mInflater = LayoutInflater.from(getContext()); + convertView = mInflater.inflate(resourceId, null); + convertView.setMinimumHeight(mMinRowSize); + + TextView tv = (TextView) convertView.findViewById(android.R.id.text1); + tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize); + viewHolder = new ViewHolder(tv, tv.getPaddingLeft(), tv.getPaddingRight(), + tv.getPaddingTop(), tv.getPaddingBottom()); + + convertView.setTag(viewHolder); } - LayoutInflater mInflater = LayoutInflater.from(getContext()); - convertView = mInflater.inflate(resourceId, null); - convertView.setMinimumHeight(mMinRowSize); - - TextView tv = (TextView) convertView.findViewById(android.R.id.text1); - viewHolder = new ViewHolder(tv, tv.getPaddingLeft(), tv.getPaddingRight(), - tv.getPaddingTop(), tv.getPaddingBottom()); - - convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } - viewHolder.textView.setText(item.label); - maybeUpdateCheckedState((ListView) parent, position, item, viewHolder); - maybeUpdateIcon(item, viewHolder.textView); + if (type == VIEW_TYPE_ACTIONS) { + updateActionView(item, (MenuItemActionView) convertView, (ListView) parent, position); + } else { + viewHolder.textView.setText(item.label); + maybeUpdateCheckedState((ListView) parent, position, item, viewHolder); + maybeUpdateIcon(item, viewHolder.textView); + } return convertView; } diff --git a/mobile/android/base/prompts/PromptListItem.java b/mobile/android/base/prompts/PromptListItem.java index 31d40599ce1..0ce8bb8df94 100644 --- a/mobile/android/base/prompts/PromptListItem.java +++ b/mobile/android/base/prompts/PromptListItem.java @@ -1,11 +1,15 @@ package org.mozilla.gecko.prompts; +import org.mozilla.gecko.gfx.BitmapUtils; +import org.mozilla.gecko.GeckoAppShell; + import org.json.JSONArray; import org.json.JSONObject; import org.json.JSONException; import android.content.Intent; import android.graphics.drawable.Drawable; +import android.util.Log; import java.util.List; import java.util.ArrayList; @@ -18,28 +22,74 @@ public class PromptListItem { public final boolean inGroup; public final boolean disabled; public final int id; - public boolean selected; - public Intent intent; + public final boolean showAsActions; + public final boolean isParent; - public boolean isParent; - public Drawable icon; + public Intent mIntent; + public boolean mSelected; + public Drawable mIcon; PromptListItem(JSONObject aObject) { - label = aObject.optString("label"); + label = aObject.isNull("label") ? "" : aObject.optString("label"); isGroup = aObject.optBoolean("isGroup"); inGroup = aObject.optBoolean("inGroup"); disabled = aObject.optBoolean("disabled"); id = aObject.optInt("id"); - isParent = aObject.optBoolean("isParent"); - selected = aObject.optBoolean("selected"); + mSelected = aObject.optBoolean("selected"); + + JSONObject obj = aObject.optJSONObject("shareData"); + if (obj != null) { + showAsActions = true; + String uri = obj.isNull("uri") ? "" : obj.optString("uri"); + String type = obj.isNull("type") ? "" : obj.optString("type"); + mIntent = GeckoAppShell.getShareIntent(GeckoAppShell.getContext(), uri, type, ""); + isParent = true; + } else { + mIntent = null; + showAsActions = false; + isParent = aObject.optBoolean("isParent"); + } + + BitmapUtils.getDrawable(GeckoAppShell.getContext(), aObject.optString("icon"), new BitmapUtils.BitmapLoader() { + @Override + public void onBitmapFound(Drawable d) { + mIcon = d; + } + }); + } + + public void setIntent(Intent i) { + mIntent = i; + } + + public Intent getIntent() { + return mIntent; + } + + public void setIcon(Drawable icon) { + mIcon = icon; + } + + public Drawable getIcon() { + return mIcon; + } + + public void setSelected(boolean selected) { + mSelected = selected; + } + + public boolean getSelected() { + return mSelected; } public PromptListItem(String aLabel) { label = aLabel; isGroup = false; inGroup = false; + isParent = false; disabled = false; id = 0; + showAsActions = false; } static PromptListItem[] getArray(JSONArray items) { diff --git a/mobile/android/base/resources/layout/gecko_app.xml b/mobile/android/base/resources/layout/gecko_app.xml index d89fab474c0..e80c4290035 100644 --- a/mobile/android/base/resources/layout/gecko_app.xml +++ b/mobile/android/base/resources/layout/gecko_app.xml @@ -36,17 +36,12 @@ android:layout_width="fill_parent" android:layout_height="fill_parent"/> - + diff --git a/mobile/android/base/resources/layout/home_banner.xml b/mobile/android/base/resources/layout/home_banner.xml index cc2e34b1cd9..8280a727f38 100644 --- a/mobile/android/base/resources/layout/home_banner.xml +++ b/mobile/android/base/resources/layout/home_banner.xml @@ -3,34 +3,13 @@ - 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/. --> - - - - - - - - - + diff --git a/mobile/android/base/resources/layout/home_banner_content.xml b/mobile/android/base/resources/layout/home_banner_content.xml new file mode 100644 index 00000000000..cc2e34b1cd9 --- /dev/null +++ b/mobile/android/base/resources/layout/home_banner_content.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + diff --git a/mobile/android/base/resources/values/dimens.xml b/mobile/android/base/resources/values/dimens.xml index 35d7c856ab8..ebd09196f3e 100644 --- a/mobile/android/base/resources/values/dimens.xml +++ b/mobile/android/base/resources/values/dimens.xml @@ -46,10 +46,12 @@ 6dp 21dp + 16sp 18dp 44dp 240dp 5dp + 16sp 8dp 5dip 40dip @@ -57,7 +59,7 @@ 256dp 0.75dp 32dp - 72dp + 36dp 10dp 16dp 10dp diff --git a/mobile/android/base/resources/values/styles.xml b/mobile/android/base/resources/values/styles.xml index 18d5b86d286..0ad1506e8eb 100644 --- a/mobile/android/base/resources/values/styles.xml +++ b/mobile/android/base/resources/values/styles.xml @@ -107,6 +107,7 @@ @style/TextAppearance true middle + @dimen/menu_item_textsize diff --git a/mobile/android/base/strings.xml.in b/mobile/android/base/strings.xml.in index f2d859c0461..6a36a3ac3fc 100644 --- a/mobile/android/base/strings.xml.in +++ b/mobile/android/base/strings.xml.in @@ -304,6 +304,7 @@ &home_reading_list_empty; &home_reading_list_hint2; &home_reading_list_hint_accessible; + &home_default_empty; &pin_site_dialog_hint; &filepicker_title; diff --git a/mobile/android/base/widget/GeckoActionProvider.java b/mobile/android/base/widget/GeckoActionProvider.java index 85245d96cf0..f4a124d2736 100644 --- a/mobile/android/base/widget/GeckoActionProvider.java +++ b/mobile/android/base/widget/GeckoActionProvider.java @@ -20,6 +20,7 @@ import android.view.View; import android.view.View.OnClickListener; import java.util.ArrayList; +import java.util.HashMap; public class GeckoActionProvider extends ActionProvider { private static int MAX_HISTORY_SIZE = 2; @@ -44,6 +45,20 @@ public class GeckoActionProvider extends ActionProvider { private final Callbacks mCallbacks = new Callbacks(); + private static HashMap mProviders = new HashMap(); + + // Gets the action provider for a particular mimetype + public static GeckoActionProvider getForType(String type, Context context) { + if (!mProviders.keySet().contains(type)) { + GeckoActionProvider provider = new GeckoActionProvider(context); + + String subType = type.substring(0, type.indexOf("/")); + provider.setHistoryFileName("history-" + subType + ".xml"); + mProviders.put(type, provider); + } + return mProviders.get(type); + } + public GeckoActionProvider(Context context) { super(context); mContext = context; @@ -141,6 +156,10 @@ public class GeckoActionProvider extends ActionProvider { return infos; } + public void chooseActivity(int position) { + mCallbacks.chooseActivity(position); + } + /** * Listener for handling default activity / menu item clicks. */ @@ -168,7 +187,6 @@ public class GeckoActionProvider extends ActionProvider { @Override public void onClick(View view) { Integer index = (Integer) view.getTag(); - ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mHistoryFileName); chooseActivity(index); } } diff --git a/mobile/android/chrome/content/Readability.js b/mobile/android/chrome/content/Readability.js index 7b147820453..5efaa0c2b40 100644 --- a/mobile/android/chrome/content/Readability.js +++ b/mobile/android/chrome/content/Readability.js @@ -718,6 +718,77 @@ Readability.prototype = { } }, + /** + * Attempts to get the excerpt from these + * sources in the following order: + * - meta description tag + * - open-graph description + * - twitter cards description + * - article's first paragraph + * If no excerpt is found, an empty string will be + * returned. + * + * @param Element - root element of the processed version page + * @return String - excerpt of the article + **/ + _getExcerpt: function(articleContent) { + let values = {}; + let metaElements = this._doc.getElementsByTagName("meta"); + + // Match "description", or Twitter's "twitter:description" (Cards) + // in name attribute. + let namePattern = /^\s*((twitter)\s*:\s*)?description\s*$/gi; + + // Match Facebook's og:description (Open Graph) in property attribute. + let propertyPattern = /^\s*og\s*:\s*description\s*$/gi; + + // Find description tags. + for (let i = 0; i < metaElements.length; i++) { + let element = metaElements[i]; + let elementName = element.getAttribute("name"); + let elementProperty = element.getAttribute("property"); + + let name; + if (namePattern.test(elementName)) { + name = elementName; + } else if (propertyPattern.test(elementProperty)) { + name = elementProperty; + } + + if (name) { + let content = element.getAttribute("content"); + if (content) { + // Convert to lowercase and remove any whitespace + // so we can match below. + name = name.toLowerCase().replace(/\s/g, ''); + values[name] = content.trim(); + } + } + } + + if ("description" in values) { + return values["description"]; + } + + if ("og:description" in values) { + // Use facebook open graph description. + return values["og:description"]; + } + + if ("twitter:description" in values) { + // Use twitter cards description. + return values["twitter:description"]; + } + + // No description meta tags, use the article's first paragraph. + let paragraphs = articleContent.getElementsByTagName("p"); + if (paragraphs.length > 0) { + return paragraphs[0].textContent; + } + + return ""; + }, + /** * Removes script tags from the document. * @@ -1434,9 +1505,13 @@ Readability.prototype = { // }).bind(this), 500); // } + let excerpt = this._getExcerpt(articleContent); + return { title: articleTitle, byline: this._articleByline, dir: this._articleDir, - content: articleContent.innerHTML }; + content: articleContent.innerHTML, + length: articleContent.textContent.length, + excerpt: excerpt }; } }; diff --git a/mobile/android/chrome/content/aboutReader.js b/mobile/android/chrome/content/aboutReader.js index 094ff43ee73..4c44bc8f57b 100644 --- a/mobile/android/chrome/content/aboutReader.js +++ b/mobile/android/chrome/content/aboutReader.js @@ -349,6 +349,8 @@ AboutReader.prototype = { result: result, title: this._article.title, url: this._article.url, + length: this._article.length, + excerpt: this._article.excerpt }); }.bind(this)); } else { diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index c432b33ef65..646fb0fabad 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -7433,33 +7433,36 @@ let Reader = { throw new Error("Reader:Add requires a tabID or an URL as argument"); } - let sendResult = function(result, title) { - this.log("Reader:Add success=" + result + ", url=" + url + ", title=" + title); + let sendResult = function(result, article) { + article = article || {}; + this.log("Reader:Add success=" + result + ", url=" + url + ", title=" + article.title + ", excerpt=" + article.excerpt); sendMessageToJava({ type: "Reader:Added", result: result, - title: title, + title: article.title, url: url, + length: article.length, + excerpt: article.excerpt }); }.bind(this); let handleArticle = function(article) { if (!article) { - sendResult(this.READER_ADD_FAILED, ""); + sendResult(this.READER_ADD_FAILED, null); return; } this.storeArticleInCache(article, function(success) { let result = (success ? this.READER_ADD_SUCCESS : this.READER_ADD_FAILED); - sendResult(result, article.title); + sendResult(result, article); }.bind(this)); }.bind(this); this.getArticleFromCache(urlWithoutRef, function (article) { // If the article is already in reading list, bail if (article) { - sendResult(this.READER_ADD_DUPLICATE, ""); + sendResult(this.READER_ADD_DUPLICATE, null); return; } @@ -7473,13 +7476,14 @@ let Reader = { } case "Reader:Remove": { - this.removeArticleFromCache(aData, function(success) { - this.log("Reader:Remove success=" + success + ", url=" + aData); + let url = aData; + this.removeArticleFromCache(url, function(success) { + this.log("Reader:Remove success=" + success + ", url=" + url); if (success) { sendMessageToJava({ type: "Reader:Removed", - url: aData + url: url }); } }.bind(this)); diff --git a/mobile/android/installer/package-manifest.in b/mobile/android/installer/package-manifest.in index 84f3cdeabed..9becbb6b0f5 100644 --- a/mobile/android/installer/package-manifest.in +++ b/mobile/android/installer/package-manifest.in @@ -246,6 +246,7 @@ @BINPATH@/components/storage.xpt @BINPATH@/components/telemetry.xpt @BINPATH@/components/toolkit_finalizationwitness.xpt +@BINPATH@/components/toolkit_osfile.xpt @BINPATH@/components/toolkitprofile.xpt #ifdef MOZ_ENABLE_XREMOTE @BINPATH@/components/toolkitremote.xpt diff --git a/mobile/android/locales/en-US/chrome/browser.properties b/mobile/android/locales/en-US/chrome/browser.properties index 91486b24508..1d9f5e15b61 100644 --- a/mobile/android/locales/en-US/chrome/browser.properties +++ b/mobile/android/locales/en-US/chrome/browser.properties @@ -320,3 +320,10 @@ openInApp.pageAction = Open in App openInApp.ok = OK openInApp.cancel = Cancel +#Tabs in context menus +browser.menu.context.default = Link +browser.menu.context.img = Image +browser.menu.context.video = Video +browser.menu.context.audio = Audio +browser.menu.context.tel = Phone +browser.menu.context.mailto = Mail diff --git a/mozglue/build/Nuwa.cpp b/mozglue/build/Nuwa.cpp index aa0971ba213..06ece36f1d0 100644 --- a/mozglue/build/Nuwa.cpp +++ b/mozglue/build/Nuwa.cpp @@ -142,8 +142,11 @@ TLSInfoList; * methods or do large allocations on the stack to avoid stack overflow. */ #ifndef NUWA_STACK_SIZE -#define PAGE_SIZE 4096 -#define PAGE_ALIGN_MASK 0xfffff000 +#ifndef PAGE_SIZE +#warning "Hard-coding page size to 4096 byte +#define PAGE_SIZE 4096ul +#endif +#define PAGE_ALIGN_MASK (~(PAGE_SIZE-1)) #define NUWA_STACK_SIZE (1024 * 128) #endif @@ -659,8 +662,6 @@ SaveTLSInfo(thread_info_t *tinfo) { */ static void RestoreTLSInfo(thread_info_t *tinfo) { - int rv; - for (TLSInfoList::const_iterator it = tinfo->tlsInfo.begin(); it != tinfo->tlsInfo.end(); it++) { diff --git a/mozglue/build/cpuacct.c b/mozglue/build/cpuacct.c index bd3badea60d..ce7d98edac4 100644 --- a/mozglue/build/cpuacct.c +++ b/mozglue/build/cpuacct.c @@ -29,6 +29,7 @@ #include #include #include +#include #include "cpuacct.h" int cpuacct_add(uid_t uid) diff --git a/netwerk/sctp/datachannel/DataChannel.cpp b/netwerk/sctp/datachannel/DataChannel.cpp index 43d4023a03d..0ddd907e00b 100644 --- a/netwerk/sctp/datachannel/DataChannel.cpp +++ b/netwerk/sctp/datachannel/DataChannel.cpp @@ -393,6 +393,10 @@ DataChannelConnection::Init(unsigned short aPort, uint16_t aNumStreams, bool aUs (const void *)&on, (socklen_t)sizeof(on)) < 0) { LOG(("Couldn't set SCTP_REUSE_PORT on SCTP socket")); } + if (usrsctp_setsockopt(mMasterSocket, IPPROTO_SCTP, SCTP_NODELAY, + (const void *)&on, (socklen_t)sizeof(on)) < 0) { + LOG(("Couldn't set SCTP_NODELAY on SCTP socket")); + } } if (!aUsingDtls) { diff --git a/security/insanity/include/insanity/bind.h b/security/insanity/include/insanity/bind.h index f000d1965ba..df2cd67922b 100644 --- a/security/insanity/include/insanity/bind.h +++ b/security/insanity/include/insanity/bind.h @@ -41,7 +41,8 @@ using std::placeholders::_1; #else -class Placeholder1 { } _1; +class Placeholder1 { }; +extern Placeholder1 _1; template V& ref(V& v) { return v; } template const V& cref(const V& v) { return v; } diff --git a/security/insanity/lib/pkixbind.cpp b/security/insanity/lib/pkixbind.cpp new file mode 100644 index 00000000000..39f094d7f41 --- /dev/null +++ b/security/insanity/lib/pkixbind.cpp @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* Copyright 2013 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _MSC_VER + +#include "insanity/bind.h" + +namespace insanity { + +Placeholder1 _1; + +} // namespace insanity + +#endif // _MSC_VER diff --git a/security/insanity/moz.build b/security/insanity/moz.build index e24b6eb409d..07f3fd38c05 100644 --- a/security/insanity/moz.build +++ b/security/insanity/moz.build @@ -5,6 +5,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. UNIFIED_SOURCES += [ + 'lib/pkixbind.cpp', 'lib/pkixbuild.cpp', 'lib/pkixcheck.cpp', 'lib/pkixder.cpp', diff --git a/services/fxaccounts/FxAccounts.jsm b/services/fxaccounts/FxAccounts.jsm index b09c3099c4c..45b40f20eb1 100644 --- a/services/fxaccounts/FxAccounts.jsm +++ b/services/fxaccounts/FxAccounts.jsm @@ -398,7 +398,7 @@ FxAccountsInternal.prototype = { getAssertion: function getAssertion(audience) { log.debug("enter getAssertion()"); let currentState = this.currentAccountState; - let mustBeValidUntil = this.now() + ASSERTION_LIFETIME; + let mustBeValidUntil = this.now() + ASSERTION_USE_PERIOD; return currentState.getUserAccountData().then(data => { if (!data) { // No signed-in user @@ -540,6 +540,7 @@ FxAccountsInternal.prototype = { let payload = {}; let d = Promise.defer(); let options = { + duration: ASSERTION_LIFETIME, localtimeOffsetMsec: this.localtimeOffsetMsec, now: this.now() }; diff --git a/services/fxaccounts/FxAccountsCommon.js b/services/fxaccounts/FxAccountsCommon.js index ffcf7b3fdbe..74ba51a7a98 100644 --- a/services/fxaccounts/FxAccountsCommon.js +++ b/services/fxaccounts/FxAccountsCommon.js @@ -33,7 +33,14 @@ this.DATA_FORMAT_VERSION = 1; this.DEFAULT_STORAGE_FILENAME = "signedInUser.json"; // Token life times. -this.ASSERTION_LIFETIME = 1000 * 60 * 5; // 5 minutes +// Having this parameter be short has limited security value and can cause +// spurious authentication values if the client's clock is skewed and +// we fail to adjust. See Bug 983256. +this.ASSERTION_LIFETIME = 1000 * 3600 * 24 * 365 * 25; // 25 years +// This is a time period we want to guarantee that the assertion will be +// valid after we generate it (e.g., the signed cert won't expire in this +// period). +this.ASSERTION_USE_PERIOD = 1000 * 60 * 5; // 5 minutes this.CERT_LIFETIME = 1000 * 3600 * 6; // 6 hours this.KEY_LIFETIME = 1000 * 3600 * 12; // 12 hours diff --git a/services/fxaccounts/FxAccountsManager.jsm b/services/fxaccounts/FxAccountsManager.jsm index 81cca5f8b5c..a293b412e11 100644 --- a/services/fxaccounts/FxAccountsManager.jsm +++ b/services/fxaccounts/FxAccountsManager.jsm @@ -356,8 +356,18 @@ this.FxAccountsManager = { ); }, + /* + * Try to get an assertion for the given audience. + * + * aOptions can include: + * + * refreshAuthentication - (bool) Force re-auth. + * + * silent - (bool) Prevent any UI interaction. + * I.e., try to get an automatic assertion. + * + */ getAssertion: function(aAudience, aOptions) { - log.debug("getAssertion " + aAudience + JSON.stringify(aOptions)); if (!aAudience) { return this._error(ERROR_INVALID_AUDIENCE); } @@ -390,6 +400,9 @@ this.FxAccountsManager = { // will return the assertion. Otherwise, we will return an error. return this._signOut().then( () => { + if (aOptions.silent) { + return Promise.resolve(null); + } return this._uiRequest(UI_REQUEST_REFRESH_AUTH, aAudience, user.accountId); } @@ -401,6 +414,11 @@ this.FxAccountsManager = { } log.debug("No signed in user"); + + if (aOptions.silent) { + return Promise.resolve(null); + } + // If there is no currently signed in user, we trigger the signIn UI // flow. return this._uiRequest(UI_REQUEST_SIGN_IN_FLOW, aAudience); diff --git a/services/fxaccounts/tests/xpcshell/test_accounts.js b/services/fxaccounts/tests/xpcshell/test_accounts.js index ea49b1ed470..ff3507abf6b 100644 --- a/services/fxaccounts/tests/xpcshell/test_accounts.js +++ b/services/fxaccounts/tests/xpcshell/test_accounts.js @@ -418,7 +418,7 @@ add_task(function test_getAssertion() { _("delta: " + Date.parse(payload.exp - start) + "\n"); let exp = Number(payload.exp); - do_check_eq(exp, now + TWO_MINUTES_MS); + do_check_eq(exp, now + ASSERTION_LIFETIME); // Reset for next call. fxa.internal._d_signCertificate = Promise.defer(); @@ -430,7 +430,7 @@ add_task(function test_getAssertion() { // There were no additional calls - same number of getcert calls as before do_check_eq(fxa.internal._getCertificateSigned_calls.length, 1); - // Wait an hour; assertion expires, but not the certificate + // Wait an hour; assertion use period expires, but not the certificate now += ONE_HOUR_MS; fxa.internal._now_is = now; @@ -456,7 +456,7 @@ add_task(function test_getAssertion() { do_check_eq(keyPair.validUntil, start + KEY_LIFETIME); do_check_eq(cert.validUntil, start + CERT_LIFETIME); exp = Number(payload.exp); - do_check_eq(exp, now + TWO_MINUTES_MS); + do_check_eq(exp, now + ASSERTION_LIFETIME); // Now we wait even longer, and expect both assertion and cert to expire. So // we will have to get a new keypair and cert. @@ -479,7 +479,7 @@ add_task(function test_getAssertion() { do_check_eq(cert.validUntil, now + CERT_LIFETIME); exp = Number(payload.exp); - do_check_eq(exp, now + TWO_MINUTES_MS); + do_check_eq(exp, now + ASSERTION_LIFETIME); _("----- DONE ----\n"); }); diff --git a/services/sync/modules/service.js b/services/sync/modules/service.js index d01e9ddf48f..65c501d5bd4 100644 --- a/services/sync/modules/service.js +++ b/services/sync/modules/service.js @@ -970,7 +970,6 @@ Sync11Service.prototype = { && (username || password || passphrase)) { Svc.Obs.notify("weave:service:setup-complete"); } - this._log.info("Logging in user " + this.identity.username); this._updateCachedURLs(); diff --git a/services/sync/tests/tps/mozmill_sanity.js b/services/sync/tests/tps/mozmill_sanity.js index 99addd1b22e..922a015ef5d 100644 --- a/services/sync/tests/tps/mozmill_sanity.js +++ b/services/sync/tests/tps/mozmill_sanity.js @@ -2,7 +2,7 @@ * 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/. */ -Components.utils.import('resource://tps/sync.jsm'); +Components.utils.import('resource://tps/mozmill/sync.jsm'); var setupModule = function(module) { controller = mozmill.getBrowserController(); diff --git a/services/sync/tps/extensions/tps/chrome.manifest b/services/sync/tps/extensions/tps/chrome.manifest index 0731ba34aab..4baf55677a7 100644 --- a/services/sync/tps/extensions/tps/chrome.manifest +++ b/services/sync/tps/extensions/tps/chrome.manifest @@ -1,4 +1,5 @@ -resource tps modules/ +resource tps resource/ + component {4e5bd3f0-41d3-11df-9879-0800200c9a66} components/tps-cmdline.js contract @mozilla.org/commandlinehandler/general-startup;1?type=tps {4e5bd3f0-41d3-11df-9879-0800200c9a66} category command-line-handler m-tps @mozilla.org/commandlinehandler/general-startup;1?type=tps diff --git a/services/sync/tps/extensions/tps/components/tps-cmdline.js b/services/sync/tps/extensions/tps/components/tps-cmdline.js index 66622d6e164..43326a0a80f 100644 --- a/services/sync/tps/extensions/tps/components/tps-cmdline.js +++ b/services/sync/tps/extensions/tps/components/tps-cmdline.js @@ -22,8 +22,8 @@ const nsIWindowWatcher = Components.interfaces.nsIWindowWatcher; Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); function TPSCmdLineHandler() {} -TPSCmdLineHandler.prototype = -{ + +TPSCmdLineHandler.prototype = { classDescription: "TPSCmdLineHandler", classID : TPS_CMDLINE_CLSID, contractID : TPS_CMDLINE_CONTRACTID, @@ -49,7 +49,7 @@ TPSCmdLineHandler.prototype = return; let phase = cmdLine.handleFlagWithParam("tpsphase", false); if (phase == null) - throw("must specify --tpsphase with --tps"); + throw Error("must specify --tpsphase with --tps"); let logfile = cmdLine.handleFlagWithParam("tpslogfile", false); if (logfile == null) logfile = ""; @@ -79,10 +79,8 @@ TPSCmdLineHandler.prototype = }; -var TPSCmdLineFactory = -{ - createInstance : function(outer, iid) - { +var TPSCmdLineFactory = { + createInstance : function(outer, iid) { if (outer != null) { throw Components.results.NS_ERROR_NO_AGGREGATION; } @@ -92,10 +90,8 @@ var TPSCmdLineFactory = }; -var TPSCmdLineModule = -{ - registerSelf : function(compMgr, fileSpec, location, type) - { +var TPSCmdLineModule = { + registerSelf : function(compMgr, fileSpec, location, type) { compMgr = compMgr.QueryInterface(nsIComponentRegistrar); compMgr.registerFactoryLocation(TPS_CMDLINE_CLSID, @@ -114,8 +110,7 @@ var TPSCmdLineModule = TPS_CMDLINE_CONTRACTID, true, true); }, - unregisterSelf : function(compMgr, fileSpec, location) - { + unregisterSelf : function(compMgr, fileSpec, location) { compMgr = compMgr.QueryInterface(nsIComponentRegistrar); compMgr.unregisterFactoryLocation(TPS_CMDLINE_CLSID, fileSpec); @@ -126,8 +121,7 @@ var TPSCmdLineModule = "m-tps", true); }, - getClassObject : function(compMgr, cid, iid) - { + getClassObject : function(compMgr, cid, iid) { if (cid.equals(TPS_CMDLINE_CLSID)) { return TPSCmdLineFactory; } @@ -139,8 +133,7 @@ var TPSCmdLineModule = throw Components.results.NS_ERROR_NO_INTERFACE; }, - canUnload : function(compMgr) - { + canUnload : function(compMgr) { return true; } }; diff --git a/services/sync/tps/extensions/tps/install.rdf b/services/sync/tps/extensions/tps/install.rdf index 47cd8a58a88..3dcdc5e4452 100644 --- a/services/sync/tps/extensions/tps/install.rdf +++ b/services/sync/tps/extensions/tps/install.rdf @@ -7,14 +7,14 @@ xmlns:em="http://www.mozilla.org/2004/em-rdf#"> tps@mozilla.org - 0.2 + 0.5 {ec8030f7-c20a-464f-9b0e-13a3a9e97384} - 3.5.0 - 12.0.* + 24.0.* + 31.0.* @@ -22,6 +22,7 @@ TPS Sync test extension Jonathan Griffin - http://www.mozilla.org/ + Henrik Skupin + https://developer.mozilla.org/en-US/docs/TPS diff --git a/services/sync/tps/extensions/tps/modules/fxaccounts.jsm b/services/sync/tps/extensions/tps/resource/fxaccounts.jsm similarity index 95% rename from services/sync/tps/extensions/tps/modules/fxaccounts.jsm rename to services/sync/tps/extensions/tps/resource/fxaccounts.jsm index f4639f291e3..5330947000f 100644 --- a/services/sync/tps/extensions/tps/modules/fxaccounts.jsm +++ b/services/sync/tps/extensions/tps/resource/fxaccounts.jsm @@ -8,7 +8,7 @@ this.EXPORTED_SYMBOLS = [ "FxAccountsHelper", ]; -const { utils: Cu } = Components; +const {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu.import("resource://gre/modules/FxAccountsClient.jsm"); Cu.import("resource://services-common/async.js"); diff --git a/services/sync/tps/extensions/tps/modules/logger.jsm b/services/sync/tps/extensions/tps/resource/logger.jsm similarity index 85% rename from services/sync/tps/extensions/tps/modules/logger.jsm rename to services/sync/tps/extensions/tps/resource/logger.jsm index 8b46f2033d9..c799753c5ca 100644 --- a/services/sync/tps/extensions/tps/modules/logger.jsm +++ b/services/sync/tps/extensions/tps/resource/logger.jsm @@ -9,12 +9,9 @@ var EXPORTED_SYMBOLS = ["Logger"]; -const CC = Components.classes; -const CI = Components.interfaces; -const CU = Components.utils; +const {classes: Cc, interfaces: Ci, utils: Cu} = Components; -var Logger = -{ +var Logger = { _foStream: null, _converter: null, _potentialError: null, @@ -25,8 +22,8 @@ var Logger = return; } - let prefs = CC["@mozilla.org/preferences-service;1"] - .getService(CI.nsIPrefBranch); + let prefs = Cc["@mozilla.org/preferences-service;1"] + .getService(Ci.nsIPrefBranch); if (path) { prefs.setCharPref("tps.logfile", path); } @@ -34,26 +31,26 @@ var Logger = path = prefs.getCharPref("tps.logfile"); } - this._file = CC["@mozilla.org/file/local;1"] - .createInstance(CI.nsILocalFile); + this._file = Cc["@mozilla.org/file/local;1"] + .createInstance(Ci.nsILocalFile); this._file.initWithPath(path); var exists = this._file.exists(); // Make a file output stream and converter to handle it. - this._foStream = CC["@mozilla.org/network/file-output-stream;1"] - .createInstance(CI.nsIFileOutputStream); + this._foStream = Cc["@mozilla.org/network/file-output-stream;1"] + .createInstance(Ci.nsIFileOutputStream); // If the file already exists, append it, otherwise create it. var fileflags = exists ? 0x02 | 0x08 | 0x10 : 0x02 | 0x08 | 0x20; this._foStream.init(this._file, fileflags, 0666, 0); - this._converter = CC["@mozilla.org/intl/converter-output-stream;1"] - .createInstance(CI.nsIConverterOutputStream); + this._converter = Cc["@mozilla.org/intl/converter-output-stream;1"] + .createInstance(Ci.nsIConverterOutputStream); this._converter.init(this._foStream, "UTF-8", 0, 0); }, write: function (data) { if (this._converter == null) { - CU.reportError( + Cu.reportError( "TPS Logger.write called with _converter == null!"); return; } diff --git a/services/sync/tps/extensions/tps/modules/addons.jsm b/services/sync/tps/extensions/tps/resource/modules/addons.jsm similarity index 97% rename from services/sync/tps/extensions/tps/modules/addons.jsm rename to services/sync/tps/extensions/tps/resource/modules/addons.jsm index 55c4e6c7f7a..a5373cadc7d 100644 --- a/services/sync/tps/extensions/tps/modules/addons.jsm +++ b/services/sync/tps/extensions/tps/resource/modules/addons.jsm @@ -15,12 +15,11 @@ Cu.import("resource://services-sync/addonutils.js"); Cu.import("resource://services-sync/util.js"); Cu.import("resource://tps/logger.jsm"); -const ADDONSGETURL = 'http://127.0.0.1:4567/'; +const ADDONSGETURL = "http://127.0.0.1:4567/"; const STATE_ENABLED = 1; const STATE_DISABLED = 2; -function GetFileAsText(file) -{ +function GetFileAsText(file) { let channel = Services.io.newChannel(file, null, null); let inputStream = channel.open(); if (channel instanceof Ci.nsIHttpChannel && diff --git a/services/sync/tps/extensions/tps/modules/bookmarks.jsm b/services/sync/tps/extensions/tps/resource/modules/bookmarks.jsm similarity index 98% rename from services/sync/tps/extensions/tps/modules/bookmarks.jsm rename to services/sync/tps/extensions/tps/resource/modules/bookmarks.jsm index d7f1e85a20c..65bd7b83b47 100644 --- a/services/sync/tps/extensions/tps/modules/bookmarks.jsm +++ b/services/sync/tps/extensions/tps/resource/modules/bookmarks.jsm @@ -10,16 +10,15 @@ var EXPORTED_SYMBOLS = ["PlacesItem", "Bookmark", "Separator", "Livemark", "BookmarkFolder", "DumpBookmarks"]; -const CC = Components.classes; -const CI = Components.interfaces; -const CU = Components.utils; +const {classes: Cc, interfaces: Ci, utils: Cu} = Components; -CU.import("resource://tps/logger.jsm"); -CU.import("resource://gre/modules/Services.jsm"); -CU.import("resource://gre/modules/PlacesUtils.jsm"); -CU.import("resource://gre/modules/BookmarkJSONUtils.jsm"); -CU.import("resource://gre/modules/Task.jsm"); -CU.import("resource://services-common/async.js"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/PlacesUtils.jsm"); +Cu.import("resource://gre/modules/BookmarkJSONUtils.jsm"); +Cu.import("resource://gre/modules/Task.jsm"); +Cu.import("resource://services-common/async.js"); + +Cu.import("resource://tps/logger.jsm"); var DumpBookmarks = function TPS_Bookmarks__DumpBookmarks() { let writer = { @@ -223,7 +222,7 @@ PlacesItem.prototype = { for (let i = 1; i < folder_parts.length; i++) { let subfolder_id = this.GetPlacesNodeId( folder_id, - CI.nsINavHistoryResultNode.RESULT_TYPE_FOLDER, + Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER, folder_parts[i]); if (subfolder_id == -1) { return -1; @@ -253,7 +252,7 @@ PlacesItem.prototype = { for (let i = 1; i < folder_parts.length; i++) { let subfolder_id = this.GetPlacesNodeId( folder_id, - CI.nsINavHistoryResultNode.RESULT_TYPE_FOLDER, + Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER, folder_parts[i]); if (subfolder_id == -1) { folder_id = PlacesUtils.bookmarks.createFolder(folder_id, @@ -703,7 +702,7 @@ BookmarkFolder.prototype = { } this.props.item_id = this.GetPlacesNodeId( this.props.folder_id, - CI.nsINavHistoryResultNode.RESULT_TYPE_FOLDER, + Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER, this.props.folder); if (!this.CheckDescription(this.props.description)) return -1; @@ -814,7 +813,7 @@ Livemark.prototype = { } this.props.item_id = this.GetPlacesNodeId( this.props.folder_id, - CI.nsINavHistoryResultNode.RESULT_TYPE_FOLDER, + Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER, this.props.livemark); if (!PlacesUtils.annotations .itemHasAnnotation(this.props.item_id, PlacesUtils.LMANNO_FEEDURI)) { diff --git a/services/sync/tps/extensions/tps/modules/forms.jsm b/services/sync/tps/extensions/tps/resource/modules/forms.jsm similarity index 96% rename from services/sync/tps/extensions/tps/modules/forms.jsm rename to services/sync/tps/extensions/tps/resource/modules/forms.jsm index 99dbcb085cc..ece2e14f7cb 100644 --- a/services/sync/tps/extensions/tps/modules/forms.jsm +++ b/services/sync/tps/extensions/tps/resource/modules/forms.jsm @@ -9,14 +9,12 @@ var EXPORTED_SYMBOLS = ["FormData"]; -const CC = Components.classes; -const CI = Components.interfaces; -const CU = Components.utils; +const {classes: Cc, interfaces: Ci, utils: Cu} = Components; -CU.import("resource://tps/logger.jsm"); +Cu.import("resource://tps/logger.jsm"); -let formService = CC["@mozilla.org/satchel/form-history;1"] - .getService(CI.nsIFormHistory2); +let formService = Cc["@mozilla.org/satchel/form-history;1"] + .getService(Ci.nsIFormHistory2); /** * FormDB diff --git a/services/sync/tps/extensions/tps/modules/history.jsm b/services/sync/tps/extensions/tps/resource/modules/history.jsm similarity index 94% rename from services/sync/tps/extensions/tps/modules/history.jsm rename to services/sync/tps/extensions/tps/resource/modules/history.jsm index f3a274cc702..c14a7509892 100644 --- a/services/sync/tps/extensions/tps/modules/history.jsm +++ b/services/sync/tps/extensions/tps/resource/modules/history.jsm @@ -9,14 +9,12 @@ var EXPORTED_SYMBOLS = ["HistoryEntry", "DumpHistory"]; -const CC = Components.classes; -const CI = Components.interfaces; -const CU = Components.utils; +const {classes: Cc, interfaces: Ci, utils: Cu} = Components; -CU.import("resource://gre/modules/Services.jsm"); -CU.import("resource://gre/modules/PlacesUtils.jsm"); -CU.import("resource://tps/logger.jsm"); -CU.import("resource://services-common/async.js"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/PlacesUtils.jsm"); +Cu.import("resource://tps/logger.jsm"); +Cu.import("resource://services-common/async.js"); var DumpHistory = function TPS_History__DumpHistory() { let writer = { @@ -55,7 +53,7 @@ var HistoryEntry = { * Returns the DBConnection object for the history service. */ get _db() { - return PlacesUtils.history.QueryInterface(CI.nsPIPlacesDatabase).DBConnection; + return PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection; }, /** diff --git a/services/sync/tps/extensions/tps/modules/passwords.jsm b/services/sync/tps/extensions/tps/resource/modules/passwords.jsm similarity index 94% rename from services/sync/tps/extensions/tps/modules/passwords.jsm rename to services/sync/tps/extensions/tps/resource/modules/passwords.jsm index 3f8b24b39dd..f7221224a48 100644 --- a/services/sync/tps/extensions/tps/modules/passwords.jsm +++ b/services/sync/tps/extensions/tps/resource/modules/passwords.jsm @@ -9,16 +9,14 @@ var EXPORTED_SYMBOLS = ["Password", "DumpPasswords"]; -const CC = Components.classes; -const CI = Components.interfaces; -const CU = Components.utils; +const {classes: Cc, interfaces: Ci, utils: Cu} = Components; -CU.import("resource://gre/modules/Services.jsm"); -CU.import("resource://tps/logger.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://tps/logger.jsm"); let nsLoginInfo = new Components.Constructor( "@mozilla.org/login-manager/loginInfo;1", - CI.nsILoginInfo, + Ci.nsILoginInfo, "init"); var DumpPasswords = function TPS__Passwords__DumpPasswords() { @@ -87,7 +85,7 @@ Password.prototype = { this.props.usernameField, this.props.passwordField); Services.logins.addLogin(login); - login.QueryInterface(CI.nsILoginMetaInfo); + login.QueryInterface(Ci.nsILoginMetaInfo); return login.guid; }, @@ -109,7 +107,7 @@ Password.prototype = { logins[i].password == this.props.password && logins[i].usernameField == this.props.usernameField && logins[i].passwordField == this.props.passwordField) { - logins[i].QueryInterface(CI.nsILoginMetaInfo); + logins[i].QueryInterface(Ci.nsILoginMetaInfo); return logins[i].guid; } } diff --git a/services/sync/tps/extensions/tps/modules/prefs.jsm b/services/sync/tps/extensions/tps/resource/modules/prefs.jsm similarity index 86% rename from services/sync/tps/extensions/tps/modules/prefs.jsm rename to services/sync/tps/extensions/tps/resource/modules/prefs.jsm index 8707f723f3e..18a6e32ee06 100644 --- a/services/sync/tps/extensions/tps/modules/prefs.jsm +++ b/services/sync/tps/extensions/tps/resource/modules/prefs.jsm @@ -9,15 +9,14 @@ var EXPORTED_SYMBOLS = ["Preference"]; -const CC = Components.classes; -const CI = Components.interfaces; -const CU = Components.utils; +const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + const WEAVE_PREF_PREFIX = "services.sync.prefs.sync."; -let prefs = CC["@mozilla.org/preferences-service;1"] - .getService(CI.nsIPrefBranch); +let prefs = Cc["@mozilla.org/preferences-service;1"] + .getService(Ci.nsIPrefBranch); -CU.import("resource://tps/logger.jsm"); +Cu.import("resource://tps/logger.jsm"); /** * Preference class constructor @@ -60,17 +59,17 @@ Preference.prototype = { // than the value type specified in the test. let prefType = prefs.getPrefType(this.name); switch (prefType) { - case CI.nsIPrefBranch.PREF_INT: + case Ci.nsIPrefBranch.PREF_INT: Logger.AssertEqual(typeof(this.value), "number", "Wrong type used for preference value"); prefs.setIntPref(this.name, this.value); break; - case CI.nsIPrefBranch.PREF_STRING: + case Ci.nsIPrefBranch.PREF_STRING: Logger.AssertEqual(typeof(this.value), "string", "Wrong type used for preference value"); prefs.setCharPref(this.name, this.value); break; - case CI.nsIPrefBranch.PREF_BOOL: + case Ci.nsIPrefBranch.PREF_BOOL: Logger.AssertEqual(typeof(this.value), "boolean", "Wrong type used for preference value"); prefs.setBoolPref(this.name, this.value); @@ -93,13 +92,13 @@ Preference.prototype = { try { let prefType = prefs.getPrefType(this.name); switch(prefType) { - case CI.nsIPrefBranch.PREF_INT: + case Ci.nsIPrefBranch.PREF_INT: value = prefs.getIntPref(this.name); break; - case CI.nsIPrefBranch.PREF_STRING: + case Ci.nsIPrefBranch.PREF_STRING: value = prefs.getCharPref(this.name); break; - case CI.nsIPrefBranch.PREF_BOOL: + case Ci.nsIPrefBranch.PREF_BOOL: value = prefs.getBoolPref(this.name); break; } diff --git a/services/sync/tps/extensions/tps/modules/tabs.jsm b/services/sync/tps/extensions/tps/resource/modules/tabs.jsm similarity index 100% rename from services/sync/tps/extensions/tps/modules/tabs.jsm rename to services/sync/tps/extensions/tps/resource/modules/tabs.jsm diff --git a/services/sync/tps/extensions/tps/modules/windows.jsm b/services/sync/tps/extensions/tps/resource/modules/windows.jsm similarity index 100% rename from services/sync/tps/extensions/tps/modules/windows.jsm rename to services/sync/tps/extensions/tps/resource/modules/windows.jsm diff --git a/services/sync/tps/extensions/tps/modules/sync.jsm b/services/sync/tps/extensions/tps/resource/mozmill/sync.jsm similarity index 82% rename from services/sync/tps/extensions/tps/modules/sync.jsm rename to services/sync/tps/extensions/tps/resource/mozmill/sync.jsm index 8ab45a90555..160c17aa678 100644 --- a/services/sync/tps/extensions/tps/modules/sync.jsm +++ b/services/sync/tps/extensions/tps/resource/mozmill/sync.jsm @@ -2,26 +2,24 @@ * 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 EXPORTED_SYMBOLS = ["TPS", "SYNC_WIPE_SERVER", "SYNC_RESET_CLIENT", "SYNC_WIPE_CLIENT"]; -const CC = Components.classes; -const CI = Components.interfaces; -const CU = Components.utils; +const {classes: Cc, interfaces: Ci, utils: Cu} = Components; -CU.import("resource://gre/modules/XPCOMUtils.jsm"); -CU.import("resource://gre/modules/Services.jsm"); -CU.import("resource://services-sync/util.js"); -CU.import("resource://tps/logger.jsm"); -var utils = {}; CU.import('resource://mozmill/modules/utils.js', utils); +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://services-sync/util.js"); +Cu.import("resource://tps/logger.jsm"); + +var utils = {}; Cu.import('resource://mozmill/modules/utils.js', utils); const SYNC_RESET_CLIENT = "reset-client"; const SYNC_WIPE_CLIENT = "wipe-client"; const SYNC_WIPE_REMOTE = "wipe-remote"; const SYNC_WIPE_SERVER = "wipe-server"; -var prefs = CC["@mozilla.org/preferences-service;1"] +var prefs = Cc["@mozilla.org/preferences-service;1"] .getService(CI.nsIPrefBranch); var syncFinishedCallback = function() { @@ -33,8 +31,8 @@ var TPS = { _waitingForSync: false, _syncErrors: 0, - QueryInterface: XPCOMUtils.generateQI([CI.nsIObserver, - CI.nsISupportsWeakReference]), + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, + Ci.nsISupportsWeakReference]), observe: function TPS__observe(subject, topic, data) { Logger.logInfo('Mozmill observed: ' + topic); @@ -63,13 +61,14 @@ var TPS = { SetupSyncAccount: function TPS__SetupSyncAccount() { try { - let serverURL = prefs.getCharPref('tps.account.serverURL'); + let serverURL = prefs.getCharPref('tps.serverURL'); if (serverURL) { Weave.Service.serverURL = serverURL; } } catch(e) {} + // Needs to be updated if this Mozmill sanity test is needed for Firefox Accounts Weave.Service.identity.account = prefs.getCharPref('tps.account.username'); Weave.Service.Identity.basicPassword = prefs.getCharPref('tps.account.password'); Weave.Service.identity.syncKey = prefs.getCharPref('tps.account.passphrase'); diff --git a/services/sync/tps/extensions/tps/modules/quit.js b/services/sync/tps/extensions/tps/resource/quit.js similarity index 73% rename from services/sync/tps/extensions/tps/modules/quit.js rename to services/sync/tps/extensions/tps/resource/quit.js index ccaa05441dd..f777d66cb1f 100644 --- a/services/sync/tps/extensions/tps/modules/quit.js +++ b/services/sync/tps/extensions/tps/resource/quit.js @@ -11,30 +11,24 @@ var EXPORTED_SYMBOLS = ["goQuitApplication"]; Components.utils.import("resource://gre/modules/Services.jsm"); -function canQuitApplication() -{ - try - { +function canQuitApplication() { + try { var cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"] - .createInstance(Components.interfaces.nsISupportsPRBool); + .createInstance(Components.interfaces.nsISupportsPRBool); Services.obs.notifyObservers(cancelQuit, "quit-application-requested", null); // Something aborted the quit process. - if (cancelQuit.data) - { + if (cancelQuit.data) { return false; } } - catch (ex) - { - } + catch (ex) {} + return true; } -function goQuitApplication() -{ - if (!canQuitApplication()) - { +function goQuitApplication() { + if (!canQuitApplication()) { return false; } @@ -43,29 +37,24 @@ function goQuitApplication() var appService; var forceQuit; - if (kAppStartup in Components.classes) - { - appService = Components.classes[kAppStartup]. - getService(Components.interfaces.nsIAppStartup); + if (kAppStartup in Components.classes) { + appService = Components.classes[kAppStartup] + .getService(Components.interfaces.nsIAppStartup); forceQuit = Components.interfaces.nsIAppStartup.eForceQuit; } - else if (kAppShell in Components.classes) - { + else if (kAppShell in Components.classes) { appService = Components.classes[kAppShell]. getService(Components.interfaces.nsIAppShellService); forceQuit = Components.interfaces.nsIAppShellService.eForceQuit; } - else - { + else { throw 'goQuitApplication: no AppStartup/appShell'; } - try - { + try { appService.quit(forceQuit); } - catch(ex) - { + catch(ex) { throw('goQuitApplication: ' + ex); } diff --git a/services/sync/tps/extensions/tps/modules/tps.jsm b/services/sync/tps/extensions/tps/resource/tps.jsm similarity index 87% rename from services/sync/tps/extensions/tps/modules/tps.jsm rename to services/sync/tps/extensions/tps/resource/tps.jsm index 1d2efa9b84f..19c8a31348b 100644 --- a/services/sync/tps/extensions/tps/modules/tps.jsm +++ b/services/sync/tps/extensions/tps/resource/tps.jsm @@ -9,32 +9,37 @@ let EXPORTED_SYMBOLS = ["TPS"]; -const {classes: CC, interfaces: CI, utils: CU} = Components; +const {classes: Cc, interfaces: Ci, utils: Cu} = Components; -CU.import("resource://gre/modules/XPCOMUtils.jsm"); -CU.import("resource://gre/modules/Services.jsm"); -CU.import("resource://services-common/async.js"); -CU.import("resource://services-sync/constants.js"); -CU.import("resource://services-sync/main.js"); -CU.import("resource://services-sync/util.js"); -CU.import("resource://tps/addons.jsm"); -CU.import("resource://tps/bookmarks.jsm"); -CU.import("resource://tps/logger.jsm"); -CU.import("resource://tps/passwords.jsm"); -CU.import("resource://tps/history.jsm"); -CU.import("resource://tps/forms.jsm"); -CU.import("resource://tps/fxaccounts.jsm"); -CU.import("resource://tps/prefs.jsm"); -CU.import("resource://tps/tabs.jsm"); -CU.import("resource://tps/windows.jsm"); +// Global modules +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://services-common/async.js"); +Cu.import("resource://services-sync/constants.js"); +Cu.import("resource://services-sync/main.js"); +Cu.import("resource://services-sync/util.js"); -var hh = CC["@mozilla.org/network/protocol;1?name=http"] - .getService(CI.nsIHttpProtocolHandler); -var prefs = CC["@mozilla.org/preferences-service;1"] - .getService(CI.nsIPrefBranch); +// TPS modules +Cu.import("resource://tps/fxaccounts.jsm"); +Cu.import("resource://tps/logger.jsm"); + +// Module wrappers for tests +Cu.import("resource://tps/modules/addons.jsm"); +Cu.import("resource://tps/modules/bookmarks.jsm"); +Cu.import("resource://tps/modules/forms.jsm"); +Cu.import("resource://tps/modules/history.jsm"); +Cu.import("resource://tps/modules/passwords.jsm"); +Cu.import("resource://tps/modules/prefs.jsm"); +Cu.import("resource://tps/modules/tabs.jsm"); +Cu.import("resource://tps/modules/windows.jsm"); + +var hh = Cc["@mozilla.org/network/protocol;1?name=http"] + .getService(Ci.nsIHttpProtocolHandler); +var prefs = Cc["@mozilla.org/preferences-service;1"] + .getService(Ci.nsIPrefBranch); var mozmillInit = {}; -CU.import('resource://mozmill/modules/init.js', mozmillInit); +Cu.import('resource://mozmill/modules/init.js', mozmillInit); const ACTION_ADD = "add"; const ACTION_VERIFY = "verify"; @@ -60,13 +65,14 @@ const SYNC_START_OVER = "start-over"; const OBSERVER_TOPICS = ["fxaccounts:onlogin", "fxaccounts:onlogout", + "private-browsing", + "sessionstore-windows-restored", "weave:engine:start-tracking", "weave:engine:stop-tracking", "weave:service:setup-complete", "weave:service:sync:finish", "weave:service:sync:error", - "sessionstore-windows-restored", - "private-browsing"]; + ]; let TPS = { _waitingForSync: false, @@ -85,18 +91,30 @@ let TPS = { _loggedIn: false, _enabledEngines: null, + /** + * Check if the Firefox Accounts feature is enabled + */ + get fxaccounts_enabled() { + try { + return Services.prefs.getBoolPref("services.sync.fxaccounts.enabled"); + } catch (e) { + return false; + } + }, + DumpError: function (msg) { this._errors++; Logger.logError("[phase" + this._currentPhase + "] " + msg); this.quit(); }, - QueryInterface: XPCOMUtils.generateQI([CI.nsIObserver, - CI.nsISupportsWeakReference]), + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, + Ci.nsISupportsWeakReference]), observe: function TPS__observe(subject, topic, data) { try { Logger.logInfo("----------event observed: " + topic); + switch(topic) { case "private-browsing": Logger.logInfo("private browsing " + data); @@ -154,7 +172,7 @@ let TPS = { break; } } - catch(e) { + catch (e) { this.DumpError("Exception caught: " + Utils.exceptionStr(e)); return; } @@ -174,7 +192,7 @@ let TPS = { } }, - quit: function () { + quit: function TPS__quit() { OBSERVER_TOPICS.forEach(function(topic) { Services.obs.removeObserver(this, topic); }, this); @@ -396,12 +414,15 @@ let TPS = { Logger.clearPotentialError(); let placesItem; bookmark['location'] = folder; + if (last_item_pos != -1) bookmark['last_item_pos'] = last_item_pos; let item_id = -1; + if (action != ACTION_MODIFY && action != ACTION_DELETE) Logger.logInfo("executing action " + action.toUpperCase() + " on bookmark " + JSON.stringify(bookmark)); + if ("uri" in bookmark) placesItem = new Bookmark(bookmark); else if ("folder" in bookmark) @@ -410,6 +431,7 @@ let TPS = { placesItem = new Livemark(bookmark); else if ("separator" in bookmark) placesItem = new Separator(bookmark); + if (action == ACTION_ADD) { item_id = placesItem.Create(); } @@ -448,7 +470,7 @@ let TPS = { Logger.logPass("executing action " + action.toUpperCase() + " on bookmarks"); } - catch(e) { + catch (e) { DumpBookmarks(); throw(e); } @@ -495,7 +517,7 @@ let TPS = { let phase = this._phaselist["phase" + this._currentPhase]; let action = phase[this._currentAction]; - Logger.logInfo("starting action: " + JSON.stringify(action)); + Logger.logInfo("starting action: " + action[0].name); action[0].apply(this, action.slice(1)); // if we're in an async operation, don't continue on to the next action @@ -627,15 +649,22 @@ let TPS = { this_phase.push([this.WipeServer]); } - // Store account details as prefs so they're accessible to the mozmill + // Store account details as prefs so they're accessible to the Mozmill // framework. - prefs.setCharPref('tps.account.username', this.config.fx_account.username); - prefs.setCharPref('tps.account.password', this.config.fx_account.password); - // old sync - // prefs.setCharPref('tps.account.passphrase', this.config.fx_account.passphrase); - if (this.config["serverURL"]) { - prefs.setCharPref('tps.account.serverURL', this.config.serverURL); + if (this.fxaccounts_enabled) { + prefs.setCharPref('tps.account.username', this.config.fx_account.username); + prefs.setCharPref('tps.account.password', this.config.fx_account.password); } + else { + prefs.setCharPref('tps.account.username', this.config.sync_account.username); + prefs.setCharPref('tps.account.password', this.config.sync_account.password); + prefs.setCharPref('tps.account.passphrase', this.config.sync_account.passphrase); + } + + if (this.config["serverURL"]) { + prefs.setCharPref('tps.serverURL', this.config.serverURL); + } + // start processing the test actions this._currentAction = 0; } @@ -682,8 +711,8 @@ let TPS = { }, RunMozmillTest: function TPS__RunMozmillTest(testfile) { - var mozmillfile = CC["@mozilla.org/file/local;1"] - .createInstance(CI.nsILocalFile); + var mozmillfile = Cc["@mozilla.org/file/local;1"] + .createInstance(Ci.nsILocalFile); if (hh.oscpu.toLowerCase().indexOf('windows') > -1) { let re = /\/(\w)\/(.*)/; this.config.testdir = this.config.testdir.replace(re, "$1://$2").replace(/\//g, "\\"); @@ -693,7 +722,7 @@ let TPS = { Logger.logInfo("Running mozmill test " + mozmillfile.path); var frame = {}; - CU.import('resource://mozmill/modules/frame.js', frame); + Cu.import('resource://mozmill/modules/frame.js', frame); frame.events.addListener('setTest', this.MozmillSetTestListener.bind(this)); frame.events.addListener('endTest', this.MozmillEndTestListener.bind(this)); this.StartAsyncOperation(); @@ -706,16 +735,16 @@ let TPS = { * When the event is observed, the function will wait an extra tick before * returning. * - * @param name + * @param aEventName * String event to wait for. */ - waitForEvent:function waitForEvent(name) { - Logger.logInfo("Waiting for " + name + "..."); + waitForEvent: function waitForEvent(aEventName) { + Logger.logInfo("Waiting for " + aEventName + "..."); let cb = Async.makeSpinningCallback(); - Svc.Obs.add(name, cb); + Svc.Obs.add(aEventName, cb); cb.wait(); - Svc.Obs.remove(name, cb); - Logger.logInfo(name + " observed!"); + Svc.Obs.remove(aEventName, cb); + Logger.logInfo(aEventName + " observed!"); let cb = Async.makeSpinningCallback(); Utils.nextTick(cb); @@ -786,43 +815,59 @@ let TPS = { this.waitForTracking(); }, + /** + * Login on the server + */ Login: function Login(force) { if (this._loggedIn && !force) { return; } - // old sync: have to add handling for this.config.sync_account - let account = this.config.fx_account; - + let account = this.fxaccounts_enabled ? this.config.fx_account + : this.config.sync_account; if (!account) { this.DumperError("No account information found! Did you use a valid " + "config file?"); return; } + // If there has been specified a custom server, set it now if (this.config["serverURL"]) { Weave.Service.serverURL = this.config.serverURL; } - Logger.logInfo("Setting client credentials."); - if (account["username"] && account["password"]) { // && account["passphrase"]) { - FxAccountsHelper.signIn(account["username"], account["password"]); - this.waitForSetupComplete(); + Logger.logInfo("Setting client credentials and login."); - // Old sync code - has to be reactivated later for fallback - //Weave.Service.identity.account = account["username"]; - //Weave.Service.identity.basicPassword = account["password"]; - //Weave.Service.identity.syncKey = account["passphrase"]; - } else { - this.DumpError("Must specify username/password in the config file"); - return; + if (this.fxaccounts_enabled) { + if (account["username"] && account["password"]) { + Logger.logInfo("Login via Firefox Accounts."); + FxAccountsHelper.signIn(account["username"], account["password"]); + } + else { + this.DumpError("Must specify username/password in the config file"); + return; + } + } + else { + if (account["username"] && account["password"] && account["passphrase"]) { + Logger.logInfo("Login via the old Sync authentication."); + Weave.Service.identity.account = account["username"]; + Weave.Service.identity.basicPassword = account["password"]; + Weave.Service.identity.syncKey = account["passphrase"]; + + // Fake the login + Weave.Service.login(); + this._loggedIn = true; + Weave.Svc.Obs.notify("weave:service:setup-complete"); + } + else { + this.DumpError("Must specify username/password/passphrase in the config file"); + return; + } } - //Weave.Service.login(); - //this._loggedIn = true; - //Weave.Svc.Obs.notify("weave:service:setup-complete"); + this.waitForSetupComplete(); Logger.AssertEqual(Weave.Status.service, Weave.STATUS_OK, "Weave status OK"); - this.waitForTracking(); }, diff --git a/storage/src/TelemetryVFS.cpp b/storage/src/TelemetryVFS.cpp index 423788c9052..bcb4fd9fd02 100644 --- a/storage/src/TelemetryVFS.cpp +++ b/storage/src/TelemetryVFS.cpp @@ -107,7 +107,10 @@ public: Telemetry::AccumulateTimeDelta(static_cast(id + mainThread), start, end); } -#ifdef MOZ_ENABLE_PROFILER_SPS + // We don't report SQLite I/O on Windows because we have a comprehensive + // mechanism for intercepting I/O on that platform that captures a superset + // of the data captured here. +#if defined(MOZ_ENABLE_PROFILER_SPS) && !defined(XP_WIN) if (IOInterposer::IsObservedOperation(op)) { const char* main_ref = "sqlite-mainthread"; const char* other_ref = "sqlite-otherthread"; @@ -118,7 +121,7 @@ public: // Report observation IOInterposer::Report(ob); } -#endif /* MOZ_ENABLE_PROFILER_SPS */ +#endif /* defined(MOZ_ENABLE_PROFILER_SPS) && !defined(XP_WIN) */ } private: diff --git a/testing/mozbase/mozlog/setup.py b/testing/mozbase/mozlog/setup.py index eadd06984f5..be3d91f5630 100644 --- a/testing/mozbase/mozlog/setup.py +++ b/testing/mozbase/mozlog/setup.py @@ -5,7 +5,7 @@ from setuptools import setup, find_packages PACKAGE_NAME = 'mozlog' -PACKAGE_VERSION = '1.5' +PACKAGE_VERSION = '1.6' setup(name=PACKAGE_NAME, version=PACKAGE_VERSION, diff --git a/testing/tps/README b/testing/tps/README index 316d43bda9f..50280682e19 100644 --- a/testing/tps/README +++ b/testing/tps/README @@ -1,7 +1,8 @@ -TPS is a test automation framework for Firefox Sync. See +TPS is a test automation framework for Firefox Sync. See https://developer.mozilla.org/en/TPS for documentation. -INSTALLATION: +Installation +============ TPS requires several packages to operate properly. To install TPS and required packages, use the INSTALL.sh script, provided: @@ -12,3 +13,30 @@ This script will create a virtalenv and install TPS into it. TPS can then be run by activating the virtualenv and executing: runtps --binary=/path/to/firefox + + +Configuration +============= +To edit the TPS configuration, do not edit config/config.json.in in the tree. +Instead, edit config.json inside your virtualenv; it will be located at +something like: + + (linux): /path/to/virtualenv/lib/python2.6/site-packages/tps-0.2.40-py2.6.egg/tps/config.json + (win): /path/to/virtualenv/Lib/site-packages/tps-0.2.40-py2.6.egg/tps/config.json + + +Setting Up Test Accounts +======================== + +Firefox Accounts +---------------- +To create a test account for using the Firefox Account authentication perform the +following steps: + +1. Go to a URL like http://restmail.net/mail/%account_prefix%@restmail.net +2. Go to https://accounts.firefox.com/signup?service=sync&context=fx_desktop_v1 +3. Sign in with the previous chosen email address and a password +4. Go back to the Restmail URL, reload the page +5. Search for the verification link and open that page + +Now you will be able to use your setup Firefox Account for Sync. diff --git a/testing/tps/config/README.txt b/testing/tps/config/README.txt deleted file mode 100644 index 78b8aae41c5..00000000000 --- a/testing/tps/config/README.txt +++ /dev/null @@ -1,7 +0,0 @@ -To edit the TPS configuration, do not edit config.json.in in the tree. -Instead, edit config.json inside your virtualenv; it will be located at -something like: - - (linux): /path/to/virtualenv/lib/python2.6/site-packages/tps-0.2.40-py2.6.egg/tps/config.json - (win): /path/to/virtualenv/Lib/site-packages/tps-0.2.40-py2.6.egg/tps/config.json - diff --git a/testing/tps/config/config.json.in b/testing/tps/config/config.json.in index 3ede6f224b2..0dc422e483f 100644 --- a/testing/tps/config/config.json.in +++ b/testing/tps/config/config.json.in @@ -1,12 +1,12 @@ { "sync_account": { - "username": "crossweaveservices@mozilla.com", - "password": "crossweaveservicescrossweaveservices", - "passphrase": "r-jwcbc-zgf42-fjn72-p5vpp-iypmi" + "username": "", + "password": "", + "passphrase": "" }, "fx_account": { - "username": "crossweaveservices@restmail.net", - "password": "crossweaveservicescrossweaveservices" + "username": "", + "password": "" }, "email": { "username": "crossweave@mozilla.com", @@ -14,10 +14,11 @@ "passednotificationlist": ["crossweave@mozilla.com"], "notificationlist": ["crossweave@mozilla.com"] }, - "platform": "win32", - "os": "win7", + "auth_type": "fx_account", "es": "localhost:9200", + "os": "Ubuntu", + "platform": "linux64", "serverURL": null, - "testdir": "__TESTDIR__", - "extensiondir": "__EXTENSIONDIR__" + "extensiondir": "__EXTENSIONDIR__", + "testdir": "__TESTDIR__" } diff --git a/testing/tps/setup.py b/testing/tps/setup.py index b62d78696e6..72fb08711fa 100644 --- a/testing/tps/setup.py +++ b/testing/tps/setup.py @@ -2,14 +2,20 @@ # 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 sys from setuptools import setup, find_packages +import sys -version = '0.4' +version = '0.5' -deps = ['mozinfo >= 0.3.3', 'mozprofile >= 0.4', - 'mozprocess >= 0.4', 'mozrunner >= 5.8', 'mozinstall >= 1.4', - 'httplib2 >= 0.7.3'] +deps = ['httplib2 >= 0.7.3', + 'mozfile >= 1.1', + 'mozhttpd >= 0.7', + 'mozinfo >= 0.7', + 'mozinstall >= 1.9', + 'mozprocess >= 0.18', + 'mozprofile >= 0.21', + 'mozrunner >= 5.35', + ] # we only support python 2.6+ right now assert sys.version_info[0] == 2 @@ -22,10 +28,10 @@ setup(name='tps', """, classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers keywords='', - author='Jonathan Griffin', - author_email='jgriffin@mozilla.com', - url='http://hg.mozilla.org/services/services-central', - license='MPL', + author='Mozilla Automation and Tools team', + author_email='tools@lists.mozilla.org', + url='https://developer.mozilla.org/en-US/docs/TPS', + license='MPL 2.0', packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), include_package_data=True, zip_safe=False, diff --git a/testing/tps/tps/__init__.py b/testing/tps/tps/__init__.py index e1056f21924..4433ee9b822 100644 --- a/testing/tps/tps/__init__.py +++ b/testing/tps/tps/__init__.py @@ -1,8 +1,6 @@ -# 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/. - -from firefoxrunner import TPSFirefoxRunner -from testrunner import TPSTestRunner -from mozhttpd import MozHttpd - +# 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/. + +from . firefoxrunner import TPSFirefoxRunner +from . testrunner import TPSTestRunner diff --git a/testing/tps/tps/cli.py b/testing/tps/tps/cli.py index 70f185101c4..54dd934f3de 100644 --- a/testing/tps/tps/cli.py +++ b/testing/tps/tps/cli.py @@ -5,53 +5,65 @@ import json import optparse import os +import re import sys - from threading import RLock from tps import TPSTestRunner + def main(): parser = optparse.OptionParser() - parser.add_option("--mobile", - action = "store_true", dest = "mobile", - default = False, - help = "run with mobile settings") - parser.add_option("--testfile", - action = "store", type = "string", dest = "testfile", - default = '../../services/sync/tests/tps/test_sync.js', - help = "path to the test file to run " - "[default: %default]") - parser.add_option("--logfile", - action = "store", type = "string", dest = "logfile", - default = 'tps.log', - help = "path to the log file [default: %default]") - parser.add_option("--resultfile", - action = "store", type = "string", dest = "resultfile", - default = 'tps_result.json', - help = "path to the result file [default: %default]") - parser.add_option("--binary", - action = "store", type = "string", dest = "binary", - default = None, - help = "path to the Firefox binary, specified either as " - "a local file or a url; if omitted, the PATH " - "will be searched;") - parser.add_option("--configfile", - action = "store", type = "string", dest = "configfile", - default = None, - help = "path to the config file to use " - "[default: %default]") - parser.add_option("--pulsefile", - action = "store", type = "string", dest = "pulsefile", - default = None, - help = "path to file containing a pulse message in " - "json format that you want to inject into the monitor") - parser.add_option("--ignore-unused-engines", + parser.add_option('--mobile', + action='store_true', + dest='mobile', + default=False, + help='run with mobile settings') + parser.add_option('--testfile', + action='store', + type='string', + dest='testfile', + default='../../services/sync/tests/tps/all_tests.json', + help='path to the test file to run [default: %default]') + parser.add_option('--logfile', + action='store', + type='string', + dest='logfile', + default='tps.log', + help='path to the log file [default: %default]') + parser.add_option('--resultfile', + action='store', + type='string', + dest='resultfile', + default='tps_result.json', + help='path to the result file [default: %default]') + parser.add_option('--binary', + action='store', + type='string', + dest='binary', + default=None, + help='path to the Firefox binary, specified either as ' + 'a local file or a url; if omitted, the PATH ' + 'will be searched;') + parser.add_option('--configfile', + action='store', + type='string', + dest='configfile', + default=None, + help='path to the config file to use default: %default]') + parser.add_option('--pulsefile', + action='store', + type='string', + dest='pulsefile', + default=None, + help='path to file containing a pulse message in ' + 'json format that you want to inject into the monitor') + parser.add_option('--ignore-unused-engines', default=False, - action="store_true", - dest="ignore_unused_engines", - help="If defined, don't load unused engines in individual tests." - " Has no effect for pulse monitor.") + action='store_true', + dest='ignore_unused_engines', + help='If defined, do not load unused engines in individual tests.' + ' Has no effect for pulse monitor.') (options, args) = parser.parse_args() configfile = options.configfile @@ -59,8 +71,8 @@ def main(): if os.environ.get('VIRTUAL_ENV'): configfile = os.path.join(os.path.dirname(__file__), 'config.json') if configfile is None or not os.access(configfile, os.F_OK): - raise Exception("Unable to find config.json in a VIRTUAL_ENV; you must " - "specify a config file using the --configfile option") + raise Exception('Unable to find config.json in a VIRTUAL_ENV; you must ' + 'specify a config file using the --configfile option') # load the config file f = open(configfile, 'r') @@ -72,17 +84,17 @@ def main(): print 'using result file', options.resultfile - extensionDir = config.get("extensiondir") + extensionDir = config.get('extensiondir') if not extensionDir or extensionDir == '__EXTENSIONDIR__': - extensionDir = os.path.join(os.getcwd(), "..", "..", "services", "sync", "tps", "extensions") + extensionDir = os.path.join(os.getcwd(), '..', '..', + 'services', 'sync', 'tps', 'extensions') else: if sys.platform == 'win32': # replace msys-style paths with proper Windows paths - import re m = re.match('^\/\w\/', extensionDir) if m: - extensionDir = "%s:/%s" % (m.group(0)[1:2], extensionDir[3:]) - extensionDir = extensionDir.replace("/", "\\") + extensionDir = '%s:/%s' % (m.group(0)[1:2], extensionDir[3:]) + extensionDir = extensionDir.replace('/', '\\') TPS = TPSTestRunner(extensionDir, testfile=options.testfile, @@ -95,5 +107,5 @@ def main(): ignore_unused_engines=options.ignore_unused_engines) TPS.run_tests() -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/testing/tps/tps/firefoxrunner.py b/testing/tps/tps/firefoxrunner.py index c307aa7a08e..e569e26e2c9 100644 --- a/testing/tps/tps/firefoxrunner.py +++ b/testing/tps/tps/firefoxrunner.py @@ -1,94 +1,95 @@ -# 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 copy -import httplib2 -import os -import shutil - -import mozinstall - -from mozprofile import Profile -from mozrunner import FirefoxRunner - -class TPSFirefoxRunner(object): - - PROCESS_TIMEOUT = 240 - - def __init__(self, binary): - if binary is not None and ('http://' in binary or 'ftp://' in binary): - self.url = binary - self.binary = None - else: - self.url = None - self.binary = binary - self.runner = None - self.installdir = None - - def __del__(self): - if self.installdir: - shutil.rmtree(self.installdir, True) - - def download_url(self, url, dest=None): - h = httplib2.Http() - resp, content = h.request(url, "GET") - if dest == None: - dest = os.path.basename(url) - - local = open(dest, 'wb') - local.write(content) - local.close() - return dest - - def download_build(self, installdir='downloadedbuild', appname='firefox'): - self.installdir = os.path.abspath(installdir) - buildName = os.path.basename(self.url) - pathToBuild = os.path.join(os.path.dirname(os.path.abspath(__file__)), - buildName) - - # delete the build if it already exists - if os.access(pathToBuild, os.F_OK): - os.remove(pathToBuild) - - # download the build - print "downloading build" - self.download_url(self.url, pathToBuild) - - # install the build - print "installing %s" % pathToBuild - shutil.rmtree(self.installdir, True) - binary = mozinstall.install(src=pathToBuild, dest=self.installdir) - - # remove the downloaded archive - os.remove(pathToBuild) - - return binary - - def run(self, profile=None, timeout=PROCESS_TIMEOUT, env=None, args=None): - """Runs the given FirefoxRunner with the given Profile, waits - for completion, then returns the process exit code - """ - if profile is None: - profile = Profile() - self.profile = profile - - if self.binary is None and self.url: - self.binary = self.download_build() - - if self.runner is None: - self.runner = FirefoxRunner(self.profile, binary=self.binary) - - self.runner.profile = self.profile - - if env is not None: - self.runner.env.update(env) - - if args is not None: - self.runner.cmdargs = copy.copy(args) - - self.runner.start() - - status = self.runner.process_handler.waitForFinish(timeout=timeout) - - return status +# 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 copy +import httplib2 +import os + +import mozfile +import mozinstall +from mozprofile import Profile +from mozrunner import FirefoxRunner + + +class TPSFirefoxRunner(object): + + PROCESS_TIMEOUT = 240 + + def __init__(self, binary): + if binary is not None and ('http://' in binary or 'ftp://' in binary): + self.url = binary + self.binary = None + else: + self.url = None + self.binary = binary + + self.runner = None + self.installdir = None + + def __del__(self): + if self.installdir: + mozfile.remove(self.installdir, True) + + def download_url(self, url, dest=None): + h = httplib2.Http() + resp, content = h.request(url, 'GET') + if dest == None: + dest = os.path.basename(url) + + local = open(dest, 'wb') + local.write(content) + local.close() + return dest + + def download_build(self, installdir='downloadedbuild', appname='firefox'): + self.installdir = os.path.abspath(installdir) + buildName = os.path.basename(self.url) + pathToBuild = os.path.join(os.path.dirname(os.path.abspath(__file__)), + buildName) + + # delete the build if it already exists + if os.access(pathToBuild, os.F_OK): + os.remove(pathToBuild) + + # download the build + print 'downloading build' + self.download_url(self.url, pathToBuild) + + # install the build + print 'installing %s' % pathToBuild + mozfile.remove(self.installdir, True) + binary = mozinstall.install(src=pathToBuild, dest=self.installdir) + + # remove the downloaded archive + os.remove(pathToBuild) + + return binary + + def run(self, profile=None, timeout=PROCESS_TIMEOUT, env=None, args=None): + """Runs the given FirefoxRunner with the given Profile, waits + for completion, then returns the process exit code + """ + if profile is None: + profile = Profile() + self.profile = profile + + if self.binary is None and self.url: + self.binary = self.download_build() + + if self.runner is None: + self.runner = FirefoxRunner(self.profile, binary=self.binary) + + self.runner.profile = self.profile + + if env is not None: + self.runner.env.update(env) + + if args is not None: + self.runner.cmdargs = copy.copy(args) + + self.runner.start() + returncode = self.runner.wait(timeout) + + return returncode diff --git a/testing/tps/tps/mozhttpd.py b/testing/tps/tps/mozhttpd.py deleted file mode 100644 index 6ec64a3a770..00000000000 --- a/testing/tps/tps/mozhttpd.py +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/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/. - -import BaseHTTPServer -import SimpleHTTPServer -import threading -import sys -import os -import urllib -import re -from urlparse import urlparse -from SocketServer import ThreadingMixIn - -DOCROOT = '.' - -class EasyServer(ThreadingMixIn, BaseHTTPServer.HTTPServer): - allow_reuse_address = True - -class MozRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): - def translate_path(self, path): - # It appears that the default path is '/' and os.path.join makes the '/' - o = urlparse(path) - - sep = '/' - if sys.platform == 'win32': - sep = '' - - ret = '%s%s' % ( sep, DOCROOT.strip('/') ) - - # Stub out addons.mozilla.org search API, which is used when installing - # add-ons. The version is hard-coded because we want tests to fail when - # the API updates so we can update our stubbed files with the changes. - if o.path.find('/en-US/firefox/api/1.5/search/guid:') == 0: - ids = urllib.unquote(o.path[len('/en-US/firefox/api/1.5/search/guid:'):]) - - if ids.count(',') > 0: - raise Exception('Searching for multiple IDs is not supported.') - - base = ids - at_loc = ids.find('@') - if at_loc > 0: - base = ids[0:at_loc] - - ret += '/%s.xml' % base - - else: - ret += '/%s' % o.path.strip('/') - - return ret - - # I found on my local network that calls to this were timing out - # I believe all of these calls are from log_message - def address_string(self): - return "a.b.c.d" - - # This produces a LOT of noise - def log_message(self, format, *args): - pass - -class MozHttpd(object): - def __init__(self, host="127.0.0.1", port=8888, docroot='.'): - global DOCROOT - self.host = host - self.port = int(port) - DOCROOT = docroot - - def start(self): - self.httpd = EasyServer((self.host, self.port), MozRequestHandler) - self.server = threading.Thread(target=self.httpd.serve_forever) - self.server.setDaemon(True) # don't hang on exit - self.server.start() - #self.testServer() - - #TODO: figure this out - def testServer(self): - fileList = os.listdir(DOCROOT) - filehandle = urllib.urlopen('http://%s:%s' % (self.host, self.port)) - data = filehandle.readlines(); - filehandle.close() - - for line in data: - found = False - # '@' denotes a symlink and we need to ignore it. - webline = re.sub('\<[a-zA-Z0-9\-\_\.\=\"\'\/\\\%\!\@\#\$\^\&\*\(\) ]*\>', '', line.strip('\n')).strip('/').strip().strip('@') - if webline != "": - if webline == "Directory listing for": - found = True - else: - for fileName in fileList: - if fileName == webline: - found = True - - if (found == False): - print "NOT FOUND: " + webline.strip() - - def stop(self): - if self.httpd: - self.httpd.shutdown() - self.httpd.server_close() - - __del__ = stop diff --git a/testing/tps/tps/phase.py b/testing/tps/tps/phase.py index 39e49ebd118..8996020d516 100644 --- a/testing/tps/tps/phase.py +++ b/testing/tps/tps/phase.py @@ -7,7 +7,7 @@ import re class TPSTestPhase(object): lineRe = re.compile( - r"^(.*?)test phase (?P\d+): (?P.*)$") + r'^(.*?)test phase (?P\d+): (?P.*)$') def __init__(self, phase, profile, testname, testpath, logfile, env, firefoxRunner, logfn, ignore_unused_engines=False): @@ -42,7 +42,7 @@ class TPSTestPhase(object): if self.ignore_unused_engines: args.append('--ignore-unused-engines') - self.log("\nlaunching Firefox for phase %s with args %s\n" % + self.log('\nLaunching Firefox for phase %s with args %s\n' % (self.phase, str(args))) self.firefoxRunner.run(env=self.env, args=args, @@ -55,7 +55,7 @@ class TPSTestPhase(object): # skip to the part of the log file that deals with the test we're running if not found_test: - if line.find("Running test %s" % self.testname) > -1: + if line.find('Running test %s' % self.testname) > -1: found_test = True else: continue @@ -63,13 +63,13 @@ class TPSTestPhase(object): # look for the status of the current phase match = self.lineRe.match(line) if match: - if match.group("matchphase") == self.phasenum: - self._status = match.group("matchstatus") + if match.group('matchphase') == self.phasenum: + self._status = match.group('matchstatus') break # set the status to FAIL if there is TPS error - if line.find("CROSSWEAVE ERROR: ") > -1 and not self._status: - self._status = "FAIL" - self.errline = line[line.find("CROSSWEAVE ERROR: ") + len("CROSSWEAVE ERROR: "):] + if line.find('CROSSWEAVE ERROR: ') > -1 and not self._status: + self._status = 'FAIL' + self.errline = line[line.find('CROSSWEAVE ERROR: ') + len('CROSSWEAVE ERROR: '):] f.close() diff --git a/testing/tps/tps/testrunner.py b/testing/tps/tps/testrunner.py index bee08938d6c..50d2a9190ed 100644 --- a/testing/tps/tps/testrunner.py +++ b/testing/tps/tps/testrunner.py @@ -11,11 +11,13 @@ import tempfile import time import traceback +from mozhttpd import MozHttpd +import mozinfo from mozprofile import Profile -from tps.firefoxrunner import TPSFirefoxRunner -from tps.phase import TPSTestPhase -from tps.mozhttpd import MozHttpd +from .firefoxrunner import TPSFirefoxRunner +from .phase import TPSTestPhase + class TempFile(object): """Class for temporary files that delete themselves when garbage-collected. @@ -41,6 +43,7 @@ class TempFile(object): __del__ = cleanup + class TPSTestRunner(object): default_env = { 'MOZ_CRASHREPORTER_DISABLE': '1', @@ -50,13 +53,17 @@ class TPSTestRunner(object): 'XPCOM_DEBUG_BREAK': 'warn', } default_preferences = { 'app.update.enabled' : False, - 'extensions.getAddons.get.url': 'http://127.0.0.1:4567/en-US/firefox/api/%API_VERSION%/search/guid:%IDS%', - 'extensions.update.enabled' : False, - 'extensions.update.notifyUser' : False, + 'browser.dom.window.dump.enabled': True, + 'browser.sessionstore.resume_from_crash': False, 'browser.shell.checkDefaultBrowser' : False, 'browser.tabs.warnOnClose' : False, 'browser.warnOnQuit': False, - 'browser.sessionstore.resume_from_crash': False, + # Allow installing extensions dropped into the profile folder + 'extensions.autoDisableScopes': 10, + 'extensions.getAddons.get.url': 'http://127.0.0.1:4567/en-US/firefox/api/%API_VERSION%/search/guid:%IDS%', + 'extensions.update.enabled' : False, + # Don't open a dialog to show available add-on updates + 'extensions.update.notifyUser' : False, 'services.sync.addons.ignoreRepositoryChecking': True, 'services.sync.firstSync': 'notReady', 'services.sync.lastversion': '1.0', @@ -67,23 +74,19 @@ class TPSTestRunner(object): 'services.sync.log.appender.console': 'Trace', 'services.sync.log.appender.debugLog.enabled': True, 'toolkit.startup.max_resumed_crashes': -1, - 'browser.dom.window.dump.enabled': True, - # Allow installing extensions dropped into the profile folder - 'extensions.autoDisableScopes': 10, - # Don't open a dialog to show available add-on updates - 'extensions.update.notifyUser' : False, } + syncVerRe = re.compile( - r"Sync version: (?P.*)\n") + r'Sync version: (?P.*)\n') ffVerRe = re.compile( - r"Firefox version: (?P.*)\n") + r'Firefox version: (?P.*)\n') ffDateRe = re.compile( - r"Firefox builddate: (?P.*)\n") + r'Firefox builddate: (?P.*)\n') def __init__(self, extensionDir, - testfile="sync.test", + testfile='sync.test', binary=None, config=None, rlock=None, mobile=False, - logfile="tps.log", resultfile="tps_result.json", + logfile='tps.log', resultfile='tps_result.json', ignore_unused_engines=False): self.extensions = [] self.testfile = testfile @@ -167,13 +170,7 @@ class TPSTestRunner(object): def run_single_test(self, testdir, testname): testpath = os.path.join(testdir, testname) - self.log("Running test %s\n" % testname) - - # Create a random account suffix that is used when creating test - # accounts on a staging server. - #account_suffix = {"account-suffix": ''.join([str(random.randint(0,9)) - # for i in range(1,6)])} - #self.config['sync_account'].update(account_suffix) + self.log("Running test %s\n" % testname, True) # Read and parse the test file, merge it with the contents of the config # file, and write the combined output to a temporary file. @@ -183,7 +180,7 @@ class TPSTestRunner(object): try: test = json.loads(testcontent) except: - test = json.loads(testcontent[testcontent.find("{"):testcontent.find("}") + 1]) + test = json.loads(testcontent[testcontent.find('{'):testcontent.find('}') + 1]) testcontent += 'var config = %s;\n' % json.dumps(self.config, indent=2) testcontent += 'var seconds_since_epoch = %d;\n' % int(time.time()) @@ -223,9 +220,9 @@ class TPSTestRunner(object): phase.run() # if a failure occurred, dump the entire sync log into the test log - if phase.status != "PASS": + if phase.status != 'PASS': for profile in profiles: - self.log("\nDumping sync log for profile %s\n" % profiles[profile].profile) + self.log('\nDumping sync log for profile %s\n' % profiles[profile].profile) for root, dirs, files in os.walk(os.path.join(profiles[profile].profile, 'weave', 'logs')): for f in files: weavelog = os.path.join(profiles[profile].profile, 'weave', 'logs', f) @@ -247,11 +244,11 @@ class TPSTestRunner(object): f = open(self.logfile) logdata = f.read() match = self.syncVerRe.search(logdata) - sync_version = match.group("syncversion") if match else 'unknown' + sync_version = match.group('syncversion') if match else 'unknown' match = self.ffVerRe.search(logdata) - firefox_version = match.group("ffver") if match else 'unknown' + firefox_version = match.group('ffver') if match else 'unknown' match = self.ffDateRe.search(logdata) - firefox_builddate = match.group("ffdate") if match else 'unknown' + firefox_builddate = match.group('ffdate') if match else 'unknown' f.close() if phase.status == 'PASS': logdata = '' @@ -281,19 +278,19 @@ class TPSTestRunner(object): tmplogfile.close() self.errorlogs[testname] = tmplogfile - resultdata = ({ "productversion": { "version": firefox_version, - "buildid": firefox_builddate, - "builddate": firefox_builddate[0:8], - "product": "Firefox", - "repository": apprepo, - "changeset": appchangeset, + resultdata = ({ 'productversion': { 'version': firefox_version, + 'buildid': firefox_builddate, + 'builddate': firefox_builddate[0:8], + 'product': 'Firefox', + 'repository': apprepo, + 'changeset': appchangeset, }, - "addonversion": { "version": sync_version, - "product": "Firefox Sync" }, - "name": testname, - "message": result[1], - "state": result[0], - "logdata": logdata + 'addonversion': { 'version': sync_version, + 'product': 'Firefox Sync' }, + 'name': testname, + 'message': result[1], + 'state': result[0], + 'logdata': logdata }) self.log(logstr, True) @@ -316,6 +313,10 @@ class TPSTestRunner(object): if self.mobile: self.preferences.update({'services.sync.client.type' : 'mobile'}) + # If sync accounts have been chosen, disable Firefox Accounts + if self.config.get('auth_type', 'fx_account') != 'fx_account': + self.preferences.update({'services.sync.fxaccounts.enabled' : False}) + # Acquire a lock to make sure no other threads are running tests # at the same time. if self.rlock: @@ -367,22 +368,13 @@ class TPSTestRunner(object): def run_test_group(self): self.results = [] - self.extensions = [] - - # set the OS we're running on - os_string = platform.uname()[2] + " " + platform.uname()[3] - if os_string.find("Darwin") > -1: - os_string = "Mac OS X " + platform.mac_ver()[0] - if platform.uname()[0].find("Linux") > -1: - os_string = "Linux " + platform.uname()[5] - if platform.uname()[0].find("Win") > -1: - os_string = "Windows " + platform.uname()[3] # reset number of passed/failed tests self.numpassed = 0 self.numfailed = 0 # build our tps.xpi extension + self.extensions = [] self.extensions.append(os.path.join(self.extensionDir, 'tps')) self.extensions.append(os.path.join(self.extensionDir, "mozmill")) @@ -422,7 +414,7 @@ class TPSTestRunner(object): # generate the postdata we'll use to post the results to the db self.postdata = { 'tests': self.results, - 'os':os_string, + 'os': '%s %sbit' % (mozinfo.version, mozinfo.bits), 'testtype': 'crossweave', 'productversion': self.productversion, 'addonversion': self.addonversion, diff --git a/testing/tps/tps/thread.py b/testing/tps/tps/thread.py deleted file mode 100644 index 24afbd67b8d..00000000000 --- a/testing/tps/tps/thread.py +++ /dev/null @@ -1,64 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -from threading import Thread - -from testrunner import TPSTestRunner - -class TPSTestThread(Thread): - - def __init__(self, extensionDir, builddata=None, - testfile=None, logfile=None, rlock=None, config=None): - assert(builddata) - assert(config) - self.extensionDir = extensionDir - self.builddata = builddata - self.testfile = testfile - self.logfile = logfile - self.rlock = rlock - self.config = config - Thread.__init__(self) - - def run(self): - # run the tests in normal mode ... - TPS = TPSTestRunner(self.extensionDir, - testfile=self.testfile, - logfile=self.logfile, - binary=self.builddata['buildurl'], - config=self.config, - rlock=self.rlock, - mobile=False) - TPS.run_tests() - - # Get the binary used by this TPS instance, and use it in subsequent - # ones, so it doesn't have to be re-downloaded each time. - binary = TPS.firefoxRunner.binary - - # ... and then again in mobile mode - TPS_mobile = TPSTestRunner(self.extensionDir, - testfile=self.testfile, - logfile=self.logfile, - binary=binary, - config=self.config, - rlock=self.rlock, - mobile=True) - TPS_mobile.run_tests() - - # ... and again via the staging server, if credentials are present - stageaccount = self.config.get('stageaccount') - if stageaccount: - username = stageaccount.get('username') - password = stageaccount.get('password') - passphrase = stageaccount.get('passphrase') - if username and password and passphrase: - stageconfig = self.config.copy() - stageconfig['account'] = stageaccount.copy() - TPS_stage = TPSTestRunner(self.extensionDir, - testfile=self.testfile, - logfile=self.logfile, - binary=binary, - config=stageconfig, - rlock=self.rlock, - mobile=False)#, autolog=self.autolog) - TPS_stage.run_tests() diff --git a/toolkit/components/build/nsToolkitCompsModule.cpp b/toolkit/components/build/nsToolkitCompsModule.cpp index b0cbf16aa94..3765e37468f 100644 --- a/toolkit/components/build/nsToolkitCompsModule.cpp +++ b/toolkit/components/build/nsToolkitCompsModule.cpp @@ -35,6 +35,7 @@ #include "nsBrowserStatusFilter.h" #include "mozilla/FinalizationWitnessService.h" +#include "mozilla/NativeOSFileInternals.h" using namespace mozilla; @@ -89,6 +90,7 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsBrowserStatusFilter) NS_GENERIC_FACTORY_CONSTRUCTOR(nsUpdateProcessor) #endif NS_GENERIC_FACTORY_CONSTRUCTOR(FinalizationWitnessService) +NS_GENERIC_FACTORY_CONSTRUCTOR(NativeOSFileInternalsService) NS_DEFINE_NAMED_CID(NS_TOOLKIT_APPSTARTUP_CID); NS_DEFINE_NAMED_CID(NS_USERINFO_CID); @@ -114,6 +116,7 @@ NS_DEFINE_NAMED_CID(NS_CHARSETMENU_CID); NS_DEFINE_NAMED_CID(NS_UPDATEPROCESSOR_CID); #endif NS_DEFINE_NAMED_CID(FINALIZATIONWITNESSSERVICE_CID); +NS_DEFINE_NAMED_CID(NATIVE_OSFILE_INTERNALS_SERVICE_CID); static const Module::CIDEntry kToolkitCIDs[] = { { &kNS_TOOLKIT_APPSTARTUP_CID, false, nullptr, nsAppStartupConstructor }, @@ -140,6 +143,7 @@ static const Module::CIDEntry kToolkitCIDs[] = { { &kNS_UPDATEPROCESSOR_CID, false, nullptr, nsUpdateProcessorConstructor }, #endif { &kFINALIZATIONWITNESSSERVICE_CID, false, nullptr, FinalizationWitnessServiceConstructor }, + { &kNATIVE_OSFILE_INTERNALS_SERVICE_CID, false, nullptr, NativeOSFileInternalsServiceConstructor }, { nullptr } }; @@ -169,6 +173,7 @@ static const Module::ContractIDEntry kToolkitContracts[] = { { NS_UPDATEPROCESSOR_CONTRACTID, &kNS_UPDATEPROCESSOR_CID }, #endif { FINALIZATIONWITNESSSERVICE_CONTRACTID, &kFINALIZATIONWITNESSSERVICE_CID }, + { NATIVE_OSFILE_INTERNALS_SERVICE_CONTRACTID, &kNATIVE_OSFILE_INTERNALS_SERVICE_CID }, { nullptr } }; diff --git a/toolkit/components/crashes/CrashManager.jsm b/toolkit/components/crashes/CrashManager.jsm index 6f89e7cd3c9..6a74e2acda6 100644 --- a/toolkit/components/crashes/CrashManager.jsm +++ b/toolkit/components/crashes/CrashManager.jsm @@ -603,7 +603,7 @@ CrashStore.prototype = Object.freeze({ try { let decoder = new TextDecoder(); - let data = yield OS.File.read(this._storePath, null, {compression: "lz4"}); + let data = yield OS.File.read(this._storePath, {compression: "lz4"}); data = JSON.parse(decoder.decode(data)); if (data.corruptDate) { diff --git a/toolkit/components/downloads/ApplicationReputation.cpp b/toolkit/components/downloads/ApplicationReputation.cpp index fbcc404b5ba..bcab2fdb582 100644 --- a/toolkit/components/downloads/ApplicationReputation.cpp +++ b/toolkit/components/downloads/ApplicationReputation.cpp @@ -113,6 +113,8 @@ private: // An array of strings created from certificate information used to whitelist // the downloaded file. nsTArray mAllowlistSpecs; + // The source URI of the download, the referrer and possibly any redirects. + nsTArray mAnylistSpecs; // When we started this query TimeStamp mStartTime; @@ -199,7 +201,7 @@ public: // Look up the given URI in the safebrowsing DBs, optionally on both the allow // list and the blocklist. If there is a match, call // PendingLookup::OnComplete. Otherwise, call PendingLookup::LookupNext. - nsresult LookupSpec(const nsACString& aSpec, bool aAllowListOnly); + nsresult LookupSpec(const nsACString& aSpec, bool aAllowlistOnly); private: // The download appeared on the allowlist, blocklist, or no list (and thus // could trigger a remote query. @@ -210,7 +212,7 @@ private: }; nsCString mSpec; - bool mAllowListOnly; + bool mAllowlistOnly; nsRefPtr mPendingLookup; nsresult LookupSpecInternal(const nsACString& aSpec); }; @@ -219,7 +221,7 @@ NS_IMPL_ISUPPORTS1(PendingDBLookup, nsIUrlClassifierCallback) PendingDBLookup::PendingDBLookup(PendingLookup* aPendingLookup) : - mAllowListOnly(false), + mAllowlistOnly(false), mPendingLookup(aPendingLookup) { LOG(("Created pending DB lookup [this = %p]", this)); @@ -233,11 +235,11 @@ PendingDBLookup::~PendingDBLookup() nsresult PendingDBLookup::LookupSpec(const nsACString& aSpec, - bool aAllowListOnly) + bool aAllowlistOnly) { LOG(("Checking principal %s", aSpec.Data())); mSpec = aSpec; - mAllowListOnly = aAllowListOnly; + mAllowlistOnly = aAllowlistOnly; nsresult rv = LookupSpecInternal(aSpec); if (NS_FAILED(rv)) { LOG(("Error in LookupSpecInternal")); @@ -280,7 +282,15 @@ PendingDBLookup::HandleEvent(const nsACString& tables) // HandleEvent is guaranteed to call either: // 1) PendingLookup::OnComplete if the URL can be classified locally, or // 2) PendingLookup::LookupNext if the URL can be cannot classified locally. - // Allow listing trumps block listing. + // Blocklisting trumps allowlisting. + nsAutoCString blockList; + Preferences::GetCString(PREF_DOWNLOAD_BLOCK_TABLE, &blockList); + if (!mAllowlistOnly && FindInReadable(tables, blockList)) { + Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, BLOCK_LIST); + LOG(("Found principal %s on blocklist [this = %p]", mSpec.get(), this)); + return mPendingLookup->OnComplete(true, NS_OK); + } + nsAutoCString allowList; Preferences::GetCString(PREF_DOWNLOAD_ALLOW_TABLE, &allowList); if (FindInReadable(tables, allowList)) { @@ -289,14 +299,6 @@ PendingDBLookup::HandleEvent(const nsACString& tables) return mPendingLookup->OnComplete(false, NS_OK); } - nsAutoCString blockList; - Preferences::GetCString(PREF_DOWNLOAD_BLOCK_TABLE, &blockList); - if (!mAllowListOnly && FindInReadable(tables, blockList)) { - Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, BLOCK_LIST); - LOG(("Found principal %s on blocklist [this = %p]", mSpec.get(), this)); - return mPendingLookup->OnComplete(true, NS_OK); - } - LOG(("Didn't find principal %s on any list [this = %p]", mSpec.get(), this)); Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, NO_LIST); return mPendingLookup->LookupNext(); @@ -324,18 +326,26 @@ PendingLookup::LookupNext() { // We must call LookupNext or SendRemoteQuery upon return. // Look up all of the URLs that could whitelist this download. - int index = mAllowlistSpecs.Length() - 1; + // Blacklist first. + int index = mAnylistSpecs.Length() - 1; + nsCString spec; + bool allowlistOnly = false; if (index >= 0) { - nsCString spec = mAllowlistSpecs[index]; - mAllowlistSpecs.RemoveElementAt(index); - nsRefPtr lookup(new PendingDBLookup(this)); - bool allowListOnly = true; - if (index == 0) { - // The last URI is the target URI, which may be used for blacklisting as - // well as whitelisting. - allowListOnly = false; + // Check the source URI and referrer. + spec = mAnylistSpecs[index]; + mAnylistSpecs.RemoveElementAt(index); + } else { + // Check the allowlists next. + index = mAllowlistSpecs.Length() - 1; + if (index >= 0) { + allowlistOnly = true; + spec = mAllowlistSpecs[index]; + mAllowlistSpecs.RemoveElementAt(index); } - return lookup->LookupSpec(spec, allowListOnly); + } + if (index >= 0) { + nsRefPtr lookup(new PendingDBLookup(this)); + return lookup->LookupSpec(spec, allowlistOnly); } // There are no more URIs to check against local list, so send the remote // query if we can. @@ -506,7 +516,16 @@ PendingLookup::DoLookupInternal() nsCString spec; rv = uri->GetSpec(spec); NS_ENSURE_SUCCESS(rv, rv); - mAllowlistSpecs.AppendElement(spec); + mAnylistSpecs.AppendElement(spec); + + nsCOMPtr referrer = nullptr; + rv = mQuery->GetReferrerURI(getter_AddRefs(referrer)); + if (referrer) { + nsCString spec; + rv = referrer->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + mAnylistSpecs.AppendElement(spec); + } // Extract the signature and parse certificates so we can use it to check // whitelists. diff --git a/toolkit/components/downloads/nsIApplicationReputation.idl b/toolkit/components/downloads/nsIApplicationReputation.idl index 51b052ce11b..59a05b73dee 100644 --- a/toolkit/components/downloads/nsIApplicationReputation.idl +++ b/toolkit/components/downloads/nsIApplicationReputation.idl @@ -44,13 +44,18 @@ interface nsIApplicationReputationService : nsISupports { * downloaded file. nsIApplicationReputationService.Start() may only be called * once with a single query. */ -[scriptable, uuid(5a054991-e489-4a1c-a0aa-ea7c69b20e3d)] +[scriptable, uuid(2c781cbe-ab0c-4c53-b06e-f0cb56f8a30b)] interface nsIApplicationReputationQuery : nsISupports { /* * The nsIURI from which the file was downloaded. This may not be null. */ readonly attribute nsIURI sourceURI; + /* + * The reference, if any. + */ + readonly attribute nsIURI referrerURI; + /* * The target filename for the downloaded file, as inferred from the source * URI or provided by the Content-Disposition attachment file name. If this diff --git a/toolkit/components/downloads/test/unit/data/block_digest.chunk b/toolkit/components/downloads/test/unit/data/block_digest.chunk new file mode 100644 index 00000000000..34c47c4bb53 --- /dev/null +++ b/toolkit/components/downloads/test/unit/data/block_digest.chunk @@ -0,0 +1,2 @@ +a:5:32:37 +,AÎJ,AÎJ„ä8æW´bbòñ_e‹;OÏÏ„CVù  \ No newline at end of file diff --git a/toolkit/components/downloads/test/unit/test_app_rep.js b/toolkit/components/downloads/test/unit/test_app_rep.js index 91b3b6d3c1e..017ea61f3b9 100644 --- a/toolkit/components/downloads/test/unit/test_app_rep.js +++ b/toolkit/components/downloads/test/unit/test_app_rep.js @@ -134,8 +134,10 @@ add_test(function test_local_list() { .getService(Ci.nsIUrlClassifierStreamUpdater); streamUpdater.updateUrl = "http://localhost:4444/downloads"; - // Load up some update chunks for the safebrowsing server to serve. This - // particular chunk contains the hash of whitelisted.com/. + // Load up some update chunks for the safebrowsing server to serve. + // This chunk contains the hash of whitelisted.com/. + registerTableUpdate("goog-badbinurl-shavar", "data/block_digest.chunk"); + // This chunk contains the hash of blocklisted.com/. registerTableUpdate("goog-downloadwhite-digest256", "data/digest.chunk"); // Download some updates, and don't continue until the downloads are done. @@ -151,22 +153,61 @@ add_test(function test_local_list() { do_throw("We didn't download or update correctly: " + aEvent); } streamUpdater.downloadUpdates( - "goog-downloadwhite-digest256", - "goog-downloadwhite-digest256;\n", + "goog-downloadwhite-digest256,goog-badbinurl-shavar", + "goog-downloadwhite-digest256,goog-badbinurl-shavar;\n", updateSuccess, handleError, handleError); }); -// After being whitelisted, we shouldn't throw. -add_test(function test_local_whitelist() { +add_test(function test_unlisted() { Services.prefs.setCharPref("browser.safebrowsing.appRepURL", "http://localhost:4444/download"); gAppRep.queryReputation({ - sourceURI: createURI("http://whitelisted.com"), + sourceURI: createURI("http://example.com"), fileSize: 12, }, function onComplete(aShouldBlock, aStatus) { - // We would get garbage if this query made it to the remote server. do_check_eq(Cr.NS_OK, aStatus); do_check_false(aShouldBlock); run_next_test(); }); }); + +add_test(function test_local_blacklist() { + Services.prefs.setCharPref("browser.safebrowsing.appRepURL", + "http://localhost:4444/download"); + gAppRep.queryReputation({ + sourceURI: createURI("http://blocklisted.com"), + fileSize: 12, + }, function onComplete(aShouldBlock, aStatus) { + do_check_eq(Cr.NS_OK, aStatus); + do_check_true(aShouldBlock); + run_next_test(); + }); +}); + +add_test(function test_referer_blacklist() { + Services.prefs.setCharPref("browser.safebrowsing.appRepURL", + "http://localhost:4444/download"); + gAppRep.queryReputation({ + sourceURI: createURI("http://example.com"), + referrerURI: createURI("http://blocklisted.com"), + fileSize: 12, + }, function onComplete(aShouldBlock, aStatus) { + do_check_eq(Cr.NS_OK, aStatus); + do_check_true(aShouldBlock); + run_next_test(); + }); +}); + +add_test(function test_blocklist_trumps_allowlist() { + Services.prefs.setCharPref("browser.safebrowsing.appRepURL", + "http://localhost:4444/download"); + gAppRep.queryReputation({ + sourceURI: createURI("http://whitelisted.com"), + referrerURI: createURI("http://blocklisted.com"), + fileSize: 12, + }, function onComplete(aShouldBlock, aStatus) { + do_check_eq(Cr.NS_OK, aStatus); + do_check_true(aShouldBlock); + run_next_test(); + }); +}); diff --git a/toolkit/components/downloads/test/unit/xpcshell.ini b/toolkit/components/downloads/test/unit/xpcshell.ini index 6abd3682a4a..fcf9d56efca 100644 --- a/toolkit/components/downloads/test/unit/xpcshell.ini +++ b/toolkit/components/downloads/test/unit/xpcshell.ini @@ -6,6 +6,7 @@ support-files = downloads_manifest.js test_downloads.manifest data/digest.chunk + data/block_digest.chunk data/signed_win.exe [test_app_rep.js] diff --git a/toolkit/components/jsdownloads/src/DownloadIntegration.jsm b/toolkit/components/jsdownloads/src/DownloadIntegration.jsm index e87da397e77..209a158bb3f 100644 --- a/toolkit/components/jsdownloads/src/DownloadIntegration.jsm +++ b/toolkit/components/jsdownloads/src/DownloadIntegration.jsm @@ -513,7 +513,7 @@ this.DownloadIntegration = { let sigInfo; try { hash = aDownload.saver.getSha256Hash(); - sigInfo = aDownload.saver.getSignatureInfo(); + sigInfo = aDownload.saver.getSignatureInfo(); } catch (ex) { // Bail if DownloadSaver doesn't have a hash. return Promise.resolve(false); @@ -522,8 +522,13 @@ this.DownloadIntegration = { return Promise.resolve(false); } let deferred = Promise.defer(); + let aReferrer = null; + if (aDownload.source.referrer) { + aReferrer: NetUtil.newURI(aDownload.source.referrer); + } gApplicationReputationService.queryReputation({ sourceURI: NetUtil.newURI(aDownload.source.url), + referrerURI: aReferrer, fileSize: aDownload.currentBytes, sha256Hash: hash, signatureInfo: sigInfo }, diff --git a/toolkit/components/osfile/NativeOSFileInternals.cpp b/toolkit/components/osfile/NativeOSFileInternals.cpp new file mode 100644 index 00000000000..16b1900fa18 --- /dev/null +++ b/toolkit/components/osfile/NativeOSFileInternals.cpp @@ -0,0 +1,921 @@ +/* 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/. */ + +/** + * Native implementation of some OS.File operations. + */ + +#include "nsString.h" +#include "nsNetCID.h" +#include "nsThreadUtils.h" +#include "nsXPCOMCID.h" +#include "nsCycleCollectionParticipant.h" +#include "nsServiceManagerUtils.h" +#include "nsProxyRelease.h" + +#include "nsINativeOSFileInternals.h" +#include "NativeOSFileInternals.h" +#include "mozilla/dom/NativeOSFileInternalsBinding.h" + +#include "nsIUnicodeDecoder.h" +#include "nsIEventTarget.h" + +#include "mozilla/dom/EncodingUtils.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Scoped.h" +#include "mozilla/HoldDropJSObjects.h" +#include "mozilla/TimeStamp.h" + +#include "prio.h" +#include "prerror.h" +#include "private/pprio.h" + +#include "jsapi.h" +#include "jsfriendapi.h" +#include "js/Utility.h" +#include "xpcpublic.h" + +#include +#if defined(XP_UNIX) +#include +#include +#include +#include +#include +#endif // defined (XP_UNIX) + +#if defined(XP_WIN) +#include +#endif // defined (XP_WIN) + +namespace mozilla { + +MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc, PR_Close) + +namespace { + +// Utilities for safely manipulating ArrayBuffer contents even in the +// absence of a JSContext. + +/** + * The C buffer underlying to an ArrayBuffer. Throughout the code, we manipulate + * this instead of a void* buffer, as this lets us transfer data across threads + * and into JavaScript without copy. + */ +struct ArrayBufferContents { + /** + * The header of the ArrayBuffer. This is the pointer actually used by JSAPI. + */ + void* header; + /** + * The data of the ArrayBuffer. This is the pointer manipulated to + * read/write the contents of the buffer. + */ + uint8_t* data; + /** + * The number of bytes in the ArrayBuffer. + */ + size_t nbytes; +}; + +/** + * RAII for ArrayBufferContents. + */ +struct ScopedArrayBufferContentsTraits { + typedef ArrayBufferContents type; + const static type empty() { + type result = {0, 0, 0}; + return result; + } + const static void release(type ptr) { + js_free(ptr.header); + ptr.header = nullptr; + ptr.data = nullptr; + ptr.nbytes = 0; + } +}; + +struct ScopedArrayBufferContents: public Scoped { + ScopedArrayBufferContents(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM): + Scoped(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_TO_PARENT) + { } + ScopedArrayBufferContents(const ArrayBufferContents& v + MOZ_GUARD_OBJECT_NOTIFIER_PARAM): + Scoped(v MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT) + { } + ScopedArrayBufferContents& operator=(ArrayBufferContents ptr) { + Scoped::operator=(ptr); + return *this; + } + + /** + * Request memory for this ArrayBufferContent. This memory may later + * be used to create an ArrayBuffer object (possibly on another + * thread) without copy. + * + * @return true In case of success, false otherwise. + */ + bool Allocate(uint32_t length) { + dispose(); + ArrayBufferContents& value = rwget(); + if (JS_AllocateArrayBufferContents(/*no context available*/nullptr, + length, + &value.header, + &value.data)) { + value.nbytes = length; + return true; + } + return false; + } +private: + explicit ScopedArrayBufferContents(ScopedArrayBufferContents& source) MOZ_DELETE; + ScopedArrayBufferContents& operator=(ScopedArrayBufferContents& source) MOZ_DELETE; +}; + +///////// Cross-platform issues + +// Platform specific constants. As OS.File always uses OS-level +// errors, we need to map a few high-level errors to OS-level +// constants. +#if defined(XP_UNIX) +#define OS_ERROR_NOMEM ENOMEM +#define OS_ERROR_INVAL EINVAL +#define OS_ERROR_TOO_LARGE EFBIG +#define OS_ERROR_RACE EIO +#elif defined(XP_WIN) +#define OS_ERROR_NOMEM ERROR_NOT_ENOUGH_MEMORY +#define OS_ERROR_INVAL ERROR_BAD_ARGUMENTS +#define OS_ERROR_TOO_LARGE ERROR_FILE_TOO_LARGE +#define OS_ERROR_RACE ERROR_SHARING_VIOLATION +#else +#error "We do not have platform-specific constants for this platform" +#endif + +///////// Results of OS.File operations + +/** + * Base class for results passed to the callbacks. + * + * This base class implements caching of JS values returned to the client. + * We make use of this caching in derived classes e.g. to avoid accidents + * when we transfer data allocated on another thread into JS. Note that + * this caching can lead to cycles (e.g. if a client adds a back-reference + * in the JS value), so we implement all Cycle Collector primitives in + * AbstractResult. + */ +class AbstractResult: public nsINativeOSFileResult { +public: + NS_DECL_NSINATIVEOSFILERESULT + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AbstractResult) + + /** + * Construct the result object. Must be called on the main thread + * as the AbstractResult is cycle-collected. + * + * @param aStartDate The instant at which the operation was + * requested. Used to collect Telemetry statistics. + */ + AbstractResult(TimeStamp aStartDate) + : mStartDate(aStartDate) + { + MOZ_ASSERT(NS_IsMainThread()); + mozilla::HoldJSObjects(this); + } + virtual ~AbstractResult() { + MOZ_ASSERT(NS_IsMainThread()); + DropJSData(); + mozilla::DropJSObjects(this); + } + + /** + * Setup the AbstractResult once data is available. + * + * @param aDispatchDate The instant at which the IO thread received + * the operation request. Used to collect Telemetry statistics. + * @param aExecutionDuration The duration of the operation on the + * IO thread. + */ + void Init(TimeStamp aDispatchDate, + TimeDuration aExecutionDuration) { + MOZ_ASSERT(!NS_IsMainThread()); + + mDispatchDuration = (aDispatchDate - mStartDate); + mExecutionDuration = aExecutionDuration; + } + + /** + * Drop any data that could lead to a cycle. + */ + void DropJSData() { + mCachedResult = JS::UndefinedValue(); + } + +protected: + virtual nsresult GetCacheableResult(JSContext *cx, JS::MutableHandleValue aResult) = 0; + +private: + TimeStamp mStartDate; + TimeDuration mDispatchDuration; + TimeDuration mExecutionDuration; + JS::Heap mCachedResult; +}; + +NS_IMPL_CYCLE_COLLECTING_ADDREF(AbstractResult) +NS_IMPL_CYCLE_COLLECTING_RELEASE(AbstractResult) + +NS_IMPL_CYCLE_COLLECTION_CLASS(AbstractResult) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AbstractResult) + NS_INTERFACE_MAP_ENTRY(nsINativeOSFileResult) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AbstractResult) + NS_IMPL_CYCLE_COLLECTION_TRACE_JSVAL_MEMBER_CALLBACK(mCachedResult) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AbstractResult) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AbstractResult) + tmp->DropJSData(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMETHODIMP +AbstractResult::GetDispatchDurationMS(double *aDispatchDuration) +{ + *aDispatchDuration = mDispatchDuration.ToMilliseconds(); + return NS_OK; +} + +NS_IMETHODIMP +AbstractResult::GetExecutionDurationMS(double *aExecutionDuration) +{ + *aExecutionDuration = mExecutionDuration.ToMilliseconds(); + return NS_OK; +} + +NS_IMETHODIMP +AbstractResult::GetResult(JSContext *cx, JS::MutableHandleValue aResult) +{ + if (mCachedResult.isUndefined()) { + nsresult rv = GetCacheableResult(cx, aResult); + if (NS_FAILED(rv)) { + return rv; + } + mCachedResult = aResult; + return NS_OK; + } + aResult.set(mCachedResult); + return NS_OK; +} + +/** + * Return a result as a string. + * + * In this implementation, attribute |result| is a string. Strings are + * passed to JS without copy. + */ +class StringResult MOZ_FINAL : public AbstractResult +{ +public: + StringResult(TimeStamp aStartDate) + : AbstractResult(aStartDate) + { + } + + /** + * Initialize the object once the contents of the result as available. + * + * @param aContents The string to pass to JavaScript. Ownership of the + * string and its contents is passed to StringResult. The string must + * be valid UTF-16. + */ + void Init(TimeStamp aDispatchDate, + TimeDuration aExecutionDuration, + nsString& aContents) { + AbstractResult::Init(aDispatchDate, aExecutionDuration); + mContents = aContents; + } + +protected: + nsresult GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult) MOZ_OVERRIDE; + +private: + nsString mContents; +}; + +nsresult +StringResult::GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mContents.get()); + + // Convert mContents to a js string without copy. Note that this + // may have the side-effect of stealing the contents of the string + // from XPCOM and into JS. + if (!xpc::StringToJsval(cx, mContents, aResult)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + + +/** + * Return a result as a Uint8Array. + * + * In this implementation, attribute |result| is a Uint8Array. The array + * is passed to JS without memory copy. + */ +class TypedArrayResult MOZ_FINAL : public AbstractResult +{ +public: + TypedArrayResult(TimeStamp aStartDate) + : AbstractResult(aStartDate) + { + } + + /** + * @param aContents The contents to pass to JS. Calling this method. + * transmits ownership of the ArrayBufferContents to the TypedArrayResult. + * Do not reuse this value anywhere else. + */ + void Init(TimeStamp aDispatchDate, + TimeDuration aExecutionDuration, + ArrayBufferContents aContents) { + AbstractResult::Init(aDispatchDate, aExecutionDuration); + mContents = aContents; + } + +protected: + nsresult GetCacheableResult(JSContext* cx, JS::MutableHandleValue aResult) MOZ_OVERRIDE; +private: + ScopedArrayBufferContents mContents; +}; + +nsresult +TypedArrayResult::GetCacheableResult(JSContext* cx, JS::MutableHandle aResult) +{ + MOZ_ASSERT(NS_IsMainThread()); + // We cannot simply construct a typed array using contents.header as + // this would allow us to have several otherwise unrelated + // ArrayBuffers with the same underlying C buffer. As this would be + // very unsafe, we need to cache the result once we have it. + + const ArrayBufferContents& contents = mContents.get(); + MOZ_ASSERT(contents.data); + + JS::Rooted + arrayBuffer(cx, JS_NewArrayBufferWithContents(cx, contents.header)); + if (!arrayBuffer) { + return NS_ERROR_OUT_OF_MEMORY; + } + + JS::Rooted + result(cx, JS_NewUint8ArrayWithBuffer(cx, arrayBuffer, + 0, contents.nbytes)); + if (!result) { + return NS_ERROR_OUT_OF_MEMORY; + } + // The memory of contents has been allocated on a thread that + // doesn't have a JSRuntime, hence without a context. Now that we + // have a context, attach the memory to where it belongs. + JS_updateMallocCounter(cx, contents.nbytes); + mContents.forget(); + + aResult.setObject(*result); + return NS_OK; +} + +//////// Callback events + +/** + * An event used to notify asynchronously of an error. + */ +class ErrorEvent MOZ_FINAL : public nsRunnable { +public: + /** + * @param aOnSuccess The success callback. + * @param aOnError The error callback. + * @param aDiscardedResult The discarded result. + * @param aOperation The name of the operation, used for error reporting. + * @param aOSError The OS error of the operation, as returned by errno/ + * GetLastError(). + * + * Note that we pass both the success callback and the error + * callback, as well as the discarded result to ensure that they are + * all released on the main thread, rather than on the IO thread + * (which would hopefully segfault). Also, we pass the callbacks as + * alread_AddRefed to ensure that we do not manipulate main-thread + * only refcounters off the main thread. + */ + ErrorEvent(already_AddRefed aOnSuccess, + already_AddRefed aOnError, + already_AddRefed aDiscardedResult, + const nsACString& aOperation, + int32_t aOSError) + : mOnSuccess(aOnSuccess) + , mOnError(aOnError) + , mDiscardedResult(aDiscardedResult) + , mOSError(aOSError) + , mOperation(aOperation) + { + MOZ_ASSERT(!NS_IsMainThread()); + } + + NS_METHOD Run() { + MOZ_ASSERT(NS_IsMainThread()); + (void)mOnError->Complete(mOperation, mOSError); + + // Ensure that the callbacks are released on the main thread. + mOnSuccess = nullptr; + mOnError = nullptr; + mDiscardedResult = nullptr; + + return NS_OK; + } + private: + // The callbacks. Maintained as nsRefPtr as they are generally + // xpconnect values, which cannot be manipulated with nsCOMPtr off + // the main thread. We store both the success callback and the + // error callback to ensure that they are safely released on the + // main thread. + nsRefPtr mOnSuccess; + nsRefPtr mOnError; + nsRefPtr mDiscardedResult; + int32_t mOSError; + nsCString mOperation; +}; + +/** + * An event used to notify of a success. + */ +class SuccessEvent MOZ_FINAL : public nsRunnable { +public: + /** + * @param aOnSuccess The success callback. + * @param aOnError The error callback. + * + * Note that we pass both the success callback and the error + * callback to ensure that they are both released on the main + * thread, rather than on the IO thread (which would hopefully + * segfault). Also, we pass them as alread_AddRefed to ensure that + * we do not manipulate xpconnect refcounters off the main thread + * (which is illegal). + */ + SuccessEvent(already_AddRefed aOnSuccess, + already_AddRefed aOnError, + already_AddRefed aResult) + : mOnSuccess(aOnSuccess) + , mOnError(aOnError) + , mResult(aResult) + { + MOZ_ASSERT(!NS_IsMainThread()); + } + + NS_METHOD Run() { + MOZ_ASSERT(NS_IsMainThread()); + (void)mOnSuccess->Complete(mResult); + + // Ensure that the callbacks are released on the main thread. + mOnSuccess = nullptr; + mOnError = nullptr; + mResult = nullptr; + + return NS_OK; + } + private: + // The callbacks. Maintained as nsRefPtr as they are generally + // xpconnect values, which cannot be manipulated with nsCOMPtr off + // the main thread. We store both the success callback and the + // error callback to ensure that they are safely released on the + // main thread. + nsRefPtr mOnSuccess; + nsRefPtr mOnError; + nsRefPtr mResult; +}; + + +//////// Action events + +/** + * Base class shared by actions. + */ +class AbstractDoEvent: public nsRunnable { +public: + AbstractDoEvent(already_AddRefed aOnSuccess, + already_AddRefed aOnError) + : mOnSuccess(aOnSuccess) + , mOnError(aOnError) +#if defined(DEBUG) + , mResolved(false) +#endif // defined(DEBUG) + { + MOZ_ASSERT(NS_IsMainThread()); + } + + /** + * Fail, asynchronously. + */ + void Fail(const nsACString& aOperation, + already_AddRefed aDiscardedResult, + int32_t aOSError = 0) { + Resolve(); + nsRefPtr event = new ErrorEvent(mOnSuccess.forget(), + mOnError.forget(), + aDiscardedResult, + aOperation, + aOSError); + nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + // Last ditch attempt to release on the main thread - some of + // the members of event are not thread-safe, so letting the + // pointer go out of scope would cause a crash. + nsCOMPtr main = do_GetMainThread(); + NS_ProxyRelease(main, event); + } + } + + /** + * Succeed, asynchronously. + */ + void Succeed(already_AddRefed aResult) { + Resolve(); + nsRefPtr event = new SuccessEvent(mOnSuccess.forget(), + mOnError.forget(), + aResult); + nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + // Last ditch attempt to release on the main thread - some of + // the members of event are not thread-safe, so letting the + // pointer go out of scope would cause a crash. + nsCOMPtr main = do_GetMainThread(); + NS_ProxyRelease(main, event); + } + + } + +private: + + /** + * Mark the event as complete, for debugging purposes. + */ + void Resolve() { +#if defined(DEBUG) + MOZ_ASSERT(!mResolved); + mResolved = true; +#endif // defined(DEBUG) + } + +private: + nsRefPtr mOnSuccess; + nsRefPtr mOnError; +#if defined(DEBUG) + // |true| once the action is complete + bool mResolved; +#endif // defined(DEBUG) +}; + +/** + * An abstract event implementing reading from a file. + * + * Concrete subclasses are responsible for handling the + * data obtained from the file and possibly post-processing it. + */ +class AbstractReadEvent: public AbstractDoEvent { +public: + /** + * @param aPath The path of the file. + */ + AbstractReadEvent(const nsAString& aPath, + const uint64_t aBytes, + already_AddRefed aOnSuccess, + already_AddRefed aOnError) + : AbstractDoEvent(aOnSuccess, aOnError) + , mPath(aPath) + , mBytes(aBytes) + { + MOZ_ASSERT(NS_IsMainThread()); + } + + NS_METHOD Run() MOZ_OVERRIDE { + MOZ_ASSERT(!NS_IsMainThread()); + TimeStamp dispatchDate = TimeStamp::Now(); + + nsresult rv = BeforeRead(); + if (NS_FAILED(rv)) { + // Error reporting is handled by BeforeRead(); + return NS_OK; + } + + ScopedArrayBufferContents buffer; + rv = Read(buffer); + if (NS_FAILED(rv)) { + // Error reporting is handled by Read(); + return NS_OK; + } + + AfterRead(dispatchDate, buffer); + return NS_OK; + } + + private: + /** + * Read synchronously. + * + * Must be called off the main thread. + * + * @param aBuffer The destination buffer. + */ + nsresult Read(ScopedArrayBufferContents& aBuffer) + { + MOZ_ASSERT(!NS_IsMainThread()); + + ScopedPRFileDesc file; +#if defined(XP_WIN) + // On Windows, we can't use PR_OpenFile because it doesn't + // handle UTF-16 encoding, which is pretty bad. In addition, + // PR_OpenFile opens files without sharing, which is not the + // general semantics of OS.File. + HANDLE handle = + ::CreateFileW(mPath.get(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + /*Security attributes*/nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, + /*Template file*/ nullptr); + + if (handle == INVALID_HANDLE_VALUE) { + Fail(NS_LITERAL_CSTRING("open"), nullptr, ::GetLastError()); + return NS_ERROR_FAILURE; + } + + file = PR_ImportFile((PROsfd)handle); + if (!file) { + // |file| is closed by PR_ImportFile + Fail(NS_LITERAL_CSTRING("ImportFile"), nullptr, PR_GetOSError()); + return NS_ERROR_FAILURE; + } + +#else + // On other platforms, PR_OpenFile will do. + NS_ConvertUTF16toUTF8 path(mPath); + file = PR_OpenFile(path.get(), PR_RDONLY, 0); + if (!file) { + Fail(NS_LITERAL_CSTRING("open"), nullptr, PR_GetOSError()); + return NS_ERROR_FAILURE; + } + +#endif // defined(XP_XIN) + + PRFileInfo64 stat; + if (PR_GetOpenFileInfo64(file, &stat) != PR_SUCCESS) { + Fail(NS_LITERAL_CSTRING("stat"), nullptr, PR_GetOSError()); + return NS_ERROR_FAILURE; + } + + uint64_t bytes = std::min((uint64_t)stat.size, mBytes); + if (bytes > UINT32_MAX) { + Fail(NS_LITERAL_CSTRING("Arithmetics"), nullptr, OS_ERROR_INVAL); + return NS_ERROR_FAILURE; + } + + if (!aBuffer.Allocate(bytes)) { + Fail(NS_LITERAL_CSTRING("allocate"), nullptr, OS_ERROR_NOMEM); + return NS_ERROR_FAILURE; + } + + uint64_t total_read = 0; + int32_t just_read = 0; + char* dest_chars = reinterpret_cast(aBuffer.rwget().data); + do { + just_read = PR_Read(file, dest_chars + total_read, + std::min(uint64_t(PR_INT32_MAX), bytes - total_read)); + if (just_read == -1) { + Fail(NS_LITERAL_CSTRING("read"), nullptr, PR_GetOSError()); + return NS_ERROR_FAILURE; + } + total_read += just_read; + } while (just_read != 0 && total_read < bytes); + if (total_read != bytes) { + // We seem to have a race condition here. + Fail(NS_LITERAL_CSTRING("read"), nullptr, OS_ERROR_RACE); + return NS_ERROR_FAILURE; + } + + return NS_OK; + } + +protected: + /** + * Any steps that need to be taken before reading. + * + * In case of error, this method should call Fail() and return + * a failure code. + */ + virtual + nsresult BeforeRead() { + return NS_OK; + } + + /** + * Proceed after reading. + */ + virtual + void AfterRead(TimeStamp aDispatchDate, ScopedArrayBufferContents& aBuffer) = 0; + + protected: + const nsString mPath; + const uint64_t mBytes; +}; + +/** + * An implementation of a Read event that provides the data + * as a TypedArray. + */ +class DoReadToTypedArrayEvent MOZ_FINAL : public AbstractReadEvent { +public: + DoReadToTypedArrayEvent(const nsAString& aPath, + const uint32_t aBytes, + already_AddRefed aOnSuccess, + already_AddRefed aOnError) + : AbstractReadEvent(aPath, aBytes, + aOnSuccess, aOnError) + , mResult(new TypedArrayResult(TimeStamp::Now())) + { } + + ~DoReadToTypedArrayEvent() { + // If AbstractReadEvent::Run() has bailed out, we may need to cleanup + // mResult, which is main-thread only data + if (!mResult) { + return; + } + nsCOMPtr main = do_GetMainThread(); + (void)NS_ProxyRelease(main, mResult); + } + +protected: + void AfterRead(TimeStamp aDispatchDate, + ScopedArrayBufferContents& aBuffer) MOZ_OVERRIDE { + MOZ_ASSERT(!NS_IsMainThread()); + mResult->Init(aDispatchDate, TimeStamp::Now() - aDispatchDate, aBuffer.forget()); + Succeed(mResult.forget()); + } + + private: + nsRefPtr mResult; +}; + +/** + * An implementation of a Read event that provides the data + * as a JavaScript string. + */ +class DoReadToStringEvent MOZ_FINAL : public AbstractReadEvent { +public: + DoReadToStringEvent(const nsAString& aPath, + const nsACString& aEncoding, + const uint32_t aBytes, + already_AddRefed aOnSuccess, + already_AddRefed aOnError) + : AbstractReadEvent(aPath, aBytes, aOnSuccess, aOnError) + , mEncoding(aEncoding) + , mResult(new StringResult(TimeStamp::Now())) + { } + + ~DoReadToStringEvent() { + // If AbstraactReadEvent::Run() has bailed out, we may need to cleanup + // mResult, which is main-thread only data + if (!mResult) { + return; + } + nsCOMPtr main = do_GetMainThread(); + (void)NS_ProxyRelease(main, mResult); + } + +protected: + nsresult BeforeRead() MOZ_OVERRIDE { + // Obtain the decoder. We do this before reading to avoid doing + // any unnecessary I/O in case the name of the encoding is incorrect. + MOZ_ASSERT(!NS_IsMainThread()); + nsAutoCString encodingName; + if (!dom::EncodingUtils::FindEncodingForLabel(mEncoding, encodingName)) { + Fail(NS_LITERAL_CSTRING("Decode"), mResult.forget(), OS_ERROR_INVAL); + return NS_ERROR_FAILURE; + } + mDecoder = dom::EncodingUtils::DecoderForEncoding(encodingName); + if (!mDecoder) { + Fail(NS_LITERAL_CSTRING("DecoderForEncoding"), mResult.forget(), OS_ERROR_INVAL); + return NS_ERROR_FAILURE; + } + + return NS_OK; + } + + void AfterRead(TimeStamp aDispatchDate, + ScopedArrayBufferContents& aBuffer) MOZ_OVERRIDE { + MOZ_ASSERT(!NS_IsMainThread()); + + int32_t maxChars; + const char* sourceChars = reinterpret_cast(aBuffer.get().data); + int32_t sourceBytes = aBuffer.get().nbytes; + if (sourceBytes < 0) { + Fail(NS_LITERAL_CSTRING("arithmetics"), mResult.forget(), OS_ERROR_TOO_LARGE); + return; + } + + nsresult rv = mDecoder->GetMaxLength(sourceChars, sourceBytes, &maxChars); + if (NS_FAILED(rv)) { + Fail(NS_LITERAL_CSTRING("GetMaxLength"), mResult.forget(), OS_ERROR_INVAL); + return; + } + + if (maxChars < 0) { + Fail(NS_LITERAL_CSTRING("arithmetics"), mResult.forget(), OS_ERROR_TOO_LARGE); + return; + } + + nsString resultString; + resultString.SetLength(maxChars); + if (resultString.Length() != (nsString::size_type)maxChars) { + Fail(NS_LITERAL_CSTRING("allocation"), mResult.forget(), OS_ERROR_TOO_LARGE); + return; + } + + + rv = mDecoder->Convert(sourceChars, &sourceBytes, + resultString.BeginWriting(), &maxChars); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + resultString.SetLength(maxChars); + + mResult->Init(aDispatchDate, TimeStamp::Now() - aDispatchDate, resultString); + Succeed(mResult.forget()); + } + + private: + nsCString mEncoding; + nsCOMPtr mDecoder; + nsRefPtr mResult; +}; + +} // osfile + +// The OS.File service + +NS_IMPL_ISUPPORTS1(NativeOSFileInternalsService, nsINativeOSFileInternalsService); + +NS_IMETHODIMP +NativeOSFileInternalsService::Read(const nsAString& aPath, + JS::HandleValue aOptions, + nsINativeOSFileSuccessCallback *aOnSuccess, + nsINativeOSFileErrorCallback *aOnError, + JSContext* cx) +{ + // Extract options + nsCString encoding; + uint64_t bytes = UINT64_MAX; + + if (aOptions.isObject()) { + dom::NativeOSFileReadOptions dict; + if (!dict.Init(cx, aOptions)) { + return NS_ERROR_INVALID_ARG; + } + + if (dict.mEncoding.WasPassed()) { + CopyUTF16toUTF8(dict.mEncoding.Value(), encoding); + } + + if (dict.mBytes.WasPassed() && !dict.mBytes.Value().IsNull()) { + bytes = dict.mBytes.Value().Value(); + } + } + + // Prepare the off main thread event and dispatch it + nsCOMPtr onSuccess(aOnSuccess); + nsCOMPtr onError(aOnError); + + nsRefPtr event; + if (encoding.IsEmpty()) { + event = new DoReadToTypedArrayEvent(aPath, bytes, + onSuccess.forget(), + onError.forget()); + } else { + event = new DoReadToStringEvent(aPath, encoding, bytes, + onSuccess.forget(), + onError.forget()); + } + + nsresult rv; + nsCOMPtr target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); + + if (NS_FAILED(rv)) { + return rv; + } + return target->Dispatch(event, NS_DISPATCH_NORMAL); +} + +} // namespace mozilla + diff --git a/toolkit/components/osfile/NativeOSFileInternals.h b/toolkit/components/osfile/NativeOSFileInternals.h new file mode 100644 index 00000000000..9bc97c8f195 --- /dev/null +++ b/toolkit/components/osfile/NativeOSFileInternals.h @@ -0,0 +1,24 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_nativeosfileinternalservice_h__ +#define mozilla_nativeosfileinternalservice_h__ + +#include "nsINativeOSFileInternals.h" +#include "mozilla/Attributes.h" + +namespace mozilla { + +class NativeOSFileInternalsService MOZ_FINAL : public nsINativeOSFileInternalsService { +public: + NS_DECL_ISUPPORTS + NS_DECL_NSINATIVEOSFILEINTERNALSSERVICE +private: + // Avoid accidental use of built-in operator= + void operator=(const NativeOSFileInternalsService& other) MOZ_DELETE; +}; + +} // namespace mozilla + +#endif // mozilla_finalizationwitnessservice_h__ diff --git a/toolkit/components/osfile/modules/moz.build b/toolkit/components/osfile/modules/moz.build index 34c1b53a164..c2407cad458 100644 --- a/toolkit/components/osfile/modules/moz.build +++ b/toolkit/components/osfile/modules/moz.build @@ -10,6 +10,7 @@ EXTRA_JS_MODULES += [ '_PromiseWorker.jsm', 'osfile_async_front.jsm', 'osfile_async_worker.js', + 'osfile_native.jsm', 'osfile_shared_allthreads.jsm', 'osfile_shared_front.jsm', 'osfile_unix_allthreads.jsm', diff --git a/toolkit/components/osfile/modules/osfile_async_front.jsm b/toolkit/components/osfile/modules/osfile_async_front.jsm index f755a98d033..6bd0d86e769 100644 --- a/toolkit/components/osfile/modules/osfile_async_front.jsm +++ b/toolkit/components/osfile/modules/osfile_async_front.jsm @@ -58,6 +58,7 @@ Cu.import("resource://gre/modules/osfile/_PromiseWorker.jsm", this); Cu.import("resource://gre/modules/Services.jsm", this); Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this); Cu.import("resource://gre/modules/AsyncShutdown.jsm", this); +let Native = Cu.import("resource://gre/modules/osfile/osfile_native.jsm", {}); /** * Constructors for decoding standard exceptions @@ -348,7 +349,7 @@ const PREF_OSFILE_LOG_REDIRECT = "toolkit.osfile.log.redirect"; * @param bool oldPref * An optional value that the DEBUG flag was set to previously. */ -let readDebugPref = function readDebugPref(prefName, oldPref = false) { +function readDebugPref(prefName, oldPref = false) { let pref = oldPref; try { pref = Services.prefs.getBoolPref(prefName); @@ -379,6 +380,19 @@ Services.prefs.addObserver(PREF_OSFILE_LOG_REDIRECT, }, false); SharedAll.Config.TEST = readDebugPref(PREF_OSFILE_LOG_REDIRECT, false); + +/** + * If |true|, use the native implementaiton of OS.File methods + * whenever possible. Otherwise, force the use of the JS version. + */ +let nativeWheneverAvailable = true; +const PREF_OSFILE_NATIVE = "toolkit.osfile.native"; +Services.prefs.addObserver(PREF_OSFILE_NATIVE, + function prefObserver(aSubject, aTopic, aData) { + nativeWheneverAvailable = readDebugPref(PREF_OSFILE_NATIVE, nativeWheneverAvailable); + }, false); + + // Update worker's DEBUG flag if it's true. // Don't start the worker just for this, though. if (SharedAll.Config.DEBUG && Scheduler.launched) { @@ -913,12 +927,32 @@ File.makeDir = function makeDir(path, options) { * read from the file. */ File.read = function read(path, bytes, options = {}) { - let promise = Scheduler.post("read", - [Type.path.toMsg(path), bytes, options], path); - return promise.then( - function onSuccess(data) { - return new Uint8Array(data.buffer, data.byteOffset, data.byteLength); - }); + if (typeof bytes == "object") { + // Passing |bytes| as an argument is deprecated. + // We should now be passing it as a field of |options|. + options = bytes || {}; + } else { + options = clone(options, ["outExecutionDuration"]); + if (typeof bytes != "undefined") { + options.bytes = bytes; + } + } + + if (options.compression || !nativeWheneverAvailable) { + // We need to use the JS implementation. + let promise = Scheduler.post("read", + [Type.path.toMsg(path), bytes, options], path); + return promise.then( + function onSuccess(data) { + if (typeof data == "string") { + return data; + } + return new Uint8Array(data.buffer, data.byteOffset, data.byteLength); + }); + } + + // Otherwise, use the native implementation. + return Scheduler.push(() => Native.read(path, options)); }; /** diff --git a/toolkit/components/osfile/modules/osfile_async_worker.js b/toolkit/components/osfile/modules/osfile_async_worker.js index 4d4ff5cf62a..562b43e751e 100644 --- a/toolkit/components/osfile/modules/osfile_async_worker.js +++ b/toolkit/components/osfile/modules/osfile_async_worker.js @@ -362,6 +362,9 @@ const EXCEPTION_NAMES = { }, read: function read(path, bytes, options) { let data = File.read(Type.path.fromMsg(path), bytes, options); + if (typeof data == "string") { + return data; + } return new Meta({ buffer: data.buffer, byteOffset: data.byteOffset, diff --git a/toolkit/components/osfile/modules/osfile_native.jsm b/toolkit/components/osfile/modules/osfile_native.jsm new file mode 100644 index 00000000000..09e33feded6 --- /dev/null +++ b/toolkit/components/osfile/modules/osfile_native.jsm @@ -0,0 +1,70 @@ +/* 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/. */ + +/** + * Native (xpcom) implementation of key OS.File functions + */ + +"use strict"; + +this.EXPORTED_SYMBOLS = ["read"]; + +let {results: Cr, utils: Cu, interfaces: Ci} = Components; + +let SharedAll = Cu.import("resource://gre/modules/osfile/osfile_shared_allthreads.jsm", {}); + +let SysAll = {}; +if (SharedAll.Constants.Win) { + Cu.import("resource://gre/modules/osfile/osfile_win_allthreads.jsm", SysAll); +} else if (SharedAll.Constants.libc) { + Cu.import("resource://gre/modules/osfile/osfile_unix_allthreads.jsm", SysAll); +} else { + throw new Error("I am neither under Windows nor under a Posix system"); +} +let {Promise} = Cu.import("resource://gre/modules/Promise.jsm", {}); +let {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}); + +/** + * The native service holding the implementation of the functions. + */ +XPCOMUtils.defineLazyServiceGetter(this, + "Internals", + "@mozilla.org/toolkit/osfile/native-internals;1", + "nsINativeOSFileInternalsService"); + +/** + * Native implementation of OS.File.read + * + * This implementation does not handle option |compression|. + */ +this.read = function(path, options = {}) { + // Sanity check on types of options + if ("encoding" in options && typeof options.encoding != "string") { + return Promise.reject(new TypeError("Invalid type for option encoding")); + } + if ("compression" in options && typeof options.compression != "string") { + return Promise.reject(new TypeError("Invalid type for option compression")); + } + if ("bytes" in options && typeof options.bytes != "number") { + return Promise.reject(new TypeError("Invalid type for option bytes")); + } + + let deferred = Promise.defer(); + Internals.read(path, + options, + function onSuccess(success) { + success.QueryInterface(Ci.nsINativeOSFileResult); + if ("outExecutionDuration" in options) { + options.outExecutionDuration = + success.executionDurationMS + + (options.outExecutionDuration || 0); + } + deferred.resolve(success.result); + }, + function onError(operation, oserror) { + deferred.reject(new SysAll.Error(operation, oserror, path)); + } + ); + return deferred.promise; +}; diff --git a/toolkit/components/osfile/modules/osfile_shared_front.jsm b/toolkit/components/osfile/modules/osfile_shared_front.jsm index 5c88177ddde..d4325a6df0a 100644 --- a/toolkit/components/osfile/modules/osfile_shared_front.jsm +++ b/toolkit/components/osfile/modules/osfile_shared_front.jsm @@ -331,14 +331,35 @@ AbstractFile.read = function read(path, bytes, options = {}) { options = bytes; bytes = options.bytes || null; } + if ("encoding" in options && typeof options.encoding != "string") { + throw new TypeError("Invalid type for option encoding"); + } + if ("compression" in options && typeof options.compression != "string") { + throw new TypeError("Invalid type for option compression: " + options.compression); + } + if ("bytes" in options && typeof options.bytes != "number") { + throw new TypeError("Invalid type for option bytes"); + } let file = exports.OS.File.open(path); try { let buffer = file.read(bytes, options); - if ("compression" in options && options.compression == "lz4") { - return Lz4.decompressFileContent(buffer, options); - } else { + if ("compression" in options) { + if (options.compression == "lz4") { + buffer = Lz4.decompressFileContent(buffer, options); + } else { + throw OS.File.Error.invalidArgument("Compression"); + } + } + if (!("encoding" in options)) { return buffer; } + let decoder; + try { + decoder = new TextDecoder(options.encoding); + } catch (ex if ex instanceof TypeError) { + throw OS.File.Error.invalidArgument("Decode"); + } + return decoder.decode(buffer); } finally { file.close(); } diff --git a/toolkit/components/osfile/modules/osfile_unix_allthreads.jsm b/toolkit/components/osfile/modules/osfile_unix_allthreads.jsm index 5202c5fe080..8be2927662c 100644 --- a/toolkit/components/osfile/modules/osfile_unix_allthreads.jsm +++ b/toolkit/components/osfile/modules/osfile_unix_allthreads.jsm @@ -139,6 +139,15 @@ Object.defineProperty(OSError.prototype, "becauseAccessDenied", { return this.unixErrno == Const.EACCES; } }); +/** + * |true| if the error was raised because some invalid argument was passed, + * |false| otherwise. + */ +Object.defineProperty(OSError.prototype, "becauseInvalidArgument", { + get: function becauseInvalidArgument() { + return this.unixErrno == Const.EINVAL; + } +}); /** * Serialize an instance of OSError to something that can be @@ -331,6 +340,10 @@ OSError.noSuchFile = function noSuchFile(operation, path) { return new OSError(operation, Const.ENOENT, path); }; +OSError.invalidArgument = function invalidArgument(operation) { + return new OSError(operation, Const.EINVAL); +}; + let EXPORTED_SYMBOLS = [ "declareFFI", "libc", diff --git a/toolkit/components/osfile/modules/osfile_win_allthreads.jsm b/toolkit/components/osfile/modules/osfile_win_allthreads.jsm index 3ef7d610a2e..9126cd0fa5d 100644 --- a/toolkit/components/osfile/modules/osfile_win_allthreads.jsm +++ b/toolkit/components/osfile/modules/osfile_win_allthreads.jsm @@ -161,6 +161,16 @@ Object.defineProperty(OSError.prototype, "becauseAccessDenied", { return this.winLastError == Const.ERROR_ACCESS_DENIED; } }); +/** + * |true| if the error was raised because some invalid argument was passed, + * |false| otherwise. + */ +Object.defineProperty(OSError.prototype, "becauseInvalidArgument", { + get: function becauseInvalidArgument() { + return this.winLastError == Const.ERROR_NOT_SUPPORTED || + this.winLastError == Const.ERROR_BAD_ARGUMENTS; + } +}); /** * Serialize an instance of OSError to something that can be @@ -368,6 +378,10 @@ OSError.noSuchFile = function noSuchFile(operation, path) { return new OSError(operation, Const.ERROR_FILE_NOT_FOUND, path); }; +OSError.invalidArgument = function invalidArgument(operation) { + return new OSError(operation, Const.ERROR_NOT_SUPPORTED); +}; + let EXPORTED_SYMBOLS = [ "declareFFI", "libc", diff --git a/toolkit/components/osfile/moz.build b/toolkit/components/osfile/moz.build index 2a3c9873fe8..65138b4dcb0 100644 --- a/toolkit/components/osfile/moz.build +++ b/toolkit/components/osfile/moz.build @@ -11,7 +11,22 @@ DIRS += [ MOCHITEST_CHROME_MANIFESTS += ['tests/mochi/chrome.ini'] XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini'] +SOURCES += [ + 'NativeOSFileInternals.cpp', +] + +XPIDL_MODULE = 'toolkit_osfile' + +XPIDL_SOURCES += [ + 'nsINativeOSFileInternals.idl', +] + +EXPORTS.mozilla += [ + 'NativeOSFileInternals.h', +] + EXTRA_PP_JS_MODULES += [ 'osfile.jsm', ] +FINAL_LIBRARY = 'toolkitcomps' diff --git a/toolkit/components/osfile/nsINativeOSFileInternals.idl b/toolkit/components/osfile/nsINativeOSFileInternals.idl new file mode 100644 index 00000000000..c1bf8be147f --- /dev/null +++ b/toolkit/components/osfile/nsINativeOSFileInternals.idl @@ -0,0 +1,93 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=2 et sw=2 tw=40: */ +/* 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 "nsISupports.idl" + +/** + * The result of a successful asynchronous operation. + */ +[scriptable, builtinclass, uuid(08B4CF29-3D65-4E79-B522-A694C322ED07)] +interface nsINativeOSFileResult: nsISupports +{ + /** + * The actual value produced by the operation. + * + * Actual type of this value depends on the options passed to the + * operation. + */ + [implicit_jscontext] + readonly attribute jsval result; + + /** + * Delay between when the operation was requested on the main thread and + * when the operation was started off main thread. + */ + readonly attribute double dispatchDurationMS; + + /** + * Duration of the off main thread execution. + */ + readonly attribute double executionDurationMS; +}; + +/** + * A callback invoked in case of success. + */ +[scriptable, function, uuid(2C1922CA-CA1B-4099-8B61-EC23CFF49412)] +interface nsINativeOSFileSuccessCallback: nsISupports +{ + void complete(in nsINativeOSFileResult result); +}; + +/** + * A callback invoked in case of error. + */ +[scriptable, function, uuid(F612E0FC-6736-4D24-AA50-FD661B3B40B6)] +interface nsINativeOSFileErrorCallback: nsISupports +{ + /** + * @param operation The name of the failed operation. Provided to aid + * debugging only, may change without notice. + * @param OSstatus The OS status of the operation (errno under Unix, + * GetLastError under Windows). + */ + void complete(in ACString operation, in long OSstatus); +}; + +/** + * A service providing native implementations of some of the features + * of OS.File. + */ +[scriptable, builtinclass, uuid(913362AD-1526-4623-9E6B-A2EB08AFBBB9)] +interface nsINativeOSFileInternalsService: nsISupports +{ + /** + * Implementation of OS.File.read + * + * @param path The absolute path to the file to read. + * @param options An object that may contain some of the following fields + * - {number} bytes The maximal number of bytes to read. + * - {string} encoding If provided, return the result as a string, decoded + * using this encoding. Otherwise, pass the result as an ArrayBuffer. + * Invalid encodings cause onError to be called with the platform-specific + * "invalid argument" constant. + * - {string} compression Unimplemented at the moment. + * @param onSuccess The success callback. + * @param onError The error callback. + */ + [implicit_jscontext] + void read(in AString path, in jsval options, + in nsINativeOSFileSuccessCallback onSuccess, + in nsINativeOSFileErrorCallback onError); +}; + + +%{ C++ + +#define NATIVE_OSFILE_INTERNALS_SERVICE_CID {0x63A69303,0x8A64,0x45A9,{0x84, 0x8C, 0xD4, 0xE2, 0x79, 0x27, 0x94, 0xE6}} +#define NATIVE_OSFILE_INTERNALS_SERVICE_CONTRACTID "@mozilla.org/toolkit/osfile/native-internals;1" + +%} diff --git a/toolkit/components/osfile/tests/mochi/main_test_osfile_async.js b/toolkit/components/osfile/tests/mochi/main_test_osfile_async.js index 85519cb7de2..062d2d57e82 100644 --- a/toolkit/components/osfile/tests/mochi/main_test_osfile_async.js +++ b/toolkit/components/osfile/tests/mochi/main_test_osfile_async.js @@ -155,7 +155,6 @@ let test = maketest("Main", function main(test) { yield test_debug(); yield test_info_features_detect(); yield test_read_write(); - yield test_read_write_all(); yield test_position(); yield test_iter(); yield test_exists(); @@ -303,92 +302,6 @@ let test_read_write = maketest("read_write", function read_write(test) { }); }); -/** - * Test OS.File.writeAtomic - */ -let test_read_write_all = maketest("read_write_all", function read_write_all(test) { - return Task.spawn(function() { - let pathDest = OS.Path.join(OS.Constants.Path.tmpDir, - "osfile async test read writeAtomic.tmp"); - let tmpPath = pathDest + ".tmp"; - - let test_with_options = function(options, suffix) { - return Task.spawn(function() { - let optionsBackup = JSON.parse(JSON.stringify(options)); - - // Check that read + writeAtomic performs a correct copy - let currentDir = yield OS.File.getCurrentDirectory(); - let pathSource = OS.Path.join(currentDir, EXISTING_FILE); - let contents = yield OS.File.read(pathSource); - test.ok(contents, "Obtained contents"); - let bytesWritten = yield OS.File.writeAtomic(pathDest, contents, options); - test.is(contents.byteLength, bytesWritten, "Wrote the correct number of bytes (" + suffix + ")"); - - // Check that options are not altered - test.is(Object.keys(options).length, Object.keys(optionsBackup).length, - "The number of options was not changed"); - for (let k in options) { - test.is(options[k], optionsBackup[k], "Option was not changed (" + suffix + ")"); - } - yield reference_compare_files(pathSource, pathDest, test); - - // Check that temporary file was removed or doesn't exist - test.info("Compare complete"); - test.ok(!(new FileUtils.File(tmpPath).exists()), "No temporary file at the end of the run (" + suffix + ")"); - - // Check that writeAtomic fails if noOverwrite is true and the destination - // file already exists! - let view = new Uint8Array(contents.buffer, 10, 200); - try { - let opt = JSON.parse(JSON.stringify(options)); - opt.noOverwrite = true; - yield OS.File.writeAtomic(pathDest, view, opt); - test.fail("With noOverwrite, writeAtomic should have refused to overwrite file (" + suffix + ")"); - } catch (err) { - test.info("With noOverwrite, writeAtomic correctly failed (" + suffix + ")"); - test.ok(err instanceof OS.File.Error, "writeAtomic correctly failed with a file error (" + suffix + ")"); - test.ok(err.becauseExists, "writeAtomic file error confirmed that the file already exists (" + suffix + ")"); - } - yield reference_compare_files(pathSource, pathDest, test); - test.ok(!(new FileUtils.File(tmpPath).exists()), "Temporary file was removed"); - - // Now write a subset - let START = 10; - let LENGTH = 100; - view = new Uint8Array(contents.buffer, START, LENGTH); - bytesWritten = yield OS.File.writeAtomic(pathDest, view, options); - test.is(bytesWritten, LENGTH, "Partial write wrote the correct number of bytes (" + suffix + ")"); - let array2 = yield OS.File.read(pathDest); - let view1 = new Uint8Array(contents.buffer, START, LENGTH); - test.is(view1.length, array2.length, "Re-read partial write with the correct number of bytes (" + suffix + ")"); - let decoder = new TextDecoder(); - test.is(decoder.decode(view1), decoder.decode(array2), "Comparing re-read of partial write (" + suffix + ")"); - - // Write strings, default encoding - let ARBITRARY_STRING = "aeiouyâêîôûçß•"; - yield OS.File.writeAtomic(pathDest, ARBITRARY_STRING, options); - let array = yield OS.File.read(pathDest); - let IN_STRING = decoder.decode(array); - test.is(ARBITRARY_STRING, IN_STRING, "String write + read with default encoding works (" + suffix + ")"); - - let opt16 = JSON.parse(JSON.stringify(options)); - opt16.encoding = "utf-16"; - yield OS.File.writeAtomic(pathDest, ARBITRARY_STRING, opt16); - array = yield OS.File.read(pathDest); - IN_STRING = (new TextDecoder("utf-16")).decode(array); - test.is(ARBITRARY_STRING, IN_STRING, "String write + read with utf-16 encoding works (" + suffix + ")"); - - // Cleanup. - OS.File.remove(pathDest); - }); - }; - - yield test_with_options({tmpPath: tmpPath}, "Renaming, not flushing"); - yield test_with_options({tmpPath: tmpPath, flush: true}, "Renaming, flushing"); - yield test_with_options({}, "Not renaming, not flushing"); - yield test_with_options({flush: true}, "Not renaming, flushing"); - }); -}); /** * Test file.{getPosition, setPosition} diff --git a/toolkit/components/osfile/tests/xpcshell/head.js b/toolkit/components/osfile/tests/xpcshell/head.js new file mode 100644 index 00000000000..d72f1a17ec5 --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/head.js @@ -0,0 +1,86 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +let {utils: Cu, interfaces: Ci} = Components; + +let {OS} = Cu.import("resource://gre/modules/osfile.jsm", {}); +let {Services} = Cu.import("resource://gre/modules/Services.jsm", {}); +let {Promise} = Cu.import("resource://gre/modules/Promise.jsm", {}); +let {Task} = Cu.import("resource://gre/modules/Task.jsm", {}); +let {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {}); +let {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm", {}); + +Services.prefs.setBoolPref("toolkit.osfile.log", true); + +/** + * As add_task, but execute the test both with native operations and + * without. + */ +function add_test_pair(generator) { + add_task(function*() { + do_print("Executing test " + generator.name + " with native operations"); + Services.prefs.setBoolPref("toolkit.osfile.native", true); + return Task.spawn(generator); + }); + add_task(function*() { + do_print("Executing test " + generator.name + " without native operations"); + Services.prefs.setBoolPref("toolkit.osfile.native", false); + return Task.spawn(generator); + }); +} + +/** + * Fetch asynchronously the contents of a file using xpcom. + * + * Used for comparing xpcom-based results to os.file-based results. + * + * @param {string} path The _absolute_ path to the file. + * @return {promise} + * @resolves {string} The contents of the file. + */ +function reference_fetch_file(path, test) { + do_print("Fetching file " + path); + let deferred = Promise.defer(); + let file = new FileUtils.File(path); + NetUtil.asyncFetch(file, + function(stream, status) { + if (!Components.isSuccessCode(status)) { + deferred.reject(status); + return; + } + let result, reject; + try { + result = NetUtil.readInputStreamToString(stream, stream.available()); + } catch (x) { + reject = x; + } + stream.close(); + if (reject) { + deferred.reject(reject); + } else { + deferred.resolve(result); + } + }); + return deferred.promise; +}; + +/** + * Compare asynchronously the contents two files using xpcom. + * + * Used for comparing xpcom-based results to os.file-based results. + * + * @param {string} a The _absolute_ path to the first file. + * @param {string} b The _absolute_ path to the second file. + * + * @resolves {null} + */ +function reference_compare_files(a, b, test) { + return Task.spawn(function*() { + do_print("Comparing files " + a + " and " + b); + let a_contents = yield reference_fetch_file(a, test); + let b_contents = yield reference_fetch_file(b, test); + do_check_eq(a_contents, b_contents); + }); +}; diff --git a/toolkit/components/osfile/tests/xpcshell/test_creationDate.js b/toolkit/components/osfile/tests/xpcshell/test_creationDate.js index 0d93b597c86..9c4fa1dfc49 100644 --- a/toolkit/components/osfile/tests/xpcshell/test_creationDate.js +++ b/toolkit/components/osfile/tests/xpcshell/test_creationDate.js @@ -1,13 +1,5 @@ "use strict"; -const Ci = Components.interfaces; -const Cu = Components.utils; - -Cu.import("resource://gre/modules/osfile.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/Task.jsm"); -Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js"); - function run_test() { do_test_pending(); run_next_test(); diff --git a/toolkit/components/osfile/tests/xpcshell/test_exception.js b/toolkit/components/osfile/tests/xpcshell/test_exception.js index e4ed10c79e8..976d04b95bb 100644 --- a/toolkit/components/osfile/tests/xpcshell/test_exception.js +++ b/toolkit/components/osfile/tests/xpcshell/test_exception.js @@ -1,16 +1,18 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ +/** + * Test that functions throw the appropriate exceptions. + */ + "use strict"; -Components.utils.import("resource://gre/modules/osfile.jsm"); +let EXISTING_FILE = do_get_file("xpcshell.ini").path; -function run_test() { - do_test_pending(); - run_next_test(); -} -add_task(function test_typeerror() { +// Tests on |open| + +add_test_pair(function test_typeerror() { let exn; try { let fd = yield OS.File.open("/tmp", {no_such_key: 1}); @@ -22,6 +24,66 @@ add_task(function test_typeerror() { do_check_true(exn.constructor.name == "TypeError"); }); -add_task(function() { - do_test_finished(); +// Tests on |read| + +add_test_pair(function* test_bad_encoding() { + do_print("Testing with a wrong encoding"); + try { + yield OS.File.read(EXISTING_FILE, { encoding: "baby-speak-encoded" }); + do_throw("Should have thrown with an ex.becauseInvalidArgument"); + } catch (ex if ex.becauseInvalidArgument) { + do_print("Wrong encoding caused the correct exception"); + } + + try { + yield OS.File.read(EXISTING_FILE, { encoding: 4 }); + do_throw("Should have thrown a TypeError"); + } catch (ex if ex.constructor.name == "TypeError") { + // Note that TypeError doesn't carry across compartments + do_print("Non-string encoding caused the correct exception"); + } + }); + +add_test_pair(function* test_bad_compression() { + do_print("Testing with a non-existing compression"); + try { + yield OS.File.read(EXISTING_FILE, { compression: "mmmh-crunchy" }); + do_throw("Should have thrown with an ex.becauseInvalidArgument"); + } catch (ex if ex.becauseInvalidArgument) { + do_print("Wrong encoding caused the correct exception"); + } + + do_print("Testing with a bad type for option compression"); + try { + yield OS.File.read(EXISTING_FILE, { compression: 5 }); + do_throw("Should have thrown a TypeError"); + } catch (ex if ex.constructor.name == "TypeError") { + // Note that TypeError doesn't carry across compartments + do_print("Non-string encoding caused the correct exception"); + } }); + +add_test_pair(function* test_bad_bytes() { + do_print("Testing with a bad type for option bytes"); + try { + yield OS.File.read(EXISTING_FILE, { bytes: "five" }); + do_throw("Should have thrown a TypeError"); + } catch (ex if ex.constructor.name == "TypeError") { + // Note that TypeError doesn't carry across compartments + do_print("Non-number bytes caused the correct exception"); + } +}); + +add_test_pair(function* read_non_existent() { + do_print("Testing with a non-existent file"); + try { + yield OS.File.read("I/do/not/exist"); + do_throw("Should have thrown with an ex.becauseNoSuchFile"); + } catch (ex if ex.becauseNoSuchFile) { + do_print("Correct exceptions"); + } +}); + +function run_test() { + run_next_test(); +} diff --git a/toolkit/components/osfile/tests/xpcshell/test_open.js b/toolkit/components/osfile/tests/xpcshell/test_open.js index 8d5470eab3b..78772ad09ae 100644 --- a/toolkit/components/osfile/tests/xpcshell/test_open.js +++ b/toolkit/components/osfile/tests/xpcshell/test_open.js @@ -6,7 +6,6 @@ Components.utils.import("resource://gre/modules/osfile.jsm"); function run_test() { - do_test_pending(); run_next_test(); } @@ -69,7 +68,3 @@ add_task(function test_error_attributes () { do_check_true(err.becauseNoSuchFile); } }); - -add_task(function() { - do_test_finished(); -}); diff --git a/toolkit/components/osfile/tests/xpcshell/test_path_constants.js b/toolkit/components/osfile/tests/xpcshell/test_path_constants.js index 6c75ed23919..d099931b0cc 100644 --- a/toolkit/components/osfile/tests/xpcshell/test_path_constants.js +++ b/toolkit/components/osfile/tests/xpcshell/test_path_constants.js @@ -4,10 +4,6 @@ "use strict"; -const Cu = Components.utils; - -Cu.import("resource://gre/modules/osfile.jsm", this); -Cu.import("resource://gre/modules/Services.jsm", this); Cu.import("resource://gre/modules/ctypes.jsm", this); Cu.import("resource://testing-common/AppData.jsm", this); diff --git a/toolkit/components/osfile/tests/xpcshell/test_read_write.js b/toolkit/components/osfile/tests/xpcshell/test_read_write.js new file mode 100644 index 00000000000..aa969d669be --- /dev/null +++ b/toolkit/components/osfile/tests/xpcshell/test_read_write.js @@ -0,0 +1,100 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +let {utils: Cu} = Components; + +let SHARED_PATH; + +let EXISTING_FILE = do_get_file("xpcshell.ini").path; + +add_task(function* init() { + do_get_profile(); + SHARED_PATH = OS.Path.join(OS.Constants.Path.profileDir, "test_osfile_read.tmp"); +}); + + +// Check that OS.File.read() is executed after the previous operation +add_test_pair(function* ordering() { + let string1 = "Initial state " + Math.random(); + let string2 = "After writing " + Math.random(); + yield OS.File.writeAtomic(SHARED_PATH, string1); + OS.File.writeAtomic(SHARED_PATH, string2); + let string3 = yield OS.File.read(SHARED_PATH, { encoding: "utf-8" }); + do_check_eq(string3, string2); +}); + +add_test_pair(function* read_write_all() { + let DEST_PATH = SHARED_PATH + Math.random(); + let TMP_PATH = DEST_PATH + ".tmp"; + + let test_with_options = function(options, suffix) { + return Task.spawn(function*() { + do_print("Running test read_write_all with options " + JSON.stringify(options)); + let TEST = "read_write_all " + suffix; + + let optionsBackup = JSON.parse(JSON.stringify(options)); + + // Check that read + writeAtomic performs a correct copy + let currentDir = yield OS.File.getCurrentDirectory(); + let pathSource = OS.Path.join(currentDir, EXISTING_FILE); + let contents = yield OS.File.read(pathSource); + do_check_true(!!contents); // Content is not empty + + let bytesWritten = yield OS.File.writeAtomic(DEST_PATH, contents, options); + do_check_eq(contents.byteLength, bytesWritten); // Correct number of bytes written + + // Check that options are not altered + do_check_eq(JSON.stringify(options), JSON.stringify(optionsBackup)); + yield reference_compare_files(pathSource, DEST_PATH, TEST); + + // Check that temporary file was removed or never created exist + do_check_false(new FileUtils.File(TMP_PATH).exists()); + + // Check that writeAtomic fails if noOverwrite is true and the destination + // file already exists! + let view = new Uint8Array(contents.buffer, 10, 200); + try { + let opt = JSON.parse(JSON.stringify(options)); + opt.noOverwrite = true; + yield OS.File.writeAtomic(DEST_PATH, view, opt); + do_throw("With noOverwrite, writeAtomic should have refused to overwrite file (" + suffix + ")"); + } catch (err if err instanceof OS.File.Error && err.becauseExists) { + do_print("With noOverwrite, writeAtomic correctly failed (" + suffix + ")"); + } + yield reference_compare_files(pathSource, DEST_PATH, TEST); + + // Check that temporary file was removed or never created + do_check_false(new FileUtils.File(TMP_PATH).exists()); + + // Now write a subset + let START = 10; + let LENGTH = 100; + view = new Uint8Array(contents.buffer, START, LENGTH); + bytesWritten = yield OS.File.writeAtomic(DEST_PATH, view, options); + do_check_eq(bytesWritten, LENGTH); + + let array2 = yield OS.File.read(DEST_PATH); + let view1 = new Uint8Array(contents.buffer, START, LENGTH); + do_check_eq(view1.length, array2.length); + let decoder = new TextDecoder(); + do_check_eq(decoder.decode(view1), decoder.decode(array2)); + + + // Cleanup. + yield OS.File.remove(DEST_PATH); + yield OS.File.remove(TMP_PATH); + }); + }; + + yield test_with_options({tmpPath: TMP_PATH}, "Renaming, not flushing"); + yield test_with_options({tmpPath: TMP_PATH, flush: true}, "Renaming, flushing"); + yield test_with_options({}, "Not renaming, not flushing"); + yield test_with_options({flush: true}, "Not renaming, flushing"); +}); + + +function run_test() { + run_next_test(); +} diff --git a/toolkit/components/osfile/tests/xpcshell/xpcshell.ini b/toolkit/components/osfile/tests/xpcshell/xpcshell.ini index 67c88548284..8fc1bc3dd47 100644 --- a/toolkit/components/osfile/tests/xpcshell/xpcshell.ini +++ b/toolkit/components/osfile/tests/xpcshell/xpcshell.ini @@ -1,5 +1,5 @@ [DEFAULT] -head = +head = head.js tail = [test_available_free_space.js] @@ -25,6 +25,7 @@ tail = [test_open.js] [test_telemetry.js] [test_duration.js] +[test_read_write.js] [test_compression.js] [test_osfile_writeAtomic_backupTo_option.js] [test_osfile_error.js] diff --git a/toolkit/components/telemetry/Telemetry.cpp b/toolkit/components/telemetry/Telemetry.cpp index 5d9b379ef65..a90a42aeff7 100644 --- a/toolkit/components/telemetry/Telemetry.cpp +++ b/toolkit/components/telemetry/Telemetry.cpp @@ -386,7 +386,7 @@ void TelemetryIOInterposeObserver::AddPath(const nsAString& aPath, void TelemetryIOInterposeObserver::Observe(Observation& aOb) { // We only report main-thread I/O - if (!NS_IsMainThread()) { + if (!IsMainThread()) { return; } @@ -2558,6 +2558,18 @@ TelemetryImpl::GetFileIOReports(JSContext *cx, JS::MutableHandleValue ret) return NS_OK; } +NS_IMETHODIMP +TelemetryImpl::MsSinceProcessStart(double* aResult) +{ + bool error; + *aResult = (TimeStamp::NowLoRes() - + TimeStamp::ProcessCreation(error)).ToMilliseconds(); + if (error) { + return NS_ERROR_NOT_AVAILABLE; + } + return NS_OK; +} + size_t TelemetryImpl::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) { @@ -2956,10 +2968,6 @@ InitIOReporting(nsIFile* aXreDir) return; } - // Initialize IO interposing - IOInterposer::Init(); - InitPoisonIOInterposer(); - sTelemetryIOObserver = new TelemetryIOInterposeObserver(aXreDir); IOInterposer::Register(IOInterposeObserver::OpAll, sTelemetryIOObserver); } diff --git a/toolkit/components/telemetry/TelemetryLog.jsm b/toolkit/components/telemetry/TelemetryLog.jsm new file mode 100644 index 00000000000..13dab1783c2 --- /dev/null +++ b/toolkit/components/telemetry/TelemetryLog.jsm @@ -0,0 +1,35 @@ +/* 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.EXPORTED_SYMBOLS = ["TelemetryLog"]; + +const Cc = Components.classes; +const Ci = Components.interfaces; + +const Telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry); +var gLogEntries = []; + +this.TelemetryLog = Object.freeze({ + log: function(id, data) { + id = String(id); + var ts; + try { + ts = Math.floor(Telemetry.msSinceProcessStart()); + } catch(e) { + // If timestamp is screwed up, we just give up instead of making up + // data. + return; + } + + var entry = [id, ts]; + if (data !== undefined) { + entry = entry.concat(Array.prototype.map.call(data, String)); + } + gLogEntries.push(entry); + }, + + entries: function() { + return gLogEntries; + } +}); diff --git a/toolkit/components/telemetry/TelemetryPing.jsm b/toolkit/components/telemetry/TelemetryPing.jsm index 4cf032cf276..ef7e1ff17ae 100644 --- a/toolkit/components/telemetry/TelemetryPing.jsm +++ b/toolkit/components/telemetry/TelemetryPing.jsm @@ -73,6 +73,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "TelemetryFile", "resource://gre/modules/TelemetryFile.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry", "resource://gre/modules/UITelemetry.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "TelemetryLog", + "resource://gre/modules/TelemetryLog.jsm"); function generateUUID() { let str = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID().toString(); @@ -688,6 +690,7 @@ let Impl = { addonHistograms: this.getAddonHistograms(), addonDetails: AddonManagerPrivate.getTelemetryDetails(), UIMeasurements: UITelemetry.getUIMeasurements(), + log: TelemetryLog.entries(), info: info }; diff --git a/toolkit/components/telemetry/moz.build b/toolkit/components/telemetry/moz.build index 57d9baa808e..e079bece5c2 100644 --- a/toolkit/components/telemetry/moz.build +++ b/toolkit/components/telemetry/moz.build @@ -29,6 +29,7 @@ EXTRA_COMPONENTS += [ EXTRA_JS_MODULES += [ 'TelemetryFile.jsm', + 'TelemetryLog.jsm', 'TelemetryStopwatch.jsm', 'ThirdPartyCookieProbe.jsm', ] diff --git a/toolkit/components/telemetry/nsITelemetry.idl b/toolkit/components/telemetry/nsITelemetry.idl index 8a581dd1294..0596f34bd45 100644 --- a/toolkit/components/telemetry/nsITelemetry.idl +++ b/toolkit/components/telemetry/nsITelemetry.idl @@ -12,7 +12,7 @@ interface nsIFetchTelemetryDataCallback : nsISupports void complete(); }; -[scriptable, uuid(6c31f68d-4a54-4dca-b6c8-ddb264d5a154)] +[scriptable, uuid(4e4bfc35-dac6-4b28-ade4-7e45760051d5)] interface nsITelemetry : nsISupports { /** @@ -249,4 +249,11 @@ interface nsITelemetry : nsISupports */ [implicit_jscontext] readonly attribute jsval fileIOReports; + + /** + * Return the number of seconds since process start using monotonic + * timestamps (unaffected by system clock changes). + * @throws NS_ERROR_NOT_AVAILABLE if TimeStamp doesn't have the data. + */ + double msSinceProcessStart(); }; diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryLog.js b/toolkit/components/telemetry/tests/unit/test_TelemetryLog.js new file mode 100644 index 00000000000..986adc36eca --- /dev/null +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryLog.js @@ -0,0 +1,38 @@ +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; + +Cu.import("resource://gre/modules/TelemetryLog.jsm", this); +Cu.import("resource://gre/modules/TelemetryPing.jsm", this); + +function check_event(event, id, data) +{ + do_print("Checking message " + id); + do_check_eq(event[0], id); + do_check_true(event[1] > 0); + + if (data === undefined) { + do_check_true(event.length == 2); + } else { + do_check_eq(event.length, data.length + 2); + for (var i = 0; i < data.length; ++i) { + do_check_eq(typeof(event[i + 2]), "string"); + do_check_eq(event[i + 2], data[i]); + } + } +} + +function run_test() +{ + TelemetryLog.log("test1", ["val", 123, undefined]); + TelemetryLog.log("test2", []); + TelemetryLog.log("test3"); + + var log = TelemetryPing.getPayload().log; + do_check_eq(log.length, 3); + check_event(log[0], "test1", ["val", "123", "undefined"]); + check_event(log[1], "test2", []); + check_event(log[2], "test3", undefined); + do_check_true(log[0][1] <= log[1][1]); + do_check_true(log[1][1] <= log[2][1]); +} diff --git a/toolkit/components/telemetry/tests/unit/xpcshell.ini b/toolkit/components/telemetry/tests/unit/xpcshell.ini index cccc7a40baa..8478e93505e 100644 --- a/toolkit/components/telemetry/tests/unit/xpcshell.ini +++ b/toolkit/components/telemetry/tests/unit/xpcshell.ini @@ -5,6 +5,7 @@ tail = [test_nsITelemetry.js] [test_TelemetryLateWrites.js] [test_TelemetryLockCount.js] +[test_TelemetryLog.js] [test_TelemetryPing.js] # Bug 676989: test fails consistently on Android # fail-if = os == "android" diff --git a/toolkit/content/browser-content.js b/toolkit/content/browser-content.js new file mode 100644 index 00000000000..69342691af8 --- /dev/null +++ b/toolkit/content/browser-content.js @@ -0,0 +1,234 @@ +/* -*- Mode: javascript; 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/. */ + +let Cc = Components.classes; +let Ci = Components.interfaces; +let Cu = Components.utils; + +Cu.import("resource://gre/modules/Services.jsm"); + +var global = this; + +let ClickEventHandler = { + init: function init() { + this._scrollable = null; + this._scrolldir = ""; + this._startX = null; + this._startY = null; + this._screenX = null; + this._screenY = null; + this._lastFrame = null; + + Cc["@mozilla.org/eventlistenerservice;1"] + .getService(Ci.nsIEventListenerService) + .addSystemEventListener(global, "mousedown", this, true); + + addMessageListener("Autoscroll:Stop", this); + }, + + isAutoscrollBlocker: function(node) { + let mmPaste = Services.prefs.getBoolPref("middlemouse.paste"); + let mmScrollbarPosition = Services.prefs.getBoolPref("middlemouse.scrollbarPosition"); + + while (node) { + if ((node instanceof content.HTMLAnchorElement || node instanceof content.HTMLAreaElement) && + node.hasAttribute("href")) { + return true; + } + + if (mmPaste && (node instanceof content.HTMLInputElement || + node instanceof content.HTMLTextAreaElement)) { + return true; + } + + if (node instanceof content.XULElement && mmScrollbarPosition + && (node.localName == "scrollbar" || node.localName == "scrollcorner")) { + return true; + } + + node = node.parentNode; + } + return false; + }, + + startScroll: function(event) { + // this is a list of overflow property values that allow scrolling + const scrollingAllowed = ['scroll', 'auto']; + + // go upward in the DOM and find any parent element that has a overflow + // area and can therefore be scrolled + for (this._scrollable = event.originalTarget; this._scrollable; + this._scrollable = this._scrollable.parentNode) { + // do not use overflow based autoscroll for and + // Elements or non-html elements such as svg or Document nodes + // also make sure to skip select elements that are not multiline + if (!(this._scrollable instanceof content.HTMLElement) || + ((this._scrollable instanceof content.HTMLSelectElement) && !this._scrollable.multiple)) { + continue; + } + + var overflowx = this._scrollable.ownerDocument.defaultView + .getComputedStyle(this._scrollable, '') + .getPropertyValue('overflow-x'); + var overflowy = this._scrollable.ownerDocument.defaultView + .getComputedStyle(this._scrollable, '') + .getPropertyValue('overflow-y'); + // we already discarded non-multiline selects so allow vertical + // scroll for multiline ones directly without checking for a + // overflow property + var scrollVert = this._scrollable.scrollTopMax && + (this._scrollable instanceof content.HTMLSelectElement || + scrollingAllowed.indexOf(overflowy) >= 0); + + // do not allow horizontal scrolling for select elements, it leads + // to visual artifacts and is not the expected behavior anyway + if (!(this._scrollable instanceof content.HTMLSelectElement) && + this._scrollable.scrollLeftMax && + scrollingAllowed.indexOf(overflowx) >= 0) { + this._scrolldir = scrollVert ? "NSEW" : "EW"; + break; + } else if (scrollVert) { + this._scrolldir = "NS"; + break; + } + } + + if (!this._scrollable) { + this._scrollable = event.originalTarget.ownerDocument.defaultView; + if (this._scrollable.scrollMaxX > 0) { + this._scrolldir = this._scrollable.scrollMaxY > 0 ? "NSEW" : "EW"; + } else if (this._scrollable.scrollMaxY > 0) { + this._scrolldir = "NS"; + } else { + this._scrollable = null; // abort scrolling + return; + } + } + + Cc["@mozilla.org/eventlistenerservice;1"] + .getService(Ci.nsIEventListenerService) + .addSystemEventListener(global, "mousemove", this, true); + addEventListener("pagehide", this, true); + + sendAsyncMessage("Autoscroll:Start", {scrolldir: this._scrolldir, + screenX: event.screenX, + screenY: event.screenY}); + this._ignoreMouseEvents = true; + this._startX = event.screenX; + this._startY = event.screenY; + this._screenX = event.screenX; + this._screenY = event.screenY; + this._scrollErrorX = 0; + this._scrollErrorY = 0; + this._lastFrame = content.mozAnimationStartTime; + + content.mozRequestAnimationFrame(this); + }, + + stopScroll: function() { + if (this._scrollable) { + this._scrollable = null; + + Cc["@mozilla.org/eventlistenerservice;1"] + .getService(Ci.nsIEventListenerService) + .removeSystemEventListener(global, "mousemove", this, true); + removeEventListener("pagehide", this, true); + } + }, + + accelerate: function(curr, start) { + const speed = 12; + var val = (curr - start) / speed; + + if (val > 1) + return val * Math.sqrt(val) - 1; + if (val < -1) + return val * Math.sqrt(-val) + 1; + return 0; + }, + + roundToZero: function(num) { + if (num > 0) + return Math.floor(num); + return Math.ceil(num); + }, + + autoscrollLoop: function(timestamp) { + if (!this._scrollable) { + // Scrolling has been canceled + return; + } + + // avoid long jumps when the browser hangs for more than + // |maxTimeDelta| ms + const maxTimeDelta = 100; + var timeDelta = Math.min(maxTimeDelta, timestamp - this._lastFrame); + // we used to scroll |accelerate()| pixels every 20ms (50fps) + var timeCompensation = timeDelta / 20; + this._lastFrame = timestamp; + + var actualScrollX = 0; + var actualScrollY = 0; + // don't bother scrolling vertically when the scrolldir is only horizontal + // and the other way around + if (this._scrolldir != 'EW') { + var y = this.accelerate(this._screenY, this._startY) * timeCompensation; + var desiredScrollY = this._scrollErrorY + y; + actualScrollY = this.roundToZero(desiredScrollY); + this._scrollErrorY = (desiredScrollY - actualScrollY); + } + if (this._scrolldir != 'NS') { + var x = this.accelerate(this._screenX, this._startX) * timeCompensation; + var desiredScrollX = this._scrollErrorX + x; + actualScrollX = this.roundToZero(desiredScrollX); + this._scrollErrorX = (desiredScrollX - actualScrollX); + } + + if (this._scrollable instanceof content.Window) { + this._scrollable.scrollBy(actualScrollX, actualScrollY); + } else { // an element with overflow + this._scrollable.scrollLeft += actualScrollX; + this._scrollable.scrollTop += actualScrollY; + } + content.mozRequestAnimationFrame(this); + }, + + sample: function(timestamp) { + this.autoscrollLoop(timestamp); + }, + + handleEvent: function(event) { + if (event.type == "mousemove") { + this._screenX = event.screenX; + this._screenY = event.screenY; + } else if (event.type == "mousedown") { + if (event.isTrusted & + !event.defaultPrevented && + event.button == 1 && + !this._scrollable && + !this.isAutoscrollBlocker(event.originalTarget)) { + this.startScroll(event); + } + } else if (event.type == "pagehide") { + if (this._scrollable) { + var doc = + this._scrollable.ownerDocument || this._scrollable.document; + if (doc == event.target) { + sendAsyncMessage("Autoscroll:Cancel"); + } + } + } + }, + + receiveMessage: function(msg) { + switch (msg.name) { + case "Autoscroll:Stop": { + this.stopScroll(); + break; + } + } + }, +}; +ClickEventHandler.init(); diff --git a/toolkit/content/jar.mn b/toolkit/content/jar.mn index 9f67b57743c..032d50c9432 100644 --- a/toolkit/content/jar.mn +++ b/toolkit/content/jar.mn @@ -28,6 +28,7 @@ toolkit.jar: content/global/plugins.html content/global/plugins.css content/global/browser-child.js (browser-child.js) + content/global/browser-content.js (browser-content.js) *+ content/global/buildconfig.html (buildconfig.html) + content/global/charsetOverlay.js (charsetOverlay.js) + content/global/charsetOverlay.xul (charsetOverlay.xul) diff --git a/toolkit/content/tests/browser/browser_bug295977_autoscroll_overflow.js b/toolkit/content/tests/browser/browser_bug295977_autoscroll_overflow.js index fd88aee9b1f..9eab2864a2a 100644 --- a/toolkit/content/tests/browser/browser_bug295977_autoscroll_overflow.js +++ b/toolkit/content/tests/browser/browser_bug295977_autoscroll_overflow.js @@ -75,7 +75,10 @@ function test() ok((scrollHori && elem.scrollLeft > 0) || (!scrollHori && elem.scrollLeft == 0), test.elem+' should'+(scrollHori ? '' : ' not')+' have scrolled horizontally'); - nextTest(); + + // Before continuing the test, we need to ensure that the IPC + // message that stops autoscrolling has had time to arrive. + executeSoon(nextTest); }; EventUtils.synthesizeMouse(elem, 50, 50, { button: 1 }, gBrowser.contentWindow); diff --git a/toolkit/content/tests/browser/browser_keyevents_during_autoscrolling.js b/toolkit/content/tests/browser/browser_keyevents_during_autoscrolling.js index 65cc477433d..56c30e1f588 100644 --- a/toolkit/content/tests/browser/browser_keyevents_during_autoscrolling.js +++ b/toolkit/content/tests/browser/browser_keyevents_during_autoscrolling.js @@ -90,6 +90,12 @@ function test() EventUtils.synthesizeMouse(root, 10, 10, { button: 1 }, gBrowser.contentWindow); + // Before continuing the test, we need to ensure that the IPC + // message that starts autoscrolling has had time to arrive. + executeSoon(continueTest); + } + + function continueTest() { // Most key events should be eaten by the browser. expectedKeyEvents = kNoKeyEvents; sendChar("A"); diff --git a/toolkit/content/widgets/browser.xml b/toolkit/content/widgets/browser.xml index 135511314bd..dde26a99eb3 100644 --- a/toolkit/content/widgets/browser.xml +++ b/toolkit/content/widgets/browser.xml @@ -17,7 +17,7 @@ - + @@ -793,6 +785,12 @@ this.addEventListener("pageshow", this.onPageShow, true); this.addEventListener("pagehide", this.onPageHide, true); this.addEventListener("DOMPopupBlocked", this.onPopupBlocked, true); + + if (this.messageManager) { + this.messageManager.addMessageListener("Autoscroll:Start", this); + this.messageManager.addMessageListener("Autoscroll:Cancel", this); + this.messageManager.loadFrameScript("chrome://global/content/browser-content.js", true); + } ]]> @@ -843,6 +841,34 @@ + + + + + + + + + + + @@ -871,20 +897,17 @@ 10 - null + false null null - null - null - null null false @@ -909,9 +933,10 @@ - - - + + + and - // Elements or non-html elements such as svg or Document nodes - // also make sure to skip select elements that are not multiline - if (!(this._scrollable instanceof HTMLElement) || - ((this._scrollable instanceof HTMLSelectElement) && !this._scrollable.multiple)) { - continue; - } - - var overflowx = this._scrollable.ownerDocument.defaultView - .getComputedStyle(this._scrollable, '') - .getPropertyValue('overflow-x'); - var overflowy = this._scrollable.ownerDocument.defaultView - .getComputedStyle(this._scrollable, '') - .getPropertyValue('overflow-y'); - // we already discarded non-multiline selects so allow vertical - // scroll for multiline ones directly without checking for a - // overflow property - var scrollVert = this._scrollable.scrollTopMax && - (this._scrollable instanceof HTMLSelectElement || - scrollingAllowed.indexOf(overflowy) >= 0); - - // do not allow horizontal scrolling for select elements, it leads - // to visual artifacts and is not the expected behavior anyway - if (!(this._scrollable instanceof HTMLSelectElement) && - this._scrollable.scrollLeftMax && - scrollingAllowed.indexOf(overflowx) >= 0) { - this._autoScrollPopup.setAttribute("scrolldir", scrollVert ? "NSEW" : "EW"); - break; - } - else if (scrollVert) { - this._autoScrollPopup.setAttribute("scrolldir", "NS"); - break; - } - } - - if (!this._scrollable) { - this._scrollable = event.originalTarget.ownerDocument.defaultView; - if (this._scrollable.scrollMaxX > 0) { - this._autoScrollPopup.setAttribute("scrolldir", this._scrollable.scrollMaxY > 0 ? "NSEW" : "EW"); - } - else if (this._scrollable.scrollMaxY > 0) { - this._autoScrollPopup.setAttribute("scrolldir", "NS"); - } - else { - this._scrollable = null; // abort scrolling - return; - } - } - + this._autoScrollPopup.setAttribute("scrolldir", scrolldir); this._autoScrollPopup.addEventListener("popuphidden", this, true); this._autoScrollPopup.showPopup(document.documentElement, - event.screenX, - event.screenY, + screenX, + screenY, "popup", null, null); this._ignoreMouseEvents = true; - this._startX = event.screenX; - this._startY = event.screenY; - this._screenX = event.screenX; - this._screenY = event.screenY; - this._scrollErrorX = 0; - this._scrollErrorY = 0; - this._lastFrame = window.mozAnimationStartTime; + this._scrolling = true; + this._startX = screenX; + this._startY = screenY; window.addEventListener("mousemove", this, true); window.addEventListener("mousedown", this, true); @@ -1010,144 +976,19 @@ window.addEventListener("keydown", this, true); window.addEventListener("keypress", this, true); window.addEventListener("keyup", this, true); - - window.mozRequestAnimationFrame(this); ]]> - - - - 0) - return Math.floor(num); - return Math.ceil(num); - ]]> - - - - - - - - 1) - return val * Math.sqrt(val) - 1; - if (val < -1) - return val * Math.sqrt(-val) + 1; - return 0; - ]]> - - - - - - - - - - - - - - - - - - - - - - - - this._AUTOSCROLL_SNAP || x < -this._AUTOSCROLL_SNAP) || (y > this._AUTOSCROLL_SNAP || y < -this._AUTOSCROLL_SNAP)) @@ -1314,17 +1155,6 @@ } ]]> - - - - + null @@ -265,7 +265,13 @@ Cu.import("resource://gre/modules/SelectParentHelper.jsm"); let dropdown = document.getElementById(this.getAttribute("selectpopup")); SelectParentHelper.hide(dropdown); + break; } + + default: + // Delegate to browser.xml. + return this._receiveMessage(aMessage); + break; } ]]> diff --git a/toolkit/crashreporter/test/unit/test_crash_AsyncShutdown.js b/toolkit/crashreporter/test/unit/test_crash_AsyncShutdown.js index 78e084a7364..ffcf02314ab 100644 --- a/toolkit/crashreporter/test/unit/test_crash_AsyncShutdown.js +++ b/toolkit/crashreporter/test/unit/test_crash_AsyncShutdown.js @@ -40,6 +40,7 @@ function setup_osfile_crash_noerror() { Services.prefs.setBoolPref("toolkit.osfile.debug.failshutdown", true); Services.prefs.setIntPref("toolkit.asyncshutdown.crash_timeout", 1); + Services.prefs.setBoolPref("toolkit.osfile.native", false); OS.File.getCurrentDirectory(); Services.obs.notifyObservers(null, "profile-before-change", null); @@ -68,6 +69,7 @@ function setup_osfile_crash_exn() { Services.prefs.setBoolPref("toolkit.osfile.debug.failshutdown", true); Services.prefs.setIntPref("toolkit.asyncshutdown.crash_timeout", 1); + Services.prefs.setBoolPref("toolkit.osfile.native", false); OS.File.read("I do not exist"); Services.obs.notifyObservers(null, "profile-before-change", null); @@ -80,7 +82,6 @@ function after_osfile_crash_exn(mdump, extra) { let state = info.conditions[0].state; do_print("Keys: " + Object.keys(state).join(", ")); do_check_eq(info.phase, "profile-before-change"); - do_check_true(state.launched); do_check_false(state.shutdown); do_check_true(state.worker); do_check_true(!!state.latestSent); diff --git a/toolkit/devtools/server/tests/browser/browser_storage_dynamic_windows.js b/toolkit/devtools/server/tests/browser/browser_storage_dynamic_windows.js index 58ba772070b..7579bbff337 100644 --- a/toolkit/devtools/server/tests/browser/browser_storage_dynamic_windows.js +++ b/toolkit/devtools/server/tests/browser/browser_storage_dynamic_windows.js @@ -114,6 +114,7 @@ function testReload() { info("shouldBeEmptyFirst is empty now"); } else if (!data.added || !Object.keys(data.added).length) { + info(JSON.stringify(data)); ok(false, "deleted object should have something if an added object " + "does not have anything"); endTestReloaded(); @@ -159,6 +160,7 @@ function testAddIframe() { }; let onStoresUpdate = data => { + info(JSON.stringify(data)); info("checking if the hosts list is correct for this iframe addition"); markOutMatched(shouldBeEmpty, data.added); diff --git a/toolkit/identity/FirefoxAccounts.jsm b/toolkit/identity/FirefoxAccounts.jsm index 2e9a0600f60..e12d80e44f6 100644 --- a/toolkit/identity/FirefoxAccounts.jsm +++ b/toolkit/identity/FirefoxAccounts.jsm @@ -87,20 +87,38 @@ FxAccountsService.prototype = { */ watch: function watch(aRpCaller) { this._rpFlows.set(aRpCaller.id, aRpCaller); + log.debug("watch: " + aRpCaller.id); log.debug("Current rp flows: " + this._rpFlows.size); - // Nothing to do but call ready() + // Log the user in, if possible, and then call ready(). let runnable = { run: () => { - this.doReady(aRpCaller.id); + this.fxAccountsManager.getAssertion(aRpCaller.audience, {silent:true}).then( + data => { + if (data) { + this.doLogin(aRpCaller.id, data); + } else { + this.doLogout(aRpCaller.id); + } + this.doReady(aRpCaller.id); + }, + error => { + log.error("get silent assertion failed: " + JSON.stringify(error)); + this.doError(aRpCaller.id, error.toString()); + } + ); } }; Services.tm.currentThread.dispatch(runnable, Ci.nsIThread.DISPATCH_NORMAL); }, - unwatch: function(aRpCaller, aTargetMM) { - // nothing to do + /** + * Delete the flow when the screen is unloaded + */ + unwatch: function(aRpCallerId, aTargetMM) { + log.debug("unwatching: " + aRpCallerId); + this._rpFlows.delete(aRpCallerId); }, /** @@ -160,7 +178,9 @@ FxAccountsService.prototype = { // Call logout() on the next tick let runnable = { run: () => { - this.doLogout(aRpCallerId); + this.fxAccountsManager.signOut().then(() => { + this.doLogout(aRpCallerId); + }); } }; Services.tm.currentThread.dispatch(runnable, diff --git a/toolkit/identity/tests/unit/head_identity.js b/toolkit/identity/tests/unit/head_identity.js index 70fa5809394..20917c794f1 100644 --- a/toolkit/identity/tests/unit/head_identity.js +++ b/toolkit/identity/tests/unit/head_identity.js @@ -9,7 +9,7 @@ const Cr = Components.results; Cu.import("resource://testing-common/httpd.js"); // XXX until bug 937114 is fixed -Cu.importGlobalProperties(['atob']); +Cu.importGlobalProperties(["atob"]); // The following boilerplate makes sure that XPCom calls // that use the profile directory work. @@ -43,7 +43,7 @@ const TEST_URL2 = "https://myfavoritebaconinacan.com"; const TEST_USER = "user@mozilla.com"; const TEST_PRIVKEY = "fake-privkey"; const TEST_CERT = "fake-cert"; -const TEST_ASSERTION = "face-assertion"; +const TEST_ASSERTION = "fake-assertion"; const TEST_IDPPARAMS = { domain: "myfavoriteflan.com", authentication: "/foo/authenticate.html", @@ -72,8 +72,8 @@ function uuid() { } function base64UrlDecode(s) { - s = s.replace(/-/g, '+'); - s = s.replace(/_/g, '/'); + s = s.replace(/-/g, "+"); + s = s.replace(/_/g, "/"); // Replace padding if it was stripped by the sender. // See http://tools.ietf.org/html/rfc4648#section-4 @@ -101,15 +101,15 @@ function mock_doc(aIdentity, aOrigin, aDoFunc) { mockedDoc.id = uuid(); mockedDoc.loggedInUser = aIdentity; mockedDoc.origin = aOrigin; - mockedDoc['do'] = aDoFunc; + mockedDoc["do"] = aDoFunc; mockedDoc._mm = TEST_MESSAGE_MANAGER; - mockedDoc.doReady = partial(aDoFunc, 'ready'); - mockedDoc.doLogin = partial(aDoFunc, 'login'); - mockedDoc.doLogout = partial(aDoFunc, 'logout'); - mockedDoc.doError = partial(aDoFunc, 'error'); - mockedDoc.doCancel = partial(aDoFunc, 'cancel'); - mockedDoc.doCoffee = partial(aDoFunc, 'coffee'); - mockedDoc.childProcessShutdown = partial(aDoFunc, 'child-process-shutdown'); + mockedDoc.doReady = partial(aDoFunc, "ready"); + mockedDoc.doLogin = partial(aDoFunc, "login"); + mockedDoc.doLogout = partial(aDoFunc, "logout"); + mockedDoc.doError = partial(aDoFunc, "error"); + mockedDoc.doCancel = partial(aDoFunc, "cancel"); + mockedDoc.doCoffee = partial(aDoFunc, "coffee"); + mockedDoc.childProcessShutdown = partial(aDoFunc, "child-process-shutdown"); mockedDoc.RP = mockedDoc; @@ -127,9 +127,9 @@ function mock_fxa_rp(aIdentity, aOrigin, aDoFunc) { mockedDoc.doReady = partial(aDoFunc, "ready"); mockedDoc.doLogin = partial(aDoFunc, "login"); mockedDoc.doLogout = partial(aDoFunc, "logout"); - mockedDoc.doError = partial(aDoFunc, 'error'); - mockedDoc.doCancel = partial(aDoFunc, 'cancel'); - mockedDoc.childProcessShutdown = partial(aDoFunc, 'child-process-shutdown'); + mockedDoc.doError = partial(aDoFunc, "error"); + mockedDoc.doCancel = partial(aDoFunc, "cancel"); + mockedDoc.childProcessShutdown = partial(aDoFunc, "child-process-shutdown"); mockedDoc.RP = mockedDoc; diff --git a/toolkit/identity/tests/unit/test_firefox_accounts.js b/toolkit/identity/tests/unit/test_firefox_accounts.js index 7434740a5fe..698f2af3b45 100644 --- a/toolkit/identity/tests/unit/test_firefox_accounts.js +++ b/toolkit/identity/tests/unit/test_firefox_accounts.js @@ -14,13 +14,19 @@ XPCOMUtils.defineLazyModuleGetter(this, "FirefoxAccounts", // data. do_get_profile(); -function MockFXAManager() {} +function MockFXAManager() { + this.signedIn = true; +} MockFXAManager.prototype = { getAssertion: function(audience) { - let deferred = Promise.defer(); - deferred.resolve(TEST_ASSERTION); - return deferred.promise; - } + let result = this.signedIn ? TEST_ASSERTION : null; + return Promise.resolve(result); + }, + + signOut: function() { + this.signedIn = false; + return Promise.resolve(null); + }, } let originalManager = FirefoxAccounts.fxAccountsManager; @@ -45,13 +51,49 @@ function test_mock() { }); } -function test_watch() { +function test_watch_signed_in() { do_test_pending(); + let received = []; + + let mockedRP = mock_fxa_rp(null, TEST_URL, function(method, data) { + received.push([method, data]); + + if (method == "ready") { + // confirm that we were signed in and then ready was called + do_check_eq(received.length, 2); + do_check_eq(received[0][0], "login"); + do_check_eq(received[0][1], TEST_ASSERTION); + do_check_eq(received[1][0], "ready"); + do_test_finished(); + run_next_test(); + } + }); + + FirefoxAccounts.RP.watch(mockedRP); +} + +function test_watch_signed_out() { + do_test_pending(); + + let received = []; + let signedInState = FirefoxAccounts.fxAccountsManager.signedIn; + FirefoxAccounts.fxAccountsManager.signedIn = false; + let mockedRP = mock_fxa_rp(null, TEST_URL, function(method) { - do_check_eq(method, "ready"); - do_test_finished(); - run_next_test(); + received.push(method); + + if (method == "ready") { + // confirm that we were signed out and then ready was called + do_check_eq(received.length, 2); + do_check_eq(received[0], "logout"); + do_check_eq(received[1], "ready"); + + // restore initial state + FirefoxAccounts.fxAccountsManager.signedIn = signedInState; + do_test_finished(); + run_next_test(); + } }); FirefoxAccounts.RP.watch(mockedRP); @@ -62,21 +104,31 @@ function test_request() { let received = []; - let mockedRP = mock_fxa_rp(null, TEST_URL, function(method) { - // We will received "ready" as a result of watch(), then "login" - // as a result of request() - received.push(method); + // initially signed out + let signedInState = FirefoxAccounts.fxAccountsManager.signedIn; + FirefoxAccounts.fxAccountsManager.signedIn = false; - if (received.length == 2) { - do_check_eq(received[0], "ready"); - do_check_eq(received[1], "login"); - do_test_finished(); - run_next_test(); + let mockedRP = mock_fxa_rp(null, TEST_URL, function(method, data) { + received.push([method, data]); + + // On watch(), we are signed out. Then we call request(). + if (received.length === 2) { + do_check_eq(received[0][0], "logout"); + do_check_eq(received[1][0], "ready"); + + // Pretend request() showed ux and the user signed in + FirefoxAccounts.fxAccountsManager.signedIn = true; + FirefoxAccounts.RP.request(mockedRP.id); } - // Second, call request() - if (method == "ready") { - FirefoxAccounts.RP.request(mockedRP.id); + if (received.length === 3) { + do_check_eq(received[2][0], "login"); + do_check_eq(received[2][1], TEST_ASSERTION); + + // restore initial state + FirefoxAccounts.fxAccountsManager.signedIn = signedInState; + do_test_finished(); + run_next_test(); } }); @@ -90,20 +142,20 @@ function test_logout() { let received = []; let mockedRP = mock_fxa_rp(null, TEST_URL, function(method) { - // We will receive "ready" as a result of watch(), and "logout" - // as a result of logout() received.push(method); - if (received.length == 2) { - do_check_eq(received[0], "ready"); - do_check_eq(received[1], "logout"); - do_test_finished(); - run_next_test(); + // At first, watch() signs us in automatically. Then we sign out. + if (received.length === 2) { + do_check_eq(received[0], "login"); + do_check_eq(received[1], "ready"); + + FirefoxAccounts.RP.logout(mockedRP.id); } - if (method == "ready") { - // Second, call logout() - FirefoxAccounts.RP.logout(mockedRP.id); + if (received.length === 3) { + do_check_eq(received[2], "logout"); + do_test_finished(); + run_next_test(); } }); @@ -122,31 +174,21 @@ function test_error() { let originalManager = FirefoxAccounts.fxAccountsManager; FirefoxAccounts.RP.fxAccountsManager = { getAssertion: function(audience) { - return Promise.reject("barf!"); + return Promise.reject(new Error("barf!")); } }; let mockedRP = mock_fxa_rp(null, TEST_URL, function(method, message) { - // We will receive "ready" as a result of watch(), and "logout" - // as a result of logout() - received.push([method, message]); + // We will immediately receive an error, due to watch()'s attempt + // to getAssertion(). + do_check_eq(method, "error"); + do_check_true(/barf/.test(message)); - if (received.length == 2) { - do_check_eq(received[0][0], "ready"); + // Put things back the way they were + FirefoxAccounts.fxAccountsManager = originalManager; - do_check_eq(received[1][0], "error"); - do_check_eq(received[1][1], "barf!"); - - // Put things back the way they were - FirefoxAccounts.fxAccountsManager = originalManager; - - do_test_finished(); - run_next_test(); - } - - if (method == "ready") { - FirefoxAccounts.RP.request(mockedRP.id); - } + do_test_finished(); + run_next_test(); }); // First, call watch() @@ -197,7 +239,8 @@ function test_child_process_shutdown() { let TESTS = [ test_overall, test_mock, - test_watch, + test_watch_signed_in, + test_watch_signed_out, test_request, test_logout, test_error, diff --git a/toolkit/modules/BrowserUtils.jsm b/toolkit/modules/BrowserUtils.jsm index 257e49697ea..3b3cfa3e5c7 100644 --- a/toolkit/modules/BrowserUtils.jsm +++ b/toolkit/modules/BrowserUtils.jsm @@ -6,7 +6,7 @@ this.EXPORTED_SYMBOLS = [ "BrowserUtils" ]; -const {interfaces: Ci, utils: Cu} = Components; +const {interfaces: Ci, utils: Cu, classes: Cc} = Components; Cu.import("resource://gre/modules/Services.jsm"); @@ -64,6 +64,31 @@ this.BrowserUtils = { return Services.io.newFileURI(aFile); }, + /** + * Return the current focus element and window. If the current focus + * is in a content process, then this function returns CPOWs + * (cross-process object wrappers) that refer to the focused + * items. Note that calling this function synchronously contacts the + * content process, which may block for a long time. + * + * @param document The document in question. + * @return [focusedElement, focusedWindow] + */ + getFocusSync: function(document) { + let elt = document.commandDispatcher.focusedElement; + var window = document.commandDispatcher.focusedWindow; + + const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + if (elt instanceof window.XULElement && + elt.localName == "browser" && + elt.namespaceURI == XUL_NS && + elt.getAttribute("remote")) { + [elt, window] = elt.syncHandler.getFocusedElementAndWindow(); + } + + return [elt, window]; + }, + /** * For a given DOM element, returns its position in "screen" * coordinates. In a content process, the coordinates returned will diff --git a/toolkit/mozapps/extensions/AddonManager.jsm b/toolkit/mozapps/extensions/AddonManager.jsm index a075bd901b0..46da6db8154 100644 --- a/toolkit/mozapps/extensions/AddonManager.jsm +++ b/toolkit/mozapps/extensions/AddonManager.jsm @@ -1074,34 +1074,30 @@ var AddonManagerInternal = { // are up to date before checking for addon updates. AddonRepository.backgroundUpdateCheck( ids, function BUC_backgroundUpdateCheckCallback() { - AddonManagerInternal.updateAddonRepositoryData( - function BUC_updateAddonCallback() { + pendingUpdates += aAddons.length; + aAddons.forEach(function BUC_forEachCallback(aAddon) { + if (aAddon.id == hotfixID) { + notifyComplete(); + return; + } - pendingUpdates += aAddons.length; - aAddons.forEach(function BUC_forEachCallback(aAddon) { - if (aAddon.id == hotfixID) { - notifyComplete(); - return; - } + // Check all add-ons for updates so that any compatibility updates will + // be applied + aAddon.findUpdates({ + onUpdateAvailable: function BUC_onUpdateAvailable(aAddon, aInstall) { + // Start installing updates when the add-on can be updated and + // background updates should be applied. + if (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE && + AddonManager.shouldAutoUpdate(aAddon)) { + aInstall.install(); + } + }, - // Check all add-ons for updates so that any compatibility updates will - // be applied - aAddon.findUpdates({ - onUpdateAvailable: function BUC_onUpdateAvailable(aAddon, aInstall) { - // Start installing updates when the add-on can be updated and - // background updates should be applied. - if (aAddon.permissions & AddonManager.PERM_CAN_UPGRADE && - AddonManager.shouldAutoUpdate(aAddon)) { - aInstall.install(); - } - }, - - onUpdateFinished: notifyComplete - }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); - }); - - notifyComplete(); + onUpdateFinished: notifyComplete + }, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE); }); + + notifyComplete(); }); }); } @@ -1425,6 +1421,8 @@ var AddonManagerInternal = { }, noMoreObjects: function updateAddonRepositoryData_noMoreObjects(aCaller) { safeCall(aCallback); + // only tests should care about this + Services.obs.notifyObservers(null, "TEST:addon-repository-data-updated", null); } }); }, diff --git a/toolkit/mozapps/extensions/content/selectAddons.js b/toolkit/mozapps/extensions/content/selectAddons.js index 6291a268be9..d0ea99838b6 100644 --- a/toolkit/mozapps/extensions/content/selectAddons.js +++ b/toolkit/mozapps/extensions/content/selectAddons.js @@ -98,21 +98,18 @@ var gChecking = { // individual addon updates. let ids = [addon.id for each (addon in aAddons)]; AddonRepository.repopulateCache(ids, function gChecking_repopulateCache() { - AddonManagerPrivate.updateAddonRepositoryData(function gChecking_updateAddonRepositoryData() { - - for (let addonItem of aAddons) { - // Ignore disabled themes - if (addonItem.type != "theme" || !addonItem.userDisabled) { - gAddons[addonItem.id] = { - addon: addonItem, - install: null, - wasActive: addonItem.isActive - } + for (let addonItem of aAddons) { + // Ignore disabled themes + if (addonItem.type != "theme" || !addonItem.userDisabled) { + gAddons[addonItem.id] = { + addon: addonItem, + install: null, + wasActive: addonItem.isActive } - - addonItem.findUpdates(self, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); } - }); + + addonItem.findUpdates(self, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + } }); }); }, diff --git a/toolkit/mozapps/extensions/content/update.js b/toolkit/mozapps/extensions/content/update.js index 93c0d6230f7..29a43fc1681 100644 --- a/toolkit/mozapps/extensions/content/update.js +++ b/toolkit/mozapps/extensions/content/update.js @@ -19,10 +19,8 @@ Components.utils.import("resource://gre/modules/Services.jsm"); Components.utils.import("resource://gre/modules/AddonManager.jsm"); Components.utils.import("resource://gre/modules/addons/AddonRepository.jsm"); - -var gInteruptable = true; -var gPendingClose = false; - +Components.utils.import("resource://gre/modules/Log.jsm"); +let logger = Log.repository.getLogger("addons.update-dialog"); var gUpdateWizard = { // When synchronizing app compatibility info this contains all installed @@ -38,6 +36,10 @@ var gUpdateWizard = { shouldAutoCheck: false, xpinstallEnabled: true, xpinstallLocked: false, + // cached AddonInstall entries for add-ons we might want to update, + // keyed by add-on ID + addonInstalls: new Map(), + shuttingDown: false, init: function gUpdateWizard_init() { @@ -110,17 +112,26 @@ var gUpdateWizard = { onWizardCancel: function gUpdateWizard_onWizardCancel() { - if (!gInteruptable) { - gPendingClose = true; - this._setUpButton("back", null, true); - this._setUpButton("next", null, true); - this._setUpButton("cancel", null, true); - return false; + gUpdateWizard.shuttingDown = true; + // Allow add-ons to continue downloading and installing + // in the background, though some may require a later restart + // Pages that are waiting for user input go into the background + // on cancel + if (gMismatchPage.waiting) { + logger.info("Dialog closed in mismatch page"); + if (gUpdateWizard.addonInstalls.size > 0) { + gInstallingPage.startInstalls([i for ([, i] of gUpdateWizard.addonInstalls)]); + } + return true; } - if (gInstallingPage.installing) { - gInstallingPage.cancelInstalls(); - return false; + // Pages that do asynchronous things will just keep running and check + // gUpdateWizard.shuttingDown to trigger background behaviour + if (!gInstallingPage.installing) { + logger.info("Dialog closed while waiting for updated compatibility information"); + } + else { + logger.info("Dialog closed while downloading and installing updates"); } return true; } @@ -143,6 +154,7 @@ var gOfflinePage = { var gVersionInfoPage = { _completeCount: 0, _totalCount: 0, + _versionInfoDone: false, onPageShow: function gVersionInfoPage_onPageShow() { gUpdateWizard.setButtonLabels(null, true, @@ -156,28 +168,29 @@ var gVersionInfoPage = { // Retrieve all add-ons in order to sync their app compatibility information AddonManager.getAllAddons(function gVersionInfoPage_getAllAddons(aAddons) { - gUpdateWizard.addons = aAddons.filter(function gVersionInfoPage_filterAddons(a) { - return a.type != "plugin" && a.id != hotfixID; - }); + if (gUpdateWizard.shuttingDown) { + logger.debug("getAllAddons completed after dialog closed"); + } + + gUpdateWizard.addons = [a for (a of aAddons) + if (a.type != "plugin" && a.id != hotfixID)]; gVersionInfoPage._totalCount = gUpdateWizard.addons.length; // Ensure compatibility overrides are up to date before checking for // individual addon updates. - let ids = [addon.id for each (addon in gUpdateWizard.addons)]; + let ids = [addon.id for (addon of gUpdateWizard.addons)]; - gInteruptable = false; - AddonRepository.repopulateCache(ids, function gVersionInfoPage_repolulateCache() { - AddonManagerPrivate.updateAddonRepositoryData(function gVersionInfoPage_updateAddonRepoData() { - gInteruptable = true; - if (gPendingClose) { - window.close(); - return; - } + AddonRepository.repopulateCache(ids, function gVersionInfoPage_repopulateCache() { - for (let addon of gUpdateWizard.addons) - addon.findUpdates(gVersionInfoPage, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); - }); + if (gUpdateWizard.shuttingDown) { + logger.debug("repopulateCache completed after dialog closed"); + } + + for (let addon of gUpdateWizard.addons) { + logger.debug("VersionInfo Finding updates for " + addon.id); + addon.findUpdates(gVersionInfoPage, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + } }, METADATA_TIMEOUT); }); }, @@ -185,15 +198,39 @@ var gVersionInfoPage = { onAllUpdatesFinished: function gVersionInfoPage_onAllUpdatesFinished() { // Filter out any add-ons that were disabled before the application was // upgraded or are already compatible - gUpdateWizard.addons = gUpdateWizard.addons.filter(function onAllUpdatesFinished_filterAddons(a) { - return a.appDisabled && gUpdateWizard.inactiveAddonIDs.indexOf(a.id) < 0; - }); + logger.debug("VersionInfo updates finished: inactive " + + gUpdateWizard.inactiveAddonIDs.toSource() + " found " + + [addon.id + ":" + addon.appDisabled for (addon of gUpdateWizard.addons)].toSource()); + let filteredAddons = []; + for (let a of gUpdateWizard.addons) { + if (a.appDisabled && gUpdateWizard.inactiveAddonIDs.indexOf(a.id) < 0) { + logger.debug("Continuing with add-on " + a.id); + filteredAddons.push(a); + } + else if (gUpdateWizard.addonInstalls.has(a.id)) { + gUpdateWizard.addonInstalls.get(a.id).cancel(); + gUpdateWizard.addonInstalls.delete(a.id); + } + } + gUpdateWizard.addons = filteredAddons; - if (gUpdateWizard.addons.length > 0) { - // There are still incompatible addons, inform the user. + if (gUpdateWizard.shuttingDown) { + // jump directly to updating auto-update add-ons in the background + if (gUpdateWizard.addonInstalls.size > 0) { + gInstallingPage.startInstalls([i for ([, i] of gUpdateWizard.addonInstalls)]); + } + return; + } + + if (filteredAddons.length > 0) { + if (!gUpdateWizard.xpinstallEnabled && gUpdateWizard.xpinstallLocked) { + document.documentElement.currentPage = document.getElementById("adminDisabled"); + return; + } document.documentElement.currentPage = document.getElementById("mismatch"); } else { + logger.info("VersionInfo: No updates require further action"); // VersionInfo compatibility updates resolved all compatibility problems, // close this window and continue starting the application... //XXX Bug 314754 - We need to use setTimeout to close the window due to @@ -205,34 +242,54 @@ var gVersionInfoPage = { ///////////////////////////////////////////////////////////////////////////// // UpdateListener onUpdateFinished: function gVersionInfoPage_onUpdateFinished(aAddon, status) { - // If the add-on is now active then it won't have been disabled by startup - if (aAddon.active) - AddonManagerPrivate.removeStartupChange("disabled", aAddon.id); - - if (status != AddonManager.UPDATE_STATUS_NO_ERROR) - gUpdateWizard.errorItems.push(aAddon); - ++this._completeCount; - // Update the status text and progress bar - var updateStrings = document.getElementById("updateStrings"); - var statusElt = document.getElementById("versioninfo.status"); - var statusString = updateStrings.getFormattedString("statusPrefix", [aAddon.name]); - statusElt.setAttribute("value", statusString); + if (status != AddonManager.UPDATE_STATUS_NO_ERROR) { + logger.debug("VersionInfo update " + this._completeCount + " of " + this._totalCount + + " failed for " + aAddon.id + ": " + status); + gUpdateWizard.errorItems.push(aAddon); + } + else { + logger.debug("VersionInfo update " + this._completeCount + " of " + this._totalCount + + " finished for " + aAddon.id); + } - // Update the status text and progress bar - var progress = document.getElementById("versioninfo.progress"); - progress.mode = "normal"; - progress.value = Math.ceil((this._completeCount / this._totalCount) * 100); + // If we're not in the background, just make a list of add-ons that have + // updates available + if (!gUpdateWizard.shuttingDown) { + // If we're still in the update check window and the add-on is now active + // then it won't have been disabled by startup + if (aAddon.active) + AddonManagerPrivate.removeStartupChange("disabled", aAddon.id); + + // Update the status text and progress bar + var updateStrings = document.getElementById("updateStrings"); + var statusElt = document.getElementById("versioninfo.status"); + var statusString = updateStrings.getFormattedString("statusPrefix", [aAddon.name]); + statusElt.setAttribute("value", statusString); + + // Update the status text and progress bar + var progress = document.getElementById("versioninfo.progress"); + progress.mode = "normal"; + progress.value = Math.ceil((this._completeCount / this._totalCount) * 100); + } if (this._completeCount == this._totalCount) this.onAllUpdatesFinished(); }, + + onUpdateAvailable: function gVersionInfoPage_onUpdateAvailable(aAddon, aInstall) { + logger.debug("VersionInfo got an install for " + aAddon.id + ": " + aAddon.version); + gUpdateWizard.addonInstalls.set(aAddon.id, aInstall); + }, }; var gMismatchPage = { + waiting: false, + onPageShow: function gMismatchPage_onPageShow() { + gMismatchPage.waiting = true; gUpdateWizard.setButtonLabels(null, true, "mismatchCheckNow", false, "mismatchDontCheck", false); @@ -252,11 +309,7 @@ var gUpdatePage = { _completeCount: 0, onPageShow: function gUpdatePage_onPageShow() { - if (!gUpdateWizard.xpinstallEnabled && gUpdateWizard.xpinstallLocked) { - document.documentElement.currentPage = document.getElementById("adminDisabled"); - return; - } - + gMismatchPage.waiting = false; gUpdateWizard.setButtonLabels(null, true, "nextButtonText", true, "cancelButtonText", false); @@ -265,11 +318,18 @@ var gUpdatePage = { gUpdateWizard.errorItems = []; this._totalCount = gUpdateWizard.addons.length; - for (let addon of gUpdateWizard.addons) + for (let addon of gUpdateWizard.addons) { + logger.debug("UpdatePage requesting update for " + addon.id); + // Redundant call to find updates again here when we already got them + // in the VersionInfo page: https://bugzilla.mozilla.org/show_bug.cgi?id=960597 addon.findUpdates(this, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED); + } }, onAllUpdatesFinished: function gUpdatePage_onAllUpdatesFinished() { + if (gUpdateWizard.shuttingDown) + return; + var nextPage = document.getElementById("noupdates"); if (gUpdateWizard.addonsToUpdate.length > 0) nextPage = document.getElementById("found"); @@ -279,6 +339,7 @@ var gUpdatePage = { ///////////////////////////////////////////////////////////////////////////// // UpdateListener onUpdateAvailable: function gUpdatePage_onUpdateAvailable(aAddon, aInstall) { + logger.debug("UpdatePage got an update for " + aAddon.id + ": " + aAddon.version); gUpdateWizard.addonsToUpdate.push(aInstall); }, @@ -288,14 +349,16 @@ var gUpdatePage = { ++this._completeCount; - // Update the status text and progress bar - var updateStrings = document.getElementById("updateStrings"); - var statusElt = document.getElementById("checking.status"); - var statusString = updateStrings.getFormattedString("statusPrefix", [aAddon.name]); - statusElt.setAttribute("value", statusString); + if (!gUpdateWizard.shuttingDown) { + // Update the status text and progress bar + var updateStrings = document.getElementById("updateStrings"); + var statusElt = document.getElementById("checking.status"); + var statusString = updateStrings.getFormattedString("statusPrefix", [aAddon.name]); + statusElt.setAttribute("value", statusString); - var progress = document.getElementById("checking.progress"); - progress.value = Math.ceil((this._completeCount / this._totalCount) * 100); + var progress = document.getElementById("checking.progress"); + progress.value = Math.ceil((this._completeCount / this._totalCount) * 100); + } if (this._completeCount == this._totalCount) this.onAllUpdatesFinished() @@ -371,24 +434,41 @@ var gInstallingPage = { _currentInstall : -1, _installing : false, + // Initialize fields we need for installing and tracking progress, + // and start iterating through the installations + startInstalls: function gInstallingPage_startInstalls(aInstallList) { + if (!gUpdateWizard.xpinstallEnabled) { + return; + } + + logger.debug("Start installs for " + + [i.existingAddon.id for (i of aInstallList)].toSource()); + this._errors = []; + this._installs = aInstallList; + this._installing = true; + this.startNextInstall(); + }, + onPageShow: function gInstallingPage_onPageShow() { gUpdateWizard.setButtonLabels(null, true, "nextButtonText", true, null, true); - this._errors = []; var foundUpdates = document.getElementById("found.updates"); var updates = foundUpdates.getElementsByTagName("listitem"); + let toInstall = []; for (let update of updates) { - if (!update.checked) + if (!update.checked) { + logger.info("User chose to cancel update of " + update.label); + update.install.cancel(); continue; - this._installs.push(update.install); + } + toInstall.push(update.install); } - this._strings = document.getElementById("updateStrings"); - this._installing = true; - this.startNextInstall(); + + this.startInstalls(toInstall); }, startNextInstall: function gInstallingPage_startNextInstall() { @@ -399,25 +479,35 @@ var gInstallingPage = { this._currentInstall++; if (this._installs.length == this._currentInstall) { + Services.obs.notifyObservers(null, "TEST:all-updates-done", null); this._installing = false; + if (gUpdateWizard.shuttingDown) { + return; + } var nextPage = this._errors.length > 0 ? "installerrors" : "finished"; document.getElementById("installing").setAttribute("next", nextPage); document.documentElement.advance(); return; } - this._installs[this._currentInstall].addListener(this); - this._installs[this._currentInstall].install(); - }, + let install = this._installs[this._currentInstall]; - cancelInstalls: function gInstallingPage_cancelInstalls() { - this._installs[this._currentInstall].removeListener(this); - this._installs[this._currentInstall].cancel(); + if (gUpdateWizard.shuttingDown && !AddonManager.shouldAutoUpdate(install.existingAddon)) { + logger.debug("Don't update " + install.existingAddon.id + " in background"); + install.cancel(); + this.startNextInstall(); + return; + } + install.addListener(this); + install.install(); }, ///////////////////////////////////////////////////////////////////////////// // InstallListener onDownloadStarted: function gInstallingPage_onDownloadStarted(aInstall) { + if (gUpdateWizard.shuttingDown) { + return; + } var strings = document.getElementById("updateStrings"); var label = strings.getFormattedString("downloadingPrefix", [aInstall.name]); var actionItem = document.getElementById("actionItem"); @@ -425,6 +515,9 @@ var gInstallingPage = { }, onDownloadProgress: function gInstallingPage_onDownloadProgress(aInstall) { + if (gUpdateWizard.shuttingDown) { + return; + } var downloadProgress = document.getElementById("downloadProgress"); downloadProgress.value = Math.ceil(100 * aInstall.progress / aInstall.maxProgress); }, @@ -439,6 +532,9 @@ var gInstallingPage = { }, onInstallStarted: function gInstallingPage_onInstallStarted(aInstall) { + if (gUpdateWizard.shuttingDown) { + return; + } var strings = document.getElementById("updateStrings"); var label = strings.getFormattedString("installingPrefix", [aInstall.name]); var actionItem = document.getElementById("actionItem"); @@ -446,9 +542,11 @@ var gInstallingPage = { }, onInstallEnded: function gInstallingPage_onInstallEnded(aInstall, aAddon) { - // Remember that this add-on was updated during startup - AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED, - aAddon.id); + if (!gUpdateWizard.shuttingDown) { + // Remember that this add-on was updated during startup + AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED, + aAddon.id); + } this.startNextInstall(); }, diff --git a/toolkit/mozapps/extensions/internal/AddonRepository.jsm b/toolkit/mozapps/extensions/internal/AddonRepository.jsm index 0c40b600244..6aa2b7ef683 100644 --- a/toolkit/mozapps/extensions/internal/AddonRepository.jsm +++ b/toolkit/mozapps/extensions/internal/AddonRepository.jsm @@ -474,8 +474,10 @@ this.AddonRepository = { get cacheEnabled() { // Act as though caching is disabled if there was an unrecoverable error // openning the database. - if (!AddonDatabase.databaseOk) + if (!AddonDatabase.databaseOk) { + logger.warn("Cache is disabled because database is not OK"); return false; + } let preference = PREF_GETADDONS_CACHE_ENABLED; let enabled = false; @@ -611,12 +613,19 @@ this.AddonRepository = { this._repopulateCacheInternal(aIds, aCallback, false, aTimeout); }, - _repopulateCacheInternal: function(aIds, aCallback, aSendPerformance, aTimeout) { + _repopulateCacheInternal: function (aIds, aCallback, aSendPerformance, aTimeout) { + // Always call AddonManager updateAddonRepositoryData after we refill the cache + function repopulateAddonManager() { + AddonManagerPrivate.updateAddonRepositoryData(aCallback); + } + + logger.debug("Repopulate add-on cache with " + aIds.toSource()); // Completely remove cache if caching is not enabled if (!this.cacheEnabled) { + logger.debug("Clearing cache because it is disabled"); this._addons = null; this._pendingCallbacks = null; - AddonDatabase.delete(aCallback); + AddonDatabase.delete(repopulateAddonManager); return; } @@ -624,9 +633,10 @@ this.AddonRepository = { getAddonsToCache(aIds, function repopulateCache_getAddonsToCache(aAddons) { // Completely remove cache if there are no add-ons to cache if (aAddons.length == 0) { + logger.debug("Clearing cache because 0 add-ons were requested"); self._addons = null; self._pendingCallbacks = null; - AddonDatabase.delete(aCallback); + AddonDatabase.delete(repopulateAddonManager); return; } @@ -634,12 +644,11 @@ this.AddonRepository = { searchSucceeded: function repopulateCacheInternal_searchSucceeded(aAddons) { self._addons = {}; aAddons.forEach(function(aAddon) { self._addons[aAddon.id] = aAddon; }); - AddonDatabase.repopulate(aAddons, aCallback); + AddonDatabase.repopulate(aAddons, repopulateAddonManager); }, searchFailed: function repopulateCacheInternal_searchFailed() { logger.warn("Search failed when repopulating cache"); - if (aCallback) - aCallback(); + repopulateAddonManager(); } }, aSendPerformance, aTimeout); }); @@ -648,7 +657,7 @@ this.AddonRepository = { /** * Asynchronously add add-ons to the cache corresponding to the specified * ids. If caching is disabled, the cache is unchanged and the callback is - * immediatly called if it is defined. + * immediately called if it is defined. * * @param aIds * The array of add-on ids to add to the cache @@ -656,6 +665,7 @@ this.AddonRepository = { * The optional callback to call once complete */ cacheAddons: function AddonRepo_cacheAddons(aIds, aCallback) { + logger.debug("cacheAddons: enabled " + this.cacheEnabled + " IDs " + aIds.toSource()); if (!this.cacheEnabled) { if (aCallback) aCallback(); @@ -1397,6 +1407,8 @@ this.AddonRepository = { // Begins a new search if one isn't currently executing _beginSearch: function(aURI, aMaxResults, aCallback, aHandleResults, aTimeout) { if (this._searching || aURI == null || aMaxResults <= 0) { + logger.warn("AddonRepository search failed: searching " + this._searching + " aURI " + aURI + + " aMaxResults " + aMaxResults); aCallback.searchFailed(); return; } diff --git a/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm b/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm index 831b10417d8..f4d95a8ac03 100644 --- a/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm +++ b/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm @@ -45,7 +45,7 @@ var gRDF = Cc["@mozilla.org/rdf/rdf-service;1"]. getService(Ci.nsIRDFService); Cu.import("resource://gre/modules/Log.jsm"); -const LOGGER_ID = "addons.updates"; +const LOGGER_ID = "addons.update-checker"; // Create a new logger for use by the Addons Update Checker // (Requires AddonManager.jsm) diff --git a/toolkit/mozapps/extensions/internal/XPIProvider.jsm b/toolkit/mozapps/extensions/internal/XPIProvider.jsm index dbf858cdea6..bc40244b97a 100644 --- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm +++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm @@ -3725,6 +3725,7 @@ var XPIProvider = { let self = this; XPIDatabase.getVisibleAddons(null, function UARD_getVisibleAddonsCallback(aAddons) { let pending = aAddons.length; + logger.debug("updateAddonRepositoryData found " + pending + " visible add-ons"); if (pending == 0) { aCallback(); return; @@ -3735,18 +3736,19 @@ var XPIProvider = { aCallback(); } - aAddons.forEach(function UARD_forEachCallback(aAddon) { - AddonRepository.getCachedAddonByID(aAddon.id, + for (let addon of aAddons) { + AddonRepository.getCachedAddonByID(addon.id, function UARD_getCachedAddonCallback(aRepoAddon) { if (aRepoAddon) { - aAddon._repositoryAddon = aRepoAddon; - aAddon.compatibilityOverrides = aRepoAddon.compatibilityOverrides; - self.updateAddonDisabledState(aAddon); + logger.debug("updateAddonRepositoryData got info for " + addon.id); + addon._repositoryAddon = aRepoAddon; + addon.compatibilityOverrides = aRepoAddon.compatibilityOverrides; + self.updateAddonDisabledState(addon); } notifyComplete(); }); - }); + }; }); }, @@ -4795,9 +4797,10 @@ AddonInstall.prototype = { * be closed before this method returns. * @param aCallback * A function to call when all of the add-on manifests have been - * loaded. + * loaded. Because this loadMultipackageManifests is an internal API + * we don't exception-wrap this callback */ - loadMultipackageManifests: function AI_loadMultipackageManifests(aZipReader, + _loadMultipackageManifests: function AI_loadMultipackageManifests(aZipReader, aCallback) { let files = []; let entries = aZipReader.findEntries("(*.[Xx][Pp][Ii]|*.[Jj][Aa][Rr])"); @@ -4842,7 +4845,7 @@ AddonInstall.prototype = { if (!addon) { // No valid add-on was found - makeSafe(aCallback)(); + aCallback(); return; } @@ -4891,7 +4894,7 @@ AddonInstall.prototype = { }, this); } else { - makeSafe(aCallback)(); + aCallback(); } }, @@ -4971,7 +4974,7 @@ AddonInstall.prototype = { } if (this.addon.type == "multipackage") { - this.loadMultipackageManifests(zipreader, function loadManifest_loadMultipackageManifests() { + this._loadMultipackageManifests(zipreader, function loadManifest_loadMultipackageManifests() { addRepositoryData(self.addon); }); return; @@ -5251,7 +5254,7 @@ AddonInstall.prototype = { * The error code to pass to the listeners */ downloadFailed: function AI_downloadFailed(aReason, aError) { - logger.warn("Download failed", aError); + logger.warn("Download of " + this.sourceURI.spec + " failed", aError); this.state = AddonManager.STATE_DOWNLOAD_FAILED; this.error = aReason; XPIProvider.removeActiveInstall(this); @@ -5327,18 +5330,20 @@ AddonInstall.prototype = { // Find and cancel any pending installs for the same add-on in the same // install location - XPIProvider.installs.forEach(function(aInstall) { + for (let aInstall of XPIProvider.installs) { if (aInstall.state == AddonManager.STATE_INSTALLED && aInstall.installLocation == this.installLocation && - aInstall.addon.id == this.addon.id) + aInstall.addon.id == this.addon.id) { + logger.debug("Cancelling previous pending install of " + aInstall.addon.id); aInstall.cancel(); - }, this); + } + } let isUpgrade = this.existingAddon && this.existingAddon._installLocation == this.installLocation; let requiresRestart = XPIProvider.installRequiresRestart(this.addon); - logger.debug("Starting install of " + this.sourceURI.spec); + logger.debug("Starting install of " + this.addon.id + " from " + this.sourceURI.spec); AddonManagerPrivate.callAddonListeners("onInstalling", createWrapper(this.addon), requiresRestart); @@ -5394,7 +5399,7 @@ AddonInstall.prototype = { stream.close(); } - logger.debug("Staged install of " + this.sourceURI.spec + " ready; waiting for restart."); + logger.debug("Staged install of " + this.addon.id + " from " + this.sourceURI.spec + " ready; waiting for restart."); this.state = AddonManager.STATE_INSTALLED; if (isUpgrade) { delete this.existingAddon.pendingUpgrade; @@ -5856,8 +5861,10 @@ UpdateChecker.prototype = { // If the existing install has not yet started downloading then send an // available update notification. If it is already downloading then // don't send any available update notification - if (currentInstall.state == AddonManager.STATE_AVAILABLE) + if (currentInstall.state == AddonManager.STATE_AVAILABLE) { + logger.debug("Found an existing AddonInstall for " + this.addon.id); sendUpdateAvailableMessages(this, currentInstall); + } else sendUpdateAvailableMessages(this, null); return; diff --git a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js index 728b3c4d2c2..a7a4a834024 100644 --- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js +++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js @@ -1311,8 +1311,6 @@ this.XPIDatabase = { * * @param aAddon * The DBAddonInternal to make visible - * @param callback - * A callback to pass the DBAddonInternal to */ makeAddonVisible: function XPIDB_makeAddonVisible(aAddon) { logger.debug("Make addon " + aAddon._key + " visible"); diff --git a/toolkit/mozapps/extensions/test/browser/browser.ini b/toolkit/mozapps/extensions/test/browser/browser.ini index 2e1606a4fdb..7de81cdf418 100644 --- a/toolkit/mozapps/extensions/test/browser/browser.ini +++ b/toolkit/mozapps/extensions/test/browser/browser.ini @@ -2,6 +2,7 @@ support-files = addon_about.xul addon_prefs.xul + cancelCompatCheck.sjs discovery.html signed_hotfix.rdf signed_hotfix.xpi @@ -35,6 +36,7 @@ support-files = [browser_addonrepository_performance.js] [browser_bug557956.js] [browser_bug616841.js] +[browser_cancelCompatCheck.js] [browser_checkAddonCompatibility.js] [browser_hotfix.js] [browser_installssl.js] diff --git a/toolkit/mozapps/extensions/test/browser/browser_bug557956.js b/toolkit/mozapps/extensions/test/browser/browser_bug557956.js index 1d66162e151..581639aa5ca 100644 --- a/toolkit/mozapps/extensions/test/browser/browser_bug557956.js +++ b/toolkit/mozapps/extensions/test/browser/browser_bug557956.js @@ -425,7 +425,7 @@ add_test(function() { }); }); -// Tests that compatibility overrides are retreived and affect addon +// Tests that compatibility overrides are retrieved and affect addon // compatibility. add_test(function() { Services.prefs.setBoolPref(PREF_STRICT_COMPAT, false); diff --git a/toolkit/mozapps/extensions/test/browser/browser_cancelCompatCheck.js b/toolkit/mozapps/extensions/test/browser/browser_cancelCompatCheck.js new file mode 100644 index 00000000000..005e215718e --- /dev/null +++ b/toolkit/mozapps/extensions/test/browser/browser_cancelCompatCheck.js @@ -0,0 +1,536 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// Test that we can cancel the add-on compatibility check while it is +// in progress (bug 772484). +// Test framework copied from browser_bug557956.js + +const URI_EXTENSION_UPDATE_DIALOG = "chrome://mozapps/content/extensions/update.xul"; + +const PREF_GETADDONS_BYIDS = "extensions.getAddons.get.url"; +const PREF_MIN_PLATFORM_COMPAT = "extensions.minCompatiblePlatformVersion"; + +let repo = {}; +Components.utils.import("resource://gre/modules/addons/AddonRepository.jsm", repo); + +/** + * Test add-ons: + * + * Addon minVersion maxVersion Notes + * addon1 0 * + * addon2 0 0 + * addon3 0 0 + * addon4 1 * + * addon5 0 0 Made compatible by update check + * addon6 0 0 Made compatible by update check + * addon7 0 0 Has a broken update available + * addon8 0 0 Has an update available + * addon9 0 0 Has an update available + * addon10 0 0 Made incompatible by override check + */ + +// describe the addons +let ao1 = { file: "browser_bug557956_1", id: "addon1@tests.mozilla.org"}; +let ao2 = { file: "browser_bug557956_2", id: "addon2@tests.mozilla.org"}; +let ao3 = { file: "browser_bug557956_3", id: "addon3@tests.mozilla.org"}; +let ao4 = { file: "browser_bug557956_4", id: "addon4@tests.mozilla.org"}; +let ao5 = { file: "browser_bug557956_5", id: "addon5@tests.mozilla.org"}; +let ao6 = { file: "browser_bug557956_6", id: "addon6@tests.mozilla.org"}; +let ao7 = { file: "browser_bug557956_7", id: "addon7@tests.mozilla.org"}; +let ao8 = { file: "browser_bug557956_8_1", id: "addon8@tests.mozilla.org"}; +let ao9 = { file: "browser_bug557956_9_1", id: "addon9@tests.mozilla.org"}; +let ao10 = { file: "browser_bug557956_10", id: "addon10@tests.mozilla.org"}; + +// Return a promise that resolves after the specified delay in MS +function delayMS(aDelay) { + let deferred = Promise.defer(); + setTimeout(deferred.resolve, aDelay); + return deferred.promise; +} + +// Return a promise that resolves when the specified observer topic is notified +function promise_observer(aTopic) { + let deferred = Promise.defer(); + Services.obs.addObserver(function observe(aSubject, aObsTopic, aData) { + Services.obs.removeObserver(arguments.callee, aObsTopic); + deferred.resolve([aSubject, aData]); + }, aTopic, false); + return deferred.promise; +} + +// Install a set of addons using a bogus update URL so that we can force +// the compatibility update to happen later +// @param aUpdateURL The real update URL to use after the add-ons are installed +function promise_install_test_addons(aAddonList, aUpdateURL) { + info("Starting add-on installs"); + var installs = []; + let deferred = Promise.defer(); + + // Use a blank update URL + Services.prefs.setCharPref(PREF_UPDATEURL, TESTROOT + "missing.rdf"); + + for (let addon of aAddonList) { + AddonManager.getInstallForURL(TESTROOT + "addons/" + addon.file + ".xpi", function(aInstall) { + installs.push(aInstall); + }, "application/x-xpinstall"); + } + + var listener = { + installCount: 0, + + onInstallEnded: function() { + this.installCount++; + if (this.installCount == installs.length) { + info("Done add-on installs"); + // Switch to the test update URL + Services.prefs.setCharPref(PREF_UPDATEURL, aUpdateURL); + deferred.resolve(); + } + } + }; + + for (let install of installs) { + install.addListener(listener); + install.install(); + } + + return deferred.promise; +} + +function promise_addons_by_ids(aAddonIDs) { + info("promise_addons_by_ids " + aAddonIDs.toSource()); + let deferred = Promise.defer(); + AddonManager.getAddonsByIDs(aAddonIDs, deferred.resolve); + return deferred.promise; +} + +function* promise_uninstall_test_addons() { + info("Starting add-on uninstalls"); + let addons = yield promise_addons_by_ids([ao1.id, ao2.id, ao3.id, ao4.id, ao5.id, + ao6.id, ao7.id, ao8.id, ao9.id, ao10.id]); + let deferred = Promise.defer(); + let uninstallCount = addons.length; + let listener = { + onUninstalled: function(aAddon) { + if (aAddon) { + info("Finished uninstalling " + aAddon.id); + } + if (--uninstallCount == 0) { + info("Done add-on uninstalls"); + AddonManager.removeAddonListener(listener); + deferred.resolve(); + } + }}; + AddonManager.addAddonListener(listener); + for (let addon of addons) { + if (addon) + addon.uninstall(); + else + listener.onUninstalled(null); + } + yield deferred.promise; +} + +// Returns promise{window}, resolves with a handle to the compatibility +// check window +function promise_open_compatibility_window(aInactiveAddonIds) { + let deferred = Promise.defer(); + // This will reset the longer timeout multiplier to 2 which will give each + // test that calls open_compatibility_window a minimum of 60 seconds to + // complete. + requestLongerTimeout(100 /* XXX was 2 */); + + var variant = Cc["@mozilla.org/variant;1"]. + createInstance(Ci.nsIWritableVariant); + variant.setFromVariant(aInactiveAddonIds); + + // Cannot be modal as we want to interract with it, shouldn't cause problems + // with testing though. + var features = "chrome,centerscreen,dialog,titlebar"; + var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]. + getService(Ci.nsIWindowWatcher); + var win = ww.openWindow(null, URI_EXTENSION_UPDATE_DIALOG, "", features, variant); + + win.addEventListener("load", function() { + function page_shown(aEvent) { + if (aEvent.target.pageid) + info("Page " + aEvent.target.pageid + " shown"); + } + + win.removeEventListener("load", arguments.callee, false); + + info("Compatibility dialog opened"); + + win.addEventListener("pageshow", page_shown, false); + win.addEventListener("unload", function() { + win.removeEventListener("unload", arguments.callee, false); + win.removeEventListener("pageshow", page_shown, false); + dump("Compatibility dialog closed\n"); + }, false); + + deferred.resolve(win); + }, false); + return deferred.promise; +} + +function promise_window_close(aWindow) { + let deferred = Promise.defer(); + aWindow.addEventListener("unload", function() { + aWindow.removeEventListener("unload", arguments.callee, false); + deferred.resolve(aWindow); + }, false); + return deferred.promise; +} + +function promise_page(aWindow, aPageId) { + let deferred = Promise.defer(); + var page = aWindow.document.getElementById(aPageId); + page.addEventListener("pageshow", function() { + page.removeEventListener("pageshow", arguments.callee, false); + executeSoon(function() { + deferred.resolve(aWindow); + }); + }, false); + return deferred.promise; +} + +function get_list_names(aList) { + var items = []; + for (let listItem of aList.childNodes) + items.push(listItem.label); + items.sort(); + return items; +} + +// These add-ons were inactive in the old application +let inactiveAddonIds = [ + ao2.id, + ao4.id, + ao5.id, + ao10.id +]; + +// Make sure the addons in the list are not installed +function* check_addons_uninstalled(aAddonList) { + let foundList = yield promise_addons_by_ids([addon.id for (addon of aAddonList)]); + for (let i = 0; i < aAddonList.length; i++) { + ok(!foundList[i], "Addon " + aAddonList[i].id + " is not installed"); + } + info("Add-on uninstall check complete"); + yield true; +} + + +// Tests that the right add-ons show up in the mismatch dialog and updates can +// be installed +// This is a task-based rewrite of the first test in browser_bug557956.js +// kept here to show the whole process so that other tests in this file can +// pick and choose which steps to perform, but disabled since the logic is already +// tested in browser_bug557956.js. +// add_task( + function start_update() { + // Don't pull compatibility data during add-on install + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, false); + let addonList = [ao3, ao5, ao6, ao7, ao8, ao9]; + yield promise_install_test_addons(addonList, TESTROOT + "cancelCompatCheck.sjs"); + + + // Check that the addons start out not compatible. + let [a5, a6, a8, a9] = yield promise_addons_by_ids([ao5.id, ao6.id, ao8.id, ao9.id]); + ok(!a5.isCompatible, "addon5 should not be compatible"); + ok(!a6.isCompatible, "addon6 should not be compatible"); + ok(!a8.isCompatible, "addon8 should not be compatible"); + ok(!a9.isCompatible, "addon9 should not be compatible"); + + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + // Check that opening the compatibility window loads and applies + // the compatibility update + let compatWindow = yield promise_open_compatibility_window(inactiveAddonIds); + var doc = compatWindow.document; + compatWindow = yield promise_page(compatWindow, "mismatch"); + var items = get_list_names(doc.getElementById("mismatch.incompatible")); + is(items.length, 4, "Should have seen 4 still incompatible items"); + is(items[0], "Addon3 1.0", "Should have seen addon3 still incompatible"); + is(items[1], "Addon7 1.0", "Should have seen addon7 still incompatible"); + is(items[2], "Addon8 1.0", "Should have seen addon8 still incompatible"); + is(items[3], "Addon9 1.0", "Should have seen addon9 still incompatible"); + + ok(a5.isCompatible, "addon5 should be compatible"); + ok(a6.isCompatible, "addon6 should be compatible"); + + // Click next to start finding updates for the addons that are still incompatible + var button = doc.documentElement.getButton("next"); + EventUtils.synthesizeMouse(button, 2, 2, { }, compatWindow); + + compatWindow = yield promise_page(compatWindow, "found"); + ok(doc.getElementById("xpinstallDisabledAlert").hidden, + "Install should be allowed"); + + var list = doc.getElementById("found.updates"); + var items = get_list_names(list); + is(items.length, 3, "Should have seen 3 updates available"); + is(items[0], "Addon7 2.0", "Should have seen update for addon7"); + is(items[1], "Addon8 2.0", "Should have seen update for addon8"); + is(items[2], "Addon9 2.0", "Should have seen update for addon9"); + + ok(!doc.documentElement.getButton("next").disabled, + "Next button should be enabled"); + + // Uncheck all + for (let listItem of list.childNodes) + EventUtils.synthesizeMouse(listItem, 2, 2, { }, compatWindow); + + ok(doc.documentElement.getButton("next").disabled, + "Next button should not be enabled"); + + // Check the ones we want to install + for (let listItem of list.childNodes) { + if (listItem.label != "Addon7 2.0") + EventUtils.synthesizeMouse(listItem, 2, 2, { }, compatWindow); + } + + var button = doc.documentElement.getButton("next"); + EventUtils.synthesizeMouse(button, 2, 2, { }, compatWindow); + + compatWindow = yield promise_page(compatWindow, "finished"); + var button = doc.documentElement.getButton("finish"); + ok(!button.hidden, "Finish button should not be hidden"); + ok(!button.disabled, "Finish button should not be disabled"); + EventUtils.synthesizeMouse(button, 2, 2, { }, compatWindow); + + compatWindow = yield promise_window_close(compatWindow); + + // Check that the appropriate add-ons have been updated + let [a8, a9] = yield promise_addons_by_ids(["addon8@tests.mozilla.org", + "addon9@tests.mozilla.org"]); + is(a8.version, "2.0", "addon8 should have updated"); + is(a9.version, "2.0", "addon9 should have updated"); + + yield promise_uninstall_test_addons(); +} +// ); + +// Test what happens when the user cancels during AddonRepository.repopulateCache() +// Add-ons that have updates available should not update if they were disabled before +// For this test, addon8 became disabled during update and addon9 was previously disabled, +// so addon8 should update and addon9 should not +add_task(function cancel_during_repopulate() { + Services.prefs.setBoolPref(PREF_STRICT_COMPAT, true); + Services.prefs.setCharPref(PREF_MIN_PLATFORM_COMPAT, "0"); + Services.prefs.setCharPref(PREF_UPDATEURL, TESTROOT + "missing.rdf"); + + let installsDone = promise_observer("TEST:all-updates-done"); + + // Don't pull compatibility data during add-on install + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, false); + // Set up our test addons so that the server-side JS has a 500ms delay to make + // sure we cancel the dialog before we get the data we want to refill our + // AddonRepository cache + let addonList = [ao5, ao8, ao9, ao10]; + yield promise_install_test_addons(addonList, + TESTROOT + "cancelCompatCheck.sjs?500"); + + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + Services.prefs.setCharPref(PREF_GETADDONS_BYIDS, TESTROOT + "browser_bug557956.xml"); + + let [a5, a8, a9] = yield promise_addons_by_ids([ao5.id, ao8.id, ao9.id]); + ok(!a5.isCompatible, "addon5 should not be compatible"); + ok(!a8.isCompatible, "addon8 should not be compatible"); + ok(!a9.isCompatible, "addon9 should not be compatible"); + + let compatWindow = yield promise_open_compatibility_window([ao9.id, ...inactiveAddonIds]); + var doc = compatWindow.document; + yield promise_page(compatWindow, "versioninfo"); + + // Brief delay to let the update window finish requesting all add-ons and start + // reloading the addon repository + yield delayMS(50); + + info("Cancel the compatibility check dialog"); + var button = doc.documentElement.getButton("cancel"); + EventUtils.synthesizeMouse(button, 2, 2, { }, compatWindow); + + info("Waiting for installs to complete"); + yield installsDone; + ok(!repo.AddonRepository.isSearching, "Background installs are done"); + + // There should be no active updates + let getInstalls = Promise.defer(); + AddonManager.getAllInstalls(getInstalls.resolve); + let installs = yield getInstalls.promise; + is (installs.length, 0, "There should be no active installs after background installs are done"); + + // addon8 should have updated in the background, + // addon9 was listed as previously disabled so it should not have updated + let [a5, a8, a9, a10] = yield promise_addons_by_ids([ao5.id, ao8.id, ao9.id, ao10.id]); + ok(a5.isCompatible, "addon5 should be compatible"); + ok(a8.isCompatible, "addon8 should have been upgraded"); + ok(!a9.isCompatible, "addon9 should not have been upgraded"); + ok(!a10.isCompatible, "addon10 should not be compatible"); + + info("Updates done"); + yield promise_uninstall_test_addons(); + info("done uninstalling add-ons"); +}); + +// User cancels after repopulateCache, while we're waiting for the addon.findUpdates() +// calls in gVersionInfoPage_onPageShow() to complete +// For this test, both addon8 and addon9 were disabled by this update, but addon8 +// is set to not auto-update, so only addon9 should update in the background +add_task(function cancel_during_findUpdates() { + Services.prefs.setBoolPref(PREF_STRICT_COMPAT, true); + Services.prefs.setCharPref(PREF_MIN_PLATFORM_COMPAT, "0"); + + let observeUpdateDone = promise_observer("TEST:addon-repository-data-updated"); + let installsDone = promise_observer("TEST:all-updates-done"); + + // Don't pull compatibility data during add-on install + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, false); + // No delay on the .sjs this time because we want the cache to repopulate + let addonList = [ao3, ao5, ao6, ao7, ao8, ao9]; + yield promise_install_test_addons(addonList, + TESTROOT + "cancelCompatCheck.sjs"); + + let [a8] = yield promise_addons_by_ids([ao8.id]); + a8.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + let compatWindow = yield promise_open_compatibility_window(inactiveAddonIds); + var doc = compatWindow.document; + yield promise_page(compatWindow, "versioninfo"); + + info("Waiting for repository-data-updated"); + yield observeUpdateDone; + + // Quick wait to make sure the findUpdates calls get queued + yield delayMS(5); + + info("Cancel the compatibility check dialog"); + var button = doc.documentElement.getButton("cancel"); + EventUtils.synthesizeMouse(button, 2, 2, { }, compatWindow); + + info("Waiting for installs to complete 2"); + yield installsDone; + ok(!repo.AddonRepository.isSearching, "Background installs are done 2"); + + // addon8 should have updated in the background, + // addon9 was listed as previously disabled so it should not have updated + let [a5, a8, a9] = yield promise_addons_by_ids([ao5.id, ao8.id, ao9.id]); + ok(a5.isCompatible, "addon5 should be compatible"); + ok(!a8.isCompatible, "addon8 should not have been upgraded"); + ok(a9.isCompatible, "addon9 should have been upgraded"); + + let getInstalls = Promise.defer(); + AddonManager.getAllInstalls(getInstalls.resolve); + let installs = yield getInstalls.promise; + is (installs.length, 0, "There should be no active installs after the dialog is cancelled 2"); + + info("findUpdates done"); + yield promise_uninstall_test_addons(); +}); + +// Cancelling during the 'mismatch' screen allows add-ons that can auto-update +// to continue updating in the background and cancels any other updates +// Same conditions as the previous test - addon8 and addon9 have updates available, +// addon8 is set to not auto-update so only addon9 should become compatible +add_task(function cancel_mismatch() { + Services.prefs.setBoolPref(PREF_STRICT_COMPAT, true); + Services.prefs.setCharPref(PREF_MIN_PLATFORM_COMPAT, "0"); + + let observeUpdateDone = promise_observer("TEST:addon-repository-data-updated"); + let installsDone = promise_observer("TEST:all-updates-done"); + + // Don't pull compatibility data during add-on install + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, false); + // No delay on the .sjs this time because we want the cache to repopulate + let addonList = [ao3, ao5, ao6, ao7, ao8, ao9]; + yield promise_install_test_addons(addonList, + TESTROOT + "cancelCompatCheck.sjs"); + + let [a8] = yield promise_addons_by_ids([ao8.id]); + a8.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DISABLE; + + // Check that the addons start out not compatible. + let [a3, a7, a8, a9] = yield promise_addons_by_ids([ao3.id, ao7.id, ao8.id, ao9.id]); + ok(!a3.isCompatible, "addon3 should not be compatible"); + ok(!a7.isCompatible, "addon7 should not be compatible"); + ok(!a8.isCompatible, "addon8 should not be compatible"); + ok(!a9.isCompatible, "addon9 should not be compatible"); + + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + let compatWindow = yield promise_open_compatibility_window(inactiveAddonIds); + var doc = compatWindow.document; + info("Wait for mismatch page"); + yield promise_page(compatWindow, "mismatch"); + info("Click the Don't Check button"); + var button = doc.documentElement.getButton("cancel"); + EventUtils.synthesizeMouse(button, 2, 2, { }, compatWindow); + + yield promise_window_close(compatWindow); + info("Waiting for installs to complete in cancel_mismatch"); + yield installsDone; + + // addon8 should not have updated in the background, + // addon9 was listed as previously disabled so it should not have updated + let [a5, a8, a9] = yield promise_addons_by_ids([ao5.id, ao8.id, ao9.id]); + ok(a5.isCompatible, "addon5 should be compatible"); + ok(!a8.isCompatible, "addon8 should not have been upgraded"); + ok(a9.isCompatible, "addon9 should have been upgraded"); + + // Make sure there are no pending addon installs + let pInstalls = Promise.defer(); + AddonManager.getAllInstalls(pInstalls.resolve); + let installs = yield pInstalls.promise; + ok(installs.length == 0, "No remaining add-on installs (" + installs.toSource() + ")"); + + yield promise_uninstall_test_addons(); + yield check_addons_uninstalled(addonList); +}); + +// Cancelling during the 'mismatch' screen with only add-ons that have +// no updates available +add_task(function cancel_mismatch_no_updates() { + Services.prefs.setBoolPref(PREF_STRICT_COMPAT, true); + Services.prefs.setCharPref(PREF_MIN_PLATFORM_COMPAT, "0"); + + let observeUpdateDone = promise_observer("TEST:addon-repository-data-updated"); + + // Don't pull compatibility data during add-on install + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, false); + // No delay on the .sjs this time because we want the cache to repopulate + let addonList = [ao3, ao5, ao6]; + yield promise_install_test_addons(addonList, + TESTROOT + "cancelCompatCheck.sjs"); + + // Check that the addons start out not compatible. + let [a3, a5, a6] = yield promise_addons_by_ids([ao3.id, ao5.id, ao6.id]); + ok(!a3.isCompatible, "addon3 should not be compatible"); + ok(!a5.isCompatible, "addon7 should not be compatible"); + ok(!a6.isCompatible, "addon8 should not be compatible"); + + Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true); + let compatWindow = yield promise_open_compatibility_window(inactiveAddonIds); + var doc = compatWindow.document; + info("Wait for mismatch page"); + yield promise_page(compatWindow, "mismatch"); + info("Click the Don't Check button"); + var button = doc.documentElement.getButton("cancel"); + EventUtils.synthesizeMouse(button, 2, 2, { }, compatWindow); + + yield promise_window_close(compatWindow); + + let [a3, a5, a6] = yield promise_addons_by_ids([ao3.id, ao5.id, ao6.id]); + ok(!a3.isCompatible, "addon3 should not be compatible"); + ok(a5.isCompatible, "addon5 should have become compatible"); + ok(a6.isCompatible, "addon6 should have become compatible"); + + // Make sure there are no pending addon installs + let pInstalls = Promise.defer(); + AddonManager.getAllInstalls(pInstalls.resolve); + let installs = yield pInstalls.promise; + ok(installs.length == 0, "No remaining add-on installs (" + installs.toSource() + ")"); + + yield promise_uninstall_test_addons(); + yield check_addons_uninstalled(addonList); +}); diff --git a/toolkit/mozapps/extensions/test/browser/cancelCompatCheck.sjs b/toolkit/mozapps/extensions/test/browser/cancelCompatCheck.sjs new file mode 100644 index 00000000000..51ac38427ca --- /dev/null +++ b/toolkit/mozapps/extensions/test/browser/cancelCompatCheck.sjs @@ -0,0 +1,44 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Delay before responding to an HTTP call attempting to read +// an addon update RDF file + +function handleRequest(req, resp) { + resp.processAsync(); + resp.setHeader("Cache-Control", "no-cache, no-store", false); + resp.setHeader("Content-Type", "text/xml;charset=utf-8", false); + + let file = null; + getObjectState("SERVER_ROOT", function(serverRoot) + { + file = serverRoot.getFile("browser/toolkit/mozapps/extensions/test/browser/browser_bug557956.rdf"); + }); + dump("*** cancelCompatCheck.sjs: " + file.path + "\n"); + let fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]. + createInstance(Components.interfaces.nsIFileInputStream); + fstream.init(file, -1, 0, 0); + let cstream = null; + cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]. + createInstance(Components.interfaces.nsIConverterInputStream); + cstream.init(fstream, "UTF-8", 0, 0); + + // The delay can be passed on the query string + let delay = req.queryString + 0; + + timer = Components.classes["@mozilla.org/timer;1"]. + createInstance(Components.interfaces.nsITimer); + timer.init(function sendFile() { + dump("cancelCompatCheck: starting to send file\n"); + let (str = {}) { + let read = 0; + do { + // read as much as we can and put it in str.value + read = cstream.readString(0xffffffff, str); + resp.write(str.value); + } while (read != 0); + } + cstream.close(); + resp.finish(); + }, delay, Components.interfaces.nsITimer.TYPE_ONE_SHOT); +} diff --git a/toolkit/mozapps/extensions/test/browser/head.js b/toolkit/mozapps/extensions/test/browser/head.js index 3f889d9c0b1..f2fcc8a19c1 100644 --- a/toolkit/mozapps/extensions/test/browser/head.js +++ b/toolkit/mozapps/extensions/test/browser/head.js @@ -89,6 +89,21 @@ for (let pref of gRestorePrefs) { // Turn logging on for all tests Services.prefs.setBoolPref(PREF_LOGGING_ENABLED, true); +// Helper to register test failures and close windows if any are left open +function checkOpenWindows(aWindowID) { + let windows = Services.wm.getEnumerator(aWindowID); + let found = false; + while (windows.hasMoreElements()) { + let win = windows.getNext().QueryInterface(Ci.nsIDOMWindow); + if (!win.closed) { + found = true; + win.close(); + } + } + if (found) + ok(false, "Found unexpected " + aWindowID + " window still open"); +} + registerCleanupFunction(function() { // Restore prefs for (let pref of gRestorePrefs) { @@ -103,24 +118,9 @@ registerCleanupFunction(function() { } // Throw an error if the add-ons manager window is open anywhere - var windows = Services.wm.getEnumerator("Addons:Manager"); - if (windows.hasMoreElements()) - ok(false, "Found unexpected add-ons manager window still open"); - while (windows.hasMoreElements()) - windows.getNext().QueryInterface(Ci.nsIDOMWindow).close(); - - windows = Services.wm.getEnumerator("Addons:Compatibility"); - if (windows.hasMoreElements()) - ok(false, "Found unexpected add-ons compatibility window still open"); - while (windows.hasMoreElements()) - windows.getNext().QueryInterface(Ci.nsIDOMWindow).close(); - - windows = Services.wm.getEnumerator("Addons:Install"); - if (windows.hasMoreElements()) - ok(false, "Found unexpected add-ons installation window still open"); - while (windows.hasMoreElements()) - windows.getNext().QueryInterface(Ci.nsIDOMWindow).close(); - + checkOpenWindows("Addons:Manager"); + checkOpenWindows("Addons:Compatibility"); + checkOpenWindows("Addons:Install"); // We can for now know that getAllInstalls actually calls its callback before // it returns so this will complete before the next test start. diff --git a/toolkit/mozapps/installer/packager.py b/toolkit/mozapps/installer/packager.py index ffc8eb01bc3..5356bebe812 100644 --- a/toolkit/mozapps/installer/packager.py +++ b/toolkit/mozapps/installer/packager.py @@ -137,6 +137,15 @@ def precompile_cache(formatter, source_path, gre_path, app_path): fd, cache = mkstemp('.zip') os.close(fd) os.remove(cache) + + # For VC12, make sure we can find the right bitness of pgort120.dll + env = os.environ.copy() + if 'VS120COMNTOOLS' in env and not buildconfig.substs['HAVE_64BIT_OS']: + vc12dir = os.path.abspath(os.path.join(env['VS120COMNTOOLS'], + '../../VC/bin')) + if os.path.exists(vc12dir): + env['PATH'] = vc12dir + ';' + env['PATH'] + try: if launcher.launch(['xpcshell', '-g', gre_path, '-a', app_path, '-f', os.path.join(os.path.dirname(__file__), @@ -144,7 +153,8 @@ def precompile_cache(formatter, source_path, gre_path, app_path): '-e', 'precompile_startupcache("resource://%s/");' % resource], extra_linker_path=gre_path, - extra_env={'MOZ_STARTUP_CACHE': cache}): + extra_env={'MOZ_STARTUP_CACHE': cache, + 'PATH': env['PATH']}): errors.fatal('Error while running startup cache precompilation') return from mozpack.mozjar import JarReader diff --git a/toolkit/xre/moz.build b/toolkit/xre/moz.build index 347146296a9..a13a9ef3bfc 100644 --- a/toolkit/xre/moz.build +++ b/toolkit/xre/moz.build @@ -24,7 +24,6 @@ if CONFIG['MOZ_INSTRUMENT_EVENT_LOOP']: EXPORTS += ['EventTracer.h'] if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': - EXPORTS += ['nsWindowsDllInterceptor.h'] UNIFIED_SOURCES += [ 'nsNativeAppSupportWin.cpp', ] diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp index 66bd32d757c..b81ff321da6 100644 --- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -14,6 +14,7 @@ #include "mozilla/ArrayUtils.h" #include "mozilla/Attributes.h" +#include "mozilla/IOInterposer.h" #include "mozilla/Likely.h" #include "mozilla/Poison.h" #include "mozilla/Preferences.h" @@ -4025,6 +4026,8 @@ XREMain::XRE_main(int argc, char* argv[], const nsXREAppData* aAppData) GeckoProfilerInitRAII profilerGuard(&aLocal); PROFILER_LABEL("Startup", "XRE_Main"); + mozilla::IOInterposerInit ioInterposerGuard; + nsresult rv = NS_OK; gArgc = argc; @@ -4228,6 +4231,8 @@ XRE_mainMetro(int argc, char* argv[], const nsXREAppData* aAppData) GeckoProfilerInitRAII profilerGuard(&aLocal); PROFILER_LABEL("Startup", "XRE_Main"); + mozilla::IOInterposerInit ioInterposerGuard; + nsresult rv = NS_OK; xreMainPtr = new XREMain(); diff --git a/tools/profiler/ProfilerIOInterposeObserver.cpp b/tools/profiler/ProfilerIOInterposeObserver.cpp index 73788fc2a3e..757189c7f15 100644 --- a/tools/profiler/ProfilerIOInterposeObserver.cpp +++ b/tools/profiler/ProfilerIOInterposeObserver.cpp @@ -10,6 +10,10 @@ using namespace mozilla; void ProfilerIOInterposeObserver::Observe(Observation& aObservation) { + if (!IsMainThread()) { + return; + } + const char* str = nullptr; switch (aObservation.ObservedOperation()) { diff --git a/tools/profiler/moz.build b/tools/profiler/moz.build index ff009ecb2eb..4710841e7be 100644 --- a/tools/profiler/moz.build +++ b/tools/profiler/moz.build @@ -24,10 +24,8 @@ if CONFIG['MOZ_ENABLE_PROFILER_SPS']: ] UNIFIED_SOURCES += [ 'BreakpadSampler.cpp', - 'IOInterposer.cpp', 'JSCustomObjectBuilder.cpp', 'JSObjectBuilder.cpp', - 'NSPRInterposer.cpp', 'nsProfiler.cpp', 'nsProfilerFactory.cpp', 'platform.cpp', @@ -90,8 +88,4 @@ EXPORTS += [ 'GeckoProfiler.h', ] -EXPORTS.mozilla += [ - 'IOInterposer.h', -] - XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell.ini'] diff --git a/tools/profiler/platform.cpp b/tools/profiler/platform.cpp index cb339ac63d2..8a4f29e8563 100644 --- a/tools/profiler/platform.cpp +++ b/tools/profiler/platform.cpp @@ -7,8 +7,6 @@ #include #include -#include "IOInterposer.h" -#include "NSPRInterposer.h" #include "ProfilerIOInterposeObserver.h" #include "platform.h" #include "PlatformMacros.h" @@ -481,11 +479,6 @@ void mozilla_sampler_init(void* stackTop) // platform specific initialization OS::Startup(); - // Initialize I/O interposing - mozilla::IOInterposer::Init(); - // Initialize NSPR I/O Interposing - mozilla::InitNSPRIOInterposing(); - // We can't open pref so we use an environment variable // to know if we should trigger the profiler on startup // NOTE: Default @@ -533,16 +526,6 @@ void mozilla_sampler_shutdown() profiler_stop(); - // Unregister IO interpose observer - mozilla::IOInterposer::Unregister(mozilla::IOInterposeObserver::OpAll, - sInterposeObserver); - // mozilla_sampler_shutdown is only called at shutdown, and late-write checks - // might need the IO interposer, so we don't clear it. Don't worry it's - // designed not to report leaks. - // mozilla::IOInterposer::Clear(); - mozilla::ClearNSPRIOInterposing(); - sInterposeObserver = nullptr; - Sampler::Shutdown(); // We can't delete the Stack because we can be between a @@ -741,6 +724,7 @@ void mozilla_sampler_stop() mozilla::IOInterposer::Unregister(mozilla::IOInterposeObserver::OpAll, sInterposeObserver); + sInterposeObserver = nullptr; sIsProfiling = false; diff --git a/widget/CommandList.h b/widget/CommandList.h new file mode 100644 index 00000000000..2bd327c86e2 --- /dev/null +++ b/widget/CommandList.h @@ -0,0 +1,52 @@ +/* -*- 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/. */ + +/** + * Define NS_DEFIVE_COMMAND(aName, aCommandStr) before including this. + * @param aName The name useful in C++ of the command. + * @param aCommandStr The command string in JS. + */ + +NS_DEFINE_COMMAND(BeginLine, cmd_beginLine) +NS_DEFINE_COMMAND(CharNext, cmd_charNext) +NS_DEFINE_COMMAND(CharPrevious, cmd_charPrevious) +NS_DEFINE_COMMAND(Copy, cmd_copy) +NS_DEFINE_COMMAND(Cut, cmd_cut) +NS_DEFINE_COMMAND(Delete, cmd_delete) +NS_DEFINE_COMMAND(DeleteCharBackward, cmd_deleteCharBackward) +NS_DEFINE_COMMAND(DeleteCharForward, cmd_deleteCharForward) +NS_DEFINE_COMMAND(DeleteToBeginningOfLine, cmd_deleteToBeginningOfLine) +NS_DEFINE_COMMAND(DeleteToEndOfLine, cmd_deleteToEndOfLine) +NS_DEFINE_COMMAND(DeleteWordBackward, cmd_deleteWordBackward) +NS_DEFINE_COMMAND(DeleteWordForward, cmd_deleteWordForward) +NS_DEFINE_COMMAND(EndLine, cmd_endLine) +NS_DEFINE_COMMAND(LineNext, cmd_lineNext) +NS_DEFINE_COMMAND(LinePrevious, cmd_linePrevious) +NS_DEFINE_COMMAND(MoveBottom, cmd_moveBottom) +NS_DEFINE_COMMAND(MovePageDown, cmd_movePageDown) +NS_DEFINE_COMMAND(MovePageUp, cmd_movePageUp) +NS_DEFINE_COMMAND(MoveTop, cmd_moveTop) +NS_DEFINE_COMMAND(Paste, cmd_paste) +NS_DEFINE_COMMAND(ScrollBottom, cmd_scrollBottom) +NS_DEFINE_COMMAND(ScrollLineDown, cmd_scrollLineDown) +NS_DEFINE_COMMAND(ScrollLineUp, cmd_scrollLineUp) +NS_DEFINE_COMMAND(ScrollPageDown, cmd_scrollPageDown) +NS_DEFINE_COMMAND(ScrollPageUp, cmd_scrollPageUp) +NS_DEFINE_COMMAND(ScrollTop, cmd_scrollTop) +NS_DEFINE_COMMAND(SelectAll, cmd_selectAll) +NS_DEFINE_COMMAND(SelectBeginLine, cmd_selectBeginLine) +NS_DEFINE_COMMAND(SelectBottom, cmd_selectBottom) +NS_DEFINE_COMMAND(SelectCharNext, cmd_selectCharNext) +NS_DEFINE_COMMAND(SelectCharPrevious, cmd_selectCharPrevious) +NS_DEFINE_COMMAND(SelectEndLine, cmd_selectEndLine) +NS_DEFINE_COMMAND(SelectLineNext, cmd_selectLineNext) +NS_DEFINE_COMMAND(SelectLinePrevious, cmd_selectLinePrevious) +NS_DEFINE_COMMAND(SelectPageDown, cmd_selectPageDown) +NS_DEFINE_COMMAND(SelectPageUp, cmd_selectPageUp) +NS_DEFINE_COMMAND(SelectTop, cmd_selectTop) +NS_DEFINE_COMMAND(SelectWordNext, cmd_selectWordNext) +NS_DEFINE_COMMAND(SelectWordPrevious, cmd_selectWordPrevious) +NS_DEFINE_COMMAND(WordNext, cmd_wordNext) +NS_DEFINE_COMMAND(WordPrevious, cmd_wordPrevious) diff --git a/widget/EventForwards.h b/widget/EventForwards.h index 82dd3eb7fa3..23104831764 100644 --- a/widget/EventForwards.h +++ b/widget/EventForwards.h @@ -8,6 +8,8 @@ #include +#include "mozilla/TypedEnum.h" + /** * XXX Following enums should be in BasicEvents.h. However, currently, it's * impossible to use foward delearation for enum. @@ -43,6 +45,17 @@ enum KeyNameIndex #undef NS_DEFINE_KEYNAME +#define NS_DEFINE_COMMAND(aName, aCommandStr) , Command##aName + +typedef int8_t CommandInt; +enum Command MOZ_ENUM_TYPE(CommandInt) +{ + CommandDoNothing + +#include "mozilla/CommandList.h" +}; +#undef NS_DEFINE_COMMAND + } // namespace mozilla /** diff --git a/widget/TextEvents.h b/widget/TextEvents.h index 9260fbb2fb0..2d659805302 100644 --- a/widget/TextEvents.h +++ b/widget/TextEvents.h @@ -143,6 +143,8 @@ public: static void GetDOMKeyName(mozilla::KeyNameIndex aKeyNameIndex, nsAString& aKeyName); + static const char* GetCommandStr(Command aCommand); + void AssignKeyEventData(const WidgetKeyboardEvent& aEvent, bool aCopyTargets) { AssignInputEventData(aEvent, aCopyTargets); diff --git a/widget/cocoa/NativeKeyBindings.h b/widget/cocoa/NativeKeyBindings.h index a794bdf264f..22d3220a48a 100644 --- a/widget/cocoa/NativeKeyBindings.h +++ b/widget/cocoa/NativeKeyBindings.h @@ -3,71 +3,46 @@ * 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 NativeKeyBindings_h_ -#define NativeKeyBindings_h_ - -#include "nsINativeKeyBindings.h" +#ifndef mozilla_widget_NativeKeyBindings_h_ +#define mozilla_widget_NativeKeyBindings_h_ #import #include "mozilla/Attributes.h" #include "mozilla/EventForwards.h" #include "nsDataHashtable.h" - -// 8477f934-febf-4c79-b7fe-bb7f9ebb9b4f -#define NS_NATIVEKEYBINDINGS_INPUT_CID \ - { 0x8477f934, 0xfebf, 0x4c79, \ - { 0xb7, 0xfe, 0xbb, 0x7f, 0x9e, 0xbb, 0x9b, 0x4f } } - -// 13a6e56f-f00b-4e19-8cf6-1a51ee7cc4bf -#define NS_NATIVEKEYBINDINGS_TEXTAREA_CID \ - { 0x13a6e56f, 0xf00b, 0x4e19, \ - { 0x8c, 0xf6, 0x1a, 0x51, 0xee, 0x7c, 0xc4, 0xbf } } - -// 36bfbd29-4e02-40f4-8fff-094f1a9ec97c -#define NS_NATIVEKEYBINDINGS_EDITOR_CID \ - { 0x36bfbd29, 0x4e02, 0x40f4, \ - { 0x8f, 0xff, 0x09, 0x4f, 0x1a, 0x9e, 0xc9, 0x7c } } +#include "nsIWidget.h" namespace mozilla { namespace widget { -enum NativeKeyBindingsType -{ - eNativeKeyBindingsType_Input, - eNativeKeyBindingsType_TextArea, - eNativeKeyBindingsType_Editor -}; - -typedef nsDataHashtable, const char *> +typedef nsDataHashtable, CommandInt> SelectorCommandHashtable; -class NativeKeyBindings MOZ_FINAL : public nsINativeKeyBindings +class NativeKeyBindings MOZ_FINAL { + typedef nsIWidget::NativeKeyBindingsType NativeKeyBindingsType; + typedef nsIWidget::DoCommandCallback DoCommandCallback; + public: - NativeKeyBindings(); + static NativeKeyBindings* GetInstance(NativeKeyBindingsType aType); + static void Shutdown(); - NS_DECL_ISUPPORTS + void Init(NativeKeyBindingsType aType); - NS_IMETHOD Init(NativeKeyBindingsType aType); - - // nsINativeKeyBindings - NS_IMETHOD_(bool) KeyDown(const WidgetKeyboardEvent& aEvent, - DoCommandCallback aCallback, - void* aCallbackData); - - NS_IMETHOD_(bool) KeyPress(const WidgetKeyboardEvent& aEvent, - DoCommandCallback aCallback, - void* aCallbackData); - - NS_IMETHOD_(bool) KeyUp(const WidgetKeyboardEvent& aEvent, - DoCommandCallback aCallback, - void* aCallbackData); + bool Execute(const WidgetKeyboardEvent& aEvent, + DoCommandCallback aCallback, + void* aCallbackData); private: + NativeKeyBindings(); + SelectorCommandHashtable mSelectorToCommand; + + static NativeKeyBindings* sInstanceForSingleLineEditor; + static NativeKeyBindings* sInstanceForMultiLineEditor; }; // NativeKeyBindings } // namespace widget } // namespace mozilla -#endif /* NativeKeyBindings_h_ */ +#endif // mozilla_widget_NativeKeyBindings_h_ diff --git a/widget/cocoa/NativeKeyBindings.mm b/widget/cocoa/NativeKeyBindings.mm index 724fb88ea05..082bea66500 100644 --- a/widget/cocoa/NativeKeyBindings.mm +++ b/widget/cocoa/NativeKeyBindings.mm @@ -10,13 +10,50 @@ #include "prlog.h" #include "mozilla/TextEvents.h" -using namespace mozilla; -using namespace mozilla::widget; +namespace mozilla { +namespace widget { #ifdef PR_LOGGING PRLogModuleInfo* gNativeKeyBindingsLog = nullptr; #endif +NativeKeyBindings* NativeKeyBindings::sInstanceForSingleLineEditor = nullptr; +NativeKeyBindings* NativeKeyBindings::sInstanceForMultiLineEditor = nullptr; + +// static +NativeKeyBindings* +NativeKeyBindings::GetInstance(NativeKeyBindingsType aType) +{ + switch (aType) { + case nsIWidget::NativeKeyBindingsForSingleLineEditor: + if (!sInstanceForSingleLineEditor) { + sInstanceForSingleLineEditor = new NativeKeyBindings(); + sInstanceForSingleLineEditor->Init(aType); + } + return sInstanceForSingleLineEditor; + case nsIWidget::NativeKeyBindingsForMultiLineEditor: + case nsIWidget::NativeKeyBindingsForRichTextEditor: + if (!sInstanceForMultiLineEditor) { + sInstanceForMultiLineEditor = new NativeKeyBindings(); + sInstanceForMultiLineEditor->Init(aType); + } + return sInstanceForMultiLineEditor; + default: + MOZ_CRASH("Not implemented"); + return nullptr; + } +} + +// static +void +NativeKeyBindings::Shutdown() +{ + delete sInstanceForSingleLineEditor; + sInstanceForSingleLineEditor = nullptr; + delete sInstanceForMultiLineEditor; + sInstanceForMultiLineEditor = nullptr; +} + NativeKeyBindings::NativeKeyBindings() { } @@ -25,7 +62,7 @@ NativeKeyBindings::NativeKeyBindings() mSelectorToCommand.Put( \ reinterpret_cast(@selector(aSel)), aCommand) -NS_IMETHODIMP +void NativeKeyBindings::Init(NativeKeyBindingsType aType) { #ifdef PR_LOGGING @@ -51,24 +88,24 @@ NativeKeyBindings::Init(NativeKeyBindingsType aType) // SEL_TO_COMMAND(centerSelectionInVisibleArea:, ); // SEL_TO_COMMAND(changeCaseOfLetter:, ); // SEL_TO_COMMAND(complete:, ); - SEL_TO_COMMAND(copy:, "cmd_copy"); + SEL_TO_COMMAND(copy:, CommandCopy); // SEL_TO_COMMAND(copyFont:, ); // SEL_TO_COMMAND(copyRuler:, ); - SEL_TO_COMMAND(cut:, "cmd_cut"); - SEL_TO_COMMAND(delete:, "cmd_delete"); - SEL_TO_COMMAND(deleteBackward:, "cmd_deleteCharBackward"); + SEL_TO_COMMAND(cut:, CommandCut); + SEL_TO_COMMAND(delete:, CommandDelete); + SEL_TO_COMMAND(deleteBackward:, CommandDeleteCharBackward); // SEL_TO_COMMAND(deleteBackwardByDecomposingPreviousCharacter:, ); - SEL_TO_COMMAND(deleteForward:, "cmd_deleteCharForward"); + SEL_TO_COMMAND(deleteForward:, CommandDeleteCharForward); // TODO: deleteTo* selectors are also supposed to add text to a kill buffer - SEL_TO_COMMAND(deleteToBeginningOfLine:, "cmd_deleteToBeginningOfLine"); - SEL_TO_COMMAND(deleteToBeginningOfParagraph:, "cmd_deleteToBeginningOfLine"); - SEL_TO_COMMAND(deleteToEndOfLine:, "cmd_deleteToEndOfLine"); - SEL_TO_COMMAND(deleteToEndOfParagraph:, "cmd_deleteToEndOfLine"); + SEL_TO_COMMAND(deleteToBeginningOfLine:, CommandDeleteToBeginningOfLine); + SEL_TO_COMMAND(deleteToBeginningOfParagraph:, CommandDeleteToBeginningOfLine); + SEL_TO_COMMAND(deleteToEndOfLine:, CommandDeleteToEndOfLine); + SEL_TO_COMMAND(deleteToEndOfParagraph:, CommandDeleteToEndOfLine); // SEL_TO_COMMAND(deleteToMark:, ); - SEL_TO_COMMAND(deleteWordBackward:, "cmd_deleteWordBackward"); - SEL_TO_COMMAND(deleteWordForward:, "cmd_deleteWordForward"); + SEL_TO_COMMAND(deleteWordBackward:, CommandDeleteWordBackward); + SEL_TO_COMMAND(deleteWordForward:, CommandDeleteWordForward); // SEL_TO_COMMAND(indent:, ); // SEL_TO_COMMAND(insertBacktab:, ); // SEL_TO_COMMAND(insertContainerBreak:, ); @@ -81,72 +118,75 @@ NativeKeyBindings::Init(NativeKeyBindingsType aType) // SEL_TO_COMMAND(insertDoubleQuoteIgnoringSubstitution:, ); // SEL_TO_COMMAND(insertSingleQuoteIgnoringSubstitution:, ); // SEL_TO_COMMAND(lowercaseWord:, ); - SEL_TO_COMMAND(moveBackward:, "cmd_charPrevious"); - SEL_TO_COMMAND(moveBackwardAndModifySelection:, "cmd_selectCharPrevious"); - if (aType == eNativeKeyBindingsType_Input) { - SEL_TO_COMMAND(moveDown:, "cmd_endLine"); + SEL_TO_COMMAND(moveBackward:, CommandCharPrevious); + SEL_TO_COMMAND(moveBackwardAndModifySelection:, CommandSelectCharPrevious); + if (aType == nsIWidget::NativeKeyBindingsForSingleLineEditor) { + SEL_TO_COMMAND(moveDown:, CommandEndLine); } else { - SEL_TO_COMMAND(moveDown:, "cmd_lineNext"); + SEL_TO_COMMAND(moveDown:, CommandLineNext); } - SEL_TO_COMMAND(moveDownAndModifySelection:, "cmd_selectLineNext"); - SEL_TO_COMMAND(moveForward:, "cmd_charNext"); - SEL_TO_COMMAND(moveForwardAndModifySelection:, "cmd_selectCharNext"); - SEL_TO_COMMAND(moveLeft:, "cmd_charPrevious"); - SEL_TO_COMMAND(moveLeftAndModifySelection:, "cmd_selectCharPrevious"); + SEL_TO_COMMAND(moveDownAndModifySelection:, CommandSelectLineNext); + SEL_TO_COMMAND(moveForward:, CommandCharNext); + SEL_TO_COMMAND(moveForwardAndModifySelection:, CommandSelectCharNext); + SEL_TO_COMMAND(moveLeft:, CommandCharPrevious); + SEL_TO_COMMAND(moveLeftAndModifySelection:, CommandSelectCharPrevious); SEL_TO_COMMAND(moveParagraphBackwardAndModifySelection:, - "cmd_selectBeginLine"); - SEL_TO_COMMAND(moveParagraphForwardAndModifySelection:, "cmd_selectEndLine"); - SEL_TO_COMMAND(moveRight:, "cmd_charNext"); - SEL_TO_COMMAND(moveRightAndModifySelection:, "cmd_selectCharNext"); - SEL_TO_COMMAND(moveToBeginningOfDocument:, "cmd_moveTop"); - SEL_TO_COMMAND(moveToBeginningOfDocumentAndModifySelection:, "cmd_selectTop"); - SEL_TO_COMMAND(moveToBeginningOfLine:, "cmd_beginLine"); + CommandSelectBeginLine); + SEL_TO_COMMAND(moveParagraphForwardAndModifySelection:, CommandSelectEndLine); + SEL_TO_COMMAND(moveRight:, CommandCharNext); + SEL_TO_COMMAND(moveRightAndModifySelection:, CommandSelectCharNext); + SEL_TO_COMMAND(moveToBeginningOfDocument:, CommandMoveTop); + SEL_TO_COMMAND(moveToBeginningOfDocumentAndModifySelection:, + CommandSelectTop); + SEL_TO_COMMAND(moveToBeginningOfLine:, CommandBeginLine); SEL_TO_COMMAND(moveToBeginningOfLineAndModifySelection:, - "cmd_selectBeginLine"); - SEL_TO_COMMAND(moveToBeginningOfParagraph:, "cmd_beginLine"); + CommandSelectBeginLine); + SEL_TO_COMMAND(moveToBeginningOfParagraph:, CommandBeginLine); SEL_TO_COMMAND(moveToBeginningOfParagraphAndModifySelection:, - "cmd_selectBeginLine"); - SEL_TO_COMMAND(moveToEndOfDocument:, "cmd_moveBottom"); - SEL_TO_COMMAND(moveToEndOfDocumentAndModifySelection:, "cmd_selectBottom"); - SEL_TO_COMMAND(moveToEndOfLine:, "cmd_endLine"); - SEL_TO_COMMAND(moveToEndOfLineAndModifySelection:, "cmd_selectEndLine"); - SEL_TO_COMMAND(moveToEndOfParagraph:, "cmd_endLine"); - SEL_TO_COMMAND(moveToEndOfParagraphAndModifySelection:, "cmd_selectEndLine"); - SEL_TO_COMMAND(moveToLeftEndOfLine:, "cmd_beginLine"); - SEL_TO_COMMAND(moveToLeftEndOfLineAndModifySelection:, "cmd_selectBeginLine"); - SEL_TO_COMMAND(moveToRightEndOfLine:, "cmd_endLine"); - SEL_TO_COMMAND(moveToRightEndOfLineAndModifySelection:, "cmd_selectEndLine"); - if (aType == eNativeKeyBindingsType_Input) { - SEL_TO_COMMAND(moveUp:, "cmd_beginLine"); + CommandSelectBeginLine); + SEL_TO_COMMAND(moveToEndOfDocument:, CommandMoveBottom); + SEL_TO_COMMAND(moveToEndOfDocumentAndModifySelection:, CommandSelectBottom); + SEL_TO_COMMAND(moveToEndOfLine:, CommandEndLine); + SEL_TO_COMMAND(moveToEndOfLineAndModifySelection:, CommandSelectEndLine); + SEL_TO_COMMAND(moveToEndOfParagraph:, CommandEndLine); + SEL_TO_COMMAND(moveToEndOfParagraphAndModifySelection:, CommandSelectEndLine); + SEL_TO_COMMAND(moveToLeftEndOfLine:, CommandBeginLine); + SEL_TO_COMMAND(moveToLeftEndOfLineAndModifySelection:, + CommandSelectBeginLine); + SEL_TO_COMMAND(moveToRightEndOfLine:, CommandEndLine); + SEL_TO_COMMAND(moveToRightEndOfLineAndModifySelection:, CommandSelectEndLine); + if (aType == nsIWidget::NativeKeyBindingsForSingleLineEditor) { + SEL_TO_COMMAND(moveUp:, CommandBeginLine); } else { - SEL_TO_COMMAND(moveUp:, "cmd_linePrevious"); + SEL_TO_COMMAND(moveUp:, CommandLinePrevious); } - SEL_TO_COMMAND(moveUpAndModifySelection:, "cmd_selectLinePrevious"); - SEL_TO_COMMAND(moveWordBackward:, "cmd_wordPrevious"); - SEL_TO_COMMAND(moveWordBackwardAndModifySelection:, "cmd_selectWordPrevious"); - SEL_TO_COMMAND(moveWordForward:, "cmd_wordNext"); - SEL_TO_COMMAND(moveWordForwardAndModifySelection:, "cmd_selectWordNext"); - SEL_TO_COMMAND(moveWordLeft:, "cmd_wordPrevious"); - SEL_TO_COMMAND(moveWordLeftAndModifySelection:, "cmd_selectWordPrevious"); - SEL_TO_COMMAND(moveWordRight:, "cmd_wordNext"); - SEL_TO_COMMAND(moveWordRightAndModifySelection:, "cmd_selectWordNext"); - SEL_TO_COMMAND(pageDown:, "cmd_movePageDown"); - SEL_TO_COMMAND(pageDownAndModifySelection:, "cmd_selectPageDown"); - SEL_TO_COMMAND(pageUp:, "cmd_movePageUp"); - SEL_TO_COMMAND(pageUpAndModifySelection:, "cmd_selectPageUp"); - SEL_TO_COMMAND(paste:, "cmd_paste"); + SEL_TO_COMMAND(moveUpAndModifySelection:, CommandSelectLinePrevious); + SEL_TO_COMMAND(moveWordBackward:, CommandWordPrevious); + SEL_TO_COMMAND(moveWordBackwardAndModifySelection:, + CommandSelectWordPrevious); + SEL_TO_COMMAND(moveWordForward:, CommandWordNext); + SEL_TO_COMMAND(moveWordForwardAndModifySelection:, CommandSelectWordNext); + SEL_TO_COMMAND(moveWordLeft:, CommandWordPrevious); + SEL_TO_COMMAND(moveWordLeftAndModifySelection:, CommandSelectWordPrevious); + SEL_TO_COMMAND(moveWordRight:, CommandWordNext); + SEL_TO_COMMAND(moveWordRightAndModifySelection:, CommandSelectWordNext); + SEL_TO_COMMAND(pageDown:, CommandMovePageDown); + SEL_TO_COMMAND(pageDownAndModifySelection:, CommandSelectPageDown); + SEL_TO_COMMAND(pageUp:, CommandMovePageUp); + SEL_TO_COMMAND(pageUpAndModifySelection:, CommandSelectPageUp); + SEL_TO_COMMAND(paste:, CommandPaste); // SEL_TO_COMMAND(pasteFont:, ); // SEL_TO_COMMAND(pasteRuler:, ); - SEL_TO_COMMAND(scrollLineDown:, "cmd_scrollLineDown"); - SEL_TO_COMMAND(scrollLineUp:, "cmd_scrollLineUp"); - SEL_TO_COMMAND(scrollPageDown:, "cmd_scrollPageDown"); - SEL_TO_COMMAND(scrollPageUp:, "cmd_scrollPageUp"); - SEL_TO_COMMAND(scrollToBeginningOfDocument:, "cmd_scrollTop"); - SEL_TO_COMMAND(scrollToEndOfDocument:, "cmd_scrollBottom"); - SEL_TO_COMMAND(selectAll:, "cmd_selectAll"); + SEL_TO_COMMAND(scrollLineDown:, CommandScrollLineDown); + SEL_TO_COMMAND(scrollLineUp:, CommandScrollLineUp); + SEL_TO_COMMAND(scrollPageDown:, CommandScrollPageDown); + SEL_TO_COMMAND(scrollPageUp:, CommandScrollPageUp); + SEL_TO_COMMAND(scrollToBeginningOfDocument:, CommandScrollTop); + SEL_TO_COMMAND(scrollToEndOfDocument:, CommandScrollBottom); + SEL_TO_COMMAND(selectAll:, CommandSelectAll); // selectLine: is complex, see KeyDown - if (aType == eNativeKeyBindingsType_Input) { - SEL_TO_COMMAND(selectParagraph:, "cmd_selectAll"); + if (aType == nsIWidget::NativeKeyBindingsForSingleLineEditor) { + SEL_TO_COMMAND(selectParagraph:, CommandSelectAll); } // SEL_TO_COMMAND(selectToMark:, ); // selectWord: is complex, see KeyDown @@ -158,24 +198,14 @@ NativeKeyBindings::Init(NativeKeyBindingsType aType) // SEL_TO_COMMAND(transposeWords:, ); // SEL_TO_COMMAND(uppercaseWord:, ); // SEL_TO_COMMAND(yank:, ); - - return NS_OK; } #undef SEL_TO_COMMAND -NS_IMPL_ISUPPORTS1(NativeKeyBindings, nsINativeKeyBindings) - -NS_IMETHODIMP_(bool) -NativeKeyBindings::KeyDown(const WidgetKeyboardEvent& aEvent, - DoCommandCallback aCallback, void* aCallbackData) -{ - return false; -} - -NS_IMETHODIMP_(bool) -NativeKeyBindings::KeyPress(const WidgetKeyboardEvent& aEvent, - DoCommandCallback aCallback, void* aCallbackData) +bool +NativeKeyBindings::Execute(const WidgetKeyboardEvent& aEvent, + DoCommandCallback aCallback, + void* aCallbackData) { PR_LOG(gNativeKeyBindingsLog, PR_LOG_ALWAYS, ("%p NativeKeyBindings::KeyPress", this)); @@ -202,7 +232,7 @@ NativeKeyBindings::KeyPress(const WidgetKeyboardEvent& aEvent, ("%p NativeKeyBindings::KeyPress, bindingCommands=%u", this, bindingCommands.Length())); - nsAutoTArray geckoCommands; + nsAutoTArray geckoCommands; for (uint32_t i = 0; i < bindingCommands.Length(); i++) { SEL selector = bindingCommands[i].selector; @@ -220,23 +250,23 @@ NativeKeyBindings::KeyPress(const WidgetKeyboardEvent& aEvent, #endif // Try to find a simple mapping in the hashtable - const char* commandStr = mSelectorToCommand.Get( - reinterpret_cast(selector)); + Command geckoCommand = static_cast(mSelectorToCommand.Get( + reinterpret_cast(selector))); - if (commandStr) { - geckoCommands.AppendElement(commandStr); + if (geckoCommand) { + geckoCommands.AppendElement(geckoCommand); } else if (selector == @selector(selectLine:)) { // This is functional, but Cocoa's version is direction-less in that // selection direction is not determined until some future directed action // is taken. See bug 282097, comment 79 for more details. - geckoCommands.AppendElement("cmd_beginLine"); - geckoCommands.AppendElement("cmd_selectEndLine"); + geckoCommands.AppendElement(CommandBeginLine); + geckoCommands.AppendElement(CommandSelectEndLine); } else if (selector == @selector(selectWord:)) { // This is functional, but Cocoa's version is direction-less in that // selection direction is not determined until some future directed action // is taken. See bug 282097, comment 79 for more details. - geckoCommands.AppendElement("cmd_wordPrevious"); - geckoCommands.AppendElement("cmd_selectWordNext"); + geckoCommands.AppendElement(CommandWordPrevious); + geckoCommands.AppendElement(CommandSelectWordNext); } } @@ -248,7 +278,7 @@ NativeKeyBindings::KeyPress(const WidgetKeyboardEvent& aEvent, } for (uint32_t i = 0; i < geckoCommands.Length(); i++) { - const char* geckoCommand = geckoCommands[i]; + Command geckoCommand = geckoCommands[i]; PR_LOG(gNativeKeyBindingsLog, PR_LOG_ALWAYS, ("%p NativeKeyBindings::KeyPress, command=%s", @@ -264,9 +294,5 @@ NativeKeyBindings::KeyPress(const WidgetKeyboardEvent& aEvent, return true; } -NS_IMETHODIMP_(bool) -NativeKeyBindings::KeyUp(const WidgetKeyboardEvent& aEvent, - DoCommandCallback aCallback, void* aCallbackData) -{ - return false; -} +} // namespace widget +} // namespace mozilla diff --git a/widget/cocoa/nsChildView.h b/widget/cocoa/nsChildView.h index e440193e894..046792aab8f 100644 --- a/widget/cocoa/nsChildView.h +++ b/widget/cocoa/nsChildView.h @@ -521,6 +521,11 @@ public: NS_IMETHOD_(void) SetInputContext(const InputContext& aContext, const InputContextAction& aAction); NS_IMETHOD_(InputContext) GetInputContext(); + NS_IMETHOD_(bool) ExecuteNativeKeyBinding( + NativeKeyBindingsType aType, + const mozilla::WidgetKeyboardEvent& aEvent, + DoCommandCallback aCallback, + void* aCallbackData) MOZ_OVERRIDE; virtual nsIMEUpdatePreference GetIMEUpdatePreference() MOZ_OVERRIDE; NS_IMETHOD GetToggledKeyState(uint32_t aKeyCode, bool* aLEDState); diff --git a/widget/cocoa/nsChildView.mm b/widget/cocoa/nsChildView.mm index 66764804c3e..18b171d35e5 100644 --- a/widget/cocoa/nsChildView.mm +++ b/widget/cocoa/nsChildView.mm @@ -50,6 +50,7 @@ #ifdef __LP64__ #include "ComplexTextInputPanel.h" #endif +#include "NativeKeyBindings.h" #include "gfxContext.h" #include "gfxQuartzSurface.h" @@ -1962,6 +1963,16 @@ nsChildView::GetInputContext() return mInputContext; } +NS_IMETHODIMP_(bool) +nsChildView::ExecuteNativeKeyBinding(NativeKeyBindingsType aType, + const WidgetKeyboardEvent& aEvent, + DoCommandCallback aCallback, + void* aCallbackData) +{ + NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType); + return keyBindings->Execute(aEvent, aCallback, aCallbackData); +} + nsIMEUpdatePreference nsChildView::GetIMEUpdatePreference() { diff --git a/widget/cocoa/nsCocoaWindow.h b/widget/cocoa/nsCocoaWindow.h index de16b5dd1f1..bcd3266320a 100644 --- a/widget/cocoa/nsCocoaWindow.h +++ b/widget/cocoa/nsCocoaWindow.h @@ -344,6 +344,11 @@ public: } return mInputContext; } + NS_IMETHOD_(bool) ExecuteNativeKeyBinding( + NativeKeyBindingsType aType, + const mozilla::WidgetKeyboardEvent& aEvent, + DoCommandCallback aCallback, + void* aCallbackData) MOZ_OVERRIDE; void SetPopupWindowLevel(); diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm index a43d1a2b789..b1a5338bc52 100644 --- a/widget/cocoa/nsCocoaWindow.mm +++ b/widget/cocoa/nsCocoaWindow.mm @@ -5,12 +5,13 @@ #include "nsCocoaWindow.h" +#include "NativeKeyBindings.h" +#include "TextInputHandler.h" #include "nsObjCExceptions.h" #include "nsCOMPtr.h" #include "nsWidgetsCID.h" #include "nsIRollupListener.h" #include "nsChildView.h" -#include "TextInputHandler.h" #include "nsWindowMap.h" #include "nsAppShell.h" #include "nsIAppShellService.h" @@ -2163,6 +2164,17 @@ nsCocoaWindow::SetInputContext(const InputContext& aContext, NS_OBJC_END_TRY_ABORT_BLOCK; } +NS_IMETHODIMP_(bool) +nsCocoaWindow::ExecuteNativeKeyBinding(NativeKeyBindingsType aType, + const WidgetKeyboardEvent& aEvent, + DoCommandCallback aCallback, + void* aCallbackData) +{ + NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType); + return keyBindings->Execute(aEvent, aCallback, aCallbackData); +} + + @implementation WindowDelegate // We try to find a gecko menu bar to paint. If one does not exist, just paint diff --git a/widget/cocoa/nsColorPicker.h b/widget/cocoa/nsColorPicker.h index ccd83fe7cb5..597b97a9725 100644 --- a/widget/cocoa/nsColorPicker.h +++ b/widget/cocoa/nsColorPicker.h @@ -27,15 +27,21 @@ public: // For NSColorPanelWrapper. void Update(NSColor* aColor); + // Call this method if you are done with this input, but the color picker needs to + // stay open as it will be associated to another input + void DoneWithRetarget(); + // Same as DoneWithRetarget + clean the static instance of sColorPanelWrapper, + // as it is not needed anymore for now void Done(); private: static NSColor* GetNSColorFromHexString(const nsAString& aColor); static void GetHexStringFromNSColor(NSColor* aColor, nsAString& aResult); + static NSColorPanelWrapper* sColorPanelWrapper; + nsString mTitle; nsString mColor; - NSColorPanelWrapper* mColorPanel; nsCOMPtr mCallback; }; diff --git a/widget/cocoa/nsColorPicker.mm b/widget/cocoa/nsColorPicker.mm index 1d56be0bbcc..ff87f28d9bf 100644 --- a/widget/cocoa/nsColorPicker.mm +++ b/widget/cocoa/nsColorPicker.mm @@ -7,6 +7,7 @@ #include "nsColorPicker.h" #include "nsCocoaUtils.h" +#include "nsThreadUtils.h" using namespace mozilla; @@ -37,6 +38,7 @@ HexStrToInt(NSString* str) } - (id)initWithPicker:(nsColorPicker*)aPicker; - (void)open:(NSColor*)aInitialColor title:(NSString*)aTitle; +- (void)retarget:(nsColorPicker*)aPicker; - (void)colorChanged:(NSColorPanel*)aPanel; @end @@ -70,6 +72,12 @@ HexStrToInt(NSString* str) mColorPicker->Done(); } +- (void)retarget:(nsColorPicker*)aPicker +{ + mColorPicker->DoneWithRetarget(); + mColorPicker = aPicker; +} + - (void)dealloc { if ([mColorPanel delegate] == self) { @@ -87,15 +95,24 @@ HexStrToInt(NSString* str) NS_IMPL_ISUPPORTS1(nsColorPicker, nsIColorPicker) +NSColorPanelWrapper* nsColorPicker::sColorPanelWrapper = nullptr; + NS_IMETHODIMP nsColorPicker::Init(nsIDOMWindow* aParent, const nsAString& aTitle, const nsAString& aInitialColor) { + MOZ_ASSERT(NS_IsMainThread(), + "Color pickers can only be opened from main thread currently"); mTitle = aTitle; mColor = aInitialColor; - mColorPanel = [[NSColorPanelWrapper alloc] initWithPicker:this]; - + if (sColorPanelWrapper) { + // Update current wrapper to target the new input instead + [sColorPanelWrapper retarget:this]; + } else { + // Create a brand new color panel wrapper + sColorPanelWrapper = [[NSColorPanelWrapper alloc] initWithPicker:this]; + } return NS_OK; } @@ -130,7 +147,7 @@ nsColorPicker::Open(nsIColorPickerShownCallback* aCallback) MOZ_ASSERT(aCallback); mCallback = aCallback; - [mColorPanel open:GetNSColorFromHexString(mColor) + [sColorPanelWrapper open:GetNSColorFromHexString(mColor) title:nsCocoaUtils::ToNSString(mTitle)]; NS_ADDREF_THIS(); @@ -146,12 +163,17 @@ nsColorPicker::Update(NSColor* aColor) } void -nsColorPicker::Done() +nsColorPicker::DoneWithRetarget() { mCallback->Done(EmptyString()); mCallback = nullptr; - - [mColorPanel release]; - NS_RELEASE_THIS(); } + +void +nsColorPicker::Done() +{ + [sColorPanelWrapper release]; + sColorPanelWrapper = nullptr; + DoneWithRetarget(); +} diff --git a/widget/cocoa/nsWidgetFactory.mm b/widget/cocoa/nsWidgetFactory.mm index c49707170be..043f68af589 100644 --- a/widget/cocoa/nsWidgetFactory.mm +++ b/widget/cocoa/nsWidgetFactory.mm @@ -28,6 +28,7 @@ #include "nsSound.h" #include "nsIdleServiceX.h" +#include "NativeKeyBindings.h" #include "OSXNotificationCenter.h" #include "nsScreenManagerCocoa.h" @@ -40,6 +41,7 @@ #include "mozilla/Module.h" using namespace mozilla; +using namespace mozilla::widget; NS_GENERIC_FACTORY_CONSTRUCTOR(nsCocoaWindow) NS_GENERIC_FACTORY_CONSTRUCTOR(nsChildView) @@ -85,59 +87,6 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(GfxInfo, Init) } } -#include "NativeKeyBindings.h" -namespace mozilla { -namespace widget { - -static nsresult -NativeKeyBindingsConstructor(nsISupports* aOuter, REFNSIID aIID, - void** aResult, NativeKeyBindingsType aType) -{ - NativeKeyBindings* inst; - - *aResult = NULL; - if (NULL != aOuter) { - return NS_ERROR_NO_AGGREGATION; - } - - inst = new NativeKeyBindings(); - NS_ADDREF(inst); - nsresult rv = inst->Init(aType); - if (NS_SUCCEEDED(rv)) { - rv = inst->QueryInterface(aIID, aResult); - } - NS_RELEASE(inst); - - return rv; -} - -static nsresult -NativeKeyBindingsInputConstructor(nsISupports* aOuter, REFNSIID aIID, - void** aResult) -{ - return NativeKeyBindingsConstructor(aOuter, aIID, aResult, - eNativeKeyBindingsType_Input); -} - -static nsresult -NativeKeyBindingsTextAreaConstructor(nsISupports* aOuter, REFNSIID aIID, - void** aResult) -{ - return NativeKeyBindingsConstructor(aOuter, aIID, aResult, - eNativeKeyBindingsType_TextArea); -} - -static nsresult -NativeKeyBindingsEditorConstructor(nsISupports* aOuter, REFNSIID aIID, - void** aResult) -{ - return NativeKeyBindingsConstructor(aOuter, aIID, aResult, - eNativeKeyBindingsType_Editor); -} - -} // namespace widget -} // namespace mozilla - NS_DEFINE_NAMED_CID(NS_WINDOW_CID); NS_DEFINE_NAMED_CID(NS_POPUP_CID); NS_DEFINE_NAMED_CID(NS_CHILD_CID); @@ -164,10 +113,6 @@ NS_DEFINE_NAMED_CID(NS_MACDOCKSUPPORT_CID); NS_DEFINE_NAMED_CID(NS_MACWEBAPPUTILS_CID); NS_DEFINE_NAMED_CID(NS_STANDALONENATIVEMENU_CID); NS_DEFINE_NAMED_CID(NS_GFXINFO_CID); -NS_DEFINE_NAMED_CID(NS_NATIVEKEYBINDINGS_INPUT_CID); -NS_DEFINE_NAMED_CID(NS_NATIVEKEYBINDINGS_TEXTAREA_CID); -NS_DEFINE_NAMED_CID(NS_NATIVEKEYBINDINGS_EDITOR_CID); - static const mozilla::Module::CIDEntry kWidgetCIDs[] = { { &kNS_WINDOW_CID, false, NULL, nsCocoaWindowConstructor }, @@ -204,12 +149,6 @@ static const mozilla::Module::CIDEntry kWidgetCIDs[] = { { &kNS_MACWEBAPPUTILS_CID, false, NULL, nsMacWebAppUtilsConstructor }, { &kNS_STANDALONENATIVEMENU_CID, false, NULL, nsStandaloneNativeMenuConstructor }, { &kNS_GFXINFO_CID, false, NULL, mozilla::widget::GfxInfoConstructor }, - { &kNS_NATIVEKEYBINDINGS_INPUT_CID, false, NULL, - mozilla::widget::NativeKeyBindingsInputConstructor }, - { &kNS_NATIVEKEYBINDINGS_TEXTAREA_CID, false, NULL, - mozilla::widget::NativeKeyBindingsTextAreaConstructor }, - { &kNS_NATIVEKEYBINDINGS_EDITOR_CID, false, NULL, - mozilla::widget::NativeKeyBindingsEditorConstructor }, { NULL } }; @@ -248,16 +187,13 @@ static const mozilla::Module::ContractIDEntry kWidgetContracts[] = { { "@mozilla.org/widget/mac-web-app-utils;1", &kNS_MACWEBAPPUTILS_CID }, { "@mozilla.org/widget/standalonenativemenu;1", &kNS_STANDALONENATIVEMENU_CID }, { "@mozilla.org/gfx/info;1", &kNS_GFXINFO_CID }, - { NS_NATIVEKEYBINDINGSINPUT_CONTRACTID, &kNS_NATIVEKEYBINDINGS_INPUT_CID }, - { NS_NATIVEKEYBINDINGSTEXTAREA_CONTRACTID, - &kNS_NATIVEKEYBINDINGS_TEXTAREA_CID }, - { NS_NATIVEKEYBINDINGSEDITOR_CONTRACTID, &kNS_NATIVEKEYBINDINGS_EDITOR_CID }, { NULL } }; static void nsWidgetCocoaModuleDtor() { + NativeKeyBindings::Shutdown(); nsLookAndFeel::Shutdown(); nsToolkit::Shutdown(); nsAppShellShutdown(); diff --git a/widget/gtk/nsNativeKeyBindings.cpp b/widget/gtk/NativeKeyBindings.cpp similarity index 59% rename from widget/gtk/nsNativeKeyBindings.cpp rename to widget/gtk/NativeKeyBindings.cpp index 850efdc1404..d766ac0eaa7 100644 --- a/widget/gtk/nsNativeKeyBindings.cpp +++ b/widget/gtk/NativeKeyBindings.cpp @@ -7,7 +7,7 @@ #include "mozilla/MathAlgorithms.h" #include "mozilla/TextEvents.h" -#include "nsNativeKeyBindings.h" +#include "NativeKeyBindings.h" #include "nsString.h" #include "nsMemory.h" #include "nsGtkKeyUtils.h" @@ -16,15 +16,10 @@ #include #include -// X.h defines KeyPress -#ifdef KeyPress -#undef KeyPress -#endif +namespace mozilla { +namespace widget { -using namespace mozilla; -using namespace mozilla::widget; - -static nsINativeKeyBindings::DoCommandCallback gCurrentCallback; +static nsIWidget::DoCommandCallback gCurrentCallback; static void *gCurrentCallbackData; static bool gHandled; @@ -32,7 +27,7 @@ static bool gHandled; static void copy_clipboard_cb(GtkWidget *w, gpointer user_data) { - gCurrentCallback("cmd_copy", gCurrentCallbackData); + gCurrentCallback(CommandCopy, gCurrentCallbackData); g_signal_stop_emission_by_name(w, "copy_clipboard"); gHandled = true; } @@ -40,7 +35,7 @@ copy_clipboard_cb(GtkWidget *w, gpointer user_data) static void cut_clipboard_cb(GtkWidget *w, gpointer user_data) { - gCurrentCallback("cmd_cut", gCurrentCallbackData); + gCurrentCallback(CommandCut, gCurrentCallbackData); g_signal_stop_emission_by_name(w, "cut_clipboard"); gHandled = true; } @@ -50,19 +45,19 @@ cut_clipboard_cb(GtkWidget *w, gpointer user_data) // We don't have this distinction, so we always use editor's notion of // lines, which are newline-terminated. -static const char *const sDeleteCommands[][2] = { +static const Command sDeleteCommands[][2] = { // backward, forward - { "cmd_deleteCharBackward", "cmd_deleteCharForward" }, // CHARS - { "cmd_deleteWordBackward", "cmd_deleteWordForward" }, // WORD_ENDS - { "cmd_deleteWordBackward", "cmd_deleteWordForward" }, // WORDS - { "cmd_deleteToBeginningOfLine", "cmd_deleteToEndOfLine" }, // LINES - { "cmd_deleteToBeginningOfLine", "cmd_deleteToEndOfLine" }, // LINE_ENDS - { "cmd_deleteToBeginningOfLine", "cmd_deleteToEndOfLine" }, // PARAGRAPH_ENDS - { "cmd_deleteToBeginningOfLine", "cmd_deleteToEndOfLine" }, // PARAGRAPHS + { CommandDeleteCharBackward, CommandDeleteCharForward }, // CHARS + { CommandDeleteWordBackward, CommandDeleteWordForward }, // WORD_ENDS + { CommandDeleteWordBackward, CommandDeleteWordForward }, // WORDS + { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // LINES + { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // LINE_ENDS + { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // PARAGRAPH_ENDS + { CommandDeleteToBeginningOfLine, CommandDeleteToEndOfLine }, // PARAGRAPHS // This deletes from the end of the previous word to the beginning of the // next word, but only if the caret is not in a word. // XXX need to implement in editor - { nullptr, nullptr } // WHITESPACE + { CommandDoNothing, CommandDoNothing } // WHITESPACE }; static void @@ -82,11 +77,11 @@ delete_from_cursor_cb(GtkWidget *w, GtkDeleteType del_type, // This works like word_ends, except we first move the caret to the // beginning/end of the current word. if (forward) { - gCurrentCallback("cmd_wordNext", gCurrentCallbackData); - gCurrentCallback("cmd_wordPrevious", gCurrentCallbackData); + gCurrentCallback(CommandWordNext, gCurrentCallbackData); + gCurrentCallback(CommandWordPrevious, gCurrentCallbackData); } else { - gCurrentCallback("cmd_wordPrevious", gCurrentCallbackData); - gCurrentCallback("cmd_wordNext", gCurrentCallbackData); + gCurrentCallback(CommandWordPrevious, gCurrentCallbackData); + gCurrentCallback(CommandWordNext, gCurrentCallbackData); } } else if (del_type == GTK_DELETE_DISPLAY_LINES || del_type == GTK_DELETE_PARAGRAPHS) { @@ -94,66 +89,67 @@ delete_from_cursor_cb(GtkWidget *w, GtkDeleteType del_type, // This works like display_line_ends, except we first move the caret to the // beginning/end of the current line. if (forward) { - gCurrentCallback("cmd_beginLine", gCurrentCallbackData); + gCurrentCallback(CommandBeginLine, gCurrentCallbackData); } else { - gCurrentCallback("cmd_endLine", gCurrentCallbackData); + gCurrentCallback(CommandEndLine, gCurrentCallbackData); } } - const char *cmd = sDeleteCommands[del_type][forward]; - if (!cmd) + Command command = sDeleteCommands[del_type][forward]; + if (!command) { return; // unsupported command + } unsigned int absCount = Abs(count); for (unsigned int i = 0; i < absCount; ++i) { - gCurrentCallback(cmd, gCurrentCallbackData); + gCurrentCallback(command, gCurrentCallbackData); } } -static const char *const sMoveCommands[][2][2] = { +static const Command sMoveCommands[][2][2] = { // non-extend { backward, forward }, extend { backward, forward } // GTK differentiates between logical position, which is prev/next, // and visual position, which is always left/right. // We should fix this to work the same way for RTL text input. { // LOGICAL_POSITIONS - { "cmd_charPrevious", "cmd_charNext" }, - { "cmd_selectCharPrevious", "cmd_selectCharNext" } + { CommandCharPrevious, CommandCharNext }, + { CommandSelectCharPrevious, CommandSelectCharNext } }, { // VISUAL_POSITIONS - { "cmd_charPrevious", "cmd_charNext" }, - { "cmd_selectCharPrevious", "cmd_selectCharNext" } + { CommandCharPrevious, CommandCharNext }, + { CommandSelectCharPrevious, CommandSelectCharNext } }, { // WORDS - { "cmd_wordPrevious", "cmd_wordNext" }, - { "cmd_selectWordPrevious", "cmd_selectWordNext" } + { CommandWordPrevious, CommandWordNext }, + { CommandSelectWordPrevious, CommandSelectWordNext } }, { // DISPLAY_LINES - { "cmd_linePrevious", "cmd_lineNext" }, - { "cmd_selectLinePrevious", "cmd_selectLineNext" } + { CommandLinePrevious, CommandLineNext }, + { CommandSelectLinePrevious, CommandSelectLineNext } }, { // DISPLAY_LINE_ENDS - { "cmd_beginLine", "cmd_endLine" }, - { "cmd_selectBeginLine", "cmd_selectEndLine" } + { CommandBeginLine, CommandEndLine }, + { CommandSelectBeginLine, CommandSelectEndLine } }, { // PARAGRAPHS - { "cmd_linePrevious", "cmd_lineNext" }, - { "cmd_selectLinePrevious", "cmd_selectLineNext" } + { CommandLinePrevious, CommandLineNext }, + { CommandSelectLinePrevious, CommandSelectLineNext } }, { // PARAGRAPH_ENDS - { "cmd_beginLine", "cmd_endLine" }, - { "cmd_selectBeginLine", "cmd_selectEndLine" } + { CommandBeginLine, CommandEndLine }, + { CommandSelectBeginLine, CommandSelectEndLine } }, { // PAGES - { "cmd_movePageUp", "cmd_movePageDown" }, - { "cmd_selectPageUp", "cmd_selectPageDown" } + { CommandMovePageUp, CommandMovePageDown }, + { CommandSelectPageUp, CommandSelectPageDown } }, { // BUFFER_ENDS - { "cmd_moveTop", "cmd_moveBottom" }, - { "cmd_selectTop", "cmd_selectBottom" } + { CommandMoveTop, CommandMoveBottom }, + { CommandSelectTop, CommandSelectBottom } }, { // HORIZONTAL_PAGES (unsupported) - { nullptr, nullptr }, - { nullptr, nullptr } + { CommandDoNothing, CommandDoNothing }, + { CommandDoNothing, CommandDoNothing } } }; @@ -169,21 +165,21 @@ move_cursor_cb(GtkWidget *w, GtkMovementStep step, gint count, return; } - const char *cmd = sMoveCommands[step][extend_selection][forward]; - if (!cmd) + Command command = sMoveCommands[step][extend_selection][forward]; + if (!command) { return; // unsupported command + } - unsigned int absCount = Abs(count); for (unsigned int i = 0; i < absCount; ++i) { - gCurrentCallback(cmd, gCurrentCallbackData); + gCurrentCallback(command, gCurrentCallbackData); } } static void paste_clipboard_cb(GtkWidget *w, gpointer user_data) { - gCurrentCallback("cmd_paste", gCurrentCallbackData); + gCurrentCallback(CommandPaste, gCurrentCallbackData); g_signal_stop_emission_by_name(w, "paste_clipboard"); gHandled = true; } @@ -192,19 +188,57 @@ paste_clipboard_cb(GtkWidget *w, gpointer user_data) static void select_all_cb(GtkWidget *w, gboolean select, gpointer user_data) { - gCurrentCallback("cmd_selectAll", gCurrentCallbackData); + gCurrentCallback(CommandSelectAll, gCurrentCallbackData); g_signal_stop_emission_by_name(w, "select_all"); gHandled = true; } -void -nsNativeKeyBindings::Init(NativeKeyBindingsType aType) +NativeKeyBindings* NativeKeyBindings::sInstanceForSingleLineEditor = nullptr; +NativeKeyBindings* NativeKeyBindings::sInstanceForMultiLineEditor = nullptr; + +// static +NativeKeyBindings* +NativeKeyBindings::GetInstance(NativeKeyBindingsType aType) { switch (aType) { - case eKeyBindings_Input: + case nsIWidget::NativeKeyBindingsForSingleLineEditor: + if (!sInstanceForSingleLineEditor) { + sInstanceForSingleLineEditor = new NativeKeyBindings(); + sInstanceForSingleLineEditor->Init(aType); + } + return sInstanceForSingleLineEditor; + + default: + // fallback to multiline editor case in release build + MOZ_ASSERT(false, "aType is invalid or not yet implemented"); + case nsIWidget::NativeKeyBindingsForMultiLineEditor: + case nsIWidget::NativeKeyBindingsForRichTextEditor: + if (!sInstanceForMultiLineEditor) { + sInstanceForMultiLineEditor = new NativeKeyBindings(); + sInstanceForMultiLineEditor->Init(aType); + } + return sInstanceForMultiLineEditor; + } +} + +// static +void +NativeKeyBindings::Shutdown() +{ + delete sInstanceForSingleLineEditor; + sInstanceForSingleLineEditor = nullptr; + delete sInstanceForMultiLineEditor; + sInstanceForMultiLineEditor = nullptr; +} + +void +NativeKeyBindings::Init(NativeKeyBindingsType aType) +{ + switch (aType) { + case nsIWidget::NativeKeyBindingsForSingleLineEditor: mNativeTarget = gtk_entry_new(); break; - case eKeyBindings_TextArea: + default: mNativeTarget = gtk_text_view_new(); if (gtk_major_version > 2 || (gtk_major_version == 2 && (gtk_minor_version > 2 || @@ -232,24 +266,16 @@ nsNativeKeyBindings::Init(NativeKeyBindingsType aType) G_CALLBACK(paste_clipboard_cb), this); } -nsNativeKeyBindings::~nsNativeKeyBindings() +NativeKeyBindings::~NativeKeyBindings() { gtk_widget_destroy(mNativeTarget); g_object_unref(mNativeTarget); } -NS_IMPL_ISUPPORTS1(nsNativeKeyBindings, nsINativeKeyBindings) - bool -nsNativeKeyBindings::KeyDown(const WidgetKeyboardEvent& aEvent, - DoCommandCallback aCallback, void *aCallbackData) -{ - return false; -} - -bool -nsNativeKeyBindings::KeyPress(const WidgetKeyboardEvent& aEvent, - DoCommandCallback aCallback, void *aCallbackData) +NativeKeyBindings::Execute(const WidgetKeyboardEvent& aEvent, + DoCommandCallback aCallback, + void* aCallbackData) { // If the native key event is set, it must be synthesized for tests. // We just ignore such events because this behavior depends on system @@ -268,7 +294,7 @@ nsNativeKeyBindings::KeyPress(const WidgetKeyboardEvent& aEvent, static_cast(aEvent.mNativeKeyEvent)->keyval; } - if (KeyPressInternal(aEvent, aCallback, aCallbackData, keyval)) { + if (ExecuteInternal(aEvent, aCallback, aCallbackData, keyval)) { return true; } @@ -278,7 +304,7 @@ nsNativeKeyBindings::KeyPress(const WidgetKeyboardEvent& aEvent, aEvent.alternativeCharCodes[i].mUnshiftedCharCode; if (ch && ch != aEvent.charCode) { keyval = gdk_unicode_to_keyval(ch); - if (KeyPressInternal(aEvent, aCallback, aCallbackData, keyval)) { + if (ExecuteInternal(aEvent, aCallback, aCallbackData, keyval)) { return true; } } @@ -302,10 +328,10 @@ Code, which should be used after fixing GNOME bug 162726: } bool -nsNativeKeyBindings::KeyPressInternal(const WidgetKeyboardEvent& aEvent, - DoCommandCallback aCallback, - void *aCallbackData, - guint aKeyval) +NativeKeyBindings::ExecuteInternal(const WidgetKeyboardEvent& aEvent, + DoCommandCallback aCallback, + void* aCallbackData, + guint aKeyval) { guint modifiers = static_cast(aEvent.mNativeKeyEvent)->state; @@ -328,9 +354,5 @@ nsNativeKeyBindings::KeyPressInternal(const WidgetKeyboardEvent& aEvent, return gHandled; } -bool -nsNativeKeyBindings::KeyUp(const WidgetKeyboardEvent& aEvent, - DoCommandCallback aCallback, void *aCallbackData) -{ - return false; -} +} // namespace widget +} // namespace mozilla diff --git a/widget/gtk/NativeKeyBindings.h b/widget/gtk/NativeKeyBindings.h new file mode 100644 index 00000000000..9cc168536e8 --- /dev/null +++ b/widget/gtk/NativeKeyBindings.h @@ -0,0 +1,49 @@ +/* -*- 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/. */ + +#ifndef mozilla_widget_NativeKeyBindings_h_ +#define mozilla_widget_NativeKeyBindings_h_ + +#include +#include "mozilla/Attributes.h" +#include "mozilla/EventForwards.h" +#include "nsIWidget.h" + +namespace mozilla { +namespace widget { + +class NativeKeyBindings MOZ_FINAL +{ + typedef nsIWidget::NativeKeyBindingsType NativeKeyBindingsType; + typedef nsIWidget::DoCommandCallback DoCommandCallback; + +public: + static NativeKeyBindings* GetInstance(NativeKeyBindingsType aType); + static void Shutdown(); + + void Init(NativeKeyBindingsType aType); + + bool Execute(const WidgetKeyboardEvent& aEvent, + DoCommandCallback aCallback, + void* aCallbackData); + +private: + ~NativeKeyBindings(); + + bool ExecuteInternal(const WidgetKeyboardEvent& aEvent, + DoCommandCallback aCallback, + void* aCallbackData, + guint aKeyval); + + GtkWidget* mNativeTarget; + + static NativeKeyBindings* sInstanceForSingleLineEditor; + static NativeKeyBindings* sInstanceForMultiLineEditor; +}; + +} // namespace widget +} // namespace mozilla + +#endif // mozilla_widget_NativeKeyBindings_h_ diff --git a/widget/gtk/moz.build b/widget/gtk/moz.build index 6ce0c6fa039..6ea91d0279a 100644 --- a/widget/gtk/moz.build +++ b/widget/gtk/moz.build @@ -17,13 +17,13 @@ EXPORTS += [ UNIFIED_SOURCES += [ 'mozcontainer.c', + 'NativeKeyBindings.cpp', 'nsBidiKeyboard.cpp', 'nsColorPicker.cpp', 'nsFilePicker.cpp', 'nsGtkKeyUtils.cpp', 'nsImageToPixbuf.cpp', 'nsLookAndFeel.cpp', - 'nsNativeKeyBindings.cpp', 'nsNativeThemeGTK.cpp', 'nsScreenGtk.cpp', 'nsScreenManagerGtk.cpp', diff --git a/widget/gtk/nsNativeKeyBindings.h b/widget/gtk/nsNativeKeyBindings.h deleted file mode 100644 index 74d237b4e44..00000000000 --- a/widget/gtk/nsNativeKeyBindings.h +++ /dev/null @@ -1,64 +0,0 @@ -/* -*- 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/. */ - -#ifndef nsNativeKeyBindings_h_ -#define nsNativeKeyBindings_h_ - -// X.h defines KeyPress -#ifdef KeyPress -#undef KeyPress -#endif - -#include "nsINativeKeyBindings.h" -#include "mozilla/Attributes.h" -#include "mozilla/EventForwards.h" -#include - -enum NativeKeyBindingsType { - eKeyBindings_Input, - eKeyBindings_TextArea -}; - -#define NS_NATIVEKEYBINDINGSINPUT_CID \ -{0x5c337258, 0xa580, 0x472e, {0x86, 0x15, 0xf2, 0x77, 0xdd, 0xc5, 0xbb, 0x06}} - -#define NS_NATIVEKEYBINDINGSTEXTAREA_CID \ -{0x2a898043, 0x180f, 0x4c8b, {0x8e, 0x54, 0x41, 0x0c, 0x7a, 0x54, 0x0f, 0x27}} - -#define NS_NATIVEKEYBINDINGSEDITOR_CID \ -{0xf916ebfb, 0x78ef, 0x464b, {0x94, 0xd0, 0xa6, 0xf2, 0xca, 0x32, 0x00, 0xae}} - -class nsNativeKeyBindings MOZ_FINAL : public nsINativeKeyBindings -{ -public: - NS_HIDDEN_(void) Init(NativeKeyBindingsType aType); - - NS_DECL_ISUPPORTS - - // nsINativeKeyBindings - virtual NS_HIDDEN_(bool) KeyDown(const mozilla::WidgetKeyboardEvent& aEvent, - DoCommandCallback aCallback, - void *aCallbackData); - - virtual NS_HIDDEN_(bool) KeyPress(const mozilla::WidgetKeyboardEvent& aEvent, - DoCommandCallback aCallback, - void *aCallbackData); - - virtual NS_HIDDEN_(bool) KeyUp(const mozilla::WidgetKeyboardEvent& aEvent, - DoCommandCallback aCallback, - void *aCallbackData); - -private: - ~nsNativeKeyBindings() NS_HIDDEN; - - bool KeyPressInternal(const mozilla::WidgetKeyboardEvent& aEvent, - DoCommandCallback aCallback, - void *aCallbackData, - guint aKeyval); - - GtkWidget *mNativeTarget; -}; - -#endif diff --git a/widget/gtk/nsWidgetFactory.cpp b/widget/gtk/nsWidgetFactory.cpp index c19250b280d..5ab99d3708b 100644 --- a/widget/gtk/nsWidgetFactory.cpp +++ b/widget/gtk/nsWidgetFactory.cpp @@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/ModuleUtils.h" +#include "NativeKeyBindings.h" #include "nsWidgetsCID.h" #include "nsAppShell.h" #include "nsAppShellSingleton.h" @@ -23,7 +24,6 @@ #include "nsFilePicker.h" #include "nsSound.h" #include "nsBidiKeyboard.h" -#include "nsNativeKeyBindings.h" #include "nsScreenManagerGtk.h" #include "nsGTKToolkit.h" @@ -52,6 +52,7 @@ #include using namespace mozilla; +using namespace mozilla::widget; /* from nsFilePicker.js */ #define XULFILEPICKER_CID \ @@ -167,50 +168,6 @@ nsColorPickerConstructor(nsISupports *aOuter, REFNSIID aIID, return picker->QueryInterface(aIID, aResult); } -static nsresult -nsNativeKeyBindingsConstructor(nsISupports *aOuter, REFNSIID aIID, - void **aResult, - NativeKeyBindingsType aKeyBindingsType) -{ - nsresult rv; - - nsNativeKeyBindings *inst; - - *aResult = nullptr; - if (nullptr != aOuter) { - rv = NS_ERROR_NO_AGGREGATION; - return rv; - } - - inst = new nsNativeKeyBindings(); - if (nullptr == inst) { - rv = NS_ERROR_OUT_OF_MEMORY; - return rv; - } - NS_ADDREF(inst); - inst->Init(aKeyBindingsType); - rv = inst->QueryInterface(aIID, aResult); - NS_RELEASE(inst); - - return rv; -} - -static nsresult -nsNativeKeyBindingsInputConstructor(nsISupports *aOuter, REFNSIID aIID, - void **aResult) -{ - return nsNativeKeyBindingsConstructor(aOuter, aIID, aResult, - eKeyBindings_Input); -} - -static nsresult -nsNativeKeyBindingsTextAreaConstructor(nsISupports *aOuter, REFNSIID aIID, - void **aResult) -{ - return nsNativeKeyBindingsConstructor(aOuter, aIID, aResult, - eKeyBindings_TextArea); -} - NS_DEFINE_NAMED_CID(NS_WINDOW_CID); NS_DEFINE_NAMED_CID(NS_CHILD_CID); NS_DEFINE_NAMED_CID(NS_APPSHELL_CID); @@ -225,9 +182,6 @@ NS_DEFINE_NAMED_CID(NS_DRAGSERVICE_CID); #endif NS_DEFINE_NAMED_CID(NS_HTMLFORMATCONVERTER_CID); NS_DEFINE_NAMED_CID(NS_BIDIKEYBOARD_CID); -NS_DEFINE_NAMED_CID(NS_NATIVEKEYBINDINGSINPUT_CID); -NS_DEFINE_NAMED_CID(NS_NATIVEKEYBINDINGSTEXTAREA_CID); -NS_DEFINE_NAMED_CID(NS_NATIVEKEYBINDINGSEDITOR_CID); NS_DEFINE_NAMED_CID(NS_SCREENMANAGER_CID); NS_DEFINE_NAMED_CID(NS_THEMERENDERER_CID); #ifdef NS_PRINTING @@ -259,9 +213,6 @@ static const mozilla::Module::CIDEntry kWidgetCIDs[] = { #endif { &kNS_HTMLFORMATCONVERTER_CID, false, nullptr, nsHTMLFormatConverterConstructor }, { &kNS_BIDIKEYBOARD_CID, false, nullptr, nsBidiKeyboardConstructor }, - { &kNS_NATIVEKEYBINDINGSINPUT_CID, false, nullptr, nsNativeKeyBindingsInputConstructor }, - { &kNS_NATIVEKEYBINDINGSTEXTAREA_CID, false, nullptr, nsNativeKeyBindingsTextAreaConstructor }, - { &kNS_NATIVEKEYBINDINGSEDITOR_CID, false, nullptr, nsNativeKeyBindingsTextAreaConstructor }, { &kNS_SCREENMANAGER_CID, false, nullptr, nsScreenManagerGtkConstructor }, { &kNS_THEMERENDERER_CID, false, nullptr, nsNativeThemeGTKConstructor }, #ifdef NS_PRINTING @@ -298,9 +249,6 @@ static const mozilla::Module::ContractIDEntry kWidgetContracts[] = { #endif { "@mozilla.org/widget/htmlformatconverter;1", &kNS_HTMLFORMATCONVERTER_CID }, { "@mozilla.org/widget/bidikeyboard;1", &kNS_BIDIKEYBOARD_CID }, - { NS_NATIVEKEYBINDINGSINPUT_CONTRACTID, &kNS_NATIVEKEYBINDINGSINPUT_CID }, - { NS_NATIVEKEYBINDINGSTEXTAREA_CONTRACTID, &kNS_NATIVEKEYBINDINGSTEXTAREA_CID }, - { NS_NATIVEKEYBINDINGSEDITOR_CONTRACTID, &kNS_NATIVEKEYBINDINGSEDITOR_CID }, { "@mozilla.org/gfx/screenmanager;1", &kNS_SCREENMANAGER_CID }, { "@mozilla.org/chrome/chrome-native-theme;1", &kNS_THEMERENDERER_CID }, #ifdef NS_PRINTING @@ -325,6 +273,7 @@ static const mozilla::Module::ContractIDEntry kWidgetContracts[] = { static void nsWidgetGtk2ModuleDtor() { + NativeKeyBindings::Shutdown(); nsLookAndFeel::Shutdown(); nsFilePicker::Shutdown(); nsSound::Shutdown(); diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp index 9fb9e4de8ff..ef8562e3e5d 100644 --- a/widget/gtk/nsWindow.cpp +++ b/widget/gtk/nsWindow.cpp @@ -119,6 +119,7 @@ extern "C" { #include "nsIDOMWheelEvent.h" +#include "NativeKeyBindings.h" #include "nsWindow.h" using namespace mozilla; @@ -5973,6 +5974,16 @@ nsWindow::GetInputContext() return context; } +NS_IMETHODIMP_(bool) +nsWindow::ExecuteNativeKeyBinding(NativeKeyBindingsType aType, + const WidgetKeyboardEvent& aEvent, + DoCommandCallback aCallback, + void* aCallbackData) +{ + NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType); + return keyBindings->Execute(aEvent, aCallback, aCallbackData); +} + NS_IMETHODIMP nsWindow::GetToggledKeyState(uint32_t aKeyCode, bool* aLEDState) { diff --git a/widget/gtk/nsWindow.h b/widget/gtk/nsWindow.h index d12acef8a5e..415b4c2f9e8 100644 --- a/widget/gtk/nsWindow.h +++ b/widget/gtk/nsWindow.h @@ -267,6 +267,11 @@ public: NS_IMETHOD_(void) SetInputContext(const InputContext& aContext, const InputContextAction& aAction); NS_IMETHOD_(InputContext) GetInputContext(); + NS_IMETHOD_(bool) ExecuteNativeKeyBinding( + NativeKeyBindingsType aType, + const mozilla::WidgetKeyboardEvent& aEvent, + DoCommandCallback aCallback, + void* aCallbackData) MOZ_OVERRIDE; NS_IMETHOD GetToggledKeyState(uint32_t aKeyCode, bool* aLEDState); // These methods are for toplevel windows only. diff --git a/widget/moz.build b/widget/moz.build index a0c3df5c2c2..2e9ce77fe09 100644 --- a/widget/moz.build +++ b/widget/moz.build @@ -95,7 +95,6 @@ XPIDL_MODULE = 'widget' EXPORTS += [ 'InputData.h', 'nsIDeviceContextSpec.h', - 'nsINativeKeyBindings.h', 'nsIPluginWidget.h', 'nsIRollupListener.h', 'nsIWidget.h', @@ -106,6 +105,7 @@ EXPORTS += [ EXPORTS.mozilla += [ 'BasicEvents.h', + 'CommandList.h', 'ContentEvents.h', 'EventClassList.h', 'EventForwards.h', diff --git a/widget/nsINativeKeyBindings.h b/widget/nsINativeKeyBindings.h deleted file mode 100644 index ab2de98e3b9..00000000000 --- a/widget/nsINativeKeyBindings.h +++ /dev/null @@ -1,49 +0,0 @@ -/* -*- 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/. */ - -#ifndef nsINativeKeyBindings_h_ -#define nsINativeKeyBindings_h_ - -#include "nsISupports.h" -#include "mozilla/EventForwards.h" - -#define NS_INATIVEKEYBINDINGS_IID \ -{0xc2baecc3, 0x1758, 0x4211, {0x96, 0xbe, 0xee, 0x1b, 0x1b, 0x7c, 0xd7, 0x6d}} - -#define NS_NATIVEKEYBINDINGS_CONTRACTID_PREFIX \ - "@mozilla.org/widget/native-key-bindings;1?type=" - -#define NS_NATIVEKEYBINDINGSINPUT_CONTRACTID \ -NS_NATIVEKEYBINDINGS_CONTRACTID_PREFIX "input" - -#define NS_NATIVEKEYBINDINGSTEXTAREA_CONTRACTID \ -NS_NATIVEKEYBINDINGS_CONTRACTID_PREFIX "textarea" - -#define NS_NATIVEKEYBINDINGSEDITOR_CONTRACTID \ -NS_NATIVEKEYBINDINGS_CONTRACTID_PREFIX "editor" - -class nsINativeKeyBindings : public nsISupports -{ - public: - typedef void (*DoCommandCallback)(const char *, void*); - - NS_DECLARE_STATIC_IID_ACCESSOR(NS_INATIVEKEYBINDINGS_IID) - - virtual NS_HIDDEN_(bool) KeyDown(const mozilla::WidgetKeyboardEvent& aEvent, - DoCommandCallback aCallback, - void *aCallbackData) = 0; - - virtual NS_HIDDEN_(bool) KeyPress(const mozilla::WidgetKeyboardEvent& aEvent, - DoCommandCallback aCallback, - void *aCallbackData) = 0; - - virtual NS_HIDDEN_(bool) KeyUp(const mozilla::WidgetKeyboardEvent& aEvent, - DoCommandCallback aCallback, - void *aCallbackData) = 0; -}; - -NS_DEFINE_STATIC_IID_ACCESSOR(nsINativeKeyBindings, NS_INATIVEKEYBINDINGS_IID) - -#endif diff --git a/widget/nsIWidget.h b/widget/nsIWidget.h index de2740de8f0..d981c7bcff1 100644 --- a/widget/nsIWidget.h +++ b/widget/nsIWidget.h @@ -100,8 +100,8 @@ typedef void* nsNativeWidget; #endif #define NS_IWIDGET_IID \ -{ 0xb979c607, 0xf0aa, 0x4fee, \ - { 0xb2, 0x7b, 0xd4, 0x46, 0xa2, 0xe, 0x8b, 0x27 } } +{ 0x5d94f2d, 0x5456, 0x4436, \ + { 0xa7, 0x2f, 0x25, 0x6f, 0x47, 0xb0, 0xb5, 0x94 } } /* * Window shadow styles @@ -1832,6 +1832,22 @@ public: */ NS_IMETHOD_(InputContext) GetInputContext() = 0; + /* + * Execute native key bindings for aType. + */ + typedef void (*DoCommandCallback)(mozilla::Command, void*); + enum NativeKeyBindingsType + { + NativeKeyBindingsForSingleLineEditor, + NativeKeyBindingsForMultiLineEditor, + NativeKeyBindingsForRichTextEditor + }; + NS_IMETHOD_(bool) ExecuteNativeKeyBinding( + NativeKeyBindingsType aType, + const mozilla::WidgetKeyboardEvent& aEvent, + DoCommandCallback aCallback, + void* aCallbackData) = 0; + /** * Set layers acceleration to 'True' or 'False' */ diff --git a/widget/shared/WidgetEventImpl.cpp b/widget/shared/WidgetEventImpl.cpp index cade8647a2b..9637f7d51fd 100644 --- a/widget/shared/WidgetEventImpl.cpp +++ b/widget/shared/WidgetEventImpl.cpp @@ -307,4 +307,19 @@ WidgetKeyboardEvent::GetDOMKeyName(KeyNameIndex aKeyNameIndex, #undef KEY_STR_NUM_INTERNAL } +/* static */ const char* +WidgetKeyboardEvent::GetCommandStr(Command aCommand) +{ +#define NS_DEFINE_COMMAND(aName, aCommandStr) , #aCommandStr + static const char* kCommands[] = { + "" // CommandDoNothing +#include "mozilla/CommandList.h" + }; +#undef NS_DEFINE_COMMAND + + MOZ_RELEASE_ASSERT(static_cast(aCommand) < ArrayLength(kCommands), + "Illegal command enumeration value"); + return kCommands[aCommand]; +} + } // namespace mozilla diff --git a/widget/windows/winrt/MetroApp.cpp b/widget/windows/winrt/MetroApp.cpp index f41f2f9b1d0..8b2c0442c7e 100644 --- a/widget/windows/winrt/MetroApp.cpp +++ b/widget/windows/winrt/MetroApp.cpp @@ -5,6 +5,7 @@ #include "MetroApp.h" #include "MetroWidget.h" +#include "mozilla/IOInterposer.h" #include "mozilla/widget/AudioSession.h" #include "nsIRunnable.h" #include "MetroUtils.h" @@ -76,10 +77,11 @@ MetroApp::Run() LogThread(); // Name this thread for debugging and register it with the profiler - // as the main gecko thread. + // and IOInterposer as the main gecko thread. char aLocal; PR_SetCurrentThreadName(gGeckoThreadName); profiler_register_thread(gGeckoThreadName, &aLocal); + IOInterposer::RegisterCurrentThread(true); HRESULT hr; hr = sCoreApp->add_Suspending(Callback<__FIEventHandler_1_Windows__CApplicationModel__CSuspendingEventArgs_t>( diff --git a/widget/xpwidgets/nsBaseWidget.h b/widget/xpwidgets/nsBaseWidget.h index 14f2a4293ea..89a95da610a 100644 --- a/widget/xpwidgets/nsBaseWidget.h +++ b/widget/xpwidgets/nsBaseWidget.h @@ -187,6 +187,11 @@ public: virtual nsresult ActivateNativeMenuItemAt(const nsAString& indexString) { return NS_ERROR_NOT_IMPLEMENTED; } virtual nsresult ForceUpdateNativeMenuAt(const nsAString& indexString) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHOD NotifyIME(const IMENotification& aIMENotification) MOZ_OVERRIDE { return NS_ERROR_NOT_IMPLEMENTED; } + NS_IMETHOD_(bool) ExecuteNativeKeyBinding( + NativeKeyBindingsType aType, + const mozilla::WidgetKeyboardEvent& aEvent, + DoCommandCallback aCallback, + void* aCallbackData) MOZ_OVERRIDE { return false; } NS_IMETHOD SetLayersAcceleration(bool aEnabled); virtual bool GetLayersAcceleration() { return mUseLayersAcceleration; } virtual bool ComputeShouldAccelerate(bool aDefault); diff --git a/xpcom/base/CycleCollectedJSRuntime.cpp b/xpcom/base/CycleCollectedJSRuntime.cpp index 3e4cf23effc..4eea32d6831 100644 --- a/xpcom/base/CycleCollectedJSRuntime.cpp +++ b/xpcom/base/CycleCollectedJSRuntime.cpp @@ -283,23 +283,28 @@ private: struct Closure { - bool cycleCollectionEnabled; - nsCycleCollectionNoteRootCallback *cb; + Closure(nsCycleCollectionNoteRootCallback* aCb) + : mCycleCollectionEnabled(true), mCb(aCb) + { + } + + bool mCycleCollectionEnabled; + nsCycleCollectionNoteRootCallback* mCb; }; static void -CheckParticipatesInCycleCollection(void *aThing, const char *name, void *aClosure) +CheckParticipatesInCycleCollection(void* aThing, const char* aName, void* aClosure) { - Closure *closure = static_cast(aClosure); + Closure* closure = static_cast(aClosure); - if (closure->cycleCollectionEnabled) { + if (closure->mCycleCollectionEnabled) { return; } if (AddToCCKind(js::GCThingTraceKind(aThing)) && xpc_IsGrayGCThing(aThing)) { - closure->cycleCollectionEnabled = true; + closure->mCycleCollectionEnabled = true; } } @@ -308,10 +313,17 @@ NoteJSHolder(void *holder, nsScriptObjectTracer *&tracer, void *arg) { Closure *closure = static_cast(arg); - closure->cycleCollectionEnabled = false; - tracer->Trace(holder, TraceCallbackFunc(CheckParticipatesInCycleCollection), closure); - if (closure->cycleCollectionEnabled) { - closure->cb->NoteNativeRoot(holder, tracer); + bool noteRoot; + if (MOZ_UNLIKELY(closure->mCb->WantAllTraces())) { + noteRoot = true; + } else { + closure->mCycleCollectionEnabled = false; + tracer->Trace(holder, TraceCallbackFunc(CheckParticipatesInCycleCollection), closure); + noteRoot = closure->mCycleCollectionEnabled; + } + + if (noteRoot) { + closure->mCb->NoteNativeRoot(holder, tracer); } return PL_DHASH_NEXT; @@ -691,7 +703,7 @@ CycleCollectedJSRuntime::TraverseNativeRoots(nsCycleCollectionNoteRootCallback& // would hurt to do this after the JS holders. TraverseAdditionalNativeRoots(aCb); - Closure closure = { true, &aCb }; + Closure closure(&aCb); mJSHolders.Enumerate(NoteJSHolder, &closure); } diff --git a/xpcom/base/nsCycleCollector.cpp b/xpcom/base/nsCycleCollector.cpp index ae4e9a1e09f..c9aea3f6ba7 100644 --- a/xpcom/base/nsCycleCollector.cpp +++ b/xpcom/base/nsCycleCollector.cpp @@ -1574,6 +1574,14 @@ public: // We don't support after-processing for weak map entries. return NS_OK; } + NS_IMETHOD NoteIncrementalRoot(uint64_t aAddress) + { + if (!mDisableLog) { + fprintf(mStream, "IncrementalRoot %p\n", (void*)aAddress); + } + // We don't support after-processing for incremental roots. + return NS_OK; + } NS_IMETHOD BeginResults() { if (!mDisableLog) { @@ -2707,8 +2715,9 @@ nsCycleCollector::ScanWeakMaps() class PurpleScanBlackVisitor { public: - PurpleScanBlackVisitor(GCGraph &aGraph, uint32_t &aCount, bool &aFailed) - : mGraph(aGraph), mCount(aCount), mFailed(aFailed) + PurpleScanBlackVisitor(GCGraph &aGraph, nsICycleCollectorListener *aListener, + uint32_t &aCount, bool &aFailed) + : mGraph(aGraph), mListener(aListener), mCount(aCount), mFailed(aFailed) { } @@ -2729,6 +2738,9 @@ public: return; } MOZ_ASSERT(pi->mParticipant, "No dead objects should be in the purple buffer."); + if (MOZ_UNLIKELY(mListener)) { + mListener->NoteIncrementalRoot((uint64_t)pi->mPointer); + } if (pi->mColor == black) { return; } @@ -2737,6 +2749,7 @@ public: private: GCGraph &mGraph; + nsICycleCollectorListener *mListener; uint32_t &mCount; bool &mFailed; }; @@ -2759,7 +2772,7 @@ nsCycleCollector::ScanIncrementalRoots() // buffer here, so these objects will be suspected and freed in the next CC // if they are garbage. bool failed = false; - PurpleScanBlackVisitor purpleScanBlackVisitor(mGraph, mWhiteNodeCount, failed); + PurpleScanBlackVisitor purpleScanBlackVisitor(mGraph, mListener, mWhiteNodeCount, failed); mPurpleBuf.VisitEntries(purpleScanBlackVisitor); timeLog.Checkpoint("ScanIncrementalRoots::fix purple"); @@ -2776,10 +2789,20 @@ nsCycleCollector::ScanIncrementalRoots() while (!etor.IsDone()) { PtrInfo *pi = etor.GetNext(); - if (pi->mRefCount != 0 || pi->mColor == black) { + // If the refcount is non-zero, pi can't have been a gray JS object. + if (pi->mRefCount != 0) { continue; } + // As an optimization, if an object has already been determined to be live, + // don't consider it further. We can't do this if there is a listener, + // because the listener wants to know the complete set of incremental roots. + if (pi->mColor == black && MOZ_LIKELY(!mListener)) { + continue; + } + + // If the object is still marked gray by the GC, nothing could have gotten + // hold of it, so it isn't an incremental root. if (pi->mParticipant == jsParticipant) { if (xpc_GCThingIsGrayCCThing(pi->mPointer)) { continue; @@ -2793,6 +2816,15 @@ nsCycleCollector::ScanIncrementalRoots() MOZ_ASSERT(false, "Non-JS thing with 0 refcount? Treating as live."); } + // At this point, pi must be an incremental root. + + // If there's a listener, tell it about this root. We don't bother with the + // optimization of skipping the Walk() if pi is black: it will just return + // without doing anything and there's no need to make this case faster. + if (MOZ_UNLIKELY(mListener)) { + mListener->NoteIncrementalRoot((uint64_t)pi->mPointer); + } + GraphWalker(ScanBlackVisitor(mWhiteNodeCount, failed)).Walk(pi); } diff --git a/xpcom/base/nsICycleCollectorListener.idl b/xpcom/base/nsICycleCollectorListener.idl index 1fec82ea1ce..9c958672c50 100644 --- a/xpcom/base/nsICycleCollectorListener.idl +++ b/xpcom/base/nsICycleCollectorListener.idl @@ -26,8 +26,9 @@ interface nsICycleCollectorHandler : nsISupports * the CC graph while it's being built. The order of calls will be a * call to begin(); then for every node in the graph a call to either * noteRefCountedObject() or noteGCedObject(), followed by calls to - * noteEdge() for every edge starting at that node; then a call to - * beginResults(); then a mixture of describeRoot() for ref counted + * noteEdge() for every edge starting at that node. Then, there may + * be calls to noteIncrementalRoot(). After that, beginResults() will + * be called, followed by a mixture of describeRoot() for ref counted * nodes the CC has identified as roots and describeGarbage() for * nodes the CC has identified as garbage. Ref counted nodes that are * not identified as either roots or garbage are neither, and have a @@ -35,7 +36,7 @@ interface nsICycleCollectorHandler : nsISupports * a call to end(). If begin() returns an error none of the other * functions will be called. */ -[scriptable, builtinclass, uuid(786215b7-1e4b-433b-b617-96b176273601)] +[scriptable, builtinclass, uuid(2d04dd00-abc4-11e3-a5e2-0800200c9a66)] interface nsICycleCollectorListener : nsISupports { nsICycleCollectorListener allTraces(); @@ -70,6 +71,11 @@ interface nsICycleCollectorListener : nsISupports in unsigned long long aKey, in unsigned long long aKeyDelegate, in unsigned long long aValue); + // An "incremental root" is an object that may have had a new + // reference to it created during an incremental collection, + // and must therefore be treated as live for safety. + void noteIncrementalRoot(in unsigned long long aAddress); + void beginResults(); void describeRoot(in unsigned long long aAddress, in unsigned long aKnownEdges); diff --git a/tools/profiler/IOInterposer.cpp b/xpcom/build/IOInterposer.cpp similarity index 89% rename from tools/profiler/IOInterposer.cpp rename to xpcom/build/IOInterposer.cpp index 9f6ed9cd2e5..b825987a104 100644 --- a/tools/profiler/IOInterposer.cpp +++ b/xpcom/build/IOInterposer.cpp @@ -9,6 +9,12 @@ #include "mozilla/Mutex.h" #include "mozilla/StaticPtr.h" +#include "mozilla/ThreadLocal.h" +#if !defined(XP_WIN) +#include "NSPRInterposer.h" +#endif // !defined(XP_WIN) +#include "nsXULAppAPI.h" +#include "PoisonIOInterposer.h" using namespace mozilla; @@ -70,6 +76,7 @@ public: // List of observers registered static StaticAutoPtr sObserverLists; +static ThreadLocal sIsMainThread; /** Find if a vector contains a specific element */ template @@ -135,6 +142,30 @@ IOInterposeObserver::Operation IOInterposer::sObservedOperations = } sObserverLists = new ObserverLists(); sObservedOperations = IOInterposeObserver::OpNone; + if (sIsMainThread.init()) { +#if defined(XP_WIN) + bool isMainThread = XRE_GetWindowsEnvironment() != + WindowsEnvironmentType_Metro; +#else + bool isMainThread = true; +#endif + sIsMainThread.set(isMainThread); + } + // Now we initialize the various interposers depending on platform +#if defined(XP_WIN) || defined(XP_MACOSX) + InitPoisonIOInterposer(); +#endif + // We don't hook NSPR on Windows because PoisonIOInterposer captures a + // superset of the former's events. +#if !defined(XP_WIN) + InitNSPRIOInterposing(); +#endif +} + +/* static */ bool +IOInterposeObserver::IsMainThread() +{ + return sIsMainThread.initialized() && sIsMainThread.get(); } /* static */ void IOInterposer::Clear() @@ -226,8 +257,6 @@ IOInterposeObserver::Operation IOInterposer::sObservedOperations = /* static */ void IOInterposer::Register(IOInterposeObserver::Operation aOp, IOInterposeObserver* aObserver) { - // IOInterposer::Init most be called before this method - MOZ_ASSERT(sObserverLists); // We should never register nullptr as observer MOZ_ASSERT(aObserver); if (!sObserverLists || !aObserver) { @@ -272,8 +301,6 @@ IOInterposeObserver::Operation IOInterposer::sObservedOperations = /* static */ void IOInterposer::Unregister(IOInterposeObserver::Operation aOp, IOInterposeObserver* aObserver) { - // IOInterposer::Init most be called before this method. - MOZ_ASSERT(sObserverLists); if (!sObserverLists) { return; } @@ -324,3 +351,18 @@ IOInterposeObserver::Operation IOInterposer::sObservedOperations = } } } + +/* static */ void +IOInterposer::RegisterCurrentThread(bool aIsMainThread) +{ + // Right now this is a no-op unless we're running on Metro. + // More cross-platform stuff will be added in the near future, stay tuned! +#if defined(XP_WIN) + if (XRE_GetWindowsEnvironment() != WindowsEnvironmentType_Metro || + !sIsMainThread.initialized()) { + return; + } + sIsMainThread.set(aIsMainThread); +#endif +} + diff --git a/tools/profiler/IOInterposer.h b/xpcom/build/IOInterposer.h similarity index 91% rename from tools/profiler/IOInterposer.h rename to xpcom/build/IOInterposer.h index 10ac05c6a00..e80ddc9a810 100644 --- a/tools/profiler/IOInterposer.h +++ b/xpcom/build/IOInterposer.h @@ -136,6 +136,14 @@ public: virtual ~IOInterposeObserver() { } + +protected: + /** + * We don't use NS_IsMainThread() because we need to be able to determine the + * main thread outside of XPCOM Initialization. IOInterposer observers should + * call this function instead. + */ + static bool IsMainThread(); }; #ifdef MOZ_ENABLE_PROFILER_SPS @@ -238,10 +246,19 @@ public: * didn't register for them all. * I.e. IOInterposer::Unregister(IOInterposeObserver::OpAll, aObserver) * - * Remark: Init() must be called before observers are unregistered + * Remark: Init() must be called before observers are unregistered. */ static void Unregister(IOInterposeObserver::Operation aOp, IOInterposeObserver* aObserver); + + /** + * Registers the current thread with the IOInterposer. + * + * @param aIsMainThread true if IOInterposer should treat the current thread + * as the main thread. + */ + static void + RegisterCurrentThread(bool aIsMainThread = false); }; #else /* MOZ_ENABLE_PROFILER_SPS */ @@ -260,10 +277,23 @@ public: static inline bool IsObservedOperation(IOInterposeObserver::Operation aOp) { return false; } + static inline void RegisterCurrentThread(bool) {} }; #endif /* MOZ_ENABLE_PROFILER_SPS */ +class IOInterposerInit +{ +public: + IOInterposerInit() + { + IOInterposer::Init(); + } + + // No destructor needed at the moment -- this stuff stays active for the + // life of the process. This may change in the future. +}; + } // namespace mozilla #endif // mozilla_IOInterposer_h diff --git a/tools/profiler/NSPRInterposer.cpp b/xpcom/build/NSPRInterposer.cpp similarity index 100% rename from tools/profiler/NSPRInterposer.cpp rename to xpcom/build/NSPRInterposer.cpp diff --git a/tools/profiler/NSPRInterposer.h b/xpcom/build/NSPRInterposer.h similarity index 100% rename from tools/profiler/NSPRInterposer.h rename to xpcom/build/NSPRInterposer.h diff --git a/xpcom/build/moz.build b/xpcom/build/moz.build index 26c03278bfd..d8738f79270 100644 --- a/xpcom/build/moz.build +++ b/xpcom/build/moz.build @@ -15,6 +15,7 @@ EXPORTS += [ EXPORTS.mozilla += [ 'FileLocation.h', + 'IOInterposer.h', 'LateWriteChecks.h', 'Omnijar.h', 'PoisonIOInterposer.h', @@ -24,6 +25,7 @@ EXPORTS.mozilla += [ ] if CONFIG['OS_ARCH'] == 'WINNT': + EXPORTS += ['nsWindowsDllInterceptor.h'] EXPORTS.mozilla += ['perfprobe.h'] SOURCES += ['perfprobe.cpp'] if CONFIG['MOZ_ENABLE_PROFILER_SPS']: @@ -55,6 +57,15 @@ UNIFIED_SOURCES += [ 'Services.cpp', ] +if CONFIG['MOZ_ENABLE_PROFILER_SPS']: + SOURCES += [ + 'IOInterposer.cpp', + ] + if CONFIG['OS_ARCH'] != 'WINNT': + SOURCES += [ + 'NSPRInterposer.cpp', + ] + # FileLocation.cpp and Omnijar.cpp cannot be built in unified mode because they # use plarena.h. SOURCES += [ @@ -91,3 +102,8 @@ LOCAL_INCLUDES += [ '/chrome/src', '/docshell/base', ] + +if CONFIG['MOZ_VPX']: + LOCAL_INCLUDES += [ + '/media/libvpx', + ] diff --git a/toolkit/xre/nsWindowsDllInterceptor.h b/xpcom/build/nsWindowsDllInterceptor.h similarity index 100% rename from toolkit/xre/nsWindowsDllInterceptor.h rename to xpcom/build/nsWindowsDllInterceptor.h diff --git a/xpcom/build/nsXPComInit.cpp b/xpcom/build/nsXPComInit.cpp index d2b353b848d..43f86f4e89d 100644 --- a/xpcom/build/nsXPComInit.cpp +++ b/xpcom/build/nsXPComInit.cpp @@ -110,7 +110,6 @@ extern nsresult nsStringInputStreamConstructor(nsISupports *, REFNSIID, void **) #include "nsChromeRegistry.h" #include "nsChromeProtocolHandler.h" -#include "mozilla/IOInterposer.h" #include "mozilla/PoisonIOInterposer.h" #include "mozilla/LateWriteChecks.h" @@ -130,6 +129,9 @@ extern nsresult nsStringInputStreamConstructor(nsISupports *, REFNSIID, void **) #endif #include "ogg/ogg.h" +#ifdef MOZ_VPX +#include "vpx_mem/vpx_mem.h" +#endif #include "GeckoProfiler.h" @@ -469,6 +471,80 @@ NS_IMPL_ISUPPORTS1(OggReporter, nsIMemoryReporter) /* static */ Atomic OggReporter::sAmount; +#ifdef MOZ_VPX +class VPXReporter MOZ_FINAL : public nsIMemoryReporter +{ +public: + NS_DECL_ISUPPORTS + + VPXReporter() + { +#ifdef DEBUG + // There must be only one instance of this class, due to |sAmount| + // being static. + static bool hasRun = false; + MOZ_ASSERT(!hasRun); + hasRun = true; +#endif + sAmount = 0; + } + + static void* Alloc(size_t size) + { + void* p = malloc(size); + sAmount += MallocSizeOfOnAlloc(p); + return p; + } + + static void* Realloc(void* p, size_t size) + { + sAmount -= MallocSizeOfOnFree(p); + void *pnew = realloc(p, size); + if (pnew) { + sAmount += MallocSizeOfOnAlloc(pnew); + } else { + // realloc failed; undo the decrement from above + sAmount += MallocSizeOfOnAlloc(p); + } + return pnew; + } + + static void* Calloc(size_t nmemb, size_t size) + { + void* p = calloc(nmemb, size); + sAmount += MallocSizeOfOnAlloc(p); + return p; + } + + static void Free(void* p) + { + sAmount -= MallocSizeOfOnFree(p); + free(p); + } + +private: + // |sAmount| can be (implicitly) accessed by multiple threads, so it + // must be thread-safe. + static Atomic sAmount; + + MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf) + MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(MallocSizeOfOnAlloc) + MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE(MallocSizeOfOnFree) + + NS_IMETHODIMP + CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData) + { + return MOZ_COLLECT_REPORT( + "explicit/media/libvpx", KIND_HEAP, UNITS_BYTES, sAmount, + "Memory allocated through libvpx for WebM media files."); + } +}; + +NS_IMPL_ISUPPORTS1(VPXReporter, nsIMemoryReporter) + +/* static */ Atomic VPXReporter::sAmount; +#endif /* MOZ_VPX */ + EXPORT_XPCOM_API(nsresult) NS_InitXPCOM2(nsIServiceManager* *result, nsIFile* binDirectory, @@ -628,6 +704,17 @@ NS_InitXPCOM2(nsIServiceManager* *result, OggReporter::Realloc, OggReporter::Free); +#ifdef MOZ_VPX + // And for VPX. + vpx_mem_set_functions(VPXReporter::Alloc, + VPXReporter::Calloc, + VPXReporter::Realloc, + VPXReporter::Free, + memcpy, + memset, + memmove); +#endif + // Initialize the JS engine. if (!JS_Init()) { NS_RUNTIMEABORT("JS_Init failed"); @@ -675,10 +762,12 @@ NS_InitXPCOM2(nsIServiceManager* *result, mozilla::SystemMemoryReporter::Init(); } - // The memory reporter manager is up and running -- register a reporter for - // ICU's and libogg's memory usage. + // The memory reporter manager is up and running -- register our reporters. RegisterStrongMemoryReporter(new ICUReporter()); RegisterStrongMemoryReporter(new OggReporter()); +#ifdef MOZ_VPX + RegisterStrongMemoryReporter(new VPXReporter()); +#endif mozilla::Telemetry::Init(); @@ -879,11 +968,6 @@ ShutdownXPCOM(nsIServiceManager* servMgr) PROFILER_MARKER("Shutdown xpcom"); // If we are doing any shutdown checks, poison writes. if (gShutdownChecks != SCM_NOTHING) { - // Calling InitIOInterposer or InitPoisonIOInterposer twice doesn't - // cause any problems, they'll safely abort the initialization on their - // own initiative. - mozilla::IOInterposer::Init(); - mozilla::InitPoisonIOInterposer(); #ifdef XP_MACOSX mozilla::OnlyReportDirtyWrites(); #endif /* XP_MACOSX */ diff --git a/xpcom/io/nsLocalFileWin.cpp b/xpcom/io/nsLocalFileWin.cpp index 7badaa32f8e..6609b5eb548 100644 --- a/xpcom/io/nsLocalFileWin.cpp +++ b/xpcom/io/nsLocalFileWin.cpp @@ -592,7 +592,7 @@ struct PRFilePrivate { // copied from nsprpub/pr/src/{io/prfile.c | md/windows/w95io.c} : // PR_Open and _PR_MD_OPEN -static nsresult +nsresult OpenFile(const nsAFlatString &name, int osflags, int mode, PRFileDesc **fd) {