author | Sebastian Hengst <archaeopteryx@coole-files.de> |
Thu, 07 Sep 2017 23:59:58 +0200 | |
changeset 379604 | b4c1ad9565ee9d00d96501c4a83083daf25c1413 |
parent 379549 | 64bf417d1bdf9bb8b562d73cc0742b1ec60a8d0e (current diff) |
parent 379603 | 52433c05f30628771e15d72f87f51c9383f8372f (diff) |
child 379605 | e5e14cc4b4a8c12399b728610fa52e5bb7888cbb |
child 379680 | 6dda26ce55913e0ad11ce7373a6daca8e83f4992 |
push id | 50703 |
push user | archaeopteryx@coole-files.de |
push date | Thu, 07 Sep 2017 22:06:26 +0000 |
treeherder | autoland@e5e14cc4b4a8 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge, merge |
milestone | 57.0a1 |
first release with | nightly linux32
b4c1ad9565ee
/
57.0a1
/
20170907220212
/
files
nightly linux64
b4c1ad9565ee
/
57.0a1
/
20170907220212
/
files
nightly mac
b4c1ad9565ee
/
57.0a1
/
20170907220212
/
files
nightly win32
b4c1ad9565ee
/
57.0a1
/
20170907220212
/
files
nightly win64
b4c1ad9565ee
/
57.0a1
/
20170907220212
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
57.0a1
/
20170907220212
/
pushlog to previous
nightly linux64
57.0a1
/
20170907220212
/
pushlog to previous
nightly mac
57.0a1
/
20170907220212
/
pushlog to previous
nightly win32
57.0a1
/
20170907220212
/
pushlog to previous
nightly win64
57.0a1
/
20170907220212
/
pushlog to previous
|
--- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -667,20 +667,16 @@ pref("network.protocol-handler.expose.ne pref("network.protocol-handler.expose.snews", false); pref("network.protocol-handler.expose.nntp", false); pref("accessibility.typeaheadfind", false); pref("accessibility.typeaheadfind.timeout", 5000); pref("accessibility.typeaheadfind.linksonly", false); pref("accessibility.typeaheadfind.flashBar", 1); -// Accessibility indicator preferences such as support URL, enabled flag. -pref("accessibility.support.url", "https://support.mozilla.org/%LOCALE%/kb/accessibility-services"); -pref("accessibility.indicator.enabled", true); - // Tracks when accessibility is loaded into the previous session. pref("accessibility.loadedInLastSession", false); pref("plugins.click_to_play", true); pref("plugins.testmode", false); // Should plugins that are hidden show the infobar UI? pref("plugins.show_infobar", false);
--- a/browser/base/content/browser.css +++ b/browser/base/content/browser.css @@ -283,18 +283,17 @@ toolbarpaletteitem { %else /* On non-OSX, these should be start-aligned */ #titlebar-buttonbox-container { -moz-box-align: start; } %endif %if !defined(MOZ_WIDGET_GTK) -#TabsToolbar > .private-browsing-indicator, -#TabsToolbar > .accessibility-indicator { +#TabsToolbar > .private-browsing-indicator { -moz-box-ordinal-group: 1000; } %endif %ifdef XP_WIN #main-window[sizemode="maximized"] #titlebar-buttonbox { -moz-appearance: -moz-window-button-box-maximized; }
--- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -1357,17 +1357,16 @@ var gBrowserInit = { gURLBar.setAttribute("enablehistory", "false"); } // Misc. inits. TabletModeUpdater.init(); CombinedStopReload.init(); gPrivateBrowsingUI.init(); BrowserPageActions.init(); - gAccessibilityServiceIndicator.init(); if (window.matchMedia("(-moz-os-version: windows-win8)").matches && window.matchMedia("(-moz-windows-default-theme)").matches) { let windowFrameColor = new Color(...Cu.import("resource:///modules/Windows8WindowFrameColor.jsm", {}) .Windows8WindowFrameColor.get()); // Default to black for foreground text. if (!windowFrameColor.isContrastRatioAcceptable(new Color(0, 0, 0))) { document.documentElement.setAttribute("darkwindowframe", "true"); @@ -1844,18 +1843,16 @@ var gBrowserInit = { CompactTheme.uninit(); TrackingProtection.uninit(); CaptivePortalWatcher.uninit(); SidebarUI.uninit(); - gAccessibilityServiceIndicator.uninit(); - // Now either cancel delayedStartup, or clean up the services initialized from // it. if (this._boundDelayedStartup) { this._cancelDelayedStartup(); } else { if (Win7Features) Win7Features.onCloseWindow(); @@ -8016,73 +8013,16 @@ function getTabModalPromptBox(aWindow) { return null; } /* DEPRECATED */ function getBrowser() { return gBrowser; } -const gAccessibilityServiceIndicator = { - init() { - // Pref to enable accessibility service indicator. - gPrefService.addObserver("accessibility.indicator.enabled", this); - // Accessibility service init/shutdown event. - Services.obs.addObserver(this, "a11y-init-or-shutdown"); - this.update(Services.appinfo.accessibilityEnabled); - }, - - update(accessibilityEnabled = false) { - if (this.enabled && accessibilityEnabled) { - this._active = true; - document.documentElement.setAttribute("accessibilitymode", "true"); - [...document.querySelectorAll(".accessibility-indicator")].forEach( - indicator => ["click", "keypress"].forEach(type => - indicator.addEventListener(type, this))); - TabsInTitlebar.updateAppearance(true); - } else if (this._active) { - this._active = false; - document.documentElement.removeAttribute("accessibilitymode"); - [...document.querySelectorAll(".accessibility-indicator")].forEach( - indicator => ["click", "keypress"].forEach(type => - indicator.removeEventListener(type, this))); - TabsInTitlebar.updateAppearance(true); - } - }, - - observe(subject, topic, data) { - if (topic == "nsPref:changed" && data === "accessibility.indicator.enabled") { - this.update(Services.appinfo.accessibilityEnabled); - } else if (topic === "a11y-init-or-shutdown") { - // When "a11y-init-or-shutdown" event is fired, "1" indicates that - // accessibility service is started and "0" that it is shut down. - this.update(data === "1"); - } - }, - - get enabled() { - return gPrefService.getBoolPref("accessibility.indicator.enabled"); - }, - - handleEvent({ key, type }) { - if ((type === "keypress" && [" ", "Enter"].includes(key)) || - type === "click") { - let a11yServicesSupportURL = - Services.urlFormatter.formatURLPref("accessibility.support.url"); - gBrowser.selectedTab = gBrowser.addTab(a11yServicesSupportURL); - } - }, - - uninit() { - gPrefService.removeObserver("accessibility.indicator.enabled", this); - Services.obs.removeObserver(this, "a11y-init-or-shutdown"); - this.update(); - } -}; - var gPrivateBrowsingUI = { init: function PBUI_init() { // Do nothing for normal windows if (!PrivateBrowsingUtils.isWindowPrivate(window)) { return; } // Disable the Clear Recent History... menu item when in PB mode
--- a/browser/base/content/browser.xul +++ b/browser/base/content/browser.xul @@ -586,30 +586,30 @@ <box id="appMenu-viewCache" hidden="true"/> #ifdef CAN_DRAW_IN_TITLEBAR <vbox id="titlebar"> <hbox id="titlebar-content"> <spacer id="titlebar-spacer" flex="1"/> <hbox id="titlebar-buttonbox-container"> #ifdef XP_WIN - <button class="accessibility-indicator" tooltiptext="&accessibilityIndicator.tooltip;" aria-live="polite"/> - <hbox class="private-browsing-indicator"/> + <hbox id="private-browsing-indicator-titlebar"> + <hbox class="private-browsing-indicator"/> + </hbox> #endif <hbox id="titlebar-buttonbox"> <toolbarbutton class="titlebar-button" id="titlebar-min" oncommand="window.minimize();"/> <toolbarbutton class="titlebar-button" id="titlebar-max" oncommand="onTitlebarMaxClick();"/> <toolbarbutton class="titlebar-button" id="titlebar-close" command="cmd_closeWindow"/> </hbox> </hbox> #ifdef XP_MACOSX <!-- OS X does not natively support RTL for its titlebar items, so we prevent this secondary buttonbox from reversing order in RTL by forcing an LTR direction. --> <hbox id="titlebar-secondary-buttonbox" dir="ltr"> - <button class="accessibility-indicator" tooltiptext="&accessibilityIndicator.tooltip;" aria-live="polite"/> <hbox class="private-browsing-indicator"/> <hbox id="titlebar-fullscreen-button"/> </hbox> #endif </hbox> </vbox> #endif @@ -646,22 +646,18 @@ customizable="true" mode="icons" iconsize="small" aria-label="&tabsToolbar.label;" context="toolbar-context-menu" collapsed="true"> #if defined(MOZ_WIDGET_GTK) - <hbox class="private-browsing-indicator" + <hbox id="private-browsing-indicator" skipintoolbarset="true"/> - <button class="accessibility-indicator" - tooltiptext="&accessibilityIndicator.tooltip;" - aria-live="polite" - skipintoolbarset="true"/> #endif <tabs id="tabbrowser-tabs" class="tabbrowser-tabs" tabbrowser="content" flex="1" setfocus="false" tooltip="tabbrowser-tab-tooltip" @@ -699,18 +695,16 @@ label="&newUserContext.label;"> <menupopup id="alltabs_containersMenuTab" /> </menu> <menuseparator id="alltabs-popup-separator-2"/> </menupopup> </toolbarbutton> #if !defined(MOZ_WIDGET_GTK) - <button class="accessibility-indicator" tooltiptext="&accessibilityIndicator.tooltip;" - aria-live="polite" skipintoolbarset="true"/> <hbox class="private-browsing-indicator" skipintoolbarset="true"/> #endif #ifdef CAN_DRAW_IN_TITLEBAR <hbox class="titlebar-placeholder" type="caption-buttons" id="titlebar-placeholder-on-TabsToolbar-for-captions-buttons" persist="width" #ifndef XP_MACOSX ordinal="1000" #endif
--- a/browser/base/content/test/alerts/browser.ini +++ b/browser/base/content/test/alerts/browser.ini @@ -1,12 +1,13 @@ [DEFAULT] support-files = head.js file_dom_notifications.html [browser_notification_close.js] skip-if = os == 'win' # Bug 1227785 [browser_notification_do_not_disturb.js] +skip-if = (os == 'win' && bits == 32) # Bug 1352791 [browser_notification_open_settings.js] [browser_notification_remove_permission.js] [browser_notification_replace.js] [browser_notification_tab_switching.js]
--- a/browser/base/content/test/tabcrashed/browser.ini +++ b/browser/base/content/test/tabcrashed/browser.ini @@ -1,12 +1,12 @@ [DEFAULT] skip-if = (!e10s || !crashreporter) support-files = head.js [browser_autoSubmitRequest.js] [browser_clearEmail.js] [browser_noPermanentKey.js] -skip-if = (os == "linux" && bits == 32 && debug) # Bug 1383315 +skip-if = (os == "linux" && debug) # Bug 1383315 [browser_showForm.js] [browser_shown.js] [browser_withoutDump.js]
--- a/browser/base/content/test/tabs/browser.ini +++ b/browser/base/content/test/tabs/browser.ini @@ -1,15 +1,14 @@ [DEFAULT] support-files = dummy_page.html test_bug1358314.html [browser_abandonment_telemetry.js] -[browser_accessibility_indicator.js] [browser_allow_process_switches_despite_related_browser.js] [browser_contextmenu_openlink_after_tabnavigated.js] [browser_isLocalAboutURI.js] [browser_tabCloseProbes.js] [browser_tabSpinnerProbe.js] skip-if = !e10s # Tab spinner is e10s only. [browser_tabSwitchPrintPreview.js] skip-if = os == 'mac'
deleted file mode 100644 --- a/browser/base/content/test/tabs/browser_accessibility_indicator.js +++ /dev/null @@ -1,124 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -const A11Y_INDICATOR_ENABLED_PREF = "accessibility.indicator.enabled"; - -/** - * Test various pref and UI properties based on whether the accessibility - * indicator is enabled and the accessibility service is initialized. - * @param {Object} win browser window to check the indicator in. - * @param {Boolean} enabled pref flag for accessibility indicator. - * @param {Boolean} active whether accessibility service is started or not. - */ -function testIndicatorState(win, enabled, active) { - is(Services.prefs.getBoolPref(A11Y_INDICATOR_ENABLED_PREF), enabled, - `Indicator is ${enabled ? "enabled" : "disabled"}.`); - is(Services.appinfo.accessibilityEnabled, active, - `Accessibility service is ${active ? "enabled" : "disabled"}.`); - - let visible = enabled && active; - is(win.document.documentElement.hasAttribute("accessibilitymode"), visible, - `accessibilitymode flag is ${visible ? "set" : "unset"}.`); - - // Browser UI has 2 indicators in markup for OSX and Windows but only 1 is - // shown depending on whether the titlebar is enabled. - let expectedVisibleCount = visible ? 1 : 0; - let visibleCount = 0; - [...win.document.querySelectorAll(".accessibility-indicator")].forEach(indicator => - win.getComputedStyle(indicator).getPropertyValue("display") !== "none" && - visibleCount++); - is(expectedVisibleCount, visibleCount, - `Indicator is ${visible ? "visible" : "invisible"}.`); -} - -/** - * Instantiate accessibility service and wait for event associated with its - * startup, if necessary. - */ -async function initAccessibilityService() { - let accService = Cc["@mozilla.org/accessibilityService;1"].getService( - Ci.nsIAccessibilityService); - - if (!Services.appinfo.accessibilityEnabled) { - await new Promise(resolve => { - let observe = (subject, topic, data) => { - Services.obs.removeObserver(observe, "a11y-init-or-shutdown"); - // "1" indicates that the accessibility service is initialized. - data === "1" && resolve(); - }; - Services.obs.addObserver(observe, "a11y-init-or-shutdown"); - }) - } - - return accService; -} - -/** - * If accessibility service is not yet disabled, wait for the event associated - * with its shutdown. - */ -async function shutdownAccessibilityService() { - if (Services.appinfo.accessibilityEnabled) { - await new Promise(resolve => { - let observe = (subject, topic, data) => { - Services.obs.removeObserver(observe, "a11y-init-or-shutdown"); - // "1" indicates that the accessibility service is shutdown. - data === "0" && resolve(); - }; - Services.obs.addObserver(observe, "a11y-init-or-shutdown"); - }) - } -} - -/** - * Force garbage collection. - */ -function forceGC() { - SpecialPowers.gc(); - SpecialPowers.forceShrinkingGC(); - SpecialPowers.forceCC(); -} - -add_task(async function test_accessibility_indicator() { - info("Test default accessibility indicator state."); - let newWin = await BrowserTestUtils.openNewBrowserWindow(); - testIndicatorState(window, true, false); - testIndicatorState(newWin, true, false); - - info("Enable accessibility and ensure the indicator is shown in all windows."); - let accService = await initAccessibilityService(); // eslint-disable-line no-unused-vars - testIndicatorState(window, true, true); - testIndicatorState(newWin, true, true); - - info("Open a new window and ensure the indicator is shown there by default."); - let dynamicWin = await BrowserTestUtils.openNewBrowserWindow({ private: true }); - testIndicatorState(dynamicWin, true, true); - await BrowserTestUtils.closeWindow(dynamicWin); - - info("Preff off accessibility indicator."); - Services.prefs.setBoolPref(A11Y_INDICATOR_ENABLED_PREF, false); - testIndicatorState(window, false, true); - testIndicatorState(newWin, false, true); - dynamicWin = await BrowserTestUtils.openNewBrowserWindow({ private: true }); - testIndicatorState(dynamicWin, false, true); - - info("Preff on accessibility indicator."); - Services.prefs.setBoolPref(A11Y_INDICATOR_ENABLED_PREF, true); - testIndicatorState(window, true, true); - testIndicatorState(newWin, true, true); - testIndicatorState(dynamicWin, true, true); - - info("Disable accessibility and ensure the indicator is hidden in all windows."); - accService = undefined; - forceGC(); - await shutdownAccessibilityService(); - testIndicatorState(window, true, false); - testIndicatorState(newWin, true, false); - testIndicatorState(dynamicWin, true, false); - - Services.prefs.clearUserPref(A11Y_INDICATOR_ENABLED_PREF); - await BrowserTestUtils.closeWindow(newWin); - await BrowserTestUtils.closeWindow(dynamicWin); -});
--- a/browser/components/preferences/in-content/privacy.js +++ b/browser/components/preferences/in-content/privacy.js @@ -1642,17 +1642,18 @@ var gPrivacyPane = { case 0: // access allowed checkbox.checked = false; break; } }, _initA11yString() { let a11yLearnMoreLink = - Services.urlFormatter.formatURLPref("accessibility.support.url"); + Services.urlFormatter.formatURLPref("app.support.baseURL") + + "accessibility"; document.getElementById("a11yLearnMoreLink") .setAttribute("href", a11yLearnMoreLink); }, updateA11yPrefs(checked) { Services.prefs.setIntPref("accessibility.force_disabled", checked ? 1 : 0); } };
--- a/browser/components/resistfingerprinting/test/browser/browser_navigator.js +++ b/browser/components/resistfingerprinting/test/browser/browser_navigator.js @@ -8,17 +8,17 @@ const { classes: Cc, Constructor: CC, in const TEST_PATH = "http://example.net/browser/browser/" + "components/resistfingerprinting/test/browser/" var spoofedUserAgent; const SPOOFED_APPNAME = "Netscape"; const SPOOFED_APPVERSION = "5.0 (Windows)"; const SPOOFED_PLATFORM = "Win64"; -const SPOOFED_OSCPU = "Windows NT 6.1"; +const SPOOFED_OSCPU = "Windows NT 6.1; Win64; x64"; const SPOOFED_BUILDID = "20100101"; const SPOOFED_HW_CONCURRENCY = 2; const CONST_APPCODENAME = "Mozilla"; const CONST_PRODUCT = "Gecko"; const CONST_PRODUCTSUB = "20100101"; const CONST_VENDOR = ""; const CONST_VENDORSUB = ""; @@ -87,17 +87,17 @@ async function testWorkerNavigator() { add_task(async function setup() { await SpecialPowers.pushPrefEnv({"set": [["privacy.resistFingerprinting", true]] }); let appInfo = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo); let appVersion = parseInt(appInfo.version); let spoofedVersion = appVersion - ((appVersion - 3) % 7); - spoofedUserAgent = `Mozilla/5.0 (Windows NT 6.1; rv:${spoofedVersion}.0) Gecko/20100101 Firefox/${spoofedVersion}.0`; + spoofedUserAgent = `Mozilla/5.0 (${SPOOFED_OSCPU}; rv:${spoofedVersion}.0) Gecko/20100101 Firefox/${spoofedVersion}.0`; }); add_task(async function runNavigatorTest() { await testNavigator(); }); add_task(async function runWorkerNavigatorTest() { await testWorkerNavigator();
--- a/browser/locales/en-US/chrome/browser/browser.dtd +++ b/browser/locales/en-US/chrome/browser/browser.dtd @@ -976,14 +976,8 @@ you can use these alternative items. Oth <!ENTITY pageActionButton.tooltip "Page actions"> <!ENTITY pageAction.addToUrlbar.label "Add to Address Bar"> <!ENTITY pageAction.removeFromUrlbar.label "Remove from Address Bar"> <!ENTITY pageAction.sendTabToDevice.label "Send Tab to Device"> <!ENTITY sendToDevice.syncNotReady.label "Syncing Devices…"> <!ENTITY libraryButton.tooltip "View history, saved bookmarks, and more"> - -<!-- LOCALIZATION NOTE: (accessibilityIndicator.tooltip): This is used to - display a tooltip for accessibility indicator in toolbar/tabbar. It is also - used as a textual label for the indicator used by assistive technology - users. --> -<!ENTITY accessibilityIndicator.tooltip "Accessibility Features Enabled">
--- a/browser/modules/WindowsJumpLists.jsm +++ b/browser/modules/WindowsJumpLists.jsm @@ -511,28 +511,28 @@ this.WinTaskbarJumpList = /** * Notification handlers */ notify: function WTBJL_notify(aTimer) { // Add idle observer on the first notification so it doesn't hit startup. this._updateIdleObserver(); - this.update(); + Services.tm.idleDispatchToMainThread(() => { this.update(); }); }, observe: function WTBJL_observe(aSubject, aTopic, aData) { switch (aTopic) { case "nsPref:changed": if (this._enabled == true && !_prefs.getBoolPref(PREF_TASKBAR_ENABLED)) this._deleteActiveJumpList(); this._refreshPrefs(); this._updateTimer(); this._updateIdleObserver(); - this.update(); + Services.tm.idleDispatchToMainThread(() => { this.update(); }); break; case "profile-before-change": this._shutdown(); break; case "browser:purge-session-history": this.update();
--- a/browser/themes/linux/browser.css +++ b/browser/themes/linux/browser.css @@ -720,16 +720,22 @@ html|span.ac-emphasize-text-url { } /* Customization mode */ %include ../shared/customizableui/customizeMode.inc.css /* End customization mode */ + +#main-window[privatebrowsingmode=temporary] #private-browsing-indicator { + background: url("chrome://browser/skin/privatebrowsing-mask.png") center no-repeat; + width: 40px; +} + %include ../shared/UITour.inc.css #UITourHighlight { /* Below are some fixes for people without an X compositor on Linux. This is why we can't have nice things: */ /* Animations don't repaint properly without an X compositor. */ animation-name: none !important; /* Opacity rounds to 0 or 1 on Linux without an X compositor, making the
--- a/browser/themes/linux/jar.mn +++ b/browser/themes/linux/jar.mn @@ -17,16 +17,17 @@ browser.jar: skin/classic/browser/menuPanel-exit@2x.png skin/classic/browser/menuPanel-help.png skin/classic/browser/menuPanel-help@2x.png skin/classic/browser/monitor.png skin/classic/browser/monitor_16-10.png * skin/classic/browser/pageInfo.css skin/classic/browser/pageInfo.png skin/classic/browser/page-livemarks.png + skin/classic/browser/privatebrowsing-mask.png skin/classic/browser/searchbar.css skin/classic/browser/setDesktopBackground.css skin/classic/browser/slowStartup-16.png skin/classic/browser/webRTC-indicator.css (../shared/webRTC-indicator.css) * skin/classic/browser/controlcenter/panel.css (controlcenter/panel.css) * skin/classic/browser/customizableui/panelUI.css (customizableui/panelUI.css) * skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css) * skin/classic/browser/downloads/downloads.css (downloads/downloads.css)
new file mode 100644 index 0000000000000000000000000000000000000000..9eaf3aec7e71bd4d7f3e3d91b47fa3717a17a947 GIT binary patch literal 1355 zc$@)C1+@B!P)<h;3K|Lk000e1NJLTq000^Q000;W1^@s6<bv(R000FNNkl<Zc-rlj zTTGK@7{{4{4#uo&DJ{nyILX06!KoAmmV#165Tu17P?*ReD!2i22WQxbY|5M*i+BQ5 zP$^K<Q3rycPDG{`o7ogWG+WH>+)Q@0E&lhs&DR^3-7d?ri(lUFd;b6D`R9G}w)q?Y z{3HAagBC9FTtF;jYgu581A}{aCSD`l2_dnBaD2hzO1Lo(=XK`hG2K4fk*(~-WwWeI z&zX6wK`aQDr%Ja~=lfSr6bJ74m3Wrpx2t!VC@z@v#l#|!K;f&G?`SIuc>f90nKxBh zR3+qx$+%D+AtFyc%V0rnh5ql%<oi|L$&{5HGsw#?6a~EZtbTnL21;&V@`FdjJdR{r zK)!joaCL-$9}-~ntGZ*-_hI7Q8K&F3_1aEQ?^BvFpp$LyDDbcT?oI#Q^K26=$b*E* zi`7#&th3-$-UzN$&*FN;V~iEdVOT$dVciU_md>L!-HdE``8B<4`%qKbSzO=w2qW1K zFq}1mvBEigMLiQ$574z`7)SJju<o8lj=bVFW!TP9lrAZ@c4S*IUOI=dqFG$tdLO4U zM{#)Ld7RE1$E7Xb!>YfBk=*+@Z5Ts`&Wg*%2e_1V&!%<jCT!l5)HAx3LbuK0O35rb z4VRE1Eg7K<F$;1}@hv#lvdN6TtSRi*bRkdv*6$gT;@edJGE-Xm4ZZ)-lx)U@&3A2F z)UowjG;907H1gbKUh4Up_3qbn!mPiEwhRk4`W6n_kIpUGC%3yXtp{6V<<He#x>FLj z6$Yv+iBKZaPZ+&bCoTQKq_v=T?KI92>{%}@`^jIZ+D4uz&a3owi_+7KPByD)(-~-e z^3Jdf7PORv<~r?B99Y+dB=79AR10Gw;mYBw5NRsQm9^+uJBgm8Nw8<CRg)$mUL{^3 z1Vo6&JFENCqz-(t{x}l6^d@!-9hf0XysmWD>RJpKZ=y!iO4~Kwit$X_5#k=N@)U#z z3HhO6UIJxSrlk0Jr+NZi>Z>pYy$v5hWR8fxJWR}21O~guMYEn1ALEUh#8&j>-#}$d z9bOk}cd`r?<mAkggr<5MpPn|1Vjy=K#|#%xsoID9;9bzmOQ4sPAUCiadsZ}~ebq37 zo?DO?RAJZ2TVd-tntdJ?n$eRpjx?WwUtE{U6)b}Vy+pW2i#N3IO>D$T?Kn<oMsYUd zI{NdbFjzPZi}4oBhDn%K4WU_i(S~W|5UVkbd6?JM(?Pvm$ycyf(+HI~<v6?5EQ19x zoSk{n@FY*&T#dQ~Z826H3?0CMATtgGno+;34-FB6wt9VRH$IE)fk`!lLkbJin1?|< z{b-~5#|dp%>#2L}!jna^*(`&F*mouH;)kb`;pdu!R<y6Nq9M|P&lUY>UU?o3nqE4Z zIwW|c&n9?eJgkha!{Nj)&=@s{gUkDINNKV4e3W2fA<ra&sBG?XEU*XJaU{e(d}WSW zyy?fvxP924d;-lWy<j&o(IfM>2%);2JSxW}UT>^)UuW5&`Vb9iXVA31m%JUQimwCp zK9L9_^T_8(IB~PNAP!Ctx$vaX3SoS6v`Bk1PLwjI5@~PA1S`r&3m}$qf11G|Zn3qo zq78S+^N>7K;led7t~_}RX<}j#H}kJSYH_N4XFS4}2qhxeP_Fr9?wA*FzZ-APqhu@B z!{cT+{B2OYD8&Xh%!wQ9Oe`iAvib`iN6zEItxjCs;YI%cX8899{RvW$k=wCC>_q?o N002ovPDHLkV1hb=mlFU0
--- a/browser/themes/osx/browser.css +++ b/browser/themes/osx/browser.css @@ -48,39 +48,31 @@ #main-window { -moz-appearance: none; background-color: #eeeeee; } /** Begin titlebar **/ -#titlebar-content { - /* Ensure the the content part of the titlebar does not shrink. */ - min-height: inherit; -} - #titlebar-buttonbox > .titlebar-button { display: none; } /* NB: these would be margin-inline-start/end if it wasn't for the fact that OS X * doesn't reverse the order of the items in the titlebar in RTL mode. */ .titlebar-placeholder[type="caption-buttons"], #titlebar-buttonbox { margin-left: 7px; } -.titlebar-placeholder[type="fullscreen-button"] { - margin-right: 4px; -} - +.titlebar-placeholder[type="fullscreen-button"], #titlebar-secondary-buttonbox { - align-items: center; - display: flex; + margin-right: 7px; + margin-left: 7px; } #main-window:not(:-moz-lwtheme) > #titlebar { -moz-appearance: -moz-window-titlebar; } #main-window:not([tabsintitlebar]) > #titlebar { height: 22px; /* The native titlebar on OS X is 22px tall. */ @@ -1227,32 +1219,77 @@ html|*.addon-webext-perm-list { } /* Customization mode */ %include ../shared/customizableui/customizeMode.inc.css /* End customization mode */ -/* Private browsing and accessibility indicators */ +.private-browsing-indicator { + background-image: url("chrome://browser/skin/privatebrowsing-mask.png"); + background-repeat: no-repeat; + background-size: 100% auto; + width: 38px; + height: 28px; + /** + * The private browsing mask graphic has a 3px flare at the top. The distance + * we want between the mask and items on either side is 7px, so we use 4px, + * since the other 3px is accounted for by the empty space on either side. + */ + margin-left: 4px; + margin-right: 4px; +} -:root[accessibilitymode][tabsintitlebar]:not([inFullscreen]) > #tab-view-deck > #browser-panel > #navigator-toolbox > #TabsToolbar > .accessibility-indicator, -:root[privatebrowsingmode=temporary][tabsintitlebar]:not([inFullscreen]) > #tab-view-deck > #browser-panel > #navigator-toolbox > #TabsToolbar > .private-browsing-indicator, -:root[accessibilitymode]:not([tabsintitlebar]) > #titlebar > #titlebar-content > #titlebar-secondary-buttonbox > .accessibility-indicator, -:root[privatebrowsingmode=temporary]:not([tabsintitlebar]) > #titlebar > #titlebar-content > #titlebar-secondary-buttonbox > .private-browsing-indicator { +#titlebar-secondary-buttonbox > .private-browsing-indicator { + position: relative; +} + +#main-window[privatebrowsingmode=temporary]:not([tabsintitlebar]) > #titlebar > #titlebar-content > #titlebar-secondary-buttonbox > .private-browsing-indicator { + background-image: url("chrome://browser/skin/privatebrowsing-mask-short.png"); + height: 20px; +} + +#main-window:not([privatebrowsingmode=temporary]) .private-browsing-indicator, +#main-window[privatebrowsingmode=temporary][inFullscreen] > #titlebar > #titlebar-content > #titlebar-secondary-buttonbox > .private-browsing-indicator, +#main-window[privatebrowsingmode=temporary]:not([inFullscreen]) > #tab-view-deck > #browser-panel > #navigator-toolbox > #TabsToolbar > .private-browsing-indicator { display: none; } -#TabsToolbar > .private-browsing-indicator:-moz-locale-dir(rtl), -#TabsToolbar > .accessibility-indicator:-moz-locale-dir(rtl) { +@media (min-resolution: 2dppx) { + .private-browsing-indicator { + background-image: url("chrome://browser/skin/privatebrowsing-mask@2x.png"); + } + #main-window[privatebrowsingmode=temporary]:not([tabsintitlebar]) > #titlebar > #titlebar-content > #titlebar-secondary-buttonbox > .private-browsing-indicator { + background-image: url("chrome://browser/skin/privatebrowsing-mask-short@2x.png"); + } +} + +#TabsToolbar > .private-browsing-indicator { + /* We offset by 38px for mask graphic, plus 4px to account for the + * margin-left, which sums to 42px. + */ + margin-right: -42px; +} + +#main-window[privatebrowsingmode=temporary] .titlebar-placeholder[type="fullscreen-button"], +#main-window[privatebrowsingmode=temporary] > #titlebar > #titlebar-content > #titlebar-secondary-buttonbox > #titlebar-fullscreen-button { + margin-left: 0px; +} + +#main-window[privatebrowsingmode=temporary][inFullscreen] .titlebar-placeholder[type="fullscreen-button"] { + /* Override display:none for .titlebar-placeholder in fullscreen so we can have consistent + position and padding for the private browsing indicator. */ + display: -moz-box; +} + +#TabsToolbar > .private-browsing-indicator:-moz-locale-dir(rtl) { -moz-box-ordinal-group: 0; } -/* End private browsing and accessibility indicators */ - %include ../shared/UITour.inc.css #UITourTooltipDescription { font-size: 1.18rem; line-height: 2rem; } #UITourTooltipClose {
--- a/browser/themes/osx/jar.mn +++ b/browser/themes/osx/jar.mn @@ -22,16 +22,20 @@ browser.jar: skin/classic/browser/panel-expander-closed.png skin/classic/browser/panel-expander-closed@2x.png skin/classic/browser/panel-expander-open.png skin/classic/browser/panel-expander-open@2x.png skin/classic/browser/panel-plus-sign.png skin/classic/browser/page-livemarks.png skin/classic/browser/page-livemarks@2x.png skin/classic/browser/pageInfo.css + skin/classic/browser/privatebrowsing-mask.png + skin/classic/browser/privatebrowsing-mask@2x.png + skin/classic/browser/privatebrowsing-mask-short.png + skin/classic/browser/privatebrowsing-mask-short@2x.png skin/classic/browser/searchbar.css skin/classic/browser/slowStartup-16.png skin/classic/browser/toolbarbutton-dropmarker.png skin/classic/browser/toolbarbutton-dropmarker@2x.png skin/classic/browser/webRTC-indicator.css * skin/classic/browser/controlcenter/panel.css (controlcenter/panel.css) * skin/classic/browser/customizableui/panelUI.css (customizableui/panelUI.css) * skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css)
new file mode 100644 index 0000000000000000000000000000000000000000..92f60e29f9fd71ed62101b7f7f22ea37423387a0 GIT binary patch literal 1074 zc$@(;1kL-2P)<h;3K|Lk000e1NJLTq001Tc000yS1^@s6;g3%U000B|Nkl<Zc-qB} z-%pcw7{xVNvLAoIl4%444X~-(j2g@wIAl{|7I(FnESb1nnPFTI#snP{iYY%Tr4)+3 zv;u9BQY}zdX$!=}mSPLCAVtJ>RX~^&5MjMEIr~0u;<9Y-7q12vAKq`z^E~GqHq3?R zv&}MRd(&~pOIy}39Nz`j1zXlV=#O{5q%MDiBH0@Nyv<0_pYAjZ2VJln9$FWKG$)#K zY)0xrov0Y`Mrdd{Q+=QV<}du~0%eB=r;bn;>h!AEMn;TszXjJa?n+`h<b@&K&G3j{ z-N8OIq;^C5=`A#-^@_UjP(SIU(Tv`I-?Xr=iOqTbluaF|%fE|ls@T^C<7fSn2&p}= z7y3|>ZbidaHr!Hp5pvI9{^2s*x)Ic-b&CIWk7^KeV@u-9&UUoNx}eUm!cja(9$bIQ zrVf(D6nk3Hoa&ZDonphda|#QOqF8wNC*}exc=p>1%-&nV+~_jAEdktN1tWoF%nmQn zjCRD`g^4J}U6bU&^`~rZv5UJ6(0_7UlBzux^f!%S+PjG9fkiyMyND;A2z^Cu#{Zn4 zx2R3B*^KWw?TT7|^C)?6{VAJUtn|ZX2r0diRPHvTx7LsF?Fe+)o%ngL8o9~UD9gNp z`yFBY-t$c4erFhpV|tu@zZ&QEso8Fac+*!mL>^p!%H|d;-l>CjuS1f`kIcA!p$`-G z8MstFl%zJ|mmLD~l53#(_8P`*Perufw<9;HTAaxa2<TQ0&<(roB@eDYWpj&NNEV>^ z*e*%suIs2gYCzz|B*qv)=PUwUPcUo_V#E^0Nb6&am>y%)%Cz>NI19OEF=m|-Z>o;A zkO$YFvbn_y617n6>XJmc(~82x%jhb(gCTtozAF>(H$K9kj={cuLLiu~6MxeVn+I`I z?x9=q;QCWGw^;r*4U`{PIov>5k`ehk)Nm+#@M<2wqYA)N6Tm?I1GMIJqb<(`Z=I;a z&7d7;xu0(6mOQxrl+7)cyR{Y-Nfs{UNo^?Fu18Ly5_KoqVJq_BrpyER5do)T%aI+c zK=s!~*h{_QtUk-ab}!Kl-LC3Q+5BR$Dk!$Mt;)r1CKSi(@l#wa&csyU$CxUd+gcA< z!d0eUMSh%ybdkI`HSN;PYkeu3TkOo1O2`uoC`&M{2HP>BB)$c*_$HB3_AX-@@k%dd z(3^H&?`HsIOBEwFor;x5&&LRm#kHU$&afs>7G<7_kw>Wmb@@-R!puvKAKp_Se{&rQ zwlpv<|IajtS(I7yWzDa=VsE{eU-`V?aFy%i=ECU7ch6%jpe)K<imYS+cvGqv@eU)M s@eSj|8bMj<l>N^x^oPgBwSbrZ7l%*`5<vo2aR2}S07*qoM6N<$f`P3K_W%F@
new file mode 100644 index 0000000000000000000000000000000000000000..ec1cf7452ef6e0251dad9113e8119793bfed4b6f GIT binary patch literal 2639 zc$@)G3b6HwP)<h;3K|Lk000e1NJLTq002w?001Zm1^@s6RPr2W000UWNkl<ZcwX(8 zTZ~=RdB=Zit-a4aGjq;d&Da=h<BoA+8*rQe=3Xa&(;-Q3N<kF*)HbP<K2Qk>rB&3b zt=g9m<t33?<*ltm)CwXfq=h&!7+<*9#DMVyk0<tI2JnpMdgfgA-fMle_7ZbC@@V9< z<)?A}qfbXOXRX=m`{}!_ttQ82{^Iz)q4giUHhkyIx$3>8u+wyf-L#nwj1EwWt4bnZ zT}sW)Gm}hHNoH-=`u2TW@7s8A%e@2Vl!!b61VHfC^T#&6@o&eTC`nIOcd>`a6ww$I zR|hpga9A3)&UAf>ikhC;`6pYyf7|bF8wOG!4S;8XE@0!uFPvES-IxFI@ZZLf1m&=s zP=zRUl>ieVk%YNqn)8jZ^ufRR{L>ei{0cY$3<EO(kN^|FBrtOOAKbj_?f*KtH$Gi| zz=kOXjmB^_$YHUh#I{cFXG?o`{^2bj0(*h;fCC5?t%q^ojhhF5Ys;J8A9^6QDS-)5 z$khQ%gw&=qY=hee2Mz#lcqTZ!2rhvPliR<%`M`nioy3a8Ig8f%>Hy|!=5y*&c0Bai z{lH`s*3!TPaGlqb&N=6BF2yN9<f@4<@3V81dfP`lEMysA3z`$>5G#lhgw}uw9<ovj zg}UUs)TJb@K_W3uELv$oRit1dj9T24omgTQXLZH$>DmIJjtF$H<o_3OD5YEO?&8Gz z>|DkGFD?g|bK>~_)`}7rQ{14KIa4JvZ5ae7F8h1k)hORs;D*~*vEg%T=vrN(I$LMx z&?qOL8>3h+5t;%(2DU1*tb4GR&wO!!&i)dW3sug(^D!fb#+e#Qh;)0lUZY4QrQvGy zZ7x$e9aGQ|t!S~oxK@DSqoirAsFqf_>rZdulQS1MwP%#dM=?=%$lBX_*s^OI-Pe}! zXA%HP-R*22+{}%4t>=}0evA63kEFO%;en^NvuZ={!WnwjbkZ}}#g4ChhN;mRj_ny{ z?CmLR)zW@l#OHozJ^dTH`QInsB@7F+BJb<+85Jy_&*F{NfTcEN^5a>y?i$Ffc_1J5 zY2D$h1M7cR3J4U%j)({U`Zj*@)N7<Q;j7=?Nzd9YlA1-*W}0I_8%<Y#nY$j_j(VJ? z1cnZeWM^<jXhBVXB?mA>$WV#5RtLbzu^Ey|3PzzcC<o38E|m<@B}%~6Ers@w`~LD4 z;%Y+I>N4?cvgCS;MQMc&6e>^vhyYf&FglHh<T$rOaF7R>bIzM;y@nye%)}gN!)B0L zQ92v5dP5(gpfTfNEvuMC(nc{iHIImo;+AWe^VTvjK|lZ}h!$Fr5=EnuU=x=`R3?S* zni{ON%+J;6=xoOr<2#cG2$>pb0*Xub8ijL~%1o7xa+}}lShQCHMSU(tiE4cuasoom z8s@~)##^gO8)*j1r4FS%sI#X>IkM*%zdm{%QiUM&4Rmtr?rm(|G2kOYz@;>$e7!pL z-Wd-6&oRb7oJOG#Bdoi*pB?vaVcn*+D7248G&n(_mW!0-v&!WFCeGysCYG$Q+r*nP z@T0$fhICv}GG%(yDs<>ajd(cbl^?#zM_-xX?!j9#$W4*es6($D=E$=rv%S5+HE5-? zNSk`M&I|ACgWiO1{M{deQy78jbOW&=j(or*Pf9vIok=WC46Uj<S;wXp8@nu(1|QqS zEB|tcC@s>aJ22WHB3NT7=pygFbee6q-AJjUqgitmG-?S)o<Bivcnw9<j=^NWQk9}z zl{AdG|M7cqi6GViLTxri6SiD?y{`^O4q#$Iv_tULinJJJ&Q-7ti%oz;Shx9F9{q<w z4*&NDj2)gt*A1Y&i9<UW9XijZEgKOl02%0Wr#^yMkkFA3lLCSu1<RTp-F)F|chFI8 zr#6-NJ=!rdQlZb4T0XbG-8jUOCnW_Z-dgQ+BB0Zdv!}*bd)=CB|9mPGy9?a?M>|jt z!~9H*`h0xR5=@{83WkC%kk-<L2vZ8yUcZK~e`gRITWspGwH@6NrS1;EH-`D~7?(J- zQH-2BkD50~s9HX^!}}|_YM3|=P35Rz2n8bEef0!u9$ihEScol2EhX$Q1d%2TL&BZ{ z<sQ87V?<K7GzBKoboG>2BoX5I6dU_b@n8swhNG{Ypg<8Et=BNT&mt}lFu~Wbd|fi2 z!1(bAM$eAX-`~Gb%+rPkqO`&rARq$1OR`8dby)*ZK6^p%(Qhwp4xfyj=luIK^oQ3j zf5ln8ZX=Q>!mOXavL7Y1DkFxH?qc76zRmA_b2pNx%+Y+ZETas(0IxptCMDI0aY3Fl zd&t&Xj=Q958kW{2L_}1l3+#DrANT+Mz1X^27MfE2s-pu92-eSWAj`uFBg5X84j_|; zVo(GGCzck~^fy;ZDp_=U&m*G(O1i|<iAjF(;(qSieFtKFIxXe6DQZr9aEc>8e?J3p z_d~a`ZvFMx#?on`jlV~}`qKkU{Bo9_urGV2Uz#hzys4Zi>2z~=&8t8n%DR(@qtm=N z@iXpu=yuAb&Mb1oH62eS;nf%RGk$EEcGCq2umAWkH|?I~rdzgR8@Aa!HCYtSRp!|L z>|4}F5;}vP>{()2G*0BIVKxsiJZ*8FcwT7~5p9$WRLACc@n3&I-=;3E8(2fRT*3r~ z$%!dW9vVhhLb`%p-{q9=w8vi_X5{2YY}$SU?Hz3y=($RTvD2S0{^1nux=h*hWY5s# zxIbR0_hBLq77D*3zd$QRQ6jWJs6ysX*Ex1-2*Cni2q>x!in@)6B1Rd2>{Dw^_3S+F z3?E;pr5Z!1L%LNjkrXLNgju*t<_9JS<Q*%VNYfDV3lLCfLW*b=5E5avT?{}K2nf-l z5m6ot0TJz4T}n)X6Ndtn(gYF^s1PFoN)&GCx|M5i&Hn;X{6$oh<4Q;Yffx@3w~*>e zG#ZWaDYeLPgF;DxfPm(Dg+ik}*u{JD%lj)iFW*U;k_MJg`Im@@;>t;(G89dDOmf-l z0UQ9r6_N6TdMcLG+LnL`K%(L_ad8mo2qy-`%9e=Wup}<V21y++xg1~)pxAHD)<3Qd zM6QT7291Uli%oDi5}Qz$8ii~0C|>SzfF*z%*m?cI_kMC_z+e#PD5z+m&a518f_PZO z(qLXH7j2fCb`Biyut+Wkm<6hT{`4cyKk@oM{r2p{)VefHD5`b>9jr)TsY|I#m8z<s z``pMko__cRpt{s0RxLf~Q2@4oaB%4E?>_dUziND9*B5l;=OWRdR|qiw`U)$S*foe& zn~^8~<Ildg_0EC)z)>K+Twn@l19n$uYkfcX*1v!0_<`X&YZq#3Z9=4EWdIW;n2<(W zU)$K0yEY#D)(;;4d3$Hu1>j|%2FT?BYeFjn_W-M4<u^)T0(c#m1EjUV6$F^|v>5=_ x1D$|jWyyL!IR^{@Da%N;c3J(6G%e{j`akW6UkWIyRSW<C002ovPDHLkV1i#V_h<kB
new file mode 100644 index 0000000000000000000000000000000000000000..586d3d9f0a4bda4175ea75f7c3725fa607875e7c GIT binary patch literal 918 zc$@*218Mw;P)<h;3K|Lk000e1NJLTq001Tc000~a1^@s64cyIT000ABNkl<Zc-qC0 zSx8i27={yqmisOmOszC6TSP2dtVGL<=r)X~teYYdLdr^W&D^rdG|^GZIcYAVGc{Az zsA#K}Xci_ep;H>Bspz;nD!iTVgo5dRtc&IhJTUY9|NlJ?%p&^ahZn1qKKd!0Z!Pqv z>m@<w`vLcTo=;JW5Cvcv&-VL%kMq;rOHgp2MiSEZ7K8KGWm5|^O~);6t@(a1X}4Cg zFmkt4w|i3yHBE<E@K`16QbXlcEeYhN25R{;OeWu1fV}-xl0a^1m^bX!b`^3x-%A3y znPE3w^YGB4QWD6`42#{Gi!ArIl0a@|Sd2>!xIM2Wf!xfnXy<HXy1iubCaMV!z28E8 zv<fw;2J|!zVyxeUMokB@Jb8ipD|}LZ!${9Ko$+^Cq4udl&Y=oa#(xprO>Smbq|*ar z>?mW>S=Wcj(W!Z9(RZRr*TFicx1zhr%?yj!tVEh?DU+u1c1-XR!ZbXIk-l;IJ};yF zrdhw6`L|-b$;}K4+jtKtE_x<)x>ii^K}As&h~C8^*f9;MM>ObdHPT$5*I-1(aV>&3 zrXkvmL(z>2VP0Qk5Zz5~W?0CEbR;_$F{#M-4Aamg8lJU*J5(mjZaZn9zWjpb0@{$* zkUOb`v&@5~sMmg_IlUF#O>Smbkb?q=PEVL9kLxi$I5C5<eHh(M0~q)*GBX?cIf|a< z0Xmy?mph>s-A!(0Sm3%8By7?$iFM3J-Ln>q4NUwK^(6*+E4rK9%&;rA$&fof5+!Qe z1GIhU#F)`Ei|WD#eAG72nYVxJ!i{a*f_ur$3=3G3g!uJ^qQp6<5VBc;=kb*=yzdZD zp5gxrTLn)lG{NO)tL+lz#qxIu*`lB~3+^R1Gwh;GBI4{-3leLehv2p82w0sc5MigB zISaMDhfBZbbcgQ#)lPD=!>kh!W0xlh<YtEXTgwq;n<EM2W`<o@bqkSevL%7s%rHM$ z9KvllMA&4R6>^@J#m)_*4-L;+T^|m#R3Ow!i7+e9ERc(w<faB{nG+V|E!Ur08I1th zU0k(H;Yt0^qzEo@h9182lohtr*fY5Qs#ko8uPkWz)QW2`2jn7WfA`=%086N4zPRN) s9z4f*PMQgFd60Wf5F^sU9I*Vq074^n>aAz0_y7O^07*qoM6N<$g0gqB7ytkO
new file mode 100644 index 0000000000000000000000000000000000000000..efb6d9b8070f646a39b04a40ee556386fb5291f6 GIT binary patch literal 2199 zc$@*32x#|-P)<h;3K|Lk000e1NJLTq002w?001}$1^@s6S0o<%000PHNkl<ZcwX(A zYiu0V8HS%ZGkdkyUVD?6IK)kC6B<LBK*A*<ZcAE7D=wuKRFSF{q0*MBv<RuB6_mD8 z)C#F-tBO)p;s-?)0$LiiL5d<!(wI0l2?-EPLUS{*9mmA6H(sy3*O_zb9IaN?DhBVY za3rsv)h9>tYBcYY?|kR%j*aNq*qWp5C%<uYZ}-|*U!t|rQ)O0r!vxF<OkrUdlod*W zBW;q-II+wKW7$!sdU0@XOKW3i%lhVRhEnPpU=?5;*xt3I{kiuZ4H2#m`y=>;AHxtB zl!rBiLMcf~rWi>L6DJm1|G<hr-gx(l(|`@UYXKhs=YXXXzmQ6*t*<?G@K@4rS|M*W z9>arSl!-8<$f_*yOpL+QIs3Mst^R#Z*8hMHfv5l^Fa#U|Ucc#{&z+1c3+>4`X{2&c zNXi9;)R`}4zv$rRudh4-yq<G21V}+1Byb+szhYxkM^>iwT2_<~3Uua+*)KW(_UD{P za##Qb(xNLjUft38%rPA0Xyd{Vlmk*ZI<q}d*M0S~`+#U())c}NAgkt8^hzmFO6fvT z4&?JnDXFXt_T^-$sfFbsuFxwQm3x%~lQRQxa!f~<R7!#ZrK?PYDRP-5fGkEBsDdg3 zrjytDg!P=SSx#YQ7s3i&<v?NHVwYsOcGwIp$+;z2#=@8ZVRORfmZXcJPZ<j1G7bIO z=1QU`$G8;pmWOCqA7SB|8bS;Gq(-vzwh#0Ei+yC`Hj@#S!Lqv+(s<hfDzES}em>2> z!3*@fF~sn(OJ9F}ONjGF<1^b~Qc09lC_^HV6w8f|G-+WccYMU?*(8A)FN@bjXxdmu zbwiNJR0d31?pwr?4fEOk`1^U$fryt4TdpB;b%-2zLl!lS6|{WwN=8m6II&}ZGo4W~ zW46BT)psu>+*rY@4<4del+>AZ_8IT6__-v@?pUa+fGJx(SnK7>Pp#n9hYn#U9R2T_ zr4>`YKm3^>H~g?k*JOJ34Wg8q;bTRK2iM_&3oKy)#o`NlXnkTi*>Rijge*nzobSI- zqA-dVMu{CZc0L6PSJYiwjYi>$WHeKfFx<|t$yF?wcy`LfcG_Vqnxrxk!19_DgOhQH z4=1Ut4Pg09E=4+?b#u@8tnO!+9{TP+d^@Sht&P4`&)WmM^^c>R*w=@Xl^DXHzGXf) zf9qOWzO<B&<5*h{oiB88=Dk6EEnx|o*Imh)dzZ6hbv>p>kVm55Q!F~Kyi7C9PRq%% z_{)9&Vz@U3fMFO2!$eBSnXW-z`T1^Izq^WCA6m_&X#d+`c0Y3vjRB@%B5gS#u~%2P zKE%V@?nRgafYeClGR-g~x?HsP_)roQc|mtRehb?kYr~WF<1+*Khr=0}X3QRC?{n|4 z>ds}<EDAB1#7K%g&mO{O`UzQ8c!duDg(B@F$#_!S^^5fgAwZ!?L{qNrIhWV%3{yyz z`-!4hhEB$?vks;u0BE@G3LbpnYrOsZDL&kDj?B0XfMs|Id*%_(#yE3ikZ^q^lj(W) zBm7PURi1GEz5qM{ljhbYu79ABs`_BQqm)z(or)urhhoWRb|VZ_Zl`oV!@3XjbJe=V z<e;+7&*wK^L+ejl7#mEHj%D?L79N2oXi~K(Kp~Ac)N}vyUm=@tkXec86$B$bDz6A2 zOoKvt-Web}=HL|;#ggA4aUm=}01aIfi-m{%FCXEmwTmf0FVRq4K-VW7YP|su^O`+V zB%~1DeCY_LvRn=Q{6;5B{l*oA8+tni>3*x9rEBV$0rUoPs;!@hr%s2T>2F+178Woo z`1h7Je)QtKc!M53O|mJQZNL66K_lcwR$=DLBg|*`89qD8KcCpermY(h!r;?DDfE9# ztT(|tZ{1}QhD51cA#qE!73SC*r`h)8F7El|t)F<ZKk&*CcK`K&-d*q5+)B%aMm`S5 zmTdoR8(lk3<YY<QQkd8M!4-vD1SZv1Er)j=CweBvJx_j-1x>X8h373D{K&3ua`4jT zo!tJDwXFZnDlWzO4=?ba-?ZtSG3<$8V!7?E(hsgVQH8=>FU@MRmfo&&JiX~<nr~Xn zvRfLcSyZJvZs7P39WNXvK9nG6RuVF+0Y%InVdtMZIM#le>+fDp)%*&5wi`Jc=Xggi z$M^N1B!sOR{3769R$*qh=djW{ERPNpvE~!AqZ46!bWKJe2zzSriy#^d!d5M#wxIi9 zzpg?o!$Y-Iqfh?@Axr8oa6R<VRl2ZzK)vOsWlEQ$Y!pfduQWpl!z>JbvD<WqCgnJe zaxgG34HKUTOq4$#v~<YlbvN{<Jn5H?*YM#reH1WdaF5}|V|u6Y{?a=PSC84|{<fm@ zU9t=)`Rkps6^5%BR>s2AObDA3HhG(+EXe1TxDaMb8%H>kK3f^gZj{dIC<jM6MF~>? zHW@oBvzE_rkeKD53#l9?G9yJcg$6YpVG_U}v3gR&$tIuh>l3&@%;GLnNF-Thlafii z5wjl+ayr7YfNWmV(Al-)RFj1XW(cqFYH0?>EE1<sIawK*Vcdz6a7J0Nrnw_0D?1%w z4v_f%)=k?V>Dcmx_{fFDj<WF?0W4u*AZA-{>3N-2NfI)SsFr;{*t+ReAOScOU`(-v zdV!TkJ5S#7yZfJgG#zysyuydTL>RiuX3^Y|NF4oq)K_cvJ@)+f{?M|vc^_~X$V^w5 z0R({C6Qjw<pMU)K4IjMOy*4?LtaogDvq_j~kq(3dXP4jH*!l3&-`*Xp3Je3U0ZBkj zQ&?VF2v`ryD~odl3<2#x98g6*$VCC8IonM@15gE+%r2Z<&R*aYU{gq8hfSY31Vu5& Z{{a}F|3zXu!;b&}002ovPDHLkV1oNq7h3=T
--- a/browser/themes/shared/browser.inc.css +++ b/browser/themes/shared/browser.inc.css @@ -65,48 +65,8 @@ #library-animatable-box { display: none; } #library-animatable-box[animate] { display: -moz-box; } -/* Private browsing and accessibility indicators */ - -.accessibility-indicator, -.private-browsing-indicator { - background-repeat: no-repeat; - background-size: 100% auto; - background-position: center; - width: 24px; - height: 24px; - margin-left: 8px; - margin-right: 8px; -} - -.accessibility-indicator { - background-image: url("chrome://browser/skin/accessibility.svg"); - -moz-user-focus: normal; - /* Clear default button styling */ - -moz-appearance: none; - margin-top: unset; - margin-bottom: unset; - min-width: unset; - color: unset; - text-shadow: unset; -} - -.accessibility-indicator:-moz-any(:hover, :active, :focus, :-moz-focusring) { - background-image: url("chrome://browser/skin/accessibility-active.svg"); - outline: 0; -} - -.private-browsing-indicator { - background-image: url("chrome://browser/skin/private-browsing.svg"); -} - -:root:not([accessibilitymode]) .accessibility-indicator, -:root:not([privatebrowsingmode=temporary]) .private-browsing-indicator { - display: none; -} - -/* End private browsing and accessibility indicators */
deleted file mode 100644 --- a/browser/themes/shared/icons/accessibility-active.svg +++ /dev/null @@ -1,10 +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/. --> -<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px"> -<path fill="#008EA4" fill-opacity="0.9886" d="M12,24L12,24C5.4,24,0,18.6,0,12l0,0C0,5.4,5.4,0,12,0l0,0c6.6,0,12,5.4,12,12l0,0 C24,18.6,18.6,24,12,24z"/> -<g> - <circle fill="#FFFFFF" cx="12" cy="6" r="2"/> - <path fill="#FFFFFF" d="M18.1,8.5h-3.6l0,0h-5l0,0H6c-0.6,0-1,0.4-1,1s0.4,1,1,1h3.5v0.6l0,0v7.8c0,0.6,0.4,1.1,1,1.1s1-0.5,1-1.1 v-4.1h1v4.1c0,0.6,0.4,1.1,1,1.1s1-0.5,1-1.1v-4.1l0,0v-4.5H18c0.6,0,1-0.4,1-1C19,8.8,18.6,8.5,18.1,8.5z"/> -</g> -</svg> \ No newline at end of file
deleted file mode 100644 --- a/browser/themes/shared/icons/accessibility.svg +++ /dev/null @@ -1,11 +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/. --> -<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px"> -<path fill="#00C8D7" fill-opacity="0.9886" d="M12,24L12,24C5.4,24,0,18.6,0,12l0,0C0,5.4,5.4,0,12,0l0,0c6.6,0,12,5.4,12,12l0,0 C24,18.6,18.6,24,12,24z"/> -<g> - <circle fill="#FFFFFF" cx="12" cy="6" r="2"/> - <path fill="#FFFFFF" d="M18.1,8.5h-3.6l0,0h-5l0,0H6c-0.6,0-1,0.4-1,1c0,0.6,0.4,1,1,1h3.5v0.6l0,0v7.8c0,0.6,0.4,1.1,1,1.1s1-0.5,1-1.1 - v-4.1h1v4.1c0,0.6,0.4,1.1,1,1.1s1-0.5,1-1.1v-4.1l0,0v-4.5H18c0.6,0,1-0.4,1-1C19,8.8,18.6,8.5,18.1,8.5z"/> -</g> -</svg>
deleted file mode 100644 --- a/browser/themes/shared/icons/private-browsing.svg +++ /dev/null @@ -1,11 +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/. --> -<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="24px"> -<path fill="#8000D7" d="M12,24L12,24C5.4,24,0,18.6,0,12l0,0C0,5.4,5.4,0,12,0l0,0c6.6,0,12,5.4,12,12l0,0 - C24,18.6,18.6,24,12,24z"/> -<path fill="#FFFFFF" d="M15.4,11c-1-0.1-1.9,0.5-2.1,1.5c0,0.3,1.2,0.7,2.3,0.7s2.1-0.7,2.1-0.9C17.7,12.1,17,11.1,15.4,11 - L15.4,11z M8.6,11c-1.6,0.1-2.3,1-2.3,1.3c0,0.2,1.1,0.9,2.1,0.9c1,0,2.3-0.4,2.3-0.7C10.5,11.5,9.5,10.9,8.6,11L8.6,11z M16.4,16.5 - c-1.7,0-2.8-2-4.4-2c-1.6,0-2.8,2-4.4,2c-2.1,0-3.6-1.9-3.6-5.3c0-2.1,0.6-2.7,3.3-2.7s3.4,1.1,4.7,1.1s2.1-1.1,4.7-1.1 - S20,9.2,20,11.2C20,14.6,18.5,16.5,16.4,16.5L16.4,16.5z"/> -</svg>
--- a/browser/themes/shared/jar.inc.mn +++ b/browser/themes/shared/jar.inc.mn @@ -107,18 +107,16 @@ skin/classic/browser/preferences/in-content/sync-devices.svg (../shared/incontentprefs/sync-devices.svg) skin/classic/browser/preferences/in-content/sync.svg (../shared/incontentprefs/sync.svg) * skin/classic/browser/preferences/in-content/containers.css (../shared/incontentprefs/containers.css) * skin/classic/browser/preferences/containers.css (../shared/preferences/containers.css) skin/classic/browser/fxa/default-avatar.svg (../shared/fxa/default-avatar.svg) skin/classic/browser/fxa/sync-illustration.svg (../shared/fxa/sync-illustration.svg) - skin/classic/browser/accessibility.svg (../shared/icons/accessibility.svg) - skin/classic/browser/accessibility-active.svg (../shared/icons/accessibility-active.svg) skin/classic/browser/arrow-left.svg (../shared/icons/arrow-left.svg) skin/classic/browser/back.svg (../shared/icons/back.svg) skin/classic/browser/back-12.svg (../shared/icons/back-12.svg) skin/classic/browser/bookmark.svg (../shared/icons/bookmark.svg) skin/classic/browser/bookmark-animation.svg (../shared/icons/bookmark-animation.svg) skin/classic/browser/bookmark-hollow.svg (../shared/icons/bookmark-hollow.svg) skin/classic/browser/bookmark-star-on-tray.svg (../shared/icons/bookmark-star-on-tray.svg) skin/classic/browser/characterEncoding.svg (../shared/icons/characterEncoding.svg) @@ -150,17 +148,16 @@ skin/classic/browser/link.svg (../shared/icons/link.svg) skin/classic/browser/mail.svg (../shared/icons/mail.svg) skin/classic/browser/menu.svg (../shared/icons/menu.svg) skin/classic/browser/new-tab.svg (../shared/icons/new-tab.svg) skin/classic/browser/new-window.svg (../shared/icons/new-window.svg) skin/classic/browser/open.svg (../shared/icons/open.svg) skin/classic/browser/page-action.svg (../shared/icons/page-action.svg) skin/classic/browser/print.svg (../shared/icons/print.svg) - skin/classic/browser/private-browsing.svg (../shared/icons/private-browsing.svg) skin/classic/browser/privateBrowsing.svg (../shared/icons/privateBrowsing.svg) skin/classic/browser/restore-session.svg (../shared/icons/restore-session.svg) skin/classic/browser/quit.svg (../shared/icons/quit.svg) skin/classic/browser/reload.svg (../shared/icons/reload.svg) skin/classic/browser/reload-to-stop.svg (../shared/icons/reload-to-stop.svg) skin/classic/browser/save.svg (../shared/icons/save.svg) skin/classic/browser/settings.svg (../shared/icons/settings.svg) skin/classic/browser/sidebars.svg (../shared/icons/sidebars.svg)
--- a/browser/themes/windows/browser.css +++ b/browser/themes/windows/browser.css @@ -229,22 +229,16 @@ * window state. Otherwise, elements in the navigator-toolbox, like the menubar, * can swallow those events. It will also place the buttons above the fog on * Windows 7 with Aero Glass. */ #titlebar-buttonbox { z-index: 1; } -#titlebar-buttonbox, -#titlebar-buttonbox-container { - display: flex; - align-items: center; -} - .titlebar-placeholder[type="caption-buttons"] { margin-left: 22px; /* space needed for Aero Snap */ } /* titlebar command buttons */ #titlebar-min { -moz-appearance: -moz-window-button-minimize; @@ -1082,36 +1076,99 @@ notification[value="translation"] { } #customization-tipPanel > .panel-arrowcontainer > .panel-arrowbox > .panel-arrow[side="right"] { margin-left: -2px; } /* End customization mode */ -/* Private browsing and accessibility indicators */ +/* Private browsing indicators */ + +/** + * Currently, we have two places where we put private browsing indicators on + * Windows. When tabsintitlebar is enabled, we draw the indicator in the titlebar. + * When tabsintitlebar is disabled, we draw the indicator at the end of the + * tabstrip. The titlebar indicator is the fiddliest of the bunch, since we + * want the bottom border of the image to line up with the bottom of the window + * caption buttons. That's why there's so much special-casing going on in here. + */ +.private-browsing-indicator { + display: none; + pointer-events: none; +} -:root:-moz-any([tabsintitlebar], [inFullscreen]):not([privatebrowsingmode=temporary]) .accessibility-indicator, -:root:-moz-any([tabsintitlebar], [inFullscreen]) .private-browsing-indicator { - margin-inline-end: 12px; +#private-browsing-indicator-titlebar { + display: block; + position: absolute; +} + +#main-window[privatebrowsingmode=temporary][tabsintitlebar] #private-browsing-indicator-titlebar > .private-browsing-indicator { + display: block; +} + +#main-window[privatebrowsingmode=temporary]:-moz-any([inFullscreen],:not([tabsintitlebar])) #TabsToolbar > .private-browsing-indicator { + display: -moz-box; +} + +#TabsToolbar > .private-browsing-indicator { + background: url("chrome://browser/skin/privatebrowsing-mask-tabstrip.png") no-repeat center -3px; + margin-inline-start: 4px; + width: 48px; } -:root:not([accessibilitymode]) .private-browsing-indicator, -.accessibility-indicator { - margin-inline-start: 12px; +/* Bug 1008183: We're intentionally using the titlebar asset here for fullscreen + * mode, since the tabstrip "mimics" the titlebar in that case with its own + * min/max/close window buttons. + */ +#private-browsing-indicator-titlebar > .private-browsing-indicator, +#main-window[inFullscreen] #TabsToolbar > .private-browsing-indicator { + background: url("chrome://browser/skin/privatebrowsing-mask-titlebar.png") no-repeat center 0px; + margin-inline-end: 4px; + width: 40px; + height: 20px; + position: relative; +} + +@media (-moz-windows-classic) { + /** + * We have to use top instead of background-position in this case, otherwise + * the bottom of the indicator would get cut off by the bounds of the + * private-browsing-indicator element. + */ + #main-window[sizemode="normal"] > #titlebar > #titlebar-content > #titlebar-buttonbox-container > #private-browsing-indicator-titlebar > .private-browsing-indicator { + top: 4px; + } } -:root[accessibilitymode][tabsintitlebar]:not([inFullscreen]) > #tab-view-deck > #browser-panel > #navigator-toolbox > #TabsToolbar > .accessibility-indicator, -:root[privatebrowsingmode=temporary][tabsintitlebar]:not([inFullscreen]) > #tab-view-deck > #browser-panel > #navigator-toolbox > #TabsToolbar > .private-browsing-indicator, -:root[accessibilitymode]:not([tabsintitlebar]) > #titlebar > #titlebar-content > #titlebar-secondary-buttonbox > .accessibility-indicator, -:root[privatebrowsingmode=temporary]:not([tabsintitlebar]) > #titlebar > #titlebar-content > #titlebar-secondary-buttonbox > .private-browsing-indicator { - display: none; +@media (-moz-os-version: windows-win7) { + @media (-moz-windows-glass) { + #main-window[sizemode="normal"] > #titlebar > #titlebar-content > #titlebar-buttonbox-container > #private-browsing-indicator-titlebar > .private-browsing-indicator { + top: 1px; + } + #main-window[sizemode="maximized"] > #titlebar > #titlebar-content > #titlebar-buttonbox-container > #private-browsing-indicator-titlebar > .private-browsing-indicator { + top: -1px; + } + } + + /** + * This next block targets Aero Basic, which has different positioning for the + * window caption buttons, and therefore needs to be special-cased. + */ + @media (-moz-windows-default-theme) { + @media (-moz-windows-compositor: 0) { + #main-window[sizemode="normal"] > #titlebar > #titlebar-content > #titlebar-buttonbox-container > #private-browsing-indicator-titlebar > .private-browsing-indicator { + background-image: url("chrome://browser/skin/privatebrowsing-mask-titlebar-win7-tall.png"); + height: 28px; + } + } + } } -/* End private browsing and accessibility indicators */ +/* End private browsing indicators */ %include ../shared/UITour.inc.css #UITourTooltipButtons { /** * Override the --arrowpanel-padding so the background extends * to the sides and bottom of the panel. */
--- a/browser/themes/windows/jar.mn +++ b/browser/themes/windows/jar.mn @@ -18,16 +18,21 @@ browser.jar: skin/classic/browser/menuPanel-exit.png skin/classic/browser/menuPanel-exit@2x.png skin/classic/browser/menuPanel-help.png skin/classic/browser/menuPanel-help@2x.png skin/classic/browser/monitor.png skin/classic/browser/monitor_16-10.png skin/classic/browser/pageInfo.css skin/classic/browser/pageInfo.png + skin/classic/browser/privatebrowsing-mask-tabstrip.png + skin/classic/browser/privatebrowsing-mask-tabstrip-win7.png + skin/classic/browser/privatebrowsing-mask-titlebar.png + skin/classic/browser/privatebrowsing-mask-titlebar-win7.png + skin/classic/browser/privatebrowsing-mask-titlebar-win7-tall.png skin/classic/browser/searchbar.css skin/classic/browser/setDesktopBackground.css skin/classic/browser/slowStartup-16.png skin/classic/browser/sync-desktopIcon.svg (../shared/sync-desktopIcon.svg) skin/classic/browser/sync-mobileIcon.svg (../shared/sync-mobileIcon.svg) skin/classic/browser/toolbarbutton-dropdown-arrow-win7.png skin/classic/browser/webRTC-indicator.css (../shared/webRTC-indicator.css) * skin/classic/browser/controlcenter/panel.css (controlcenter/panel.css) @@ -84,9 +89,11 @@ browser.jar: [extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}] chrome.jar: % override chrome://browser/skin/page-livemarks.png chrome://browser/skin/feeds/feedIcon16.png % override chrome://browser/skin/feeds/audioFeedIcon.png chrome://browser/skin/feeds/feedIcon.png % override chrome://browser/skin/feeds/audioFeedIcon16.png chrome://browser/skin/feeds/feedIcon16.png % override chrome://browser/skin/feeds/videoFeedIcon.png chrome://browser/skin/feeds/feedIcon.png % override chrome://browser/skin/feeds/videoFeedIcon16.png chrome://browser/skin/feeds/feedIcon16.png +% override chrome://browser/skin/privatebrowsing-mask-tabstrip.png chrome://browser/skin/privatebrowsing-mask-tabstrip-win7.png os=WINNT osversion<=6.1 +% override chrome://browser/skin/privatebrowsing-mask-titlebar.png chrome://browser/skin/privatebrowsing-mask-titlebar-win7.png os=WINNT osversion<=6.1 % override chrome://browser/skin/toolbarbutton-dropdown-arrow.png chrome://browser/skin/toolbarbutton-dropdown-arrow-win7.png os=WINNT osversion<=6.1
new file mode 100644 index 0000000000000000000000000000000000000000..bd5d46a76a8def4a3d8db31aed36126f5b7b062b GIT binary patch literal 949 zc$@*X14{gfP)<h;3K|Lk000e1NJLTq001xm001Be1^@s6RMh(%000AgNkl<Zc-rli zNl#Nz7(n%BAOy-(h{_Cz3llfSonfOJSH`#kV$eho5K0v&ipZplwzL2(5&=OI1cg`2 zBtU0qX-f$}fljt`1YO|$NJuIvr7!8?hWL_`H22=-JLmP~_1ymp=j-6$;5-hHm8Kx5 zm_j*Cp87hI{h*NAG*U+G6ft6XVjKV$Z~`}QJn_y~Wwzh1h~Y{&sgn~u8A5*VRWX4B z-~vwIR+Y(u2#VLcpC8>Y3J2j!I0A0q2(DfOXi+Q))Z!WdZr}*6-rhD;dNfD+0Ug8{ za0FLy_8yQSCh*0NfF5)RTTTyPqW}?sGvIiJ1ENsC6(K<eMhs8Xkvc7CP5eGZi)6_> zX^j6m!%X5&dajisS`){n*eR2Ovgj@eH+`N@W*#`67*HC?mh#etguhf!d7>{x)9aN< z-AnIo4lMI)#9P}Ev)!rRbBlOtL;2;7#pf|>`Bbxf$F6g^V2^ks-fFnfzx*nVw=_(j z-FSf_$jk)CfCbPZSTdc^WZSp?+4qLzliDKbTQQ!ruM?SUd0}rA0Slm^O)fg*FC{2I z^=;F#hhzZvuiyLj9rqr5_J%|M@1A@_yZbPtN=0TS&38b%l$*9d_z4PN%H%_<CXgAR z6Z)s|_1f$U3{CZ|&UJ&z;yMyFsI9L0JG_mYV1}k3p?+ncbX*VXqCu54H>7%*%uI0f z5x_zL^Mt=ZzzlQgP)7V&Wv#r%Q9lcBlf(KzzF^}hptoRDpA%fEE(mK{ti9f=lnrXl zus+^AZ+-*;BxWK<9{~|8X?N;8;V-ophi2RF4r@Fg3(K0rX;4}nHl6F}7l6mgtyfx{ zu;+QU?lyOW9>o!pnF)>o3jm74nKH%5ngxW&usEu<Wd&7e-Y5MK1T3HgZFZqUWT=Ym zpL?0cRcuO(j^mOG{Yr}%J?F>QbyA}<Kdx@IfYK}{JFmoNKsX9WnIp(TkK{<o5;zK? zMQAdu?7BCl24~B?kGia&a!qb<^>Sa~__lGy>awic-2#@#oOh{43+o-pGkmhMz%k$f zKx5qKR1u|3{y3yYnH|JX!`r1bVQiu^dG_!zdlK59fOP=<0}DyWlMFxuF~)a5G1`PX zhBL)yJAlHHb|lRbI0KG80+=Y^CxJ7_(MLcS3P?l;F$NsZZa`sZv%D>i5a0|rdItOj Xfp>l0?Pb^300000NkvXXu0mjfJ{+=N
new file mode 100644 index 0000000000000000000000000000000000000000..e2eba8b89e4fd2b1b9d33a5f93f6c1bbfdaf9889 GIT binary patch literal 403 zc$@)~0c`$>P)<h;3K|Lk000e1NJLTq001xm001Be0{{R3V+so{0002GP)t-s|NsB> z==qcrzpYBnoF&BM#P9Im_R5Oi@7wj%q2;`6*P0;1)uQH>7{Hht!M$$S;=t~lB*g93 z^rJM$!+hK0!S30t=+Bkn@!<CH;P=|C>BN29z;xN1Cd8>i%<9nc>e2GLXV#PyztWrJ zz;oHwq~`SI_}8ZAp)kj}WYx%q-k>hVtVzzIGRWx5@#xI)*sAE-uIZg7#O22C)}`m> z$MDOH;Oo@$<;U=`RMG3x^X}R8&644pBEy}2(rf?#00DGTPE!Ct=GbNc004(cL_t(| zUhUE~62kxxz`*Ft5N2j(=Kp^yGp^E|Yme1$r%XbU*d++0U>KC!mR-+fSFTmr7V|17 z5EYJzs;Zy{vj$O3prrzjPFH$;7#O3|gsBP~<_nP}_#$f$M+F<8v~$7UmjFWra^SC{ x=)gP!gcpFT83RagyFZ>H{_FkuCT6}A`~U&>5GQ}a@O}UQ002ovPDHLkV1j|%*ysQN
new file mode 100644 index 0000000000000000000000000000000000000000..4a723c54e2316e978062be38a7be8eaa1a0d11f2 GIT binary patch literal 940 zc$@*O15^BoP)<h;3K|Lk000e1NJLTq001xm001Be1^@s6RMh(%000AXNkl<Zc-rlg z$xl;J6o(1_1yoX*5x@cn=*F-yu3Q@3nvl3aOi+RVG8+L@W(zb*feJ04fCvJp@M?uJ zccO(-+8}dVOItdDF7O_cCWS!Wdj`^H<R-t(x#!&TeH}bp&;I_tCrZM7?^1Pf2v1C< z028nQqbu`~0)>Ii(vB-)j7kBfGl73bpa3<uqaiA#fV9A6F-D<~7idF6lnHr(CL|!j zs1#uOj|2jcK>RF5r2rFQfqefaDKBmoBa)&Zo~SuSC}1(ZjOF>UWv*dH4358!<c}Vx z`?F;%#_(8U^r+xTV2h+RcA8IQ4=|m$0KoS>7Os>;wJMm`yOsP3@y4daV6ka;9U`7e z_cWX%FAQ#)8>^q&vaH+faEFzPH)^hRDWApi6axj5s~~aW0(t(8Qf}M~hL>Ew2HEPY zZfDo@YwxJYdzTH8PPP8Hdo*i-H;C*4CUOF?Xb>l`Tt28VJAoYAKldzKj@{#ct=fZk z@*#~G-oW2QR=|%f<zAY`@RIA>AQN>jZUanr!H6~^qDphmyGT`bgR`nzG}@h@I+e*@ zeUrPIOe<1lUaZn|=Z|QiF6mJj;SD0YfQghqD;mN}4qDD<iSz(7_fcakPv}rWEt6g^ z=RO?RbP9B2e%{KU*U90o>}uPB`2Er!X_$o??k=(dxv04X4dJCC^uuJ`t*&L)pM~+e z)uvl8S<UEA>#SL{4u@W8w!)ojZ~g7AWq5<gu3S<AzDOWu3L}yXdaYdAzhnj=GLe7M z?7%G1J-~D-0?&gxr?MHfOKTFH^)Td2*Md>pvuHY8Uzh4_*$n1VY0%s0(-VlE#E2%} zw^5Q7T&c!b(}o{vvtv!Fv$5{HTTh{tDN|@Kl<gTqTD{t2H?CM50=CGI9#*BvBe#Q` zKn`ktgNDfDW!Rwb!MXB<B(GvsvR8>Ji_V&ZIwxuZOY^BwKt0K;NCiDrp?%|I-_Q9Q z0jh+&!1)FlCu$O-Qh*6p01h-|(d%Vw#^_i0`l%3L0VZGrMqGi^fO0|2g??cZL&)bu zPGIf~FaQfM5fzB5z1#LE-KSPj8PYq>3LpOVDuOR?M_@NF01Gpbn+|LqL=KoT6I;_$ z{7MC>v<gKUt$N-aLA&RH0a#o=eWDWaFhr5W<KW`Tc)BAVXXosk{`n1(UhvtS<;6z; O0000<MNUMnLSTY#G_!92
new file mode 100644 index 0000000000000000000000000000000000000000..835912b534b2d16ac01a52c44ff6f3548ac0032c GIT binary patch literal 860 zc$@)T1Ec(jP)<h;3K|Lk000e1NJLTq001Ze000yS1^@s6|3-hi0009cNkl<Zc-qy` z+fR~V90&0J4X4gTQ&USkVt1~KwywHsb=l_DjkZ}%QS-<$vF0HhC^|t!5uKr?nY!{- zO7IB02#Ops&p-&s(YmnbcR`5D@yd;K@x6Gz&-*^#&*0kW>1ht_MmIiqM}~z`zy!Ac z16YF|2C`*XD5WM?U|qaG%FA33wWrR^SI2S{XVaQ8E$K`Xee>_T6dkG9e0PReR2$bP z@29NJ1_A8C1=&(8KoG89I$j~!rl<2&%=^RY*=G6v0dBTC4IZ~tpfT3o<|%16yVvH~ zYlrrImkZ9YCfR<=J)!yyMWFgbTikgS*DEKo1tuRb76B<zEKsUqxQg9X<ME00_sPGa z8rw4C?{%h&&e6AaxP!<RGWmejF<p3n)+!b#RV0o=IjOgK2R%o>P8^4B&qW}o!T(G( z_1y+{5ZMA#P(a!6<`ImI&@e+u3PYbCPS-R%;O&?;EiNy_q_Mgf5BqnXMbI^+WQKmY zX2t+LKB}<<1w=nP&d*xG0;Q7FshDe*nmlN1?W5k(bazk<U0F0+$uFEdfF3ef`uVW* zdaDx78gd3U<RjW6=;2%Fh-`uB43LBKGncVIX-@jE*!D=c<%K{jKO9b@+Ul_Fxm@1P zZg9IzYMTSj&c56BShxju5ZMBg4@lzReHqIbk(5NWD)9-e4F)e0<+T17z9qVYOg>;G zdgw(Zk-SY1E|rs6+FiNPaUQWE+_1<-H?8OUJ>29hPhn~6<A;`s?o|2#qY*HD2_u?n z5?5Z7@J@%Zm(4$QHuo-Wbh4ie?G=PKs38L-QwEdH>auLx-6D?EOigC!t4Q2SZGg!i zn6`*rCd{Ns@r#JYjl8frO@0_%^D>IP20brz0V|GdQ9+*<_FD5Cy~9Wxmt69Ge_%y) zr-FydPzsp-0?VS>6ddyWTn{=3g@6T_e89rkMo~-Rgt&_=p6x+@(NzWvU;&dCn8kW3 zD5gfTRLu!v5_a;JuN}8$MOP8n1_M}_xqK?v00_xp=JrtwNcEz^@FrDJIAi@Pg0|Pe m02VNT4RGF2pbU{vyskfVIUEteAA!IC0000<MNUMnLSTZ~^p(5-
new file mode 100644 index 0000000000000000000000000000000000000000..111dc7d04167c3767538fb6b64e34717fb259ef0 GIT binary patch literal 370 zc$@)p0ge8NP)<h;3K|Lk000e1NJLTq001Ze000yS0ssI2a3tvL0003wNkl<ZcmZRW zBD{Y<(5S^=G-~m#^lP&e4lg!2aisRnlOyjx-2Zld>Z2Lb2d7CKIMH_R{q3(n!NWDT z7a5(HqjF?#-VLf*{OG{DuP=V0p{t9YU7Gy_Q~dG4cT}_Z=9(7}L%%%x@$tboOv^q# z_zq{^TKke}7GGKT6lmgslACkXjsUqUozA?v@CjngtBar3dYzjhvVX4Hkpsmyfg;zI zJfoV$r@HO~O}xJB`9g!^5RsiJS0NVf$+->@S)g|e;y$2as#(0o^Bg=R-(LIj;`B#g zf`V{?3GKz1k3d1V0$>18GdtZ_1&U^Z=+?TIRL;k9RF6D8_8u)&Ut9VdS>)OA4?tzq zFF=4<`E<{N*H=D2J^o>1$VG@CFpghe{`~Id*Rzuz&Q>}+l8ekyi$^U603#jiY0Ssy Q{Qv*}07*qoM6N<$f_}`eLI3~&
--- a/dom/base/Selection.cpp +++ b/dom/base/Selection.cpp @@ -918,16 +918,29 @@ Selection::FocusOffset() if (GetDirection() == eDirNext){ return mAnchorFocusRange->EndOffset(); } return mAnchorFocusRange->StartOffset(); } +nsIContent* +Selection::GetChildAtAnchorOffset() +{ + if (!mAnchorFocusRange) + return nullptr; + + if (GetDirection() == eDirNext) { + return mAnchorFocusRange->GetChildAtStartOffset(); + } + + return mAnchorFocusRange->GetChildAtEndOffset(); +} + static nsresult CompareToRangeStart(nsINode* aCompareNode, int32_t aCompareOffset, nsRange* aRange, int32_t* aCmp) { nsINode* start = aRange->GetStartContainer(); NS_ENSURE_STATE(aCompareNode && start); // If the nodes that we're comparing are not in the same document, // assume that aCompareNode will fall at the end of the ranges.
--- a/dom/base/Selection.h +++ b/dom/base/Selection.h @@ -166,16 +166,18 @@ public: JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; // WebIDL methods nsINode* GetAnchorNode(); uint32_t AnchorOffset(); nsINode* GetFocusNode(); uint32_t FocusOffset(); + nsIContent* GetChildAtAnchorOffset(); + /* * IsCollapsed -- is the whole selection just one point, or unset? */ bool IsCollapsed() const { uint32_t cnt = mRanges.Length(); if (cnt == 0) { return true;
--- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -3011,23 +3011,24 @@ nsDocument::InitCSP(nsIChannel* aChannel // The document may already have some sandbox flags set (e.g. if the document // is an iframe with the sandbox attribute set). If we have a CSP sandbox // directive, intersect the CSP sandbox flags with the existing flags. This // corresponds to the _least_ permissive policy. uint32_t cspSandboxFlags = SANDBOXED_NONE; rv = csp->GetCSPSandboxFlags(&cspSandboxFlags); NS_ENSURE_SUCCESS(rv, rv); - mSandboxFlags |= cspSandboxFlags; - // Probably the iframe sandbox attribute already caused the creation of a // new NullPrincipal. Only create a new NullPrincipal if CSP requires so // and no one has been created yet. bool needNewNullPrincipal = (cspSandboxFlags & SANDBOXED_ORIGIN) && !(mSandboxFlags & SANDBOXED_ORIGIN); + + mSandboxFlags |= cspSandboxFlags; + if (needNewNullPrincipal) { principal = NullPrincipal::CreateWithInheritedAttributes(principal); principal->SetCsp(csp); SetPrincipal(principal); } // ----- Enforce frame-ancestor policy on any applied policies nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
--- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -2391,16 +2391,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns NS_IMPL_CYCLE_COLLECTION_UNLINK(mOuterWindow) } if (tmp->mListenerManager) { tmp->mListenerManager->Disconnect(); NS_IMPL_CYCLE_COLLECTION_UNLINK(mListenerManager) } + tmp->UpdateTopInnerWindow(); NS_IMPL_CYCLE_COLLECTION_UNLINK(mTopInnerWindow) NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocation) NS_IMPL_CYCLE_COLLECTION_UNLINK(mHistory) NS_IMPL_CYCLE_COLLECTION_UNLINK(mCustomElements) NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalStorage) NS_IMPL_CYCLE_COLLECTION_UNLINK(mSessionStorage) if (tmp->mApplicationCache) { @@ -2622,17 +2623,20 @@ nsGlobalWindow::SetInitialPrincipalToSub // NullPrincipal. bool isNullPrincipal; MOZ_ASSERT(NS_SUCCEEDED(mDoc->NodePrincipal()->GetIsNullPrincipal(&isNullPrincipal)) && isNullPrincipal); #endif } GetDocShell()->CreateAboutBlankContentViewer(newWindowPrincipal); - mDoc->SetIsInitialDocument(true); + + if (mDoc) { + mDoc->SetIsInitialDocument(true); + } nsCOMPtr<nsIPresShell> shell = GetDocShell()->GetPresShell(); if (shell && !shell->DidInitialize()) { // Ensure that if someone plays with this document they will get // layout happening. nsRect r = shell->GetPresContext()->GetVisibleArea(); shell->Initialize(r.Width(), r.Height()); @@ -4376,16 +4380,27 @@ nsPIDOMWindowInner::Thaw() void nsPIDOMWindowInner::SyncStateFromParentWindow() { nsGlobalWindow::Cast(this)->SyncStateFromParentWindow(); } void +nsGlobalWindow::UpdateTopInnerWindow() +{ + if (!IsInnerWindow() || AsInner()->IsTopInnerWindow()) { + return; + } + + AsInner()->UpdateWebSocketCount(-(int32_t)mNumOfOpenWebSockets); + AsInner()->UpdateUserMediaCount(-(int32_t)mNumOfActiveUserMedia); +} + +void nsPIDOMWindowInner::AddPeerConnection() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(IsInnerWindow()); mTopInnerWindow ? mTopInnerWindow->mActivePeerConnections++ : mActivePeerConnections++; } @@ -4514,48 +4529,52 @@ void nsPIDOMWindowInner::UpdateWebSocketCount(int32_t aDelta) { MOZ_ASSERT(NS_IsMainThread()); if (aDelta == 0) { return; } - uint32_t& counter = mTopInnerWindow ? mTopInnerWindow->mNumOfOpenWebSockets - : mNumOfOpenWebSockets; - - MOZ_DIAGNOSTIC_ASSERT(aDelta > 0 || ((aDelta + counter) < counter)); - - counter += aDelta; + if (!IsTopInnerWindow()) { + mTopInnerWindow->UpdateWebSocketCount(aDelta); + } + + MOZ_DIAGNOSTIC_ASSERT( + aDelta > 0 || ((aDelta + mNumOfOpenWebSockets) < mNumOfOpenWebSockets)); + + mNumOfOpenWebSockets += aDelta; } bool nsPIDOMWindowInner::HasOpenWebSockets() const { MOZ_ASSERT(NS_IsMainThread()); - return (mTopInnerWindow ? mTopInnerWindow->mNumOfOpenWebSockets - : mNumOfOpenWebSockets) > 0; + return mNumOfOpenWebSockets || + (mTopInnerWindow && mTopInnerWindow->mNumOfOpenWebSockets); } void nsPIDOMWindowInner::UpdateUserMediaCount(int32_t aDelta) { MOZ_ASSERT(NS_IsMainThread()); if (aDelta == 0) { return; } - uint32_t& counter = mTopInnerWindow ? mTopInnerWindow->mNumOfActiveUserMedia - : mNumOfActiveUserMedia; - - MOZ_DIAGNOSTIC_ASSERT(aDelta > 0 || ((aDelta + counter) < counter)); - - counter += aDelta; + if (!IsTopInnerWindow()) { + mTopInnerWindow->UpdateUserMediaCount(aDelta); + } + + MOZ_DIAGNOSTIC_ASSERT( + aDelta > 0 || ((aDelta + mNumOfActiveUserMedia) < mNumOfActiveUserMedia)); + + mNumOfActiveUserMedia += aDelta; } bool nsPIDOMWindowInner::HasActiveUserMedia() const { return (mTopInnerWindow ? mTopInnerWindow->mNumOfActiveUserMedia : mNumOfActiveUserMedia) > 0; }
--- a/dom/base/nsGlobalWindow.h +++ b/dom/base/nsGlobalWindow.h @@ -1314,16 +1314,18 @@ public: JS::MutableHandle<JS::Value> aRetval, mozilla::ErrorResult& aError); already_AddRefed<nsWindowRoot> GetWindowRootOuter(); already_AddRefed<nsWindowRoot> GetWindowRoot(mozilla::ErrorResult& aError); mozilla::dom::Performance* GetPerformance(); + void UpdateTopInnerWindow(); + protected: // Web IDL helpers // Redefine the property called aPropName on this window object to be a value // property with the value aValue, much like we would do for a [Replaceable] // property in IDL. void RedefineProperty(JSContext* aCx, const char* aPropName, JS::Handle<JS::Value> aValue,
--- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -1779,22 +1779,30 @@ nsJSContext::EndCycleCollectionCallback( } // static bool InterSliceGCRunnerFired(TimeStamp aDeadline, void* aData) { nsJSContext::KillInterSliceGCRunner(); MOZ_ASSERT(sActiveIntersliceGCBudget > 0); - // We use longer budgets when timer runs since that means - // there hasn't been idle time recently and we may have significant amount - // garbage to collect. - int64_t budget = sActiveIntersliceGCBudget * 2; - if (!aDeadline.IsNull()) { - budget = int64_t((aDeadline - TimeStamp::Now()).ToMilliseconds()); + // We use longer budgets when the CC has been locked out but the CC has tried + // to run since that means we may have significant amount garbage to collect + // and better to GC in several longer slices than in a very long one. + int64_t budget = aDeadline.IsNull() ? + int64_t(sActiveIntersliceGCBudget) : + int64_t((aDeadline - TimeStamp::Now()).ToMilliseconds()); + if (sCCLockedOut && sCCLockedOutTime) { + int64_t lockedTime = PR_Now() - sCCLockedOutTime; + int32_t maxSliceGCBudget = sActiveIntersliceGCBudget * 10; + double percentOfLockedTime = + std::min((double)lockedTime / NS_MAX_CC_LOCKEDOUT_TIME, 1.0); + budget = + static_cast<int64_t>( + std::max((double)budget, percentOfLockedTime * maxSliceGCBudget)); } TimeStamp startTimeStamp = TimeStamp::Now(); TimeDuration duration = sGCUnnotifiedTotalTime; uintptr_t reason = reinterpret_cast<uintptr_t>(aData); nsJSContext::GarbageCollectNow(aData ? static_cast<JS::gcreason::Reason>(reason) : JS::gcreason::INTER_SLICE_GC,
--- a/dom/base/nsPIDOMWindow.h +++ b/dom/base/nsPIDOMWindow.h @@ -781,16 +781,19 @@ public: // window of that outer. inline bool IsCurrentInnerWindow() const; // Returns true if the document of this window is the active document. This // is not identical to IsCurrentInnerWindow() because document.open() will // keep the same document active but create a new window. inline bool HasActiveDocument(); + // Returns true if this window is the same as mTopInnerWindow + inline bool IsTopInnerWindow() const; + bool AddAudioContext(mozilla::dom::AudioContext* aAudioContext); void RemoveAudioContext(mozilla::dom::AudioContext* aAudioContext); void MuteAudioContexts(); void UnmuteAudioContexts(); bool GetAudioCaptured() const; nsresult SetAudioCapture(bool aCapture);
--- a/dom/base/nsPIDOMWindowInlines.h +++ b/dom/base/nsPIDOMWindowInlines.h @@ -94,16 +94,22 @@ bool nsPIDOMWindowInner::HasActiveDocument() { return IsCurrentInnerWindow() || (mOuterWindow && mOuterWindow->GetCurrentInnerWindow() && mOuterWindow->GetCurrentInnerWindow()->GetDoc() == mDoc); } +bool +nsPIDOMWindowInner::IsTopInnerWindow() const +{ + return mTopInnerWindow == this; +} + template <class T> nsIDocShell* nsPIDOMWindow<T>::GetDocShell() const { if (mOuterWindow) { return mOuterWindow->GetDocShell(); }
--- a/dom/base/nsRange.h +++ b/dom/base/nsRange.h @@ -97,16 +97,26 @@ public: return static_cast<uint32_t>(mStart.Offset()); } uint32_t EndOffset() const { return static_cast<uint32_t>(mEnd.Offset()); } + nsIContent* GetChildAtStartOffset() const + { + return mStart.GetChildAtOffset(); + } + + nsIContent* GetChildAtEndOffset() const + { + return mEnd.GetChildAtOffset(); + } + bool IsPositioned() const { return mIsPositioned; } void SetMaySpanAnonymousSubtrees(bool aMaySpanAnonymousSubtrees) { mMaySpanAnonymousSubtrees = aMaySpanAnonymousSubtrees;
--- a/dom/cache/CacheTypes.ipdlh +++ b/dom/cache/CacheTypes.ipdlh @@ -81,16 +81,18 @@ struct CacheResponse nsCString[] urlList; uint32_t status; nsCString statusText; HeadersEntry[] headers; HeadersGuardEnum headersGuard; CacheReadStreamOrVoid body; IPCChannelInfo channelInfo; OptionalPrincipalInfo principalInfo; + uint32_t paddingInfo; + int64_t paddingSize; }; union CacheResponseOrVoid { void_t; CacheResponse; };
--- a/dom/cache/DBAction.cpp +++ b/dom/cache/DBAction.cpp @@ -4,33 +4,62 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/cache/DBAction.h" #include "mozilla/dom/cache/Connection.h" #include "mozilla/dom/cache/DBSchema.h" #include "mozilla/dom/cache/FileUtils.h" +#include "mozilla/dom/cache/QuotaClient.h" #include "mozilla/dom/quota/PersistenceType.h" #include "mozilla/net/nsFileProtocolHandler.h" #include "mozIStorageConnection.h" #include "mozIStorageService.h" #include "mozStorageCID.h" #include "nsIFile.h" #include "nsIURI.h" #include "nsIFileURL.h" #include "nsThreadUtils.h" namespace mozilla { namespace dom { namespace cache { +using mozilla::dom::quota::AssertIsOnIOThread; using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT; using mozilla::dom::quota::PersistenceType; +namespace { + +nsresult +WipeDatabase(const QuotaInfo& aQuotaInfo, nsIFile* aDBFile, + nsIFile* aDBDir) +{ + MOZ_DIAGNOSTIC_ASSERT(aDBFile); + MOZ_DIAGNOSTIC_ASSERT(aDBDir); + + nsresult rv = RemoveNsIFile(aQuotaInfo, aDBFile); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + // Note, the -wal journal file will be automatically deleted by sqlite when + // the new database is created. No need to explicitly delete it here. + + // Delete the morgue as well. + rv = BodyDeleteDir(aQuotaInfo, aDBDir); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = WipePaddingFile(aQuotaInfo, aDBDir); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + return rv; +} + +} + DBAction::DBAction(Mode aMode) : mMode(aMode) { } DBAction::~DBAction() { } @@ -95,35 +124,73 @@ DBAction::RunOnTarget(Resolver* aResolve nsresult DBAction::OpenConnection(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir, mozIStorageConnection** aConnOut) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_DIAGNOSTIC_ASSERT(aDBDir); MOZ_DIAGNOSTIC_ASSERT(aConnOut); - nsCOMPtr<mozIStorageConnection> conn; - bool exists; nsresult rv = aDBDir->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!exists) { if (NS_WARN_IF(mMode != Create)) { return NS_ERROR_FILE_NOT_FOUND; } rv = aDBDir->Create(nsIFile::DIRECTORY_TYPE, 0755); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } + rv = OpenDBConnection(aQuotaInfo, aDBDir, aConnOut); + + return rv; +} + +SyncDBAction::SyncDBAction(Mode aMode) + : DBAction(aMode) +{ +} + +SyncDBAction::~SyncDBAction() +{ +} + +void +SyncDBAction::RunWithDBOnTarget(Resolver* aResolver, + const QuotaInfo& aQuotaInfo, nsIFile* aDBDir, + mozIStorageConnection* aConn) +{ + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_DIAGNOSTIC_ASSERT(aResolver); + MOZ_DIAGNOSTIC_ASSERT(aDBDir); + MOZ_DIAGNOSTIC_ASSERT(aConn); + + nsresult rv = RunSyncWithDBOnTarget(aQuotaInfo, aDBDir, aConn); + aResolver->Resolve(rv); +} + +// static +nsresult +OpenDBConnection(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir, + mozIStorageConnection** aConnOut) +{ + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_DIAGNOSTIC_ASSERT(aDBDir); + MOZ_DIAGNOSTIC_ASSERT(aConnOut); + + nsCOMPtr<mozIStorageConnection> conn; + nsCOMPtr<nsIFile> dbFile; - rv = aDBDir->Clone(getter_AddRefs(dbFile)); + nsresult rv = aDBDir->Clone(getter_AddRefs(dbFile)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = dbFile->Append(NS_LITERAL_STRING("caches.sqlite")); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + bool exists = false; rv = dbFile->Exists(&exists); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Use our default file:// protocol handler directly to construct the database // URL. This avoids any problems if a plugin registers a custom file:// // handler. If such a custom handler used javascript, then we would have a // bad time running off the main thread here. RefPtr<nsFileProtocolHandler> handler = new nsFileProtocolHandler(); @@ -182,54 +249,11 @@ DBAction::OpenConnection(const QuotaInfo rv = db::InitializeConnection(conn); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } conn.forget(aConnOut); return rv; } -nsresult -DBAction::WipeDatabase(const QuotaInfo& aQuotaInfo, nsIFile* aDBFile, - nsIFile* aDBDir) -{ - MOZ_DIAGNOSTIC_ASSERT(aDBFile); - MOZ_DIAGNOSTIC_ASSERT(aDBDir); - - nsresult rv = RemoveNsIFile(aQuotaInfo, aDBFile); - if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - - // Note, the -wal journal file will be automatically deleted by sqlite when - // the new database is created. No need to explicitly delete it here. - - // Delete the morgue as well. - rv = BodyDeleteDir(aQuotaInfo, aDBDir); - if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - - return rv; -} - -SyncDBAction::SyncDBAction(Mode aMode) - : DBAction(aMode) -{ -} - -SyncDBAction::~SyncDBAction() -{ -} - -void -SyncDBAction::RunWithDBOnTarget(Resolver* aResolver, - const QuotaInfo& aQuotaInfo, nsIFile* aDBDir, - mozIStorageConnection* aConn) -{ - MOZ_ASSERT(!NS_IsMainThread()); - MOZ_DIAGNOSTIC_ASSERT(aResolver); - MOZ_DIAGNOSTIC_ASSERT(aDBDir); - MOZ_DIAGNOSTIC_ASSERT(aConn); - - nsresult rv = RunSyncWithDBOnTarget(aQuotaInfo, aDBDir, aConn); - aResolver->Resolve(rv); -} - } // namespace cache } // namespace dom } // namespace mozilla
--- a/dom/cache/DBAction.h +++ b/dom/cache/DBAction.h @@ -13,16 +13,20 @@ class mozIStorageConnection; class nsIFile; namespace mozilla { namespace dom { namespace cache { +nsresult +OpenDBConnection(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir, + mozIStorageConnection** aConnOut); + class DBAction : public Action { protected: // The mode specifies whether the database should already exist or if its // ok to create a new database. enum Mode { Existing, @@ -44,19 +48,16 @@ protected: private: virtual void RunOnTarget(Resolver* aResolver, const QuotaInfo& aQuotaInfo, Data* aOptionalData) override; nsresult OpenConnection(const QuotaInfo& aQuotaInfo, nsIFile* aQuotaDir, mozIStorageConnection** aConnOut); - nsresult WipeDatabase(const QuotaInfo& aQuotaInfo, nsIFile* aDBFile, - nsIFile* aDBDir); - const Mode mMode; }; class SyncDBAction : public DBAction { protected: explicit SyncDBAction(Mode aMode);
--- a/dom/cache/DBSchema.cpp +++ b/dom/cache/DBSchema.cpp @@ -5,16 +5,17 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/cache/DBSchema.h" #include "ipc/IPCMessageUtils.h" #include "mozilla/BasePrincipal.h" #include "mozilla/dom/HeadersBinding.h" #include "mozilla/dom/InternalHeaders.h" +#include "mozilla/dom/InternalResponse.h" #include "mozilla/dom/RequestBinding.h" #include "mozilla/dom/ResponseBinding.h" #include "mozilla/dom/cache/CacheTypes.h" #include "mozilla/dom/cache/SavedTypes.h" #include "mozilla/dom/cache/Types.h" #include "mozilla/dom/cache/TypeUtils.h" #include "mozIStorageConnection.h" #include "mozIStorageStatement.h" @@ -30,17 +31,17 @@ namespace mozilla { namespace dom { namespace cache { namespace db { const int32_t kFirstShippedSchemaVersion = 15; namespace { // Update this whenever the DB schema is changed. -const int32_t kLatestSchemaVersion = 25; +const int32_t kLatestSchemaVersion = 26; // --------- // The following constants define the SQL schema. These are defined in the // same order the SQL should be executed in CreateOrMigrateSchema(). They are // broken out as constants for convenient use in validation and migration. // --------- // The caches table is the single source of truth about what Cache // objects exist for the origin. The contents of the Cache are stored // in the entries table that references back to caches. @@ -96,17 +97,18 @@ const char* const kTableEntries = "response_headers_guard INTEGER NOT NULL, " "response_body_id TEXT NULL, " "response_security_info_id INTEGER NULL REFERENCES security_info(id), " "response_principal_info TEXT NOT NULL, " "cache_id INTEGER NOT NULL REFERENCES caches(id) ON DELETE CASCADE, " "request_redirect INTEGER NOT NULL, " "request_referrer_policy INTEGER NOT NULL, " "request_integrity TEXT NOT NULL, " - "request_url_fragment TEXT NOT NULL" + "request_url_fragment TEXT NOT NULL, " + "response_padding_size INTEGER NULL " // New columns must be added at the end of table to migrate and // validate properly. ")"; // Create an index to support the QueryCache() matching algorithm. This // needs to quickly find entries in a given Cache that match the request // URL. The url query is separated in order to support the ignoreSearch // option. Finally, we index hashes of the URL values instead of the // actual strings to avoid excessive disk bloat. The index will duplicate @@ -314,16 +316,17 @@ static nsresult QueryCache(mozIStorageCo uint32_t aMaxResults = UINT32_MAX); static nsresult MatchByVaryHeader(mozIStorageConnection* aConn, const CacheRequest& aRequest, EntryId entryId, bool* aSuccessOut); static nsresult DeleteEntries(mozIStorageConnection* aConn, const nsTArray<EntryId>& aEntryIdList, nsTArray<nsID>& aDeletedBodyIdListOut, nsTArray<IdCount>& aDeletedSecurityIdListOut, + int64_t* aDeletedPaddingSizeOut, uint32_t aPos=0, int32_t aLen=-1); static nsresult InsertSecurityInfo(mozIStorageConnection* aConn, nsICryptoHash* aCrypto, const nsACString& aData, int32_t *aIdOut); static nsresult DeleteSecurityInfo(mozIStorageConnection* aConn, int32_t aId, int32_t aCount); static nsresult DeleteSecurityInfoList(mozIStorageConnection* aConn, const nsTArray<IdCount>& aDeletedStorageIdList); @@ -593,33 +596,38 @@ CreateCacheId(mozIStorageConnection* aCo rv = state->GetInt64(0, aCacheIdOut); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return rv; } nsresult DeleteCacheId(mozIStorageConnection* aConn, CacheId aCacheId, - nsTArray<nsID>& aDeletedBodyIdListOut) + nsTArray<nsID>& aDeletedBodyIdListOut, + int64_t* aDeletedPaddingSizeOut) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_DIAGNOSTIC_ASSERT(aConn); + MOZ_DIAGNOSTIC_ASSERT(aDeletedPaddingSizeOut); // Delete the bodies explicitly as we need to read out the body IDs // anyway. These body IDs must be deleted one-by-one as content may // still be referencing them invidivually. AutoTArray<EntryId, 256> matches; nsresult rv = QueryAll(aConn, aCacheId, matches); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } AutoTArray<IdCount, 16> deletedSecurityIdList; + int64_t deletedPaddingSize = 0; rv = DeleteEntries(aConn, matches, aDeletedBodyIdListOut, - deletedSecurityIdList); + deletedSecurityIdList, &deletedPaddingSize); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + *aDeletedPaddingSizeOut = deletedPaddingSize; + rv = DeleteSecurityInfoList(aConn, deletedSecurityIdList); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Delete the remainder of the cache using cascade semantics. nsCOMPtr<mozIStorageStatement> state; rv = aConn->CreateStatement(NS_LITERAL_CSTRING( "DELETE FROM caches WHERE id=:id;" ), getter_AddRefs(state)); @@ -686,16 +694,47 @@ FindOrphanedCacheIds(mozIStorageConnecti if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } aOrphanedListOut.AppendElement(cacheId); } return rv; } nsresult +FindOverallPaddingSize(mozIStorageConnection* aConn, + int64_t* aOverallPaddingSizeOut) +{ + MOZ_DIAGNOSTIC_ASSERT(aConn); + MOZ_DIAGNOSTIC_ASSERT(aOverallPaddingSizeOut); + + nsCOMPtr<mozIStorageStatement> state; + nsresult rv = aConn->CreateStatement(NS_LITERAL_CSTRING( + "SELECT response_padding_size FROM entries " + "WHERE response_padding_size IS NOT NULL;" + ), getter_AddRefs(state)); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + int64_t overallPaddingSize = 0; + bool hasMoreData = false; + while (NS_SUCCEEDED(state->ExecuteStep(&hasMoreData)) && hasMoreData) { + int64_t padding_size = 0; + rv = state->GetInt64(0, &padding_size); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + MOZ_DIAGNOSTIC_ASSERT(padding_size >= 0); + MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - padding_size >= overallPaddingSize); + overallPaddingSize += padding_size; + } + + *aOverallPaddingSizeOut = overallPaddingSize; + + return rv; +} + +nsresult GetKnownBodyIds(mozIStorageConnection* aConn, nsTArray<nsID>& aBodyIdListOut) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_DIAGNOSTIC_ASSERT(aConn); nsCOMPtr<mozIStorageStatement> state; nsresult rv = aConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT request_body_id, response_body_id FROM entries;" @@ -787,69 +826,79 @@ CacheMatchAll(mozIStorageConnection* aCo } nsresult CachePut(mozIStorageConnection* aConn, CacheId aCacheId, const CacheRequest& aRequest, const nsID* aRequestBodyId, const CacheResponse& aResponse, const nsID* aResponseBodyId, - nsTArray<nsID>& aDeletedBodyIdListOut) + nsTArray<nsID>& aDeletedBodyIdListOut, + int64_t* aDeletedPaddingSizeOut) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_DIAGNOSTIC_ASSERT(aConn); + MOZ_DIAGNOSTIC_ASSERT(aDeletedPaddingSizeOut); CacheQueryParams params(false, false, false, false, NS_LITERAL_STRING("")); AutoTArray<EntryId, 256> matches; nsresult rv = QueryCache(aConn, aCacheId, aRequest, params, matches); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } AutoTArray<IdCount, 16> deletedSecurityIdList; + int64_t deletedPaddingSize = 0; rv = DeleteEntries(aConn, matches, aDeletedBodyIdListOut, - deletedSecurityIdList); + deletedSecurityIdList, &deletedPaddingSize); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = InsertEntry(aConn, aCacheId, aRequest, aRequestBodyId, aResponse, aResponseBodyId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Delete the security values after doing the insert to avoid churning // the security table when its not necessary. rv = DeleteSecurityInfoList(aConn, deletedSecurityIdList); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + *aDeletedPaddingSizeOut = deletedPaddingSize; + return rv; } nsresult CacheDelete(mozIStorageConnection* aConn, CacheId aCacheId, const CacheRequest& aRequest, const CacheQueryParams& aParams, - nsTArray<nsID>& aDeletedBodyIdListOut, bool* aSuccessOut) + nsTArray<nsID>& aDeletedBodyIdListOut, + int64_t* aDeletedPaddingSizeOut, bool* aSuccessOut) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_DIAGNOSTIC_ASSERT(aConn); + MOZ_DIAGNOSTIC_ASSERT(aDeletedPaddingSizeOut); MOZ_DIAGNOSTIC_ASSERT(aSuccessOut); *aSuccessOut = false; AutoTArray<EntryId, 256> matches; nsresult rv = QueryCache(aConn, aCacheId, aRequest, aParams, matches); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (matches.IsEmpty()) { return rv; } AutoTArray<IdCount, 16> deletedSecurityIdList; + int64_t deletedPaddingSize = 0; rv = DeleteEntries(aConn, matches, aDeletedBodyIdListOut, - deletedSecurityIdList); + deletedSecurityIdList, &deletedPaddingSize); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + *aDeletedPaddingSizeOut = deletedPaddingSize; + rv = DeleteSecurityInfoList(aConn, deletedSecurityIdList); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } *aSuccessOut = true; return rv; } @@ -1328,63 +1377,78 @@ MatchByVaryHeader(mozIStorageConnection* return rv; } nsresult DeleteEntries(mozIStorageConnection* aConn, const nsTArray<EntryId>& aEntryIdList, nsTArray<nsID>& aDeletedBodyIdListOut, nsTArray<IdCount>& aDeletedSecurityIdListOut, + int64_t* aDeletedPaddingSizeOut, uint32_t aPos, int32_t aLen) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_DIAGNOSTIC_ASSERT(aConn); + MOZ_DIAGNOSTIC_ASSERT(aDeletedPaddingSizeOut); if (aEntryIdList.IsEmpty()) { return NS_OK; } MOZ_DIAGNOSTIC_ASSERT(aPos < aEntryIdList.Length()); if (aLen < 0) { aLen = aEntryIdList.Length() - aPos; } // Sqlite limits the number of entries allowed for an IN clause, // so split up larger operations. if (aLen > kMaxEntriesPerStatement) { + int64_t overallDeletedPaddingSize = 0; uint32_t curPos = aPos; int32_t remaining = aLen; while (remaining > 0) { + int64_t deletedPaddingSize = 0; int32_t max = kMaxEntriesPerStatement; int32_t curLen = std::min(max, remaining); nsresult rv = DeleteEntries(aConn, aEntryIdList, aDeletedBodyIdListOut, - aDeletedSecurityIdListOut, curPos, curLen); + aDeletedSecurityIdListOut, + &deletedPaddingSize, curPos, curLen); if (NS_FAILED(rv)) { return rv; } + MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - deletedPaddingSize >= + overallDeletedPaddingSize); + overallDeletedPaddingSize += deletedPaddingSize; curPos += curLen; remaining -= curLen; } + + *aDeletedPaddingSizeOut += overallDeletedPaddingSize; return NS_OK; } nsCOMPtr<mozIStorageStatement> state; nsAutoCString query( - "SELECT request_body_id, response_body_id, response_security_info_id " + "SELECT " + "request_body_id, " + "response_body_id, " + "response_security_info_id, " + "response_padding_size " "FROM entries WHERE id IN (" ); AppendListParamsToQuery(query, aEntryIdList, aPos, aLen); query.AppendLiteral(")"); nsresult rv = aConn->CreateStatement(query, getter_AddRefs(state)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = BindListParamsToQuery(state, aEntryIdList, aPos, aLen); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + int64_t overallPaddingSize = 0; bool hasMoreData = false; while (NS_SUCCEEDED(state->ExecuteStep(&hasMoreData)) && hasMoreData) { // extract 0 to 2 nsID structs per row for (uint32_t i = 0; i < 2; ++i) { bool isNull = false; rv = state->GetIsNull(i, &isNull); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -1418,18 +1482,34 @@ DeleteEntries(mozIStorageConnection* aCo } } // Otherwise add a new entry for this ID with a count of 1 if (!found) { aDeletedSecurityIdListOut.AppendElement(IdCount(securityId)); } } + + // It's possible to have null padding size for non-opaque response + rv = state->GetIsNull(3, &isNull); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + if (!isNull) { + int64_t paddingSize = 0; + rv = state->GetInt64(3, &paddingSize); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + MOZ_DIAGNOSTIC_ASSERT(paddingSize >= 0); + MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - overallPaddingSize >= paddingSize); + overallPaddingSize += paddingSize; + } } + *aDeletedPaddingSizeOut = overallPaddingSize; + // Dependent records removed via ON DELETE CASCADE query = NS_LITERAL_CSTRING( "DELETE FROM entries WHERE id IN (" ); AppendListParamsToQuery(query, aEntryIdList, aPos, aLen); query.AppendLiteral(")"); @@ -1665,16 +1745,17 @@ InsertEntry(mozIStorageConnection* aConn "request_body_id, " "response_type, " "response_status, " "response_status_text, " "response_headers_guard, " "response_body_id, " "response_security_info_id, " "response_principal_info, " + "response_padding_size, " "cache_id " ") VALUES (" ":request_method, " ":request_url_no_query, " ":request_url_no_query_hash, " ":request_url_query, " ":request_url_query_hash, " ":request_url_fragment, " @@ -1690,16 +1771,17 @@ InsertEntry(mozIStorageConnection* aConn ":request_body_id, " ":response_type, " ":response_status, " ":response_status_text, " ":response_headers_guard, " ":response_body_id, " ":response_security_info_id, " ":response_principal_info, " + ":response_padding_size, " ":cache_id " ");" ), getter_AddRefs(state)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = state->BindUTF8StringByName(NS_LITERAL_CSTRING("request_method"), aRequest.method()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -1807,16 +1889,28 @@ InsertEntry(mozIStorageConnection* aConn cInfo.attrs().CreateSuffix(suffix); serializedInfo.Append(suffix); } rv = state->BindUTF8StringByName(NS_LITERAL_CSTRING("response_principal_info"), serializedInfo); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + if (aResponse.paddingSize() == InternalResponse::UNKNOWN_PADDING_SIZE) { + MOZ_DIAGNOSTIC_ASSERT(aResponse.type() != ResponseType::Opaque); + rv = state->BindNullByName(NS_LITERAL_CSTRING("response_padding_size")); + } else { + MOZ_DIAGNOSTIC_ASSERT(aResponse.paddingSize() >= 0); + MOZ_DIAGNOSTIC_ASSERT(aResponse.type() == ResponseType::Opaque); + + rv = state->BindInt64ByName(NS_LITERAL_CSTRING("response_padding_size"), + aResponse.paddingSize()); + } + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + rv = state->BindInt64ByName(NS_LITERAL_CSTRING("cache_id"), aCacheId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = state->Execute(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT last_insert_rowid()" @@ -1892,17 +1986,17 @@ InsertEntry(mozIStorageConnection* aConn if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } const nsTArray<nsCString>& responseUrlList = aResponse.urlList(); for (uint32_t i = 0; i < responseUrlList.Length(); ++i) { rv = state->BindUTF8StringByName(NS_LITERAL_CSTRING("url"), responseUrlList[i]); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = state->BindInt64ByName(NS_LITERAL_CSTRING("entry_id"), entryId); + rv = state->BindInt32ByName(NS_LITERAL_CSTRING("entry_id"), entryId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = state->Execute(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } return rv; } @@ -1919,16 +2013,17 @@ ReadResponse(mozIStorageConnection* aCon nsresult rv = aConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT " "entries.response_type, " "entries.response_status, " "entries.response_status_text, " "entries.response_headers_guard, " "entries.response_body_id, " "entries.response_principal_info, " + "entries.response_padding_size, " "security_info.data " "FROM entries " "LEFT OUTER JOIN security_info " "ON entries.response_security_info_id=security_info.id " "WHERE entries.id=:id;" ), getter_AddRefs(state)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -1980,17 +2075,37 @@ ReadResponse(mozIStorageConnection* aCon NS_WARNING("Something went wrong parsing a serialized principal!"); return NS_ERROR_FAILURE; } aSavedResponseOut->mValue.principalInfo() = mozilla::ipc::ContentPrincipalInfo(attrs, void_t(), specNoSuffix); } - rv = state->GetBlobAsUTF8String(6, aSavedResponseOut->mValue.channelInfo().securityInfo()); + bool nullPadding = false; + rv = state->GetIsNull(6, &nullPadding); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + if (nullPadding) { + MOZ_DIAGNOSTIC_ASSERT(aSavedResponseOut->mValue.type() != + ResponseType::Opaque); + aSavedResponseOut->mValue.paddingSize() = + InternalResponse::UNKNOWN_PADDING_SIZE; + } else { + MOZ_DIAGNOSTIC_ASSERT(aSavedResponseOut->mValue.type() == + ResponseType::Opaque); + int64_t paddingSize = 0; + rv = state->GetInt64(6, &paddingSize); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + MOZ_DIAGNOSTIC_ASSERT(paddingSize >= 0); + aSavedResponseOut->mValue.paddingSize() = paddingSize; + } + + rv = state->GetBlobAsUTF8String(7, aSavedResponseOut->mValue.channelInfo().securityInfo()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConn->CreateStatement(NS_LITERAL_CSTRING( "SELECT " "name, " "value " "FROM response_headers " "WHERE entry_id=:entry_id;" @@ -2477,28 +2592,30 @@ nsresult MigrateFrom16To17(mozIStorageCo nsresult MigrateFrom17To18(mozIStorageConnection* aConn, bool& aRewriteSchema); nsresult MigrateFrom18To19(mozIStorageConnection* aConn, bool& aRewriteSchema); nsresult MigrateFrom19To20(mozIStorageConnection* aConn, bool& aRewriteSchema); nsresult MigrateFrom20To21(mozIStorageConnection* aConn, bool& aRewriteSchema); nsresult MigrateFrom21To22(mozIStorageConnection* aConn, bool& aRewriteSchema); nsresult MigrateFrom22To23(mozIStorageConnection* aConn, bool& aRewriteSchema); nsresult MigrateFrom23To24(mozIStorageConnection* aConn, bool& aRewriteSchema); nsresult MigrateFrom24To25(mozIStorageConnection* aConn, bool& aRewriteSchema); +nsresult MigrateFrom25To26(mozIStorageConnection* aConn, bool& aRewriteSchema); // Configure migration functions to run for the given starting version. Migration sMigrationList[] = { Migration(15, MigrateFrom15To16), Migration(16, MigrateFrom16To17), Migration(17, MigrateFrom17To18), Migration(18, MigrateFrom18To19), Migration(19, MigrateFrom19To20), Migration(20, MigrateFrom20To21), Migration(21, MigrateFrom21To22), Migration(22, MigrateFrom22To23), Migration(23, MigrateFrom23To24), Migration(24, MigrateFrom24To25), + Migration(25, MigrateFrom25To26), }; uint32_t sMigrationListLength = sizeof(sMigrationList) / sizeof(Migration); nsresult RewriteEntriesSchema(mozIStorageConnection* aConn) { nsresult rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( "PRAGMA writable_schema = ON" )); @@ -3025,13 +3142,40 @@ nsresult MigrateFrom24To25(mozIStorageCo MOZ_DIAGNOSTIC_ASSERT(aConn); // The only change between 24 and 25 was a new nsIContentPolicy type. nsresult rv = aConn->SetSchemaVersion(25); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return rv; } +nsresult MigrateFrom25To26(mozIStorageConnection* aConn, bool& aRewriteSchema) +{ + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_DIAGNOSTIC_ASSERT(aConn); + + // Add the response_padding_size column. + // Note: only opaque repsonse should be non-null interger. + nsresult rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "ALTER TABLE entries " + "ADD COLUMN response_padding_size INTEGER NULL " + )); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "UPDATE entries SET response_padding_size = 0" + "WHERE response_type = 4" // opaque response + )); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = aConn->SetSchemaVersion(26); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + aRewriteSchema = true; + + return rv; +} + } // anonymous namespace } // namespace db } // namespace cache } // namespace dom } // namespace mozilla
--- a/dom/cache/DBSchema.h +++ b/dom/cache/DBSchema.h @@ -37,28 +37,33 @@ CreateOrMigrateSchema(mozIStorageConnect nsresult InitializeConnection(mozIStorageConnection* aConn); nsresult CreateCacheId(mozIStorageConnection* aConn, CacheId* aCacheIdOut); nsresult DeleteCacheId(mozIStorageConnection* aConn, CacheId aCacheId, - nsTArray<nsID>& aDeletedBodyIdListOut); + nsTArray<nsID>& aDeletedBodyIdListOut, + int64_t* aDeletedPaddingSizeOut); // TODO: Consider removing unused IsCacheOrphaned after writing cleanup code. (bug 1110446) nsresult IsCacheOrphaned(mozIStorageConnection* aConn, CacheId aCacheId, bool* aOrphanedOut); nsresult FindOrphanedCacheIds(mozIStorageConnection* aConn, nsTArray<CacheId>& aOrphanedListOut); nsresult +FindOverallPaddingSize(mozIStorageConnection* aConn, + int64_t* aOverallPaddingSizeOut); + +nsresult GetKnownBodyIds(mozIStorageConnection* aConn, nsTArray<nsID>& aBodyIdListOut); nsresult CacheMatch(mozIStorageConnection* aConn, CacheId aCacheId, const CacheRequest& aRequest, const CacheQueryParams& aParams, bool* aFoundResponseOut, SavedResponse* aSavedResponseOut); nsresult @@ -68,23 +73,25 @@ CacheMatchAll(mozIStorageConnection* aCo nsTArray<SavedResponse>& aSavedResponsesOut); nsresult CachePut(mozIStorageConnection* aConn, CacheId aCacheId, const CacheRequest& aRequest, const nsID* aRequestBodyId, const CacheResponse& aResponse, const nsID* aResponseBodyId, - nsTArray<nsID>& aDeletedBodyIdListOut); + nsTArray<nsID>& aDeletedBodyIdListOut, + int64_t* aDeletedPaddingSizeOut); nsresult CacheDelete(mozIStorageConnection* aConn, CacheId aCacheId, const CacheRequest& aRequest, const CacheQueryParams& aParams, nsTArray<nsID>& aDeletedBodyIdListOut, + int64_t* aDeletedPaddingSizeOut, bool* aSuccessOut); nsresult CacheKeys(mozIStorageConnection* aConn, CacheId aCacheId, const CacheRequestOrVoid& aRequestOrVoid, const CacheQueryParams& aParams, nsTArray<SavedRequest>& aSavedRequestsOut);
--- a/dom/cache/FileUtils.cpp +++ b/dom/cache/FileUtils.cpp @@ -1,49 +1,77 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/cache/FileUtils.h" +#include "mozilla/dom/InternalResponse.h" #include "mozilla/dom/quota/FileStreams.h" #include "mozilla/dom/quota/QuotaManager.h" #include "mozilla/SnappyCompressOutputStream.h" #include "mozilla/Unused.h" +#include "nsIBinaryInputStream.h" +#include "nsIBinaryOutputStream.h" #include "nsIFile.h" #include "nsIUUIDGenerator.h" #include "nsNetCID.h" +#include "nsNetUtil.h" #include "nsISimpleEnumerator.h" #include "nsServiceManagerUtils.h" #include "nsString.h" #include "nsThreadUtils.h" namespace mozilla { namespace dom { namespace cache { +#define PADDING_FILE_NAME ".padding" +#define PADDING_TMP_FILE_NAME ".padding-tmp" + using mozilla::dom::quota::FileInputStream; using mozilla::dom::quota::FileOutputStream; using mozilla::dom::quota::PERSISTENCE_TYPE_DEFAULT; using mozilla::dom::quota::QuotaManager; +using mozilla::dom::quota::QuotaObject; namespace { +// Const variable for generate padding size. +// XXX This will be tweaked to something more meaningful in Bug 1383656. +const int64_t kRoundUpNumber = 20480; + enum BodyFileType { BODY_FILE_FINAL, BODY_FILE_TMP }; nsresult BodyIdToFile(nsIFile* aBaseDir, const nsID& aId, BodyFileType aType, nsIFile** aBodyFileOut); +int64_t +RoundUp(const int64_t aX, const int64_t aY); + +// The alogrithm for generating padding refers to the mitigation approach in +// https://github.com/whatwg/storage/issues/31. +// First, generate a random number between 0 and 100kB. +// Next, round up the sum of random number and response size to the nearest +// 20kB. +// Finally, the virtual padding size will be the result minus the response size. +int64_t +BodyGeneratePadding(const int64_t aBodyFileSize, const uint32_t aPaddingInfo); + +nsresult +LockedDirectoryPaddingWrite(nsIFile* aBaseDir, DirPaddingFile aPaddingFileType, + int64_t aPaddingSize); + } // namespace // static nsresult BodyCreateDir(nsIFile* aBaseDir) { MOZ_DIAGNOSTIC_ASSERT(aBaseDir); @@ -243,16 +271,55 @@ BodyOpen(const QuotaInfo& aQuotaInfo, ns fileStream.forget(aStreamOut); return rv; } // static nsresult +BodyMaybeUpdatePaddingSize(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir, + const nsID& aId, const uint32_t aPaddingInfo, + int64_t* aPaddingSizeOut) +{ + MOZ_DIAGNOSTIC_ASSERT(aBaseDir); + MOZ_DIAGNOSTIC_ASSERT(aPaddingSizeOut); + + nsCOMPtr<nsIFile> bodyFile; + nsresult rv = + BodyIdToFile(aBaseDir, aId, BODY_FILE_TMP, getter_AddRefs(bodyFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + MOZ_DIAGNOSTIC_ASSERT(bodyFile); + + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_DIAGNOSTIC_ASSERT(quotaManager); + + int64_t fileSize = 0; + RefPtr<QuotaObject> quotaObject = + quotaManager->GetQuotaObject(PERSISTENCE_TYPE_DEFAULT, aQuotaInfo.mGroup, + aQuotaInfo.mOrigin, bodyFile, &fileSize); + MOZ_DIAGNOSTIC_ASSERT(quotaObject); + MOZ_DIAGNOSTIC_ASSERT(fileSize >= 0); + + if (*aPaddingSizeOut == InternalResponse::UNKNOWN_PADDING_SIZE) { + *aPaddingSizeOut = BodyGeneratePadding(fileSize, aPaddingInfo); + } + + MOZ_DIAGNOSTIC_ASSERT(*aPaddingSizeOut >= 0); + + if (!quotaObject->IncreaseSize(*aPaddingSizeOut)) { + return NS_ERROR_FILE_NO_DEVICE_SPACE; + } + + return rv; +} + +// static +nsresult BodyDeleteFiles(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir, const nsTArray<nsID>& aIdList) { nsresult rv = NS_OK; for (uint32_t i = 0; i < aIdList.Length(); ++i) { nsCOMPtr<nsIFile> tmpFile; rv = BodyIdToFile(aBaseDir, aIdList[i], BODY_FILE_TMP, @@ -305,16 +372,72 @@ BodyIdToFile(nsIFile* aBaseDir, const ns } rv = (*aBodyFileOut)->Append(fileName); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return rv; } +int64_t +RoundUp(const int64_t aX, const int64_t aY) +{ + MOZ_DIAGNOSTIC_ASSERT(aX >= 0); + MOZ_DIAGNOSTIC_ASSERT(aY > 0); + + MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - ((aX - 1) / aY) * aY >= aY); + return aY + ((aX - 1) / aY) * aY; +} + +int64_t +BodyGeneratePadding(const int64_t aBodyFileSize, const uint32_t aPaddingInfo) +{ + // Generate padding + int64_t randomSize = static_cast<int64_t>(aPaddingInfo); + MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - aBodyFileSize >= randomSize); + randomSize += aBodyFileSize; + + return RoundUp(randomSize, kRoundUpNumber) - aBodyFileSize; +} + +nsresult +LockedDirectoryPaddingWrite(nsIFile* aBaseDir, DirPaddingFile aPaddingFileType, + int64_t aPaddingSize) +{ + MOZ_DIAGNOSTIC_ASSERT(aBaseDir); + MOZ_DIAGNOSTIC_ASSERT(aPaddingSize >= 0); + + nsCOMPtr<nsIFile> file; + nsresult rv = aBaseDir->Clone(getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + if (aPaddingFileType == DirPaddingFile::TMP_FILE) { + rv = file->Append(NS_LITERAL_STRING(PADDING_TMP_FILE_NAME)); + } else { + rv = file->Append(NS_LITERAL_STRING(PADDING_FILE_NAME)); + } + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + nsCOMPtr<nsIOutputStream> outputStream; + rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), file); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + nsCOMPtr<nsIBinaryOutputStream> binaryStream = + do_CreateInstance("@mozilla.org/binaryoutputstream;1"); + if (NS_WARN_IF(!binaryStream)) { return NS_ERROR_FAILURE; } + + rv = binaryStream->SetOutputStream(outputStream); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = binaryStream->Write64(aPaddingSize); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + return rv; +} + } // namespace nsresult BodyDeleteOrphanedFiles(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir, nsTArray<nsID>& aKnownBodyIdList) { MOZ_DIAGNOSTIC_ASSERT(aBaseDir); @@ -543,21 +666,257 @@ RemoveNsIFile(const QuotaInfo& aQuotaInf rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) { return NS_OK; } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aFile->Remove( /* recursive */ false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + if (fileSize > 0) { + DecreaseUsageForQuotaInfo(aQuotaInfo, fileSize); + } + + return rv; +} + +// static +void +DecreaseUsageForQuotaInfo(const QuotaInfo& aQuotaInfo, + const int64_t& aUpdatingSize) +{ + MOZ_DIAGNOSTIC_ASSERT(aUpdatingSize > 0); + QuotaManager* quotaManager = QuotaManager::Get(); MOZ_DIAGNOSTIC_ASSERT(quotaManager); quotaManager->DecreaseUsageForOrigin(PERSISTENCE_TYPE_DEFAULT, aQuotaInfo.mGroup, aQuotaInfo.mOrigin, - fileSize); + aUpdatingSize); +} + +// static +bool +DirectoryPaddingFileExists(nsIFile* aBaseDir, DirPaddingFile aPaddingFileType) +{ + MOZ_DIAGNOSTIC_ASSERT(aBaseDir); + + nsCOMPtr<nsIFile> file; + nsresult rv = aBaseDir->Clone(getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { return false; } + + nsString fileName; + if (aPaddingFileType == DirPaddingFile::TMP_FILE) { + fileName = NS_LITERAL_STRING(PADDING_TMP_FILE_NAME); + } else { + fileName = NS_LITERAL_STRING(PADDING_FILE_NAME); + } + + rv = file->Append(fileName); + if (NS_WARN_IF(NS_FAILED(rv))) { return false; } + + bool exists = false; + rv = file->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { return false; } + + return exists; +} + +// static +nsresult +LockedDirectoryPaddingGet(nsIFile* aBaseDir, int64_t* aPaddingSizeOut) +{ + MOZ_DIAGNOSTIC_ASSERT(aBaseDir); + MOZ_DIAGNOSTIC_ASSERT(aPaddingSizeOut); + MOZ_DIAGNOSTIC_ASSERT(!DirectoryPaddingFileExists(aBaseDir, + DirPaddingFile::TMP_FILE)); + + nsCOMPtr<nsIFile> file; + nsresult rv = aBaseDir->Clone(getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = file->Append(NS_LITERAL_STRING(PADDING_FILE_NAME)); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + nsCOMPtr<nsIInputStream> stream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + nsCOMPtr<nsIInputStream> bufferedStream; + rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedStream), stream, 512); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + nsCOMPtr<nsIBinaryInputStream> binaryStream = + do_CreateInstance("@mozilla.org/binaryinputstream;1"); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = binaryStream->SetInputStream(bufferedStream); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + uint64_t paddingSize = 0; + rv = binaryStream->Read64(&paddingSize); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + *aPaddingSizeOut = paddingSize; + + return rv; +} + +// static +nsresult +LockedDirectoryPaddingInit(nsIFile* aBaseDir) +{ + MOZ_DIAGNOSTIC_ASSERT(aBaseDir); + + nsresult rv = LockedDirectoryPaddingWrite(aBaseDir, DirPaddingFile::FILE, 0); + Unused << NS_WARN_IF(NS_FAILED(rv)); return rv; } +// static +nsresult +LockedUpdateDirectoryPaddingFile(nsIFile* aBaseDir, + mozIStorageConnection* aConn, + const int64_t aIncreaseSize, + const int64_t aDecreaseSize, + const bool aTemporaryFileExist) +{ + MOZ_DIAGNOSTIC_ASSERT(aBaseDir); + MOZ_DIAGNOSTIC_ASSERT(aConn); + MOZ_DIAGNOSTIC_ASSERT(aIncreaseSize >= 0); + MOZ_DIAGNOSTIC_ASSERT(aDecreaseSize >= 0); + + int64_t currentPaddingSize = 0; + nsresult rv = LockedDirectoryPaddingGet(aBaseDir, ¤tPaddingSize); + if (NS_WARN_IF(NS_FAILED(rv)) || aTemporaryFileExist) { + // Fail to read padding size from the dir padding file, so try to restore. + if (rv != NS_ERROR_FILE_NOT_FOUND && + rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) { + // Not delete the temporary padding file here, because we're going to + // overwrite it below anyway. + rv = LockedDirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::FILE); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + } + + // We don't need to add the aIncreaseSize or aDecreaseSize here, because + // it's already encompassed within the database. + rv = db::FindOverallPaddingSize(aConn, ¤tPaddingSize); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + } else { + if (aIncreaseSize > 0) { + MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - currentPaddingSize >= aIncreaseSize); + currentPaddingSize += aIncreaseSize; + } + + if (aDecreaseSize > 0) { + MOZ_DIAGNOSTIC_ASSERT(currentPaddingSize >= aDecreaseSize); + currentPaddingSize -= aDecreaseSize; + } + +#ifdef DEBUG + int64_t paddingSizeFromDB = 0; + rv = db::FindOverallPaddingSize(aConn, &paddingSizeFromDB); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + MOZ_DIAGNOSTIC_ASSERT(paddingSizeFromDB == currentPaddingSize); +#endif // DEBUG + } + + MOZ_DIAGNOSTIC_ASSERT(currentPaddingSize >= 0); + + rv = LockedDirectoryPaddingTemporaryWrite(aBaseDir, currentPaddingSize); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + return rv; +} + +// static +nsresult +LockedDirectoryPaddingTemporaryWrite(nsIFile* aBaseDir, int64_t aPaddingSize) +{ + MOZ_DIAGNOSTIC_ASSERT(aBaseDir); + MOZ_DIAGNOSTIC_ASSERT(aPaddingSize >= 0); + + nsresult rv = LockedDirectoryPaddingWrite(aBaseDir, DirPaddingFile::TMP_FILE, + aPaddingSize); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + return rv; +} + +// static +nsresult +LockedDirectoryPaddingFinalizeWrite(nsIFile* aBaseDir) +{ + MOZ_DIAGNOSTIC_ASSERT(aBaseDir); + MOZ_DIAGNOSTIC_ASSERT(DirectoryPaddingFileExists(aBaseDir, + DirPaddingFile::TMP_FILE)); + + nsCOMPtr<nsIFile> file; + nsresult rv = aBaseDir->Clone(getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = file->Append(NS_LITERAL_STRING(PADDING_TMP_FILE_NAME)); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = file->RenameTo(nullptr, NS_LITERAL_STRING(PADDING_FILE_NAME)); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + return rv; +} + +// static +nsresult +LockedDirectoryPaddingRestore(nsIFile* aBaseDir, mozIStorageConnection* aConn) +{ + MOZ_DIAGNOSTIC_ASSERT(aBaseDir); + MOZ_DIAGNOSTIC_ASSERT(aConn); + + // The content of padding file is untrusted, so remove it here. + nsresult rv = LockedDirectoryPaddingDeleteFile(aBaseDir, + DirPaddingFile::TMP_FILE); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = LockedDirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::FILE); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + int64_t paddingSize = 0; + rv = db::FindOverallPaddingSize(aConn, &paddingSize); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + MOZ_DIAGNOSTIC_ASSERT(paddingSize >= 0); + + LockedDirectoryPaddingWrite(aBaseDir, DirPaddingFile::FILE, paddingSize); + + return rv; +} + +// static +nsresult +LockedDirectoryPaddingDeleteFile(nsIFile* aBaseDir, + DirPaddingFile aPaddingFileType) +{ + MOZ_DIAGNOSTIC_ASSERT(aBaseDir); + + nsCOMPtr<nsIFile> file; + nsresult rv = aBaseDir->Clone(getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + if (aPaddingFileType == DirPaddingFile::TMP_FILE) { + rv = file->Append(NS_LITERAL_STRING(PADDING_TMP_FILE_NAME)); + } else { + rv = file->Append(NS_LITERAL_STRING(PADDING_FILE_NAME)); + } + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = file->Remove( /* recursive */ false); + if (rv == NS_ERROR_FILE_NOT_FOUND || + rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) { + return NS_OK; + } + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + return rv; +} } // namespace cache } // namespace dom } // namespace mozilla
--- a/dom/cache/FileUtils.h +++ b/dom/cache/FileUtils.h @@ -14,16 +14,22 @@ struct nsID; class nsIFile; namespace mozilla { namespace dom { namespace cache { +enum DirPaddingFile +{ + FILE, + TMP_FILE +}; + nsresult BodyCreateDir(nsIFile* aBaseDir); // Note that this function can only be used during the initialization of the // database. We're unlikely to be able to delete the DB successfully past // that point due to the file being in use. nsresult BodyDeleteDir(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir); @@ -43,16 +49,20 @@ BodyCancelWrite(nsIFile* aBaseDir, nsISu nsresult BodyFinalizeWrite(nsIFile* aBaseDir, const nsID& aId); nsresult BodyOpen(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir, const nsID& aId, nsIInputStream** aStreamOut); nsresult +BodyMaybeUpdatePaddingSize(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir, + const nsID& aId, int64_t* aPaddingSizeOut); + +nsresult BodyDeleteFiles(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir, const nsTArray<nsID>& aIdList); nsresult BodyDeleteOrphanedFiles(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir, nsTArray<nsID>& aKnownBodyIdList); nsresult @@ -65,13 +75,59 @@ bool MarkerFileExists(const QuotaInfo& aQuotaInfo); nsresult RemoveNsIFileRecursively(const QuotaInfo& aQuotaInfo, nsIFile* aFile); nsresult RemoveNsIFile(const QuotaInfo& aQuotaInfo, nsIFile* aFile); +void +DecreaseUsageForQuotaInfo(const QuotaInfo& aQuotaInfo, + const int64_t& aUpdatingSize); + +/** + * This function is used to check if the directory padding file is existed. + */ + +bool +DirectoryPaddingFileExists(nsIFile* aBaseDir, DirPaddingFile aPaddingFileType); + +/** + * + * The functions below are used to read/write/delete the directory padding file + * after acquiring the mutex lock. The mutex lock is held by + * CacheQuotaClient to prevent multi-thread accessing issue. To avoid deadlock, + * these functions should only access by static functions in + * dom/cache/QuotaClient.cpp. + * + */ + +nsresult +LockedDirectoryPaddingGet(nsIFile* aBaseDir, int64_t* aPaddingSizeOut); + +nsresult +LockedDirectoryPaddingInit(nsIFile* aBaseDir); + +nsresult +LockedUpdateDirectoryPaddingFile(nsIFile* aBaseDir, + mozIStorageConnection* aConn, + const int64_t aIncreaseSize, + const int64_t aDecreaseSize, + const bool aTemporaryFileExist); + +nsresult +LockedDirectoryPaddingTemporaryWrite(nsIFile* aBaseDir, int64_t aPaddingSize); + +nsresult +LockedDirectoryPaddingFinalizeWrite(nsIFile* aBaseDir); + +nsresult +LockedDirectoryPaddingRestore(nsIFile* aBaseDir, mozIStorageConnection* aConn); + +nsresult +LockedDirectoryPaddingDeleteFile(nsIFile* aBaseDir, + DirPaddingFile aPaddingFileType); } // namespace cache } // namespace dom } // namespace mozilla #endif // mozilla_dom_cache_FileUtils_h
--- a/dom/cache/Manager.cpp +++ b/dom/cache/Manager.cpp @@ -15,16 +15,17 @@ #include "mozilla/dom/cache/DBAction.h" #include "mozilla/dom/cache/DBSchema.h" #include "mozilla/dom/cache/FileUtils.h" #include "mozilla/dom/cache/ManagerId.h" #include "mozilla/dom/cache/CacheTypes.h" #include "mozilla/dom/cache/SavedTypes.h" #include "mozilla/dom/cache/StreamList.h" #include "mozilla/dom/cache/Types.h" +#include "mozilla/dom/cache/QuotaClient.h" #include "mozilla/ipc/BackgroundParent.h" #include "mozStorageHelper.h" #include "nsIInputStream.h" #include "nsID.h" #include "nsIFile.h" #include "nsIThread.h" #include "nsThreadUtils.h" #include "nsTObserverArray.h" @@ -45,16 +46,18 @@ public: SetupAction() : SyncDBAction(DBAction::Create) { } virtual nsresult RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir, mozIStorageConnection* aConn) override { + MOZ_DIAGNOSTIC_ASSERT(aDBDir); + nsresult rv = BodyCreateDir(aDBDir); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // executes in its own transaction rv = db::CreateOrMigrateSchema(aConn); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // If the Context marker file exists, then the last session was @@ -72,31 +75,56 @@ public: mozStorageTransaction trans(aConn, false, mozIStorageConnection::TRANSACTION_IMMEDIATE); // Clean up orphaned Cache objects AutoTArray<CacheId, 8> orphanedCacheIdList; nsresult rv = db::FindOrphanedCacheIds(aConn, orphanedCacheIdList); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + int64_t overallDeletedPaddingSize = 0; for (uint32_t i = 0; i < orphanedCacheIdList.Length(); ++i) { AutoTArray<nsID, 16> deletedBodyIdList; - rv = db::DeleteCacheId(aConn, orphanedCacheIdList[i], deletedBodyIdList); + int64_t deletedPaddingSize = 0; + rv = db::DeleteCacheId(aConn, orphanedCacheIdList[i], deletedBodyIdList, + &deletedPaddingSize); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = BodyDeleteFiles(aQuotaInfo, aDBDir, deletedBodyIdList); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + if (deletedPaddingSize > 0) { + DecreaseUsageForQuotaInfo(aQuotaInfo, deletedPaddingSize); + } + + MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - deletedPaddingSize >= + overallDeletedPaddingSize); + overallDeletedPaddingSize += deletedPaddingSize; } // Clean up orphaned body objects AutoTArray<nsID, 64> knownBodyIdList; rv = db::GetKnownBodyIds(aConn, knownBodyIdList); rv = BodyDeleteOrphanedFiles(aQuotaInfo, aDBDir, knownBodyIdList); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + // Commit() explicitly here, because we want to ensure the padding file + // has the correct content. + rv = MaybeUpdatePaddingFile(aDBDir, aConn, /* aIncreaceSize */ 0, + overallDeletedPaddingSize, + [&trans]() mutable { return trans.Commit(); }); + // We'll restore padding file below, so just warn here if failure happens. + Unused << NS_WARN_IF(NS_FAILED(rv)); + } + + if (DirectoryPaddingFileExists(aDBDir, DirPaddingFile::TMP_FILE) || + !DirectoryPaddingFileExists(aDBDir, DirPaddingFile::FILE)) { + rv = RestorePaddingFile(aDBDir, aConn); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } return rv; } }; // ---------------------------------------------------------------------------- @@ -435,47 +463,60 @@ protected: // a Cache object that has been orphaned. class Manager::DeleteOrphanedCacheAction final : public SyncDBAction { public: DeleteOrphanedCacheAction(Manager* aManager, CacheId aCacheId) : SyncDBAction(DBAction::Existing) , mManager(aManager) , mCacheId(aCacheId) + , mDeletedPaddingSize(0) { } virtual nsresult RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir, mozIStorageConnection* aConn) override { + mQuotaInfo.emplace(aQuotaInfo); + mozStorageTransaction trans(aConn, false, mozIStorageConnection::TRANSACTION_IMMEDIATE); - nsresult rv = db::DeleteCacheId(aConn, mCacheId, mDeletedBodyIdList); + nsresult rv = db::DeleteCacheId(aConn, mCacheId, mDeletedBodyIdList, + &mDeletedPaddingSize); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = trans.Commit(); - if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + rv = MaybeUpdatePaddingFile(aDBDir, aConn, /* aIncreaceSize */ 0, + mDeletedPaddingSize, + [&trans]() mutable { return trans.Commit(); }); + Unused << NS_WARN_IF(NS_FAILED(rv)); return rv; } virtual void CompleteOnInitiatingThread(nsresult aRv) override { mManager->NoteOrphanedBodyIdList(mDeletedBodyIdList); + if (mDeletedPaddingSize > 0) { + DecreaseUsageForQuotaInfo(mQuotaInfo.ref(), mDeletedPaddingSize); + } + // ensure we release the manager on the initiating thread mManager = nullptr; } private: RefPtr<Manager> mManager; const CacheId mCacheId; nsTArray<nsID> mDeletedBodyIdList; + Maybe<QuotaInfo> mQuotaInfo; + // Track any pad amount associated with orphaned entries. + int64_t mDeletedPaddingSize; }; // ---------------------------------------------------------------------------- class Manager::CacheMatchAction final : public Manager::BaseAction { public: CacheMatchAction(Manager* aManager, ListenerId aListenerId, @@ -616,16 +657,18 @@ public: : DBAction(DBAction::Existing) , mManager(aManager) , mListenerId(aListenerId) , mCacheId(aCacheId) , mList(aPutList.Length()) , mExpectedAsyncCopyCompletions(1) , mAsyncResult(NS_OK) , mMutex("cache::Manager::CachePutAllAction") + , mUpdatedPaddingSize(0) + , mDeletedPaddingSize(0) { MOZ_DIAGNOSTIC_ASSERT(!aPutList.IsEmpty()); MOZ_DIAGNOSTIC_ASSERT(aPutList.Length() == aRequestStreamList.Length()); MOZ_DIAGNOSTIC_ASSERT(aPutList.Length() == aResponseStreamList.Length()); for (uint32_t i = 0; i < aPutList.Length(); ++i) { Entry* entry = mList.AppendElement(); entry->mRequest = aPutList[i].request(); @@ -676,17 +719,16 @@ private: rv = StartStreamCopy(aQuotaInfo, mList[i], ResponseStream, &mExpectedAsyncCopyCompletions); if (NS_WARN_IF(NS_FAILED(rv))) { break; } } - // Always call OnAsyncCopyComplete() manually here. This covers the // case where there is no async copying and also reports any startup // errors correctly. If we hit an error, then OnAsyncCopyComplete() // will cancel any async copying. OnAsyncCopyComplete(rv); } // Called once for each asynchronous file copy whether it succeeds or @@ -757,35 +799,60 @@ private: if (e.mRequestStream) { rv = BodyFinalizeWrite(mDBDir, e.mRequestBodyId); if (NS_WARN_IF(NS_FAILED(rv))) { DoResolve(rv); return; } } if (e.mResponseStream) { + // Gerenate padding size for opaque response if needed. + if (e.mResponse.type() == ResponseType::Opaque) { + // It'll generate padding if we've not set it yet. + rv = BodyMaybeUpdatePaddingSize(mQuotaInfo.ref(), mDBDir, + e.mResponseBodyId, + e.mResponse.paddingInfo(), + &e.mResponse.paddingSize()); + if (NS_WARN_IF(NS_FAILED(rv))) { + DoResolve(rv); + return; + } + + MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - e.mResponse.paddingSize() >= + mUpdatedPaddingSize); + mUpdatedPaddingSize += e.mResponse.paddingSize(); + } + rv = BodyFinalizeWrite(mDBDir, e.mResponseBodyId); if (NS_WARN_IF(NS_FAILED(rv))) { DoResolve(rv); return; } } + int64_t deletedPaddingSize = 0; rv = db::CachePut(mConn, mCacheId, e.mRequest, e.mRequestStream ? &e.mRequestBodyId : nullptr, e.mResponse, e.mResponseStream ? &e.mResponseBodyId : nullptr, - mDeletedBodyIdList); + mDeletedBodyIdList, &deletedPaddingSize); if (NS_WARN_IF(NS_FAILED(rv))) { DoResolve(rv); return; } + + MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - mDeletedPaddingSize >= + deletedPaddingSize); + mDeletedPaddingSize += deletedPaddingSize; } - rv = trans.Commit(); + // Update padding file when it's necessary + rv = MaybeUpdatePaddingFile(mDBDir, mConn, mUpdatedPaddingSize, + mDeletedPaddingSize, + [&trans]() mutable { return trans.Commit(); }); Unused << NS_WARN_IF(NS_FAILED(rv)); DoResolve(rv); } virtual void CompleteOnInitiatingThread(nsresult aRv) override { @@ -793,16 +860,20 @@ private: for (uint32_t i = 0; i < mList.Length(); ++i) { mList[i].mRequestStream = nullptr; mList[i].mResponseStream = nullptr; } mManager->NoteOrphanedBodyIdList(mDeletedBodyIdList); + if (mDeletedPaddingSize > 0) { + DecreaseUsageForQuotaInfo(mQuotaInfo.ref(), mDeletedPaddingSize); + } + Listener* listener = mManager->GetListener(mListenerId); mManager = nullptr; if (listener) { listener->OnOpComplete(ErrorResult(aRv), CachePutAllResult()); } } virtual void @@ -932,16 +1003,19 @@ private: MutexAutoLock lock(mMutex); MOZ_ASSERT(mCopyContextList.IsEmpty()); } #endif // Clean up any files we might have written before hitting the error. if (NS_FAILED(aRv)) { BodyDeleteFiles(mQuotaInfo.ref(), mDBDir, mBodyIdWrittenList); + if (mUpdatedPaddingSize > 0) { + DecreaseUsageForQuotaInfo(mQuotaInfo.ref(), mUpdatedPaddingSize); + } } // Must be released on the target thread where it was opened. mConn = nullptr; // Drop our ref to the target thread as we are done with this thread. // Also makes our thread assertions catch any incorrect method calls // after resolve. @@ -975,69 +1049,87 @@ private: // thread activity is guaranteed complete nsTArray<nsID> mDeletedBodyIdList; // accessed from any thread while mMutex locked Mutex mMutex; nsTArray<nsCOMPtr<nsISupports>> mCopyContextList; Maybe<QuotaInfo> mQuotaInfo; + // Track how much pad amount has been added for new entries so that it can be + // removed if an error occurs. + int64_t mUpdatedPaddingSize; + // Track any pad amount associated with overwritten entries. + int64_t mDeletedPaddingSize; }; // ---------------------------------------------------------------------------- class Manager::CacheDeleteAction final : public Manager::BaseAction { public: CacheDeleteAction(Manager* aManager, ListenerId aListenerId, CacheId aCacheId, const CacheDeleteArgs& aArgs) : BaseAction(aManager, aListenerId) , mCacheId(aCacheId) , mArgs(aArgs) , mSuccess(false) + , mDeletedPaddingSize(0) { } virtual nsresult RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir, mozIStorageConnection* aConn) override { + mQuotaInfo.emplace(aQuotaInfo); + mozStorageTransaction trans(aConn, false, mozIStorageConnection::TRANSACTION_IMMEDIATE); nsresult rv = db::CacheDelete(aConn, mCacheId, mArgs.request(), mArgs.params(), mDeletedBodyIdList, - &mSuccess); + &mDeletedPaddingSize, &mSuccess); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } - rv = trans.Commit(); + rv = MaybeUpdatePaddingFile(aDBDir, aConn, /* aIncreaceSize */ 0, + mDeletedPaddingSize, + [&trans]() mutable { return trans.Commit(); }); if (NS_WARN_IF(NS_FAILED(rv))) { mSuccess = false; return rv; } return rv; } virtual void Complete(Listener* aListener, ErrorResult&& aRv) override { mManager->NoteOrphanedBodyIdList(mDeletedBodyIdList); + + if (mDeletedPaddingSize > 0) { + DecreaseUsageForQuotaInfo(mQuotaInfo.ref(), mDeletedPaddingSize); + } + aListener->OnOpComplete(Move(aRv), CacheDeleteResult(mSuccess)); } virtual bool MatchesCacheId(CacheId aCacheId) const override { return aCacheId == mCacheId; } private: const CacheId mCacheId; const CacheDeleteArgs mArgs; bool mSuccess; nsTArray<nsID> mDeletedBodyIdList; + Maybe<QuotaInfo> mQuotaInfo; + // Track any pad amount associated with deleted entries. + int64_t mDeletedPaddingSize; }; // ---------------------------------------------------------------------------- class Manager::CacheKeysAction final : public Manager::BaseAction { public: CacheKeysAction(Manager* aManager, ListenerId aListenerId, @@ -1277,16 +1369,18 @@ public: &exists, &mCacheId); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!exists) { mCacheDeleted = false; return NS_OK; } + // Don't delete the removing padding size here, we'll delete it on + // DeleteOrphanedCacheAction. rv = db::StorageForgetCache(aConn, mNamespace, mArgs.key()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = trans.Commit(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } mCacheDeleted = true; return rv;
--- a/dom/cache/QuotaClient.cpp +++ b/dom/cache/QuotaClient.cpp @@ -13,27 +13,34 @@ #include "nsIFile.h" #include "nsISimpleEnumerator.h" #include "nsThreadUtils.h" namespace { using mozilla::Atomic; using mozilla::dom::ContentParentId; +using mozilla::dom::cache::DirPaddingFile; using mozilla::dom::cache::Manager; +using mozilla::dom::cache::QuotaInfo; +using mozilla::dom::quota::AssertIsOnIOThread; using mozilla::dom::quota::Client; using mozilla::dom::quota::PersistenceType; using mozilla::dom::quota::QuotaManager; using mozilla::dom::quota::UsageInfo; using mozilla::ipc::AssertIsOnBackgroundThread; +using mozilla::MutexAutoLock; +using mozilla::Unused; static nsresult GetBodyUsage(nsIFile* aDir, const Atomic<bool>& aCanceled, UsageInfo* aUsageInfo) { + AssertIsOnIOThread(); + nsCOMPtr<nsISimpleEnumerator> entries; nsresult rv = aDir->GetDirectoryEntries(getter_AddRefs(entries)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool hasMore; while (NS_SUCCEEDED(rv = entries->HasMoreElements(&hasMore)) && hasMore && !aCanceled) { nsCOMPtr<nsISupports> entry; @@ -60,56 +67,105 @@ GetBodyUsage(nsIFile* aDir, const Atomic aUsageInfo->AppendToFileUsage(fileSize); } return NS_OK; } class CacheQuotaClient final : public Client { + static CacheQuotaClient* sInstance; + public: + CacheQuotaClient() + : mDirPaddingFileMutex("DOMCacheQuotaClient.mDirPaddingFileMutex") + { + AssertIsOnBackgroundThread(); + MOZ_DIAGNOSTIC_ASSERT(!sInstance); + sInstance = this; + } + + static CacheQuotaClient* + Get() + { + MOZ_DIAGNOSTIC_ASSERT(sInstance); + return sInstance; + } + virtual Type GetType() override { return DOMCACHE; } virtual nsresult InitOrigin(PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin, const AtomicBool& aCanceled, UsageInfo* aUsageInfo) override { + AssertIsOnIOThread(); + // The QuotaManager passes a nullptr UsageInfo if there is no quota being // enforced against the origin. if (!aUsageInfo) { return NS_OK; } return GetUsageForOrigin(aPersistenceType, aGroup, aOrigin, aCanceled, aUsageInfo); } virtual nsresult GetUsageForOrigin(PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin, const AtomicBool& aCanceled, UsageInfo* aUsageInfo) override { + AssertIsOnIOThread(); MOZ_DIAGNOSTIC_ASSERT(aUsageInfo); QuotaManager* qm = QuotaManager::Get(); MOZ_DIAGNOSTIC_ASSERT(qm); nsCOMPtr<nsIFile> dir; nsresult rv = qm->GetDirectoryForOrigin(aPersistenceType, aOrigin, getter_AddRefs(dir)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = dir->Append(NS_LITERAL_STRING(DOMCACHE_DIRECTORY_NAME)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + int64_t paddingSize = 0; + { + // If the tempoary file still exists after locking, it means the previous + // action fails, so restore the padding file. + MutexAutoLock lock(mDirPaddingFileMutex); + + if (mozilla::dom::cache:: + DirectoryPaddingFileExists(dir, DirPaddingFile::TMP_FILE) || + NS_WARN_IF(NS_FAILED(mozilla::dom::cache:: + LockedDirectoryPaddingGet(dir, + &paddingSize)))) { + nsCOMPtr<mozIStorageConnection> conn; + QuotaInfo quotaInfo; + quotaInfo.mGroup = aGroup; + quotaInfo.mOrigin = aOrigin; + rv = mozilla::dom::cache:: + OpenDBConnection(quotaInfo, dir, getter_AddRefs(conn)); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = mozilla::dom::cache::LockedDirectoryPaddingRestore(dir, conn); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = mozilla::dom::cache::LockedDirectoryPaddingGet(dir, &paddingSize); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + } + } + + aUsageInfo->AppendToFileUsage(paddingSize); + nsCOMPtr<nsISimpleEnumerator> entries; rv = dir->GetDirectoryEntries(getter_AddRefs(entries)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } bool hasMore; while (NS_SUCCEEDED(rv = entries->HasMoreElements(&hasMore)) && hasMore && !aCanceled) { nsCOMPtr<nsISupports> entry; @@ -151,16 +207,22 @@ public: rv = file->GetFileSize(&fileSize); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_DIAGNOSTIC_ASSERT(fileSize >= 0); aUsageInfo->AppendToDatabaseUsage(fileSize); continue; } + // Ignore directory padding file + if (leafName.EqualsLiteral(PADDING_FILE_NAME) || + leafName.EqualsLiteral(PADDING_TMP_FILE_NAME)) { + continue; + } + NS_WARNING("Unknown Cache file found!"); } return NS_OK; } virtual void OnOriginClearCompleted(PersistenceType aPersistenceType, @@ -210,34 +272,240 @@ public: ShutdownWorkThreads() override { AssertIsOnBackgroundThread(); // spins the event loop and synchronously shuts down all Managers Manager::ShutdownAll(); } + nsresult + UpgradeStorageFrom2_0To3_0(nsIFile* aDirectory) override + { + AssertIsOnIOThread(); + MOZ_DIAGNOSTIC_ASSERT(aDirectory); + + MutexAutoLock lock(mDirPaddingFileMutex); + + nsresult rv = mozilla::dom::cache::LockedDirectoryPaddingInit(aDirectory); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + return rv; + } + + // static + template<typename Callable> + nsresult + MaybeUpdatePaddingFileInternal(nsIFile* aBaseDir, + mozIStorageConnection* aConn, + const int64_t aIncreaseSize, + const int64_t aDecreaseSize, + Callable aCommitHook) + { + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_DIAGNOSTIC_ASSERT(aBaseDir); + MOZ_DIAGNOSTIC_ASSERT(aConn); + MOZ_DIAGNOSTIC_ASSERT(aIncreaseSize >= 0); + MOZ_DIAGNOSTIC_ASSERT(aDecreaseSize >= 0); + + nsresult rv; + + // Temporary should be removed at the end of each action. If not, it means + // the failure happened. + bool temporaryPaddingFileExist = + mozilla::dom::cache::DirectoryPaddingFileExists(aBaseDir, + DirPaddingFile::TMP_FILE); + + if (aIncreaseSize == aDecreaseSize && !temporaryPaddingFileExist) { + // Early return here, since most cache actions won't modify padding size. + rv = aCommitHook(); + Unused << NS_WARN_IF(NS_FAILED(rv)); + return rv; + } + + { + MutexAutoLock lock(mDirPaddingFileMutex); + rv = + mozilla::dom::cache:: + LockedUpdateDirectoryPaddingFile(aBaseDir, aConn, aIncreaseSize, + aDecreaseSize, + temporaryPaddingFileExist); + if (NS_WARN_IF(NS_FAILED(rv))) { + mozilla::dom::cache:: + LockedDirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::TMP_FILE); + return rv; + } + + rv = aCommitHook(); + if (NS_WARN_IF(NS_FAILED(rv))) { + mozilla::dom::cache:: + LockedDirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::TMP_FILE); + return rv; + } + + rv = mozilla::dom::cache::LockedDirectoryPaddingFinalizeWrite(aBaseDir); + if (NS_WARN_IF(NS_FAILED(rv))) { + // Force restore file next time. + mozilla::dom::cache:: + LockedDirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::FILE); + } + } + + return rv; + } + + // static + nsresult + RestorePaddingFileInternal(nsIFile* aBaseDir, mozIStorageConnection* aConn) + { + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_DIAGNOSTIC_ASSERT(aBaseDir); + MOZ_DIAGNOSTIC_ASSERT(aConn); + + MutexAutoLock lock(mDirPaddingFileMutex); + + nsresult rv = + mozilla::dom::cache::LockedDirectoryPaddingRestore(aBaseDir, aConn); + Unused << NS_WARN_IF(NS_FAILED(rv)); + + return rv; + } + + // static + nsresult + WipePaddingFileInternal(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir) + { + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_DIAGNOSTIC_ASSERT(aBaseDir); + + MutexAutoLock lock(mDirPaddingFileMutex); + + // Remove temporary file if we have one. + nsresult rv = + mozilla::dom::cache:: + LockedDirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::TMP_FILE); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + MOZ_DIAGNOSTIC_ASSERT(mozilla::dom::cache:: + DirectoryPaddingFileExists(aBaseDir, + DirPaddingFile::FILE)); + + int64_t paddingSize = 0; + rv = mozilla::dom::cache::LockedDirectoryPaddingGet(aBaseDir, &paddingSize); + if (NS_WARN_IF(NS_FAILED(rv))) { + // If read file fail, there is nothing we can do to recover the file. + NS_WARNING("Cannnot read padding size from file!"); + paddingSize = 0; + } + + if (paddingSize > 0) { + mozilla::dom::cache::DecreaseUsageForQuotaInfo(aQuotaInfo, paddingSize); + } + + rv = mozilla::dom::cache:: + LockedDirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::FILE); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + rv = mozilla::dom::cache::LockedDirectoryPaddingInit(aBaseDir); + Unused << NS_WARN_IF(NS_FAILED(rv)); + + return rv; + } + private: ~CacheQuotaClient() { AssertIsOnBackgroundThread(); + MOZ_DIAGNOSTIC_ASSERT(sInstance == this); + + sInstance = nullptr; } - NS_INLINE_DECL_REFCOUNTING(CacheQuotaClient, override) + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CacheQuotaClient, override) + + // Mutex lock to protect directroy padding files. It should only be acquired + // in DOM Cache IO threads and Quota IO thread. + mozilla::Mutex mDirPaddingFileMutex; }; +// static +CacheQuotaClient* CacheQuotaClient::sInstance = nullptr; + } // namespace namespace mozilla { namespace dom { namespace cache { +// static already_AddRefed<quota::Client> CreateQuotaClient() { AssertIsOnBackgroundThread(); RefPtr<CacheQuotaClient> ref = new CacheQuotaClient(); return ref.forget(); } +// static +template<typename Callable> +nsresult +MaybeUpdatePaddingFile(nsIFile* aBaseDir, + mozIStorageConnection* aConn, + const int64_t aIncreaseSize, + const int64_t aDecreaseSize, + Callable aCommitHook) +{ + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_DIAGNOSTIC_ASSERT(aBaseDir); + MOZ_DIAGNOSTIC_ASSERT(aConn); + MOZ_DIAGNOSTIC_ASSERT(aIncreaseSize >= 0); + MOZ_DIAGNOSTIC_ASSERT(aDecreaseSize >= 0); + + RefPtr<CacheQuotaClient> cacheQuotaClient = CacheQuotaClient::Get(); + MOZ_DIAGNOSTIC_ASSERT(cacheQuotaClient); + + nsresult rv = + cacheQuotaClient->MaybeUpdatePaddingFileInternal(aBaseDir, aConn, + aIncreaseSize, + aDecreaseSize, + aCommitHook); + Unused << NS_WARN_IF(NS_FAILED(rv)); + + return rv; +} + +// static +nsresult +RestorePaddingFile(nsIFile* aBaseDir, mozIStorageConnection* aConn) +{ + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_DIAGNOSTIC_ASSERT(aBaseDir); + MOZ_DIAGNOSTIC_ASSERT(aConn); + + RefPtr<CacheQuotaClient> cacheQuotaClient = CacheQuotaClient::Get(); + MOZ_DIAGNOSTIC_ASSERT(cacheQuotaClient); + + nsresult rv = + cacheQuotaClient->RestorePaddingFileInternal(aBaseDir, aConn); + Unused << NS_WARN_IF(NS_FAILED(rv)); + + return rv; +} + +// static +nsresult +WipePaddingFile(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir) +{ + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_DIAGNOSTIC_ASSERT(aBaseDir); + + RefPtr<CacheQuotaClient> cacheQuotaClient = CacheQuotaClient::Get(); + MOZ_DIAGNOSTIC_ASSERT(cacheQuotaClient); + + nsresult rv = + cacheQuotaClient->WipePaddingFileInternal(aQuotaInfo, aBaseDir); + Unused << NS_WARN_IF(NS_FAILED(rv)); + + return rv; +} } // namespace cache } // namespace dom } // namespace mozilla
--- a/dom/cache/QuotaClient.h +++ b/dom/cache/QuotaClient.h @@ -3,22 +3,60 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef mozilla_dom_cache_QuotaClient_h #define mozilla_dom_cache_QuotaClient_h #include "mozilla/Attributes.h" +#include "mozilla/dom/cache/Types.h" #include "mozilla/dom/quota/Client.h" namespace mozilla { namespace dom { namespace cache { already_AddRefed<quota::Client> CreateQuotaClient(); +/** + * The following functions are used to access the directory padding file. The + * directory padding file lives in DOM Cache base directory + * (e.g. foo.com/cache/.padding). It is used to keep the current overall padding + * size for an origin, so that the QuotaManager doesn't need to access the + * database when getting quota clients' usage. + * + * For the directory padding file, it's only accessed on Quota IO thread + * (for getting current usage) and Cache IO threads (for tracking padding size + * change). Besides, the padding file is protected by a mutex lock held by + * CacheQuotaClient. + * + * Each padding file should only take 8 bytes (int64_t) to record the overall + * padding size. Besides, we use the temporary padding file to indicate if the + * previous action is completed successfully. If the temporary file exists, it + * represents that the previous action is failed and the content of padding file + * cannot be trusted, and we need to restore the padding file from the database. + */ + +/** + * Note: The aCommitHook argument will be invoked while a lock is held. Callers + * should be careful not to pass a hook that might lock on something else and + * trigger a deadlock. + */ +template<typename Callable> +nsresult +MaybeUpdatePaddingFile(nsIFile* aBaseDir, + mozIStorageConnection* aConn, + const int64_t aIncreaseSize, + const int64_t aDecreaseSize, + Callable aCommitHook); + +nsresult +RestorePaddingFile(nsIFile* aBaseDir, mozIStorageConnection* aConn); + +nsresult +WipePaddingFile(const QuotaInfo& aQuotaInfo, nsIFile* aBaseDir); } // namespace cache } // namespace dom } // namespace mozilla #endif // mozilla_dom_cache_QuotaClient_h
--- a/dom/cache/TypeUtils.cpp +++ b/dom/cache/TypeUtils.cpp @@ -196,16 +196,19 @@ TypeUtils::ToCacheResponseWithoutBody(Ca ToHeadersEntryList(aOut.headers(), headers); aOut.headersGuard() = headers->Guard(); aOut.channelInfo() = aIn.GetChannelInfo().AsIPCChannelInfo(); if (aIn.GetPrincipalInfo()) { aOut.principalInfo() = *aIn.GetPrincipalInfo(); } else { aOut.principalInfo() = void_t(); } + + aOut.paddingInfo() = aIn.GetPaddingInfo(); + aOut.paddingSize() = aIn.GetPaddingSize(); } void TypeUtils::ToCacheResponse(JSContext* aCx, CacheResponse& aOut, Response& aIn, nsTArray<UniquePtr<AutoIPCStream>>& aStreamCleanupList, ErrorResult& aRv) { if (aIn.BodyUsed()) { @@ -299,16 +302,18 @@ TypeUtils::ToResponse(const CacheRespons case ResponseType::Opaqueredirect: ir = ir->OpaqueRedirectResponse(); break; default: MOZ_CRASH("Unexpected ResponseType!"); } MOZ_DIAGNOSTIC_ASSERT(ir); + ir->SetPaddingSize(aIn.paddingSize()); + RefPtr<Response> ref = new Response(GetGlobalObject(), ir, nullptr); return ref.forget(); } already_AddRefed<InternalRequest> TypeUtils::ToInternalRequest(const CacheRequest& aIn) { nsAutoCString url(aIn.urlWithoutQuery()); url.Append(aIn.urlQuery());
--- a/dom/cache/test/mochitest/mochitest.ini +++ b/dom/cache/test/mochitest/mochitest.ini @@ -40,13 +40,14 @@ support-files = [test_cache_put_reorder.html] [test_cache_https.html] [test_cache_redirect.html] [test_cache_restart.html] [test_cache_shrink.html] [test_cache_orphaned_cache.html] [test_cache_orphaned_body.html] scheme=https +[test_cache_padding.html] [test_cache_untrusted.html] [test_cache_updateUsage.html] [test_chrome_constructor.html] [test_cache_worker_gc.html] scheme=https
new file mode 100644 --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_padding.html @@ -0,0 +1,197 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Test Cache generate padding size for opaque repsonse</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="large_url_list.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +function setupTestIframe() { + return new Promise(function(resolve) { + var iframe = document.createElement("iframe"); + iframe.src = "empty.html"; + iframe.onload = function() { + window.caches = iframe.contentWindow.caches; + resolve(); + }; + document.body.appendChild(iframe); + }); +} + +function clearStorage() { + return new Promise(function(resolve, reject) { + var qms = SpecialPowers.Services.qms; + var principal = SpecialPowers.wrap(document).nodePrincipal; + var request = qms.clearStoragesForPrincipal(principal); + var cb = SpecialPowers.wrapCallback(resolve); + request.callback = cb; + }); +} + +function resetStorage() { + return new Promise(function(resolve, reject) { + var qms = SpecialPowers.Services.qms; + var request = qms.reset(); + var cb = SpecialPowers.wrapCallback(resolve); + request.callback = cb; + }); +} + +function getStorageUsage(fromMemory) { + return new Promise(function(resolve, reject) { + var qms = SpecialPowers.Services.qms; + var principal = SpecialPowers.wrap(document).nodePrincipal; + var cb = SpecialPowers.wrapCallback(function(request) { + var result = request.result; + resolve(result.usage); + }); + + // Actually, the flag is used to distingulish getting group usage and origin + // usage, but we utilize this to get usage from in-memory and the disk. + // Default value for "fromMemory" is false. + qms.getUsageForPrincipal(principal, cb, !!fromMemory); + }); +} + +async function verifyUsage() { + // Although it returns group usage when passing true, it calculate the usage + // from tracking usage object (in-memory object) in QuotaManager. + let memoryUsage = await getStorageUsage(/* fromMemory */ true); + // This will returns the origin usage by re-calculating usage from directory. + let diskUsage = await getStorageUsage(/* fromMemory */ false); + + is(memoryUsage, diskUsage, + "In-memory usage and disk usage should be the same."); + return memoryUsage; +} + +async function waitForIOToComplete(cache, request) { + info("Wait for IO complete."); + // The following lines ensure we've deleted orphaned body. + // First, wait for cache operation delete the orphaned body. + await cache.match(request); + + // Finally, wait for -wal file finish its job. + return await resetStorage(); +} + +function fetchOpaqueResponse(url) { + return fetch(url, { mode: "no-cors" }); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({ + "set": [["dom.caches.enabled", true], + ["dom.caches.testing.enabled", true], + ["dom.quotaManager.testing", true]] +}, async function() { + + // This test is mainly to verify we only generate different padding size for + // the opaque response which is comming from netwrok. + // Besides, this test utilizes verifyUsage() to ensure Cache Acions does + // update thier usage/padding size to the QM, does record padding size to + // the directory padding file and does do above two things synchronously. + // So that, opaque response's size is bigger than the normal response's size + // and we always have the same usage bewteen from in-memory and from + // the file-system. + // Note: For the cloned and cached opaque response, the padding size shouldn't + // be changed. Thus, it makes the attacker harder to get the padding size. + + const name = 'cachePadding'; + const other_name = 'cachePaddingOther'; + const cors_base = 'https://example.com/test/dom/cache/test/mochitest/'; + const url = 'test_cache_add.js'; + + await setupTestIframe(); + + info("Stage 1: Clean storage."); + await clearStorage(); + + let cache = await caches.open(name); + await waitForIOToComplete(cache, url); + let usage1 = await verifyUsage(); + + info("Stage 2: Verify opaque responses have padding."); + cache = await caches.open(name); + await cache.add(url); + await waitForIOToComplete(cache, url); + let usage2 = await verifyUsage(); + let sizeForNormalResponse = usage2 - usage1; + + let opaqueResponse = await fetchOpaqueResponse(cors_base + url); + cache = await caches.open(name); + await cache.put(cors_base + url, opaqueResponse.clone()); + await waitForIOToComplete(cache, url); + let usage3 = await verifyUsage(); + let sizeForOpaqueResponse = usage3 - usage2; + ok(sizeForOpaqueResponse > sizeForNormalResponse, + "The opaque response should have larger size than the normal response."); + + info("Stage 3: Verify the cloned response has the same size."); + cache = await caches.open(name); + await cache.put(cors_base + url, opaqueResponse.clone()); + await waitForIOToComplete(cache, url); + let usage4 = await verifyUsage(); + // Since we put the same request and response again, the size should be the + // same (DOM Cache removes the previous cached request and response) + ok(usage4 == usage3, + "We won't generate different padding for cloned response"); + + info("Stage 4: Verify the cached response has the same size."); + cache = await caches.open(name); + opaqueResponse = await cache.match(cors_base + url); + ok(opaqueResponse); + + await cache.put(cors_base + url, opaqueResponse); + await waitForIOToComplete(cache, url); + let usage5 = await verifyUsage(); + ok(usage5 == usage3, + "We won't generate different padding for cached response"); + + info("Stage 5: Verify padding size may changes in different fetch()s."); + let paddingSizeChange = false; + // Since we randomly generate padding size and rounding the overall size up, + // we will probably have the same size. So, fetch it multiple times. + for (let i = 0; i < 10; i++) { + opaqueResponse = await fetchOpaqueResponse(cors_base + url); + cache = await caches.open(name); + await cache.put(cors_base + url, opaqueResponse); + await waitForIOToComplete(cache, url); + let usage6 = await verifyUsage(); + if (usage6 != usage5) { + paddingSizeChange = true; + break; + } + } + ok(paddingSizeChange, + "We should generate different padding size for fetching response"); + + info("Stage 6: Verify the padding is removed once on caches.delete() and " + + "cache.delete()."); + // Add an opauqe response on other cache storage and then delete that storage. + cache = await caches.open(other_name); + opaqueResponse = await fetchOpaqueResponse(cors_base + url); + await cache.put(cors_base + url, opaqueResponse); + await caches.delete(other_name); + await caches.has(other_name); + // Force remove orphaned cached in the next action + await resetStorage(); + + // Delete the opauqe repsonse on current cache storage. + cache = await caches.open(name); + await cache.delete(cors_base + url); + await waitForIOToComplete(cache, url); + let usage7 = await verifyUsage(); + ok(usage7 == usage2, + "The opaque response should be removed by caches.delete() and " + + "cache.delete()"); + + await SimpleTest.finish(); +}); +</script> +</body> +</html>
--- a/dom/fetch/FetchDriver.cpp +++ b/dom/fetch/FetchDriver.cpp @@ -428,19 +428,22 @@ FetchDriver::BeginAndGetFilteredResponse } else { switch (mRequest->GetResponseTainting()) { case LoadTainting::Basic: filteredResponse = aResponse->BasicResponse(); break; case LoadTainting::CORS: filteredResponse = aResponse->CORSResponse(); break; - case LoadTainting::Opaque: + case LoadTainting::Opaque: { filteredResponse = aResponse->OpaqueResponse(); + nsresult rv = filteredResponse->GeneratePaddingInfo(); + if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } break; + } default: MOZ_CRASH("Unexpected case"); } } MOZ_ASSERT(filteredResponse); MOZ_ASSERT(mObserver); if (!ShouldCheckSRI(mRequest, filteredResponse)) { @@ -609,16 +612,22 @@ FetchDriver::OnStartRequest(nsIRequest* // the outer fetch(). In gecko, however, we serialize the Response through // the channel and must regenerate the tainting from the channel in the // interception case. mRequest->MaybeIncreaseResponseTainting(loadInfo->GetTainting()); // Resolves fetch() promise which may trigger code running in a worker. Make // sure the Response is fully initialized before calling this. mResponse = BeginAndGetFilteredResponse(response, foundOpaqueRedirect); + if (NS_WARN_IF(!mResponse)) { + // Fail to generate a paddingInfo for opaque response. + MOZ_DIAGNOSTIC_ASSERT(mResponse->Type() == ResponseType::Opaque); + FailWithNetworkError(); + return rv; + } // From "Main Fetch" step 19: SRI-part1. if (ShouldCheckSRI(mRequest, mResponse) && mSRIMetadata.IsEmpty()) { nsIConsoleReportCollector* reporter = nullptr; if (mObserver) { reporter = mObserver->GetReporter(); }
--- a/dom/fetch/InternalResponse.cpp +++ b/dom/fetch/InternalResponse.cpp @@ -6,28 +6,38 @@ #include "InternalResponse.h" #include "mozilla/Assertions.h" #include "mozilla/dom/InternalHeaders.h" #include "mozilla/dom/cache/CacheTypes.h" #include "mozilla/ipc/PBackgroundSharedTypes.h" #include "mozilla/ipc/IPCStreamUtils.h" +#include "nsIRandomGenerator.h" #include "nsIURI.h" #include "nsStreamUtils.h" namespace mozilla { namespace dom { +namespace { + +// Const variable for generate padding size +// XXX This will be tweaked to something more meaningful in Bug 1383656. +const uint32_t kMaxRandomNumber = 102400; + +} // namespace + InternalResponse::InternalResponse(uint16_t aStatus, const nsACString& aStatusText) : mType(ResponseType::Default) , mStatus(aStatus) , mStatusText(aStatusText) , mHeaders(new InternalHeaders(HeadersGuardEnum::Response)) , mBodySize(UNKNOWN_BODY_SIZE) + , mPaddingSize(UNKNOWN_PADDING_SIZE) { } already_AddRefed<InternalResponse> InternalResponse::FromIPC(const IPCInternalResponse& aIPCResponse) { if (aIPCResponse.type() == ResponseType::Error) { return InternalResponse::NetworkError(); @@ -137,16 +147,21 @@ InternalResponse::ToIPC(IPCInternalRespo } already_AddRefed<InternalResponse> InternalResponse::Clone(CloneType aCloneType) { RefPtr<InternalResponse> clone = CreateIncompleteCopy(); clone->mHeaders = new InternalHeaders(*mHeaders); + + // Make sure the clone response will have the same padding size. + clone->mPaddingInfo = mPaddingInfo; + clone->mPaddingSize = mPaddingSize; + if (mWrappedResponse) { clone->mWrappedResponse = mWrappedResponse->Clone(aCloneType); MOZ_ASSERT(!mBody); return clone.forget(); } if (!mBody || aCloneType == eDontCloneInputStream) { return clone.forget(); @@ -184,16 +199,89 @@ InternalResponse::CORSResponse() MOZ_ASSERT(!mWrappedResponse, "Can't CORSResponse a already wrapped response"); RefPtr<InternalResponse> cors = CreateIncompleteCopy(); cors->mType = ResponseType::Cors; cors->mHeaders = InternalHeaders::CORSHeaders(Headers()); cors->mWrappedResponse = this; return cors.forget(); } +uint32_t +InternalResponse::GetPaddingInfo() +{ + // If it's an opaque response, the paddingInfo should be generated only when + // paddingSize is unknown size. + // If it's not, the paddingInfo should be nothing and the paddingSize should + // be unknown size. + MOZ_DIAGNOSTIC_ASSERT((mType == ResponseType::Opaque && + mPaddingSize == UNKNOWN_PADDING_SIZE && + mPaddingInfo.isSome()) || + (mType == ResponseType::Opaque && + mPaddingSize != UNKNOWN_PADDING_SIZE && + mPaddingInfo.isNothing()) || + (mType != ResponseType::Opaque && + mPaddingSize == UNKNOWN_PADDING_SIZE && + mPaddingInfo.isNothing())); + return mPaddingInfo.isSome() ? mPaddingInfo.ref() : 0; +} + +nsresult +InternalResponse::GeneratePaddingInfo() +{ + MOZ_DIAGNOSTIC_ASSERT(mType == ResponseType::Opaque); + MOZ_DIAGNOSTIC_ASSERT(mPaddingSize == UNKNOWN_PADDING_SIZE); + + // Utilize random generator to generator a random number + nsresult rv; + uint32_t randomNumber = 0; + nsCOMPtr<nsIRandomGenerator> randomGenerator = + do_GetService("@mozilla.org/security/random-generator;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + MOZ_DIAGNOSTIC_ASSERT(randomGenerator); + + uint8_t* buffer; + rv = randomGenerator->GenerateRandomBytes(sizeof(randomNumber), &buffer); + if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + + memcpy(&randomNumber, buffer, sizeof(randomNumber)); + free(buffer); + + mPaddingInfo.emplace(randomNumber % kMaxRandomNumber); + + return rv; +} + +int64_t +InternalResponse::GetPaddingSize() +{ + // We initialize padding size to an unknown size (-1). After cached, we only + // pad opaque response. Opaque response's padding size might be unknown before + // cached. + MOZ_DIAGNOSTIC_ASSERT(mType == ResponseType::Opaque || + mPaddingSize == UNKNOWN_PADDING_SIZE); + MOZ_DIAGNOSTIC_ASSERT(mPaddingSize == UNKNOWN_PADDING_SIZE || + mPaddingSize >= 0); + + return mPaddingSize; +} + +void +InternalResponse::SetPaddingSize(int64_t aPaddingSize) +{ + // We should only pad the opaque response. + MOZ_DIAGNOSTIC_ASSERT((mType == ResponseType::Opaque) != + (aPaddingSize == + InternalResponse::UNKNOWN_PADDING_SIZE)); + MOZ_DIAGNOSTIC_ASSERT(aPaddingSize == UNKNOWN_PADDING_SIZE || + aPaddingSize >= 0); + + mPaddingSize = aPaddingSize; +} + void InternalResponse::SetPrincipalInfo(UniquePtr<mozilla::ipc::PrincipalInfo> aPrincipalInfo) { mPrincipalInfo = Move(aPrincipalInfo); } LoadTainting InternalResponse::GetTainting() const
--- a/dom/fetch/InternalResponse.h +++ b/dom/fetch/InternalResponse.h @@ -228,16 +228,28 @@ public: MOZ_ASSERT(aBodySize == UNKNOWN_BODY_SIZE || aBodySize >= 0); // If body is not given, then size must be unknown. MOZ_ASSERT_IF(!aBody, aBodySize == UNKNOWN_BODY_SIZE); mBody = aBody; mBodySize = aBodySize; } + uint32_t + GetPaddingInfo(); + + nsresult + GeneratePaddingInfo(); + + int64_t + GetPaddingSize(); + + void + SetPaddingSize(int64_t aPaddingSize); + void InitChannelInfo(nsIChannel* aChannel) { mChannelInfo.InitFromChannel(aChannel); } void InitChannelInfo(const mozilla::ipc::IPCChannelInfo& aChannelInfo) @@ -296,18 +308,23 @@ private: // Unless stated otherwise, it is the empty list. The current url is the last // element in mURLlist nsTArray<nsCString> mURLList; const uint16_t mStatus; const nsCString mStatusText; RefPtr<InternalHeaders> mHeaders; nsCOMPtr<nsIInputStream> mBody; int64_t mBodySize; + // It's used to passed to the CacheResponse to generate padding size. Once, we + // generate the padding size for resposne, we don't need it anymore. + Maybe<uint32_t> mPaddingInfo; + int64_t mPaddingSize; public: static const int64_t UNKNOWN_BODY_SIZE = -1; + static const int64_t UNKNOWN_PADDING_SIZE = -1; private: ChannelInfo mChannelInfo; UniquePtr<mozilla::ipc::PrincipalInfo> mPrincipalInfo; // For filtered responses. // Cache, and SW interception should always serialize/access the underlying // unfiltered headers and when deserializing, create an InternalResponse // with the unfiltered headers followed by wrapping it.
--- a/dom/quota/ActorsParent.cpp +++ b/dom/quota/ActorsParent.cpp @@ -120,17 +120,17 @@ namespace { /******************************************************************************* * Constants ******************************************************************************/ const uint32_t kSQLitePageSizeOverride = 512; // Major storage version. Bump for backwards-incompatible changes. -const uint32_t kMajorStorageVersion = 2; +const uint32_t kMajorStorageVersion = 3; // Minor storage version. Bump for backwards-compatible changes. const uint32_t kMinorStorageVersion = 0; // The storage version we store in the SQLite database is a (signed) 32-bit // integer. The major version is left-shifted 16 bits so the max value is // 0xFFFF. The minor version occupies the lower 16 bits and its max is 0xFFFF. static_assert(kMajorStorageVersion <= 0xFFFF, @@ -1809,16 +1809,39 @@ private: nsresult MaybeStripObsoleteOriginAttributes(const OriginProps& aOriginProps, bool* aStripped); nsresult ProcessOriginDirectory(const OriginProps& aOriginProps) override; }; +// XXXtt: The following class is duplicated from +// UpgradeStorageFrom1_0To2_0Helper and it should be extracted out in +// bug 1395102. +class UpgradeStorageFrom2_0To3_0Helper final + : public StorageDirectoryHelper +{ +public: + UpgradeStorageFrom2_0To3_0Helper(nsIFile* aDirectory, + bool aPersistent) + : StorageDirectoryHelper(aDirectory, aPersistent) + { } + + nsresult + DoUpgrade(); + +private: + nsresult + MaybeUpgradeClients(const OriginProps& aOriginProps); + + nsresult + ProcessOriginDirectory(const OriginProps& aOriginProps) override; +}; + class RestoreDirectoryMetadata2Helper final : public StorageDirectoryHelper { public: RestoreDirectoryMetadata2Helper(nsIFile* aDirectory, bool aPersistent) : StorageDirectoryHelper(aDirectory, aPersistent) { } @@ -2924,16 +2947,65 @@ QuotaObject::Release() bool QuotaObject::MaybeUpdateSize(int64_t aSize, bool aTruncate) { QuotaManager* quotaManager = QuotaManager::Get(); MOZ_ASSERT(quotaManager); MutexAutoLock lock(quotaManager->mQuotaMutex); + return LockedMaybeUpdateSize(aSize, aTruncate); +} + +bool +QuotaObject::IncreaseSize(int64_t aDelta) +{ + MOZ_ASSERT(aDelta >= 0); + + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + MutexAutoLock lock(quotaManager->mQuotaMutex); + + AssertNoOverflow(mSize, aDelta); + int64_t size = mSize + aDelta; + + return LockedMaybeUpdateSize(size, /* aTruncate */ false); +} + +void +QuotaObject::DisableQuotaCheck() +{ + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + MutexAutoLock lock(quotaManager->mQuotaMutex); + + mQuotaCheckDisabled = true; +} + +void +QuotaObject::EnableQuotaCheck() +{ + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + MutexAutoLock lock(quotaManager->mQuotaMutex); + + mQuotaCheckDisabled = false; +} + +bool +QuotaObject::LockedMaybeUpdateSize(int64_t aSize, bool aTruncate) +{ + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + quotaManager->mQuotaMutex.AssertCurrentThreadOwns(); + if (mQuotaCheckDisabled) { return true; } if (mSize == aSize) { return true; } @@ -3122,38 +3194,16 @@ QuotaObject::MaybeUpdateSize(int64_t aSi } quotaManager->mTemporaryStorageUsage = newTemporaryStorageUsage; mSize = aSize; return true; } -void -QuotaObject::DisableQuotaCheck() -{ - QuotaManager* quotaManager = QuotaManager::Get(); - MOZ_ASSERT(quotaManager); - - MutexAutoLock lock(quotaManager->mQuotaMutex); - - mQuotaCheckDisabled = true; -} - -void -QuotaObject::EnableQuotaCheck() -{ - QuotaManager* quotaManager = QuotaManager::Get(); - MOZ_ASSERT(quotaManager); - - MutexAutoLock lock(quotaManager->mQuotaMutex); - - mQuotaCheckDisabled = false; -} - /******************************************************************************* * Quota manager ******************************************************************************/ QuotaManager::QuotaManager() : mQuotaMutex("QuotaManager.mQuotaMutex"), mTemporaryStorageLimit(0), mTemporaryStorageUsage(0), @@ -3769,20 +3819,25 @@ QuotaManager::RemoveQuota() NS_ASSERTION(mTemporaryStorageUsage == 0, "Should be zero!"); } already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject(PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin, - nsIFile* aFile) + nsIFile* aFile, + int64_t* aFileSizeOut /* = nullptr */) { NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + if (aFileSizeOut) { + *aFileSizeOut = 0; + } + if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) { return nullptr; } nsString path; nsresult rv = aFile->GetPath(path); NS_ENSURE_SUCCESS(rv, nullptr); @@ -3844,35 +3899,46 @@ QuotaManager::GetQuotaObject(Persistence originInfo->mQuotaObjects.Put(path, quotaObject); } // Addref the QuotaObject and move the ownership to the result. This must // happen before we unlock! result = quotaObject->LockedAddRef(); } + if (aFileSizeOut) { + *aFileSizeOut = fileSize; + } + // The caller becomes the owner of the QuotaObject, that is, the caller is // is responsible to delete it when the last reference is removed. return result.forget(); } already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject(PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin, - const nsAString& aPath) -{ + const nsAString& aPath, + int64_t* aFileSizeOut /* = nullptr */) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + if (aFileSizeOut) { + *aFileSizeOut = 0; + } + nsresult rv; nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, nullptr); rv = file->InitWithPath(aPath); NS_ENSURE_SUCCESS(rv, nullptr); - return GetQuotaObject(aPersistenceType, aGroup, aOrigin, file); + return GetQuotaObject(aPersistenceType, aGroup, aOrigin, file, aFileSizeOut); } Nullable<bool> QuotaManager::OriginPersisted(const nsACString& aGroup, const nsACString& aOrigin) { AssertIsOnIOThread(); @@ -4744,16 +4810,79 @@ QuotaManager::UpgradeStorageFrom1_0To2_0 rv = aConnection->SetSchemaVersion(MakeStorageVersion(2, 0)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } +nsresult +QuotaManager::UpgradeStorageFrom2_0To3_0(mozIStorageConnection* aConnection) +{ + AssertIsOnIOThread(); + MOZ_ASSERT(aConnection); + + // The upgrade is mainly to create a directory padding file in DOM Cache + // directory to record the overall padding size of an origin. + + nsresult rv; + + for (const PersistenceType persistenceType : kAllPersistenceTypes) { + nsCOMPtr<nsIFile> directory = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = directory->InitWithPath(GetStoragePath(persistenceType)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool exists; + rv = directory->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!exists) { + continue; + } + + bool persistent = persistenceType == PERSISTENCE_TYPE_PERSISTENT; + RefPtr<UpgradeStorageFrom2_0To3_0Helper> helper = + new UpgradeStorageFrom2_0To3_0Helper(directory, persistent); + + rv = helper->DoUpgrade(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + +#ifdef DEBUG + { + int32_t storageVersion; + rv = aConnection->GetSchemaVersion(&storageVersion); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT(storageVersion == MakeStorageVersion(2, 0)); + } +#endif + + rv = aConnection->SetSchemaVersion(MakeStorageVersion(3, 0)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + #ifdef DEBUG void QuotaManager::AssertStorageIsInitialized() const { AssertIsOnIOThread(); MOZ_ASSERT(mStorageInitialized); } @@ -4892,24 +5021,26 @@ QuotaManager::EnsureStorageIsInitialized if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(NS_SUCCEEDED(connection->GetSchemaVersion(&storageVersion))); MOZ_ASSERT(storageVersion == kStorageVersion); } else { // This logic needs to change next time we change the storage! - static_assert(kStorageVersion == int32_t((2 << 16) + 0), + static_assert(kStorageVersion == int32_t((3 << 16) + 0), "Upgrade function needed due to storage version increase."); while (storageVersion != kStorageVersion) { if (storageVersion == 0) { rv = UpgradeStorageFrom0_0To1_0(connection); } else if (storageVersion == MakeStorageVersion(1, 0)) { rv = UpgradeStorageFrom1_0To2_0(connection); + } else if (storageVersion == MakeStorageVersion(2, 0)) { + rv = UpgradeStorageFrom2_0To3_0(connection); } else { NS_WARNING("Unable to initialize storage, no upgrade path is " "available!"); return NS_ERROR_FAILURE; } if (NS_WARN_IF(NS_FAILED(rv))) { return rv; @@ -9263,16 +9394,226 @@ UpgradeStorageFrom1_0To2_0Helper::Proces return rv; } } return NS_OK; } nsresult +UpgradeStorageFrom2_0To3_0Helper::DoUpgrade() +{ + AssertIsOnIOThread(); + + DebugOnly<bool> exists; + MOZ_ASSERT(NS_SUCCEEDED(mDirectory->Exists(&exists))); + MOZ_ASSERT(exists); + + nsCOMPtr<nsISimpleEnumerator> entries; + nsresult rv = mDirectory->GetDirectoryEntries(getter_AddRefs(entries)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool hasMore; + while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { + nsCOMPtr<nsISupports> entry; + rv = entries->GetNext(getter_AddRefs(entry)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIFile> originDir = do_QueryInterface(entry); + MOZ_ASSERT(originDir); + + bool isDirectory; + rv = originDir->IsDirectory(&isDirectory); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!isDirectory) { + nsString leafName; + rv = originDir->GetLeafName(leafName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Unknown files during upgrade are allowed. Just warn if we find them. + if (!IsOSMetadata(leafName)) { + UNKNOWN_FILE_WARNING(leafName); + } + continue; + } + + OriginProps originProps; + rv = originProps.Init(originDir); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Only update DOM Cache directory for adding padding file. + rv = MaybeUpgradeClients(originProps); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + int64_t timestamp; + nsCString group; + nsCString origin; + Nullable<bool> isApp; + nsresult rv = GetDirectoryMetadata(originDir, + timestamp, + group, + origin, + isApp); + if (NS_FAILED(rv) || isApp.IsNull()) { + originProps.mNeedsRestore = true; + } + + nsCString suffix; + rv = GetDirectoryMetadata2(originDir, + timestamp, + suffix, + group, + origin, + isApp.SetValue()); + if (NS_FAILED(rv)) { + originProps.mTimestamp = GetLastModifiedTime(originDir, mPersistent); + originProps.mNeedsRestore2 = true; + } else { + originProps.mTimestamp = timestamp; + } + + mOriginProps.AppendElement(Move(originProps)); + } + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (mOriginProps.IsEmpty()) { + return NS_OK; + } + + rv = ProcessOriginDirectories(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult +UpgradeStorageFrom2_0To3_0Helper::MaybeUpgradeClients( + const OriginProps& aOriginProps) +{ + AssertIsOnIOThread(); + MOZ_ASSERT(aOriginProps.mDirectory); + + QuotaManager* quotaManager = QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + nsCOMPtr<nsISimpleEnumerator> entries; + nsresult rv = + aOriginProps.mDirectory->GetDirectoryEntries(getter_AddRefs(entries)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool hasMore; + while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { + nsCOMPtr<nsISupports> entry; + rv = entries->GetNext(getter_AddRefs(entry)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsIFile> file = do_QueryInterface(entry); + if (NS_WARN_IF(!file)) { + return rv; + } + + bool isDirectory; + rv = file->IsDirectory(&isDirectory); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsString leafName; + rv = file->GetLeafName(leafName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!isDirectory) { + // Unknown files during upgrade are allowed. Just warn if we find them. + if (!IsOriginMetadata(leafName) && + !IsTempMetadata(leafName)) { + UNKNOWN_FILE_WARNING(leafName); + } + continue; + } + + Client::Type clientType; + rv = Client::TypeFromText(leafName, clientType); + if (NS_FAILED(rv)) { + UNKNOWN_FILE_WARNING(leafName); + continue; + } + + Client* client = quotaManager->GetClient(clientType); + MOZ_ASSERT(client); + + rv = client->UpgradeStorageFrom2_0To3_0(file); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult +UpgradeStorageFrom2_0To3_0Helper::ProcessOriginDirectory( + const OriginProps& aOriginProps) +{ + AssertIsOnIOThread(); + + nsresult rv; + + if (aOriginProps.mNeedsRestore) { + rv = CreateDirectoryMetadata(aOriginProps.mDirectory, + aOriginProps.mTimestamp, + aOriginProps.mSuffix, + aOriginProps.mGroup, + aOriginProps.mOrigin); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + if (aOriginProps.mNeedsRestore2) { + rv = CreateDirectoryMetadata2(aOriginProps.mDirectory, + aOriginProps.mTimestamp, + /* aPersisted */ false, + aOriginProps.mSuffix, + aOriginProps.mGroup, + aOriginProps.mOrigin); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + return NS_OK; +} + +nsresult RestoreDirectoryMetadata2Helper::RestoreMetadata2File() { AssertIsOnIOThread(); nsresult rv; OriginProps originProps; rv = originProps.Init(mDirectory);
--- a/dom/quota/Client.h +++ b/dom/quota/Client.h @@ -86,24 +86,30 @@ public: } else { return NS_ERROR_FAILURE; } return NS_OK; } - // Methods which are called on the IO thred. + // Methods which are called on the IO thread. virtual nsresult UpgradeStorageFrom1_0To2_0(nsIFile* aDirectory) { return NS_OK; } virtual nsresult + UpgradeStorageFrom2_0To3_0(nsIFile* aDirectory) + { + return NS_OK; + } + + virtual nsresult InitOrigin(PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin, const AtomicBool& aCanceled, UsageInfo* aUsageInfo) = 0; virtual nsresult GetUsageForOrigin(PersistenceType aPersistenceType, @@ -114,17 +120,17 @@ public: virtual void OnOriginClearCompleted(PersistenceType aPersistenceType, const nsACString& aOrigin) = 0; virtual void ReleaseIOThreadObjects() = 0; - // Methods which are called on the background thred. + // Methods which are called on the background thread. virtual void AbortOperations(const nsACString& aOrigin) = 0; virtual void AbortOperationsForProcess(ContentParentId aContentParentId) = 0; virtual void StartIdleMaintenance() = 0;
--- a/dom/quota/QuotaManager.h +++ b/dom/quota/QuotaManager.h @@ -172,23 +172,25 @@ public: MutexAutoLock lock(mQuotaMutex); LockedRemoveQuotaForOrigin(aPersistenceType, aGroup, aOrigin); } already_AddRefed<QuotaObject> GetQuotaObject(PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin, - nsIFile* aFile); + nsIFile* aFile, + int64_t* aFileSizeOut = nullptr); already_AddRefed<QuotaObject> GetQuotaObject(PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin, - const nsAString& aPath); + const nsAString& aPath, + int64_t* aFileSizeOut = nullptr); Nullable<bool> OriginPersisted(const nsACString& aGroup, const nsACString& aOrigin); void PersistOrigin(const nsACString& aGroup, const nsACString& aOrigin); @@ -470,16 +472,19 @@ private: nsresult UpgradeStorageFrom0_0To1_0(mozIStorageConnection* aConnection); nsresult UpgradeStorageFrom1_0To2_0(mozIStorageConnection* aConnection); nsresult + UpgradeStorageFrom2_0To3_0(mozIStorageConnection* aConnection); + + nsresult InitializeRepository(PersistenceType aPersistenceType); nsresult InitializeOrigin(PersistenceType aPersistenceType, const nsACString& aGroup, const nsACString& aOrigin, int64_t aAccessTime, bool aPersisted,
--- a/dom/quota/QuotaObject.h +++ b/dom/quota/QuotaObject.h @@ -36,16 +36,19 @@ public: Path() const { return mPath; } bool MaybeUpdateSize(int64_t aSize, bool aTruncate); + bool + IncreaseSize(int64_t aDelta); + void DisableQuotaCheck(); void EnableQuotaCheck(); private: QuotaObject(OriginInfo* aOriginInfo, const nsAString& aPath, int64_t aSize) @@ -68,16 +71,19 @@ private: AssertCurrentThreadOwnsQuotaMutex(); ++mRefCnt; RefPtr<QuotaObject> result = dont_AddRef(this); return result.forget(); } + bool + LockedMaybeUpdateSize(int64_t aSize, bool aTruncate); + mozilla::ThreadSafeAutoRefCnt mRefCnt; OriginInfo* mOriginInfo; nsString mPath; int64_t mSize; bool mQuotaCheckDisabled; };
new file mode 100644 --- /dev/null +++ b/dom/quota/test/unit/test_version3_0upgrade.js @@ -0,0 +1,76 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +var testGenerator = testSteps(); + +function* testSteps() +{ + const origins = [ + "storage/default/chrome/", + "storage/default/http+++www.mozilla.org/" + ]; + const paddingFilePath = "cache/.padding"; + + info("Clearing"); + + clear(continueToNextStepSync); + yield undefined; + + // The profile contains two cache storages: + // - storage/default/chrome/cache, + // - storage/default/http+++www.mozilla.org/cache + // The file create_cache.js in the package was run locally, specifically it + // was temporarily added to xpcshell.ini and then executed: + // mach xpcshell-test --interactive dom/quota/test/unit/create_cache.js + // Note: it only creates the directory "storage/default/chrome/cache". + // To make it become the profile in the test, two more manual steps are + // needed. + // 1. Remove the folder "storage/temporary". + // 2. Copy the content under the "storage/default/chrome" to + // "storage/default/http+++www.mozilla.org". + installPackage("version3_0upgrade_profile"); + + info("Checking padding file before upgrade (QM version 2.0)"); + + for (let origin of origins) { + let paddingFile = getRelativeFile(origin + paddingFilePath); + + let exists = paddingFile.exists(); + ok(!exists, "Padding file doesn't exist"); + } + + info("Initializing"); + + // Initialize QuotaManager to trigger upgrade the QM to version 3.0 + let request = init(continueToNextStepSync); + yield undefined; + + ok(request.resultCode == NS_OK, "Initialization succeeded"); + + info("Checking padding files after upgrade (QM version 3.0)"); + + for (let origin of origins) { + let paddingFile = getRelativeFile(origin + paddingFilePath); + + let exists = paddingFile.exists(); + ok(exists, "Padding file does exist"); + + info("Reading out contents of padding file"); + + File.createFromNsIFile(paddingFile).then(grabArgAndContinueHandler); + let domFile = yield undefined; + + let fileReader = new FileReader(); + fileReader.onload = continueToNextStepSync; + fileReader.readAsArrayBuffer(domFile); + yield undefined; + + let paddingFileInfo = new Float64Array(fileReader.result); + ok(paddingFileInfo.length == 1, "Padding file does take 64 bytes."); + ok(paddingFileInfo[0] == 0, "Padding size does default to zero."); + } + + finishTest(); +}
new file mode 100644 index 0000000000000000000000000000000000000000..8c16d698ee28c2ab1cf411d0645dce57d093b80e GIT binary patch literal 5261 zc$^FHW@h1H0D-BEYTgV;fP+DXp|~W!C^0=%KQx4sf%)g!^^pbZ)<>3Ba5FHnykKTv z022W?O%OmbAtf~}u{5UyVG=tjCaGgFDWjyMKwDe8yu4g5H@_+~Cnr%azbGAH0S74- zh-0xJIin~)H<h4)BH*wB8MvIu>!BEsCkw<1xDC|HO)W`GNi0dk7pln#y%`pd7Cbn0 z;J|@ta{{N#pWStY3C+F@KCQPzfjliBRwlwe-7+JhtZZPrG0C{=Q*rRb2s}ZiOMILp zCnjeg1tce-fP8Z9nr<8m$Pz{dD^jfilf`<)g*llesrdZ+_RiV7$1V&9J{I2Yom{u> z$f6amlz5#kq}|@seZfni(dCv@$VA_1Cfd3D+NWRd=yjDU&t=a&s=2q|iR6xgM}ifi zd<V1-^fEv0-%>bfS;LR4FaJ;a<(#juF`oDFM$xV2^ICZ?PAH$Z-{|yn-45Nfv?K08 zT#9K|_vq-IU0tac+qW${E6#WSdL!+7`#wLOv`<5<nE%KrbM389^QX(7yUun0Y5mFl z>+9FwKWTg7uR&rx!-4-U@9#Cm#YA5|XYTyE@;0Z_`R#qb<Gpv6F1}-svUpFhVal$+ zyplD->K0pb+~0*uA5(XW4a??@tPNV3bMpLL<DJ1P-!56bEp@xh<=nm_Q%WBvFU|`o z?`>8I+VIfKJNi>$?f<}?bsF}jeER*u>d}*yzC9YVanCZ&?Uzg9*z|8@Eiqo3_u}a5 zv|09_=awi+UH()kd4JCJ?(grj_io<Nv(M|wYnHy7UvH<~<~=l#w|iz$Kg(&}tt!&T zE+2C^IM?sR{MhhI8k1&=gkD=!bbf`zD}&uno9}*_7x-(_0`c<gTkmNXR_gKHE?Tqf zF?(8w?6VsST21!<zH0k;<(v)4B^Mq(cv3U1>iyHNkG^dy`{%Z8(xr&QuRlLttugso zfoN^w*4@g||J$lw+g|zl=-INee^s--ipO4U(l4GDsmqgUBm6`EdGxa@obi{dJemEJ zXaBWXDQ&Skm5=SI@$*WjKQ9t8H{5yg{ovZ}OK!Y7AJ5Ek6<wIhx;E#+({Go;zpef8 z>BHCT!mG<Nm&I2t)6(xVU+2fR>+92veseZFHEq`}i&6XUF~{QY%dP1-N3w%=*-75F zEULNxalO~l`;Yr}PqUBWW>)<^>#^RvzX`X?XRWe|T`lX=^|aJU^!1hO-QOZB%B&2J zuHg0IsDEer_}H8&+Pq%BzfAR9{?IX__3lT>vqub^pQl<x6+4TsUcFkc*Ftl3V{&<| zdC0@L%KDcr+pW^nl8ipZs1&{0EqmK`>#f2E1#SPIbeP?G%N`wk`Kzq;oPxMtyytw* zeQKGpE{o@Q;6$~pACyh?=B+TB+#CGq*_TJ{ucQ3^*{=Dhy4^h}b9dh9n67oVRJCf= zeDaP=pL#EPb5&t&taZG{J1e%uDPei3?;SRI##S$sygqeySVnKzd*$1&%iR23UVik^ zlZxb<oTFAQ;~26~wz{HJZ}!!k1e<j^E4N-U_SIjvE^BZ2W0lEmlWNYT33+UbjrNGF zyR_nM_|<j&Jz37v9<_hp|AP@+w&b;`nr3V7=QIU#8#GN8pIvhP|9&ngKQrh;S~&Mf zc^;SZ{~vv;pQ{Mg^ufRD-%9!O|G^v|^Lzi9K@5Yx7617`j00aD|Fs9pUG9Hg4`wWr z-~XQ-#7Ow{@t-`{=$GQQ|FgC2VKz&@US~G_|AGYWc#A_q3xAokuAP>1G3fNg_y4y} zo!>eOY|4SNrRA4C+anc?JAT%&<1RR5aTnES1tpH!7JEUdMabCEf^oE994#0}3&ufP zFq)9s)XN1jOOeV;Zc>{3Dul{Qbkp&c#;bv*uitmJ^*NAN1q>p7py|m)sfi`2@nF;R zvWme5DXqh5(9{Wr{znXWTEG9{S{;0Qp=Fo=^COjQ!k6y$IX&7oEo!&doy(#}>eYFf z*W`S>{QKVK<lEOAxc&DuPEYMN>EW89kf88hgn30nAIJLFzg^_|jI1B!rMq2ydM`@; zqGs&H(>C18{T?MuPvSe8ZEw)E_cfoH^R*ePyBEy(!Ya8qxbs8Av(1kty?S<VMqTQ9 zl{^2go?YI!M@gOa{hyAPPtwk0g#P-twj*>IV@LJM)z3L&RpeGJ>!_1|EwkW=cHEPe z?LxKpZltpxaI;{IuuSmpdC0EiJ6Gq#*Zl0uPx&AIPkSo9^y4J`Tm65G?jyo0s6oy9 zo06=lAt)-C7#R2vv4LDe&0f7eGH5qOaB=f}IjQ3r>Tz2Cl&?;pL0UpmLP|<OKtKWz zv?SeW;9Rf69I=1i3Fo*+SNfV5K@r8sB*%=aqbmVr2{62M1ks3YFDoSKF`^x}X#yyw zIU$*b=mL{&syd3PJy=Y|*&Qa`GI12k-eR!~-ZjQ&IIfPd1l*!6jdqMU4M*xG6K4x* zcNp20bwt<#?i`aIR=P-G_195>iRg%dHT8(|8)}aj;kPA??W9_X)(j-w%d#lRBa$6w z*plx>Efg<qAY=u_UNS-PQV|b{K?<)aNVi!9#pVZCY{p(E5e`$#8VO<(BP8STGGNwE qtdRN%kD-{wHgZJe@iAZ&;;fKD9K%djHjo}ZAe_L;!0?<8!~+0-0*k=_
--- a/dom/quota/test/unit/xpcshell.ini +++ b/dom/quota/test/unit/xpcshell.ini @@ -11,23 +11,25 @@ support-files = idbSubdirUpgrade1_profile.zip idbSubdirUpgrade2_profile.zip morgueCleanup_profile.zip obsoleteOriginAttributes_profile.zip originAttributesUpgrade_profile.zip removeAppsUpgrade_profile.zip storagePersistentUpgrade_profile.zip tempMetadataCleanup_profile.zip + version3_0upgrade_profile.zip [test_basics.js] [test_bad_origin_directory.js] skip-if = release_or_beta [test_defaultStorageUpgrade.js] [test_getUsage.js] [test_idbSubdirUpgrade.js] [test_morgueCleanup.js] [test_obsoleteOriginAttributesUpgrade.js] [test_originAttributesUpgrade.js] [test_persist.js] [test_removeAppsUpgrade.js] [test_storagePersistentUpgrade.js] [test_tempMetadataCleanup.js] [test_unknownFiles.js] +[test_version3_0upgrade.js]
--- a/dom/script/ScriptSettings.cpp +++ b/dom/script/ScriptSettings.cpp @@ -684,20 +684,16 @@ AutoEntryScript::AutoEntryScript(JSObjec const char* aReason, bool aIsMainThread) : AutoEntryScript(xpc::NativeGlobal(aObject), aReason, aIsMainThread) { } AutoEntryScript::~AutoEntryScript() { - // GC when we pop a script entry point. This is a useful heuristic that helps - // us out on certain (flawed) benchmarks like sunspider, because it lets us - // avoid GCing during the timing loop. - JS_MaybeGC(cx()); } AutoEntryScript::DocshellEntryMonitor::DocshellEntryMonitor(JSContext* aCx, const char* aReason) : JS::dbg::AutoEntryMonitor(aCx) , mReason(aReason) { }
new file mode 100644 --- /dev/null +++ b/dom/security/test/csp/file_sandbox_13.html @@ -0,0 +1,25 @@ +<!DOCTYPE HTML> +<html> +<head> <meta charset="utf-8"> </head> +<script type="text/javascript"> + function ok(result, desc) { + window.parent.postMessage({ok: result, desc: desc}, "*"); + } + + function doStuff() { + ok(true, "documents sandboxed with allow-scripts should be able to run inline scripts"); + } +</script> +<script src='file_sandbox_fail.js'></script> +<body onLoad='ok(true, "documents sandboxed with allow-scripts should be able to run script from event listeners");doStuff();'> + I am sandboxed but with only inline "allow-scripts" + + <!-- Content-Security-Policy: default-src 'none'; script-src 'unsafe-inline'; sandbox allow-scripts --> + + <!-- these should be stopped by CSP --> + <img src="/tests/dom/security/test/csp/file_CSP.sjs?testid=img13_bad&type=img/png" /> + <img src="http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=img13a_bad&type=img/png"> </img> + <script src='/tests/dom/security/test/csp/file_CSP.sjs?testid=script13_bad&type=text/javascript'></script> + <script src='http://example.org/tests/dom/security/test/csp/file_CSP.sjs?testid=script13a_bad&type=text/javascript'></script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/dom/security/test/csp/file_sandbox_allow_scripts.html @@ -0,0 +1,12 @@ +<!DOCTYPE HTML> +<html> + <head> + <meta charset='utf-8'> + <title>Bug 1396320: Fix CSP sandbox regression for allow-scripts</title> + </head> +<body> +<script type='application/javascript'> + window.parent.postMessage({result: document.domain }, '*'); +</script> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/dom/security/test/csp/file_sandbox_allow_scripts.html^headers^ @@ -0,0 +1,1 @@ +Content-Security-Policy: sandbox allow-scripts;
--- a/dom/security/test/csp/mochitest.ini +++ b/dom/security/test/csp/mochitest.ini @@ -177,16 +177,17 @@ support-files = file_sandbox_5.html file_sandbox_6.html file_sandbox_7.html file_sandbox_8.html file_sandbox_9.html file_sandbox_10.html file_sandbox_11.html file_sandbox_12.html + file_sandbox_13.html file_require_sri_meta.sjs file_require_sri_meta.js file_sendbeacon.html file_upgrade_insecure_docwrite_iframe.sjs file_data-uri_blocked.html file_data-uri_blocked.html^headers^ file_strict_dynamic_js_url.html file_strict_dynamic_script_events.html @@ -318,8 +319,12 @@ skip-if = toolkit == 'android' [test_data_csp_merge.html] [test_report_font_cache.html] [test_data_doc_ignore_meta_csp.html] [test_meta_csp_self.html] [test_uir_top_nav.html] support-files = file_uir_top_nav.html file_uir_top_nav_dummy.html +[test_sandbox_allow_scripts.html] +support-files = + file_sandbox_allow_scripts.html + file_sandbox_allow_scripts.html^headers^
--- a/dom/security/test/csp/test_sandbox.html +++ b/dom/security/test/csp/test_sandbox.html @@ -106,17 +106,17 @@ var testCases = [ results: { img12_bad: -1, script12_bad: -1 }, nrOKmessages: 4 // sends 4 ok message }, { // Test 13: same as Test 5 and Test 11, but: // * using sandbox flag 'allow-scripts' in CSP and not as iframe attribute // * not using allow-same-origin in CSP (so a new NullPrincipal is created). csp: "default-src 'none'; script-src 'unsafe-inline'; sandbox allow-scripts", - file: "file_sandbox_5.html", + file: "file_sandbox_13.html", results: { img13_bad: -1, img13a_bad: -1, script13_bad: -1, script13a_bad: -1 }, nrOKmessages: 2 // sends 2 ok message }, ]; // a postMessage handler that is used by sandboxed iframes without // 'allow-same-origin' to communicate pass/fail back to this main page. // it expects to be called with an object like:
new file mode 100644 --- /dev/null +++ b/dom/security/test/csp/test_sandbox_allow_scripts.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Bug 1396320: Fix CSP sandbox regression for allow-scripts</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<iframe style="width:100%;" id="testframe"></iframe> +<script class="testbody" type="text/javascript"> + +/* Description of the test: + * Load an iframe using a CSP of 'sandbox allow-scripts' and make sure + * the security context of the iframe is sandboxed (cross origin) + */ +SimpleTest.waitForExplicitFinish(); + +window.addEventListener("message", receiveMessage); +function receiveMessage(event) { + is(event.data.result, "", + "document.domain of sandboxed iframe should be opaque"); + window.removeEventListener("message", receiveMessage); + SimpleTest.finish(); +} + +let testframe = document.getElementById("testframe"); +testframe.src = "file_sandbox_allow_scripts.html"; + +</script> +</body> +</html>
--- a/editor/libeditor/HTMLTableEditor.cpp +++ b/editor/libeditor/HTMLTableEditor.cpp @@ -2916,18 +2916,17 @@ HTMLEditor::GetCellFromRange(nsRange* aR nsCOMPtr<nsINode> startContainer = aRange->GetStartContainer(); if (NS_WARN_IF(!startContainer)) { return NS_ERROR_FAILURE; } uint32_t startOffset = aRange->StartOffset(); - nsCOMPtr<nsINode> childNode = - startContainer->GetChildAt(static_cast<int32_t>(startOffset)); + nsCOMPtr<nsINode> childNode = aRange->GetChildAtStartOffset(); // This means selection is probably at a text node (or end of doc?) if (!childNode) { return NS_ERROR_FAILURE; } nsCOMPtr<nsINode> endContainer = aRange->GetEndContainer(); if (NS_WARN_IF(!endContainer)) { return NS_ERROR_FAILURE; @@ -3197,18 +3196,17 @@ HTMLEditor::GetSelectedOrParentTableElem } else { nsCOMPtr<nsINode> anchorNode = selection->GetAnchorNode(); if (NS_WARN_IF(!anchorNode)) { return NS_ERROR_FAILURE; } // Get child of anchor node, if exists if (anchorNode->HasChildNodes()) { - int32_t anchorOffset = selection->AnchorOffset(); - nsINode* selectedNode = anchorNode->GetChildAt(anchorOffset); + nsINode* selectedNode = selection->GetChildAtAnchorOffset(); if (!selectedNode) { selectedNode = anchorNode; // If anchor doesn't have a child, we can't be selecting a table element, // so don't do the following: } else { if (selectedNode->IsHTMLElement(nsGkAtoms::td)) { tableOrCellElement = do_QueryInterface(selectedNode); aTagName = tdName;
--- a/gfx/harfbuzz/NEWS +++ b/gfx/harfbuzz/NEWS @@ -1,8 +1,65 @@ +Overview of changes leading to 1.5.1 +Tuesday, September 5, 2017 +==================================== + +- Fix "unsafe-to-break" in fallback shaping and other corner cases. + All our tests pass with --verify now, meaning unsafe-to-break API + works as expected. +- Add --unicodes to hb-view / hb-shape. +- [indic] Treat Consonant_With_Stacker as consonant. This will need + further tweaking. +- hb_buffer_diff() tweaks. + + +Overview of changes leading to 1.5.0 +Wednesday, August 23, 2017 +==================================== + +- Misc new API, for appending a buffer to another, and for comparing + contents of two buffers for types of differences. + +- New "unsafe-to-break" API. Can be used to speed up reshaping + in line-breaking situations. Essentially, after shaping, it returns + positions in the input string (some of the cluster boundaries) that + are "safe to break" in that if the text is segmented at that position + and two sides reshaped and concatenated, the shaping result is + exactly the same as shaping the text in one piece. + + hb-view and hb-shape and hb-shape now take --verify, which verifies + the above property. + + Some corner cases of the implementation are still not quite working. + Those will be fixed in subsequent releases. + +- New API: + +hb_buffer_append() + +hb_glyph_flags_t +HB_GLYPH_FLAG_UNSAFE_TO_BREAK +HB_GLYPH_FLAG_DEFINED +hb_glyph_info_get_glyph_flags() + +HB_BUFFER_SERIALIZE_FLAG_GLYPH_FLAGS + +hb_buffer_diff_flags_t +HB_BUFFER_DIFF_FLAG_EQUAL +HB_BUFFER_DIFF_FLAG_CONTENT_TYPE_MISMATCH +HB_BUFFER_DIFF_FLAG_LENGTH_MISMATCH +HB_BUFFER_DIFF_FLAG_NOTDEF_PRESENT +HB_BUFFER_DIFF_FLAG_DOTTED_CIRCLE_PRESENT +HB_BUFFER_DIFF_FLAG_CODEPOINT_MISMATCH +HB_BUFFER_DIFF_FLAG_CLUSTER_MISMATCH +HB_BUFFER_DIFF_FLAG_GLYPH_FLAGS_MISMATCH +HB_BUFFER_DIFF_FLAG_POSITION_MISMATCH +hb_buffer_diff + + Overview of changes leading to 1.4.8 Tuesday, August 8, 2017 ==================================== - Major fix to avar table handling. - Rename hb-shape --show-message to --trace. - Build fixes.
--- a/gfx/harfbuzz/README-mozilla +++ b/gfx/harfbuzz/README-mozilla @@ -1,14 +1,14 @@ -gfx/harfbuzz status as of 2017-08-08: +gfx/harfbuzz status as of 2017-09-05: This directory contains the harfbuzz source from the 'master' branch of https://github.com/behdad/harfbuzz. -Current version: 1.4.8 +Current version: 1.5.1 UPDATING: Note that gfx/harfbuzz/src/hb-version.h is not present in the upstream Git repository. It is created at build time by the harfbuzz build system; but as we don't use that build system in mozilla, it is necessary to refresh this file when updating harfbuzz, and check it into the mozilla tree.
--- a/gfx/harfbuzz/configure.ac +++ b/gfx/harfbuzz/configure.ac @@ -1,11 +1,11 @@ AC_PREREQ([2.64]) AC_INIT([HarfBuzz], - [1.4.8], + [1.5.1], [https://github.com/behdad/harfbuzz/issues/new], [harfbuzz], [http://harfbuzz.org/]) AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_SRCDIR([src/harfbuzz.pc.in]) AC_CONFIG_HEADERS([config.h])
--- a/gfx/harfbuzz/src/harfbuzz-icu.pc +++ b/gfx/harfbuzz/src/harfbuzz-icu.pc @@ -1,13 +1,13 @@ prefix=/usr/local exec_prefix=/usr/local libdir=/usr/local/lib includedir=/usr/local/include Name: harfbuzz Description: HarfBuzz text shaping library ICU integration -Version: 1.4.8 +Version: 1.5.1 Requires: harfbuzz Requires.private: icu-uc Libs: -L${libdir} -lharfbuzz-icu Cflags: -I${includedir}/harfbuzz
--- a/gfx/harfbuzz/src/harfbuzz.pc +++ b/gfx/harfbuzz/src/harfbuzz.pc @@ -1,13 +1,13 @@ prefix=/usr/local exec_prefix=/usr/local libdir=/usr/local/lib includedir=/usr/local/include Name: harfbuzz Description: HarfBuzz text shaping library -Version: 1.4.8 +Version: 1.5.1 Libs: -L${libdir} -lharfbuzz Libs.private: Requires.private: glib-2.0 >= 2.19.1 Cflags: -I${includedir}/harfbuzz
--- a/gfx/harfbuzz/src/hb-buffer-private.hh +++ b/gfx/harfbuzz/src/hb-buffer-private.hh @@ -45,23 +45,26 @@ #define HB_BUFFER_MAX_LEN_DEFAULT 0x3FFFFFFF /* Shaping more than a billion chars? Let us know! */ #endif ASSERT_STATIC (sizeof (hb_glyph_info_t) == 20); ASSERT_STATIC (sizeof (hb_glyph_info_t) == sizeof (hb_glyph_position_t)); HB_MARK_AS_FLAG_T (hb_buffer_flags_t); HB_MARK_AS_FLAG_T (hb_buffer_serialize_flags_t); +HB_MARK_AS_FLAG_T (hb_buffer_diff_flags_t); enum hb_buffer_scratch_flags_t { HB_BUFFER_SCRATCH_FLAG_DEFAULT = 0x00000000u, HB_BUFFER_SCRATCH_FLAG_HAS_NON_ASCII = 0x00000001u, HB_BUFFER_SCRATCH_FLAG_HAS_DEFAULT_IGNORABLES = 0x00000002u, HB_BUFFER_SCRATCH_FLAG_HAS_SPACE_FALLBACK = 0x00000004u, HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT = 0x00000008u, + HB_BUFFER_SCRATCH_FLAG_HAS_UNSAFE_TO_BREAK = 0x00000010u, + /* Reserved for complex shapers' internal use. */ HB_BUFFER_SCRATCH_FLAG_COMPLEX0 = 0x01000000u, HB_BUFFER_SCRATCH_FLAG_COMPLEX1 = 0x02000000u, HB_BUFFER_SCRATCH_FLAG_COMPLEX2 = 0x04000000u, HB_BUFFER_SCRATCH_FLAG_COMPLEX3 = 0x08000000u, }; HB_MARK_AS_FLAG_T (hb_buffer_scratch_flags_t); @@ -227,35 +230,41 @@ struct hb_buffer_t { for (unsigned int j = 0; j < len; j++) info[j].mask = mask; } inline void add_masks (hb_mask_t mask) { for (unsigned int j = 0; j < len; j++) info[j].mask |= mask; } - HB_INTERNAL void set_masks (hb_mask_t value, - hb_mask_t mask, - unsigned int cluster_start, - unsigned int cluster_end); + HB_INTERNAL void set_masks (hb_mask_t value, hb_mask_t mask, + unsigned int cluster_start, unsigned int cluster_end); - HB_INTERNAL void merge_clusters (unsigned int start, - unsigned int end) + inline void merge_clusters (unsigned int start, unsigned int end) { if (end - start < 2) return; merge_clusters_impl (start, end); } - HB_INTERNAL void merge_clusters_impl (unsigned int start, - unsigned int end); - HB_INTERNAL void merge_out_clusters (unsigned int start, - unsigned int end); + HB_INTERNAL void merge_clusters_impl (unsigned int start, unsigned int end); + HB_INTERNAL void merge_out_clusters (unsigned int start, unsigned int end); /* Merge clusters for deleting current glyph, and skip it. */ HB_INTERNAL void delete_glyph (void); + inline void unsafe_to_break (unsigned int start, + unsigned int end) + { + if (end - start < 2) + return; + unsafe_to_break_impl (start, end); + } + HB_INTERNAL void unsafe_to_break_impl (unsigned int start, unsigned int end); + HB_INTERNAL void unsafe_to_break_from_outbuffer (unsigned int start, unsigned int end); + + /* Internal methods */ HB_INTERNAL bool enlarge (unsigned int size); inline bool ensure (unsigned int size) { return likely (!size || size < allocated) ? true : enlarge (size); } inline bool ensure_inplace (unsigned int size) { return likely (!size || size < allocated); } @@ -277,19 +286,76 @@ struct hb_buffer_t { return true; va_list ap; va_start (ap, fmt); bool ret = message_impl (font, fmt, ap); va_end (ap); return ret; } HB_INTERNAL bool message_impl (hb_font_t *font, const char *fmt, va_list ap) HB_PRINTF_FUNC(3, 0); + + static inline void + set_cluster (hb_glyph_info_t &info, unsigned int cluster, unsigned int mask = 0) + { + if (info.cluster != cluster) + { + if (mask & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) + info.mask |= HB_GLYPH_FLAG_UNSAFE_TO_BREAK; + else + info.mask &= ~HB_GLYPH_FLAG_UNSAFE_TO_BREAK; + } + info.cluster = cluster; + } + + int + _unsafe_to_break_find_min_cluster (const hb_glyph_info_t *info, + unsigned int start, unsigned int end, + unsigned int cluster) const + { + for (unsigned int i = start; i < end; i++) + cluster = MIN (cluster, info[i].cluster); + return cluster; + } + void + _unsafe_to_break_set_mask (hb_glyph_info_t *info, + unsigned int start, unsigned int end, + unsigned int cluster) + { + for (unsigned int i = start; i < end; i++) + if (cluster != info[i].cluster) + { + scratch_flags |= HB_BUFFER_SCRATCH_FLAG_HAS_UNSAFE_TO_BREAK; + info[i].mask |= HB_GLYPH_FLAG_UNSAFE_TO_BREAK; + } + } }; +/* Loop over clusters. Duplicated in foreach_syllable(). */ +#define foreach_cluster(buffer, start, end) \ + for (unsigned int \ + _count = buffer->len, \ + start = 0, end = _count ? _next_cluster (buffer, 0) : 0; \ + start < _count; \ + start = end, end = _next_cluster (buffer, start)) + +static inline unsigned int +_next_cluster (hb_buffer_t *buffer, unsigned int start) +{ + hb_glyph_info_t *info = buffer->info; + unsigned int count = buffer->len; + + unsigned int cluster = info[start].cluster; + while (++start < count && cluster == info[start].cluster) + ; + + return start; +} + + #define HB_BUFFER_XALLOCATE_VAR(b, func, var) \ b->func (offsetof (hb_glyph_info_t, var) - offsetof(hb_glyph_info_t, var1), \ sizeof (b->info[0].var)) #define HB_BUFFER_ALLOCATE_VAR(b, var) HB_BUFFER_XALLOCATE_VAR (b, allocate_var, var ()) #define HB_BUFFER_DEALLOCATE_VAR(b, var) HB_BUFFER_XALLOCATE_VAR (b, deallocate_var, var ()) #define HB_BUFFER_ASSERT_VAR(b, var) HB_BUFFER_XALLOCATE_VAR (b, assert_var, var ())
--- a/gfx/harfbuzz/src/hb-buffer-serialize.cc +++ b/gfx/harfbuzz/src/hb-buffer-serialize.cc @@ -140,30 +140,36 @@ static unsigned int p += MAX (0, snprintf (p, ARRAY_LENGTH (b) - (p - b), "%u", info[i].codepoint)); if (!(flags & HB_BUFFER_SERIALIZE_FLAG_NO_CLUSTERS)) { p += MAX (0, snprintf (p, ARRAY_LENGTH (b) - (p - b), ",\"cl\":%u", info[i].cluster)); } if (!(flags & HB_BUFFER_SERIALIZE_FLAG_NO_POSITIONS)) { - p += snprintf (p, ARRAY_LENGTH (b) - (p - b), ",\"dx\":%d,\"dy\":%d", - pos[i].x_offset, pos[i].y_offset); - p += snprintf (p, ARRAY_LENGTH (b) - (p - b), ",\"ax\":%d,\"ay\":%d", - pos[i].x_advance, pos[i].y_advance); + p += MAX (0, snprintf (p, ARRAY_LENGTH (b) - (p - b), ",\"dx\":%d,\"dy\":%d", + pos[i].x_offset, pos[i].y_offset)); + p += MAX (0, snprintf (p, ARRAY_LENGTH (b) - (p - b), ",\"ax\":%d,\"ay\":%d", + pos[i].x_advance, pos[i].y_advance)); + } + + if (flags & HB_BUFFER_SERIALIZE_FLAG_GLYPH_FLAGS) + { + if (info[i].mask & HB_GLYPH_FLAG_DEFINED) + p += MAX (0, snprintf (p, ARRAY_LENGTH (b) - (p - b), ",\"fl\":%u", info[i].mask & HB_GLYPH_FLAG_DEFINED)); } if (flags & HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS) { hb_glyph_extents_t extents; hb_font_get_glyph_extents(font, info[i].codepoint, &extents); p += MAX (0, snprintf (p, ARRAY_LENGTH (b) - (p - b), ",\"xb\":%d,\"yb\":%d", - extents.x_bearing, extents.y_bearing)); + extents.x_bearing, extents.y_bearing)); p += MAX (0, snprintf (p, ARRAY_LENGTH (b) - (p - b), ",\"w\":%d,\"h\":%d", - extents.width, extents.height)); + extents.width, extents.height)); } *p++ = '}'; unsigned int l = p - b; if (buf_size > l) { memcpy (buf, b, l); @@ -221,16 +227,22 @@ static unsigned int p += MAX (0, snprintf (p, ARRAY_LENGTH (b) - (p - b), "@%d,%d", pos[i].x_offset, pos[i].y_offset)); *p++ = '+'; p += MAX (0, snprintf (p, ARRAY_LENGTH (b) - (p - b), "%d", pos[i].x_advance)); if (pos[i].y_advance) p += MAX (0, snprintf (p, ARRAY_LENGTH (b) - (p - b), ",%d", pos[i].y_advance)); } + if (flags & HB_BUFFER_SERIALIZE_FLAG_GLYPH_FLAGS) + { + if (info[i].mask &HB_GLYPH_FLAG_DEFINED) + p += MAX (0, snprintf (p, ARRAY_LENGTH (b) - (p - b), "#%X", info[i].mask &HB_GLYPH_FLAG_DEFINED)); + } + if (flags & HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS) { hb_glyph_extents_t extents; hb_font_get_glyph_extents(font, info[i].codepoint, &extents); p += MAX (0, snprintf (p, ARRAY_LENGTH (b) - (p - b), "<%d,%d,%d,%d>", extents.x_bearing, extents.y_bearing, extents.width, extents.height)); } unsigned int l = p - b;
--- a/gfx/harfbuzz/src/hb-buffer.cc +++ b/gfx/harfbuzz/src/hb-buffer.cc @@ -26,20 +26,16 @@ * Red Hat Author(s): Owen Taylor, Behdad Esfahbod * Google Author(s): Behdad Esfahbod */ #include "hb-buffer-private.hh" #include "hb-utf-private.hh" -#ifndef HB_DEBUG_BUFFER -#define HB_DEBUG_BUFFER (HB_DEBUG+0) -#endif - /** * SECTION: hb-buffer * @title: Buffers * @short_description: Input and output buffers * @include: hb.h * * Buffers serve dual role in HarfBuzz; they hold the input characters that are * passed hb_shape(), and after shaping they hold the output glyphs. @@ -262,17 +258,17 @@ hb_buffer_t::add (hb_codepoint_t codepo hb_glyph_info_t *glyph; if (unlikely (!ensure (len + 1))) return; glyph = &info[len]; memset (glyph, 0, sizeof (*glyph)); glyph->codepoint = codepoint; - glyph->mask = 1; + glyph->mask = 0; glyph->cluster = cluster; len++; } void hb_buffer_t::add_info (const hb_glyph_info_t &glyph_info) { @@ -545,104 +541,136 @@ hb_buffer_t::reverse_clusters (void) reverse_range (start, i); } void hb_buffer_t::merge_clusters_impl (unsigned int start, unsigned int end) { if (cluster_level == HB_BUFFER_CLUSTER_LEVEL_CHARACTERS) + { + unsafe_to_break (start, end); return; + } - uint32_t cluster = info[start].cluster; + unsigned int cluster = info[start].cluster; for (unsigned int i = start + 1; i < end; i++) cluster = MIN (cluster, info[i].cluster); /* Extend end */ while (end < len && info[end - 1].cluster == info[end].cluster) end++; /* Extend start */ while (idx < start && info[start - 1].cluster == info[start].cluster) start--; /* If we hit the start of buffer, continue in out-buffer. */ if (idx == start) for (unsigned int i = out_len; i && out_info[i - 1].cluster == info[start].cluster; i--) - out_info[i - 1].cluster = cluster; + set_cluster (out_info[i - 1], cluster); for (unsigned int i = start; i < end; i++) - info[i].cluster = cluster; + set_cluster (info[i], cluster); } void hb_buffer_t::merge_out_clusters (unsigned int start, unsigned int end) { if (cluster_level == HB_BUFFER_CLUSTER_LEVEL_CHARACTERS) return; if (unlikely (end - start < 2)) return; - uint32_t cluster = out_info[start].cluster; + unsigned int cluster = out_info[start].cluster; for (unsigned int i = start + 1; i < end; i++) cluster = MIN (cluster, out_info[i].cluster); /* Extend start */ while (start && out_info[start - 1].cluster == out_info[start].cluster) start--; /* Extend end */ while (end < out_len && out_info[end - 1].cluster == out_info[end].cluster) end++; /* If we hit the end of out-buffer, continue in buffer. */ if (end == out_len) for (unsigned int i = idx; i < len && info[i].cluster == out_info[end - 1].cluster; i++) - info[i].cluster = cluster; + set_cluster (info[i], cluster); for (unsigned int i = start; i < end; i++) - out_info[i].cluster = cluster; + set_cluster (out_info[i], cluster); } void hb_buffer_t::delete_glyph () { + /* The logic here is duplicated in hb_ot_hide_default_ignorables(). */ + unsigned int cluster = info[idx].cluster; if (idx + 1 < len && cluster == info[idx + 1].cluster) { /* Cluster survives; do nothing. */ goto done; } if (out_len) { /* Merge cluster backward. */ if (cluster < out_info[out_len - 1].cluster) { + unsigned int mask = info[idx].mask; unsigned int old_cluster = out_info[out_len - 1].cluster; for (unsigned i = out_len; i && out_info[i - 1].cluster == old_cluster; i--) - out_info[i - 1].cluster = cluster; + set_cluster (out_info[i - 1], cluster, mask); } goto done; } if (idx + 1 < len) { /* Merge cluster forward. */ merge_clusters (idx, idx + 2); goto done; } done: skip_glyph (); } void +hb_buffer_t::unsafe_to_break_impl (unsigned int start, unsigned int end) +{ + unsigned int cluster = (unsigned int) -1; + cluster = _unsafe_to_break_find_min_cluster (info, start, end, cluster); + _unsafe_to_break_set_mask (info, start, end, cluster); +} +void +hb_buffer_t::unsafe_to_break_from_outbuffer (unsigned int start, unsigned int end) +{ + if (!have_output) + { + unsafe_to_break_impl (start, end); + return; + } + + assert (start <= out_len); + assert (idx <= end); + + unsigned int cluster = (unsigned int) -1; + cluster = _unsafe_to_break_find_min_cluster (out_info, start, out_len, cluster); + cluster = _unsafe_to_break_find_min_cluster (info, idx, end, cluster); + _unsafe_to_break_set_mask (out_info, start, out_len, cluster); + _unsafe_to_break_set_mask (info, idx, end, cluster); +} + +void hb_buffer_t::guess_segment_properties (void) { assert (content_type == HB_BUFFER_CONTENT_TYPE_UNICODE || (!len && content_type == HB_BUFFER_CONTENT_TYPE_INVALID)); /* If script is set to INVALID, guess from buffer contents */ if (props.script == HB_SCRIPT_INVALID) { for (unsigned int i = 0; i < len; i++) { @@ -1375,16 +1403,33 @@ hb_buffer_get_glyph_positions (hb_buffer if (length) *length = buffer->len; return (hb_glyph_position_t *) buffer->pos; } /** + * hb_glyph_info_get_glyph_flags: + * @info: a #hb_glyph_info_t. + * + * Returns glyph flags encoded within a #hb_glyph_info_t. + * + * Return value: + * The #hb_glyph_flags_t encoded within @info. + * + * Since: 1.5.0 + **/ +hb_glyph_flags_t +(hb_glyph_info_get_glyph_flags) (const hb_glyph_info_t *info) +{ + return hb_glyph_info_get_glyph_flags (info); +} + +/** * hb_buffer_reverse: * @buffer: an #hb_buffer_t. * * Reverses buffer contents. * * Since: 0.9.2 **/ void @@ -1661,16 +1706,68 @@ hb_buffer_add_codepoints (hb_buffer_t int text_length, unsigned int item_offset, int item_length) { hb_buffer_add_utf<hb_utf32_t<false> > (buffer, text, text_length, item_offset, item_length); } +/** + * hb_buffer_append: + * @buffer: an #hb_buffer_t. + * @source: source #hb_buffer_t. + * @start: start index into source buffer to copy. Use 0 to copy from start of buffer. + * @end: end index into source buffer to copy. Use (unsigned int) -1 to copy to end of buffer. + * + * Append (part of) contents of another buffer to this buffer. + * + * Since: 1.5.0 + **/ +HB_EXTERN void +hb_buffer_append (hb_buffer_t *buffer, + hb_buffer_t *source, + unsigned int start, + unsigned int end) +{ + assert (!buffer->have_output && !source->have_output); + assert (buffer->have_positions == source->have_positions || + !buffer->len || !source->len); + assert (buffer->content_type == source->content_type || + !buffer->len || !source->len); + + if (end > source->len) + end = source->len; + if (start > end) + start = end; + if (start == end) + return; + + if (!buffer->len) + buffer->content_type = source->content_type; + if (!buffer->have_positions && source->have_positions) + buffer->clear_positions (); + + if (buffer->len + (end - start) < buffer->len) /* Overflows. */ + { + buffer->in_error = true; + return; + } + + unsigned int orig_len = buffer->len; + hb_buffer_set_length (buffer, buffer->len + (end - start)); + if (buffer->in_error) + return; + + memcpy (buffer->info + orig_len, source->info + start, (end - start) * sizeof (buffer->info[0])); + if (buffer->have_positions) + memcpy (buffer->pos + orig_len, source->pos + start, (end - start) * sizeof (buffer->pos[0])); +} + + static int compare_info_codepoint (const hb_glyph_info_t *pa, const hb_glyph_info_t *pb) { return (int) pb->codepoint - (int) pa->codepoint; } static inline void @@ -1731,17 +1828,18 @@ normalize_glyphs_cluster (hb_buffer_t *b * <note>This has nothing to do with Unicode normalization.</note> * * Since: 0.9.2 **/ void hb_buffer_normalize_glyphs (hb_buffer_t *buffer) { assert (buffer->have_positions); - assert (buffer->content_type == HB_BUFFER_CONTENT_TYPE_GLYPHS); + assert (buffer->content_type == HB_BUFFER_CONTENT_TYPE_GLYPHS || + (!buffer->len && buffer->content_type == HB_BUFFER_CONTENT_TYPE_INVALID)); bool backward = HB_DIRECTION_IS_BACKWARD (buffer->props.direction); unsigned int count = buffer->len; if (unlikely (!count)) return; hb_glyph_info_t *info = buffer->info; unsigned int start = 0; @@ -1770,16 +1868,108 @@ hb_buffer_t::sort (unsigned int start, u { hb_glyph_info_t t = info[i]; memmove (&info[j + 1], &info[j], (i - j) * sizeof (hb_glyph_info_t)); info[j] = t; } } } + +/* + * Comparing buffers. + */ + +/** + * hb_buffer_diff: + * + * If dottedcircle_glyph is (hb_codepoint_t) -1 then %HB_BUFFER_DIFF_FLAG_DOTTED_CIRCLE_PRESENT + * and %HB_BUFFER_DIFF_FLAG_NOTDEF_PRESENT are never returned. This should be used by most + * callers if just comparing two buffers is needed. + * + * Since: 1.5.0 + **/ +hb_buffer_diff_flags_t +hb_buffer_diff (hb_buffer_t *buffer, + hb_buffer_t *reference, + hb_codepoint_t dottedcircle_glyph, + unsigned int position_fuzz) +{ + if (buffer->content_type != reference->content_type && buffer->len && reference->len) + return HB_BUFFER_DIFF_FLAG_CONTENT_TYPE_MISMATCH; + + hb_buffer_diff_flags_t result = HB_BUFFER_DIFF_FLAG_EQUAL; + bool contains = dottedcircle_glyph != (hb_codepoint_t) -1; + + unsigned int count = reference->len; + + if (buffer->len != count) + { + /* + * we can't compare glyph-by-glyph, but we do want to know if there + * are .notdef or dottedcircle glyphs present in the reference buffer + */ + const hb_glyph_info_t *info = reference->info; + unsigned int i; + for (i = 0; i < count; i++) + { + if (contains && info[i].codepoint == dottedcircle_glyph) + result |= HB_BUFFER_DIFF_FLAG_DOTTED_CIRCLE_PRESENT; + if (contains && info[i].codepoint == 0) + result |= HB_BUFFER_DIFF_FLAG_NOTDEF_PRESENT; + } + result |= HB_BUFFER_DIFF_FLAG_LENGTH_MISMATCH; + return hb_buffer_diff_flags_t (result); + } + + if (!count) + return hb_buffer_diff_flags_t (result); + + const hb_glyph_info_t *buf_info = buffer->info; + const hb_glyph_info_t *ref_info = reference->info; + for (unsigned int i = 0; i < count; i++) + { + if (buf_info->codepoint != ref_info->codepoint) + result |= HB_BUFFER_DIFF_FLAG_CODEPOINT_MISMATCH; + if (buf_info->cluster != ref_info->cluster) + result |= HB_BUFFER_DIFF_FLAG_CLUSTER_MISMATCH; + if ((buf_info->mask & HB_GLYPH_FLAG_DEFINED) != (ref_info->mask & HB_GLYPH_FLAG_DEFINED)) + result |= HB_BUFFER_DIFF_FLAG_GLYPH_FLAGS_MISMATCH; + if (contains && ref_info->codepoint == dottedcircle_glyph) + result |= HB_BUFFER_DIFF_FLAG_DOTTED_CIRCLE_PRESENT; + if (contains && ref_info->codepoint == 0) + result |= HB_BUFFER_DIFF_FLAG_NOTDEF_PRESENT; + buf_info++; + ref_info++; + } + + if (buffer->content_type == HB_BUFFER_CONTENT_TYPE_GLYPHS) + { + assert (buffer->have_positions); + const hb_glyph_position_t *buf_pos = buffer->pos; + const hb_glyph_position_t *ref_pos = reference->pos; + for (unsigned int i = 0; i < count; i++) + { + if ((unsigned int) abs (buf_pos->x_advance - ref_pos->x_advance) > position_fuzz || + (unsigned int) abs (buf_pos->y_advance - ref_pos->y_advance) > position_fuzz || + (unsigned int) abs (buf_pos->x_offset - ref_pos->x_offset) > position_fuzz || + (unsigned int) abs (buf_pos->y_offset - ref_pos->y_offset) > position_fuzz) + { + result |= HB_BUFFER_DIFF_FLAG_POSITION_MISMATCH; + break; + } + buf_pos++; + ref_pos++; + } + } + + return result; +} + + /* * Debugging. */ /** * hb_buffer_set_message_func: * @buffer: an #hb_buffer_t. * @func: (closure user_data) (destroy destroy) (scope notified):
--- a/gfx/harfbuzz/src/hb-buffer.h +++ b/gfx/harfbuzz/src/hb-buffer.h @@ -58,24 +58,37 @@ HB_BEGIN_DECLS * allow selecting more fine-grained cluster handling. * * The #hb_glyph_info_t is the structure that holds information about the * glyphs and their relation to input text. * */ typedef struct hb_glyph_info_t { hb_codepoint_t codepoint; - hb_mask_t mask; + hb_mask_t mask; /* Holds hb_glyph_flags_t after hb_shape(), plus other things. */ uint32_t cluster; /*< private >*/ hb_var_int_t var1; hb_var_int_t var2; } hb_glyph_info_t; +typedef enum { /*< flags >*/ + HB_GLYPH_FLAG_UNSAFE_TO_BREAK = 0x00000001, + + HB_GLYPH_FLAG_DEFINED = 0x00000001 /* OR of all defined flags */ +} hb_glyph_flags_t; + +HB_EXTERN hb_glyph_flags_t +hb_glyph_info_get_glyph_flags (const hb_glyph_info_t *info); + +#define hb_glyph_info_get_glyph_flags(info) \ + ((hb_glyph_flags_t) ((unsigned int) (info)->mask & HB_GLYPH_FLAG_DEFINED)) + + /** * hb_glyph_position_t: * @x_advance: how much the line advances after drawing this glyph when setting * text in horizontal direction. * @y_advance: how much the line advances after drawing this glyph when setting * text in vertical direction. * @x_offset: how much the glyph moves on the X-axis before drawing it, this * should not affect how much the line advances. @@ -158,16 +171,17 @@ hb_buffer_set_user_data (hb_buffer_t void * data, hb_destroy_func_t destroy, hb_bool_t replace); HB_EXTERN void * hb_buffer_get_user_data (hb_buffer_t *buffer, hb_user_data_key_t *key); + /** * hb_buffer_content_type_t: * @HB_BUFFER_CONTENT_TYPE_INVALID: Initial value for new buffer. * @HB_BUFFER_CONTENT_TYPE_UNICODE: The buffer contains input characters (before shaping). * @HB_BUFFER_CONTENT_TYPE_GLYPHS: The buffer contains output glyphs (after shaping). */ typedef enum { HB_BUFFER_CONTENT_TYPE_INVALID = 0, @@ -354,16 +368,21 @@ hb_buffer_add_latin1 (hb_buffer_t *buf HB_EXTERN void hb_buffer_add_codepoints (hb_buffer_t *buffer, const hb_codepoint_t *text, int text_length, unsigned int item_offset, int item_length); +HB_EXTERN void +hb_buffer_append (hb_buffer_t *buffer, + hb_buffer_t *source, + unsigned int start, + unsigned int end); HB_EXTERN hb_bool_t hb_buffer_set_length (hb_buffer_t *buffer, unsigned int length); HB_EXTERN unsigned int hb_buffer_get_length (hb_buffer_t *buffer); @@ -398,17 +417,18 @@ hb_buffer_normalize_glyphs (hb_buffer_t * * Since: 0.9.20 */ typedef enum { /*< flags >*/ HB_BUFFER_SERIALIZE_FLAG_DEFAULT = 0x00000000u, HB_BUFFER_SERIALIZE_FLAG_NO_CLUSTERS = 0x00000001u, HB_BUFFER_SERIALIZE_FLAG_NO_POSITIONS = 0x00000002u, HB_BUFFER_SERIALIZE_FLAG_NO_GLYPH_NAMES = 0x00000004u, - HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS = 0x00000008u + HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS = 0x00000008u, + HB_BUFFER_SERIALIZE_FLAG_GLYPH_FLAGS = 0x00000010u } hb_buffer_serialize_flags_t; /** * hb_buffer_serialize_format_t: * @HB_BUFFER_SERIALIZE_FORMAT_TEXT: a human-readable, plain text format. * @HB_BUFFER_SERIALIZE_FORMAT_JSON: a machine-readable JSON format. * @HB_BUFFER_SERIALIZE_FORMAT_INVALID: invalid format. * @@ -448,16 +468,55 @@ hb_buffer_deserialize_glyphs (hb_buffer_ const char *buf, int buf_len, const char **end_ptr, hb_font_t *font, hb_buffer_serialize_format_t format); /* + * Compare buffers + */ + +typedef enum { /*< flags >*/ + HB_BUFFER_DIFF_FLAG_EQUAL = 0x0000, + + /* Buffers with different content_type cannot be meaningfully compared + * in any further detail. */ + HB_BUFFER_DIFF_FLAG_CONTENT_TYPE_MISMATCH = 0x0001, + + /* For buffers with differing length, the per-glyph comparison is not + * attempted, though we do still scan reference for dottedcircle / .notdef + * glyphs. */ + HB_BUFFER_DIFF_FLAG_LENGTH_MISMATCH = 0x0002, + + /* We want to know if dottedcircle / .notdef glyphs are present in the + * reference, as we may not care so much about other differences in this + * case. */ + HB_BUFFER_DIFF_FLAG_NOTDEF_PRESENT = 0x0004, + HB_BUFFER_DIFF_FLAG_DOTTED_CIRCLE_PRESENT = 0x0008, + + /* If the buffers have the same length, we compare them glyph-by-glyph + * and report which aspect(s) of the glyph info/position are different. */ + HB_BUFFER_DIFF_FLAG_CODEPOINT_MISMATCH = 0x0010, + HB_BUFFER_DIFF_FLAG_CLUSTER_MISMATCH = 0x0020, + HB_BUFFER_DIFF_FLAG_GLYPH_FLAGS_MISMATCH = 0x0040, + HB_BUFFER_DIFF_FLAG_POSITION_MISMATCH = 0x0080 + +} hb_buffer_diff_flags_t; + +/* Compare the contents of two buffers, report types of differences. */ +hb_buffer_diff_flags_t +hb_buffer_diff (hb_buffer_t *buffer, + hb_buffer_t *reference, + hb_codepoint_t dottedcircle_glyph, + unsigned int position_fuzz); + + +/* * Debugging. */ typedef hb_bool_t (*hb_buffer_message_func_t) (hb_buffer_t *buffer, hb_font_t *font, const char *message, void *user_data);
--- a/gfx/harfbuzz/src/hb-coretext.cc +++ b/gfx/harfbuzz/src/hb-coretext.cc @@ -1175,24 +1175,30 @@ resize_and_retry: hb_glyph_info_t *info = buffer->info; hb_glyph_position_t *pos = buffer->pos; if (HB_DIRECTION_IS_HORIZONTAL (buffer->props.direction)) for (unsigned int i = 0; i < count; i++) { pos->x_advance = info->mask; pos->x_offset = info->var1.i32; pos->y_offset = info->var2.i32; + + info->mask = HB_GLYPH_FLAG_UNSAFE_TO_BREAK; + info++, pos++; } else for (unsigned int i = 0; i < count; i++) { pos->y_advance = info->mask; pos->x_offset = info->var1.i32; pos->y_offset = info->var2.i32; + + info->mask = HB_GLYPH_FLAG_UNSAFE_TO_BREAK; + info++, pos++; } /* Fix up clusters so that we never return out-of-order indices; * if core text has reordered glyphs, we'll merge them to the * beginning of the reordered cluster. CoreText is nice enough * to tell us whenever it has produced nonmonotonic results... * Note that we assume the input clusters were nonmonotonic to
--- a/gfx/harfbuzz/src/hb-directwrite.cc +++ b/gfx/harfbuzz/src/hb-directwrite.cc @@ -875,16 +875,18 @@ retry_getglyphs: hb_glyph_info_t *info = &buffer->info[i]; hb_glyph_position_t *pos = &buffer->pos[i]; /* TODO vertical */ pos->x_advance = x_mult * (int32_t) info->mask; pos->x_offset = x_mult * (isRightToLeft ? -info->var1.i32 : info->var1.i32); pos->y_offset = y_mult * info->var2.i32; + + info->mask = HB_GLYPH_FLAG_UNSAFE_TO_BREAK; } if (isRightToLeft) hb_buffer_reverse (buffer); free (clusterMap); free (glyphIndices); free (textProperties);
--- a/gfx/harfbuzz/src/hb-face-private.hh +++ b/gfx/harfbuzz/src/hb-face-private.hh @@ -50,20 +50,20 @@ struct hb_face_t { void *user_data; hb_destroy_func_t destroy; unsigned int index; /* Face index in a collection, zero-based. */ mutable unsigned int upem; /* Units-per-EM. */ mutable unsigned int num_glyphs; /* Number of glyphs. */ enum dirty_t { - NOTHING = 0x0000, - INDEX = 0x0001, - UPEM = 0x0002, - NUM_GLYPHS = 0x0004, + DIRTY_NOTHING = 0x0000, + DIRTY_INDEX = 0x0001, + DIRTY_UPEM = 0x0002, + DIRTY_NUM_GLYPHS = 0x0004, } dirty; struct hb_shaper_data_t shaper_data; /* Various shaper data. */ /* Various non-shaping data. */ /* ... */ /* Cache */
--- a/gfx/harfbuzz/src/hb-face.cc +++ b/gfx/harfbuzz/src/hb-face.cc @@ -46,17 +46,17 @@ const hb_face_t _hb_face_nil = { NULL, /* reference_table_func */ NULL, /* user_data */ NULL, /* destroy */ 0, /* index */ 1000, /* upem */ 0, /* num_glyphs */ - hb_face_t::NOTHING, /* dirty */ + hb_face_t::DIRTY_NOTHING, /* dirty */ { #define HB_SHAPER_IMPLEMENT(shaper) HB_SHAPER_DATA_INVALID, #include "hb-shaper-list.hh" #undef HB_SHAPER_IMPLEMENT }, NULL, /* shape_plans */ @@ -365,17 +365,17 @@ hb_face_set_index (hb_face_t *face, unsigned int index) { if (face->immutable) return; if (face->index == index) return; - face->dirty |= face->INDEX; + face->dirty |= face->DIRTY_INDEX; face->index = index; } /** * hb_face_get_index: * @face: a face. * @@ -405,17 +405,17 @@ hb_face_set_upem (hb_face_t *face, unsigned int upem) { if (face->immutable) return; if (face->upem == upem) return; - face->dirty |= face->UPEM; + face->dirty |= face->DIRTY_UPEM; face->upem = upem; } /** * hb_face_get_upem: * @face: a face. * @@ -454,17 +454,17 @@ hb_face_set_glyph_count (hb_face_t *f unsigned int glyph_count) { if (face->immutable) return; if (face->num_glyphs == glyph_count) return; - face->dirty |= face->NUM_GLYPHS; + face->dirty |= face->DIRTY_NUM_GLYPHS; face->num_glyphs = glyph_count; } /** * hb_face_get_glyph_count: * @face: a face. *
--- a/gfx/harfbuzz/src/hb-font-private.hh +++ b/gfx/harfbuzz/src/hb-font-private.hh @@ -112,23 +112,23 @@ struct hb_font_t { unsigned int num_coords; int *coords; hb_font_funcs_t *klass; void *user_data; hb_destroy_func_t destroy; enum dirty_t { - NOTHING = 0x0000, - FACE = 0x0001, - PARENT = 0x0002, - FUNCS = 0x0004, - SCALE = 0x0008, - PPEM = 0x0010, - VARIATIONS = 0x0020, + DIRTY_NOTHING = 0x0000, + DIRTY_FACE = 0x0001, + DIRTY_PARENT = 0x0002, + DIRTY_FUNCS = 0x0004, + DIRTY_SCALE = 0x0008, + DIRTY_PPEM = 0x0010, + DIRTY_VARIATIONS = 0x0020, } dirty; struct hb_shaper_data_t shaper_data; /* Convert from font-space to user-space */ inline int dir_scale (hb_direction_t direction) { return HB_DIRECTION_IS_VERTICAL(direction) ? y_scale : x_scale; }
--- a/gfx/harfbuzz/src/hb-font.cc +++ b/gfx/harfbuzz/src/hb-font.cc @@ -1191,17 +1191,17 @@ hb_font_get_empty (void) 0, /* num_coords */ NULL, /* coords */ const_cast<hb_font_funcs_t *> (&_hb_font_funcs_nil), /* klass */ NULL, /* user_data */ NULL, /* destroy */ - hb_font_t::NOTHING, /* dirty */ + hb_font_t::DIRTY_NOTHING, /* dirty */ { #define HB_SHAPER_IMPLEMENT(shaper) HB_SHAPER_DATA_INVALID, #include "hb-shaper-list.hh" #undef HB_SHAPER_IMPLEMENT } }; @@ -1348,17 +1348,17 @@ hb_font_set_parent (hb_font_t *font, return; if (!parent) parent = hb_font_get_empty (); if (parent == font->parent) return; - font->dirty |= font->PARENT; + font->dirty |= font->DIRTY_PARENT; hb_font_t *old = font->parent; font->parent = hb_font_reference (parent); hb_font_destroy (old); } @@ -1395,17 +1395,17 @@ hb_font_set_face (hb_font_t *font, return; if (unlikely (!face)) face = hb_face_get_empty (); if (font->face == face) return; - font->dirty |= font->FACE; + font->dirty |= font->DIRTY_FACE; hb_face_t *old = font->face; font->face = hb_face_reference (face); hb_face_destroy (old); } @@ -1450,17 +1450,17 @@ hb_font_set_funcs (hb_font_t *fo } if (font->destroy) font->destroy (font->user_data); if (!klass) klass = hb_font_funcs_get_empty (); - font->dirty |= font->FUNCS; + font->dirty |= font->DIRTY_FUNCS; hb_font_funcs_reference (klass); hb_font_funcs_destroy (font->klass); font->klass = klass; font->user_data = font_data; font->destroy = destroy; } @@ -1510,17 +1510,17 @@ hb_font_set_scale (hb_font_t *font, int y_scale) { if (font->immutable) return; if (font->x_scale == x_scale && font->y_scale == y_scale) return; - font->dirty |= font->SCALE; + font->dirty |= font->DIRTY_SCALE; font->x_scale = x_scale; font->y_scale = y_scale; } /** * hb_font_get_scale: * @font: a font. @@ -1556,17 +1556,17 @@ hb_font_set_ppem (hb_font_t *font, unsigned int y_ppem) { if (font->immutable) return; if (font->x_ppem == x_ppem && font->y_ppem == y_ppem) return; - font->dirty |= font->PPEM; + font->dirty |= font->DIRTY_PPEM; font->x_ppem = x_ppem; font->y_ppem = y_ppem; } /** * hb_font_get_ppem: * @font: a font. @@ -1598,17 +1598,17 @@ static void if (font->num_coords == coords_length && (coords_length == 0 || 0 == memcmp (font->coords, coords, coords_length * sizeof (coords[0])))) { free (coords); return; } - font->dirty |= font->VARIATIONS; + font->dirty |= font->DIRTY_VARIATIONS; free (font->coords); font->coords = coords; font->num_coords = coords_length; } /**
--- a/gfx/harfbuzz/src/hb-ft.cc +++ b/gfx/harfbuzz/src/hb-ft.cc @@ -488,17 +488,17 @@ reference_table (hb_face_t *face HB_UNU /* Note: FreeType like HarfBuzz uses the NONE tag for fetching the entire blob */ error = FT_Load_Sfnt_Table (ft_face, tag, 0, NULL, &length); if (error) return NULL; buffer = (FT_Byte *) malloc (length); - if (buffer == NULL) + if (!buffer) return NULL; error = FT_Load_Sfnt_Table (ft_face, tag, 0, buffer, &length); if (error) return NULL; return hb_blob_create ((const char *) buffer, length, HB_MEMORY_MODE_WRITABLE, @@ -516,17 +516,17 @@ reference_table (hb_face_t *face HB_UNU * Since: 0.9.2 **/ hb_face_t * hb_ft_face_create (FT_Face ft_face, hb_destroy_func_t destroy) { hb_face_t *face; - if (ft_face->stream->read == NULL) { + if (!ft_face->stream->read) { hb_blob_t *blob; blob = hb_blob_create ((const char *) ft_face->stream->base, (unsigned int) ft_face->stream->size, HB_MEMORY_MODE_READONLY, ft_face, destroy); face = hb_face_create (blob, ft_face->face_index); hb_blob_destroy (blob); @@ -627,19 +627,19 @@ hb_ft_font_create (FT_Face ft_ { if (!FT_Get_Var_Blend_Coordinates (ft_face, mm_var->num_axis, ft_coords)) { for (unsigned int i = 0; i < mm_var->num_axis; ++i) coords[i] = ft_coords[i] >>= 2; hb_font_set_var_coords_normalized (font, coords, mm_var->num_axis); } - free (coords); - free (ft_coords); } + free (coords); + free (ft_coords); free (mm_var); } #endif return font; } /**
--- a/gfx/harfbuzz/src/hb-graphite2.cc +++ b/gfx/harfbuzz/src/hb-graphite2.cc @@ -350,27 +350,28 @@ hb_bool_t for (unsigned int i = 0; i < ci; ++i) { for (unsigned int j = 0; j < clusters[i].num_glyphs; ++j) { hb_glyph_info_t *info = &buffer->info[clusters[i].base_glyph + j]; info->codepoint = gids[clusters[i].base_glyph + j]; info->cluster = clusters[i].cluster; + info->mask = HB_GLYPH_FLAG_UNSAFE_TO_BREAK; info->var1.i32 = clusters[i].advance; // all glyphs in the cluster get the same advance } } buffer->len = glyph_count; unsigned int upem = hb_face_get_upem (face); float xscale = (float) font->x_scale / upem; float yscale = (float) font->y_scale / upem; yscale *= yscale / xscale; /* Positioning. */ - int currclus = -1; + unsigned int currclus = (unsigned int) -1; const hb_glyph_info_t *info = buffer->info; hb_glyph_position_t *pPos = hb_buffer_get_glyph_positions (buffer, NULL); if (!HB_DIRECTION_IS_BACKWARD(buffer->props.direction)) { curradvx = 0; for (is = gr_seg_first_slot (seg); is; pPos++, ++info, is = gr_slot_next_in_segment (is)) { pPos->x_offset = gr_slot_origin_X (is) * xscale - curradvx;
--- a/gfx/harfbuzz/src/hb-ot-font.cc +++ b/gfx/harfbuzz/src/hb-ot-font.cc @@ -219,17 +219,17 @@ struct hb_ot_face_glyf_accelerator_t struct hb_ot_face_cbdt_accelerator_t { hb_blob_t *cblc_blob; hb_blob_t *cbdt_blob; const OT::CBLC *cblc; const OT::CBDT *cbdt; unsigned int cbdt_len; - float upem; + unsigned int upem; inline void init (hb_face_t *face) { upem = face->get_upem(); cblc_blob = OT::Sanitizer<OT::CBLC>::sanitize (face->reference_table (HB_OT_TAG_CBLC)); cbdt_blob = OT::Sanitizer<OT::CBDT>::sanitize (face->reference_table (HB_OT_TAG_CBDT)); cbdt_len = hb_blob_get_length (cbdt_blob); @@ -249,21 +249,21 @@ struct hb_ot_face_cbdt_accelerator_t hb_blob_destroy (this->cblc_blob); hb_blob_destroy (this->cbdt_blob); } inline bool get_extents (hb_codepoint_t glyph, hb_glyph_extents_t *extents) const { unsigned int x_ppem = upem, y_ppem = upem; /* TODO Use font ppem if available. */ - if (cblc == NULL) + if (!cblc) return false; // Not a color bitmap font. const OT::IndexSubtableRecord *subtable_record = this->cblc->find_table(glyph, &x_ppem, &y_ppem); - if (subtable_record == NULL) + if (!subtable_record || !x_ppem || !y_ppem) return false; if (subtable_record->get_extents (extents)) return true; unsigned int image_offset = 0, image_length = 0, image_format = 0; if (!subtable_record->get_image_data (glyph, &image_offset, &image_length, &image_format)) return false;
--- a/gfx/harfbuzz/src/hb-ot-layout-common-private.hh +++ b/gfx/harfbuzz/src/hb-ot-layout-common-private.hh @@ -936,17 +936,17 @@ struct Coverage switch (u.format) { case 1: u.format1.add_coverage (glyphs); break; case 2: u.format2.add_coverage (glyphs); break; default: break; } } struct Iter { - Iter (void) : format (0) {}; + Iter (void) : format (0), u () {}; inline void init (const Coverage &c_) { format = c_.u.format; switch (format) { case 1: u.format1.init (c_.u.format1); return; case 2: u.format2.init (c_.u.format2); return; default: return; } } @@ -977,18 +977,18 @@ struct Coverage case 2: return u.format2.get_coverage (); default:return -1; } } private: unsigned int format; union { + CoverageFormat2::Iter format2; /* Put this one first since it's larger; helps shut up compiler. */ CoverageFormat1::Iter format1; - CoverageFormat2::Iter format2; } u; }; protected: union { USHORT format; /* Format identifier */ CoverageFormat1 format1; CoverageFormat2 format2;
--- a/gfx/harfbuzz/src/hb-ot-layout-gpos-table.hh +++ b/gfx/harfbuzz/src/hb-ot-layout-gpos-table.hh @@ -427,16 +427,17 @@ struct MarkArray : ArrayOf<MarkRecord> / bool found; const Anchor& glyph_anchor = anchors.get_anchor (glyph_index, mark_class, class_count, &found); /* If this subtable doesn't have an anchor for this base and this class, * return false such that the subsequent subtables have a chance at it. */ if (unlikely (!found)) return_trace (false); hb_position_t mark_x, mark_y, base_x, base_y; + buffer->unsafe_to_break (glyph_pos, buffer->idx); mark_anchor.get_anchor (c, buffer->cur().codepoint, &mark_x, &mark_y); glyph_anchor.get_anchor (c, buffer->info[glyph_pos].codepoint, &base_x, &base_y); hb_glyph_position_t &o = buffer->cur_pos(); o.x_offset = base_x - mark_x; o.y_offset = base_y - mark_y; o.attach_type() = ATTACH_TYPE_MARK; o.attach_chain() = (int) glyph_pos - (int) buffer->idx; @@ -638,16 +639,17 @@ struct PairSet const PairValueRecord *record = &StructAtOffset<PairValueRecord> (record_array, record_size * mid); hb_codepoint_t mid_x = record->secondGlyph; if (x < mid_x) max = mid - 1; else if (x > mid_x) min = mid + 1; else { + buffer->unsafe_to_break (buffer->idx, pos + 1); valueFormats[0].apply_value (c, this, &record->values[0], buffer->cur_pos()); valueFormats[1].apply_value (c, this, &record->values[len1], buffer->pos[pos]); if (len2) pos++; buffer->idx = pos; return_trace (true); } } @@ -785,16 +787,17 @@ struct PairPosFormat2 unsigned int len1 = valueFormat1.get_len (); unsigned int len2 = valueFormat2.get_len (); unsigned int record_len = len1 + len2; unsigned int klass1 = (this+classDef1).get_class (buffer->cur().codepoint); unsigned int klass2 = (this+classDef2).get_class (buffer->info[skippy_iter.idx].codepoint); if (unlikely (klass1 >= class1Count || klass2 >= class2Count)) return_trace (false); + buffer->unsafe_to_break (buffer->idx, skippy_iter.idx + 1); const Value *v = &values[record_len * (klass1 * class2Count + klass2)]; valueFormat1.apply_value (c, this, v, buffer->cur_pos()); valueFormat2.apply_value (c, this, v + len1, buffer->pos[skippy_iter.idx]); buffer->idx = skippy_iter.idx; if (len2) buffer->idx++; @@ -924,16 +927,17 @@ struct CursivePosFormat1 if (!skippy_iter.next ()) return_trace (false); const EntryExitRecord &next_record = entryExitRecord[(this+coverage).get_coverage (buffer->info[skippy_iter.idx].codepoint)]; if (!next_record.entryAnchor) return_trace (false); unsigned int i = buffer->idx; unsigned int j = skippy_iter.idx; + buffer->unsafe_to_break (i, j); hb_position_t entry_x, entry_y, exit_x, exit_y; (this+this_record.exitAnchor).get_anchor (c, buffer->info[i].codepoint, &exit_x, &exit_y); (this+next_record.entryAnchor).get_anchor (c, buffer->info[j].codepoint, &entry_x, &entry_y); hb_glyph_position_t *pos = buffer->pos; hb_position_t d; /* Main-direction adjustment */
--- a/gfx/harfbuzz/src/hb-ot-layout-gsub-table.hh +++ b/gfx/harfbuzz/src/hb-ot-layout-gsub-table.hh @@ -1009,24 +1009,27 @@ struct ReverseChainSingleSubstFormat1 return_trace (false); /* No chaining to this type */ unsigned int index = (this+coverage).get_coverage (c->buffer->cur().codepoint); if (likely (index == NOT_COVERED)) return_trace (false); const OffsetArrayOf<Coverage> &lookahead = StructAfter<OffsetArrayOf<Coverage> > (backtrack); const ArrayOf<GlyphID> &substitute = StructAfter<ArrayOf<GlyphID> > (lookahead); + unsigned int start_index = 0, end_index = 0; if (match_backtrack (c, backtrack.len, (USHORT *) backtrack.array, - match_coverage, this) && + match_coverage, this, + &start_index) && match_lookahead (c, lookahead.len, (USHORT *) lookahead.array, match_coverage, this, - 1)) + 1, &end_index)) { + c->buffer->unsafe_to_break_from_outbuffer (start_index, end_index); c->replace_glyph_inplace (substitute[index]); /* Note: We DON'T decrease buffer->idx. The main loop does it * for us. This is useful for preventing surprises if someone * calls us through a Context lookup. */ return_trace (true); } return_trace (false);
--- a/gfx/harfbuzz/src/hb-ot-layout-gsubgpos-private.hh +++ b/gfx/harfbuzz/src/hb-ot-layout-gsubgpos-private.hh @@ -886,48 +886,54 @@ static inline bool ligate_input (hb_appl } return_trace (true); } static inline bool match_backtrack (hb_apply_context_t *c, unsigned int count, const USHORT backtrack[], match_func_t match_func, - const void *match_data) + const void *match_data, + unsigned int *match_start) { TRACE_APPLY (NULL); hb_apply_context_t::skipping_iterator_t &skippy_iter = c->iter_context; skippy_iter.reset (c->buffer->backtrack_len (), count); skippy_iter.set_match_func (match_func, match_data, backtrack); for (unsigned int i = 0; i < count; i++) if (!skippy_iter.prev ()) return_trace (false); + *match_start = skippy_iter.idx; + return_trace (true); } static inline bool match_lookahead (hb_apply_context_t *c, unsigned int count, const USHORT lookahead[], match_func_t match_func, const void *match_data, - unsigned int offset) + unsigned int offset, + unsigned int *end_index) { TRACE_APPLY (NULL); hb_apply_context_t::skipping_iterator_t &skippy_iter = c->iter_context; skippy_iter.reset (c->buffer->idx + offset - 1, count); skippy_iter.set_match_func (match_func, match_data, lookahead); for (unsigned int i = 0; i < count; i++) if (!skippy_iter.next ()) return_trace (false); + *end_index = skippy_iter.idx + 1; + return_trace (true); } struct LookupRecord { inline bool sanitize (hb_sanitize_context_t *c) const @@ -1140,20 +1146,21 @@ static inline bool context_apply_lookup ContextApplyLookupContext &lookup_context) { unsigned int match_length = 0; unsigned int match_positions[HB_MAX_CONTEXT_LENGTH]; return match_input (c, inputCount, input, lookup_context.funcs.match, lookup_context.match_data, &match_length, match_positions) - && apply_lookup (c, + && (c->buffer->unsafe_to_break (c->buffer->idx, c->buffer->idx + match_length), + apply_lookup (c, inputCount, match_positions, lookupCount, lookupRecord, - match_length); + match_length)); } struct Rule { inline void closure (hb_closure_context_t *c, ContextClosureLookupContext &lookup_context) const { TRACE_CLOSURE (this); const LookupRecord *lookupRecord = &StructAtOffset<LookupRecord> (inputZ, inputZ[0].static_size * (inputCount ? inputCount - 1 : 0)); @@ -1661,33 +1668,35 @@ static inline bool chain_context_apply_l unsigned int inputCount, /* Including the first glyph (not matched) */ const USHORT input[], /* Array of input values--start with second glyph */ unsigned int lookaheadCount, const USHORT lookahead[], unsigned int lookupCount, const LookupRecord lookupRecord[], ChainContextApplyLookupContext &lookup_context) { - unsigned int match_length = 0; + unsigned int start_index = 0, match_length = 0, end_index = 0; unsigned int match_positions[HB_MAX_CONTEXT_LENGTH]; return match_input (c, inputCount, input, lookup_context.funcs.match, lookup_context.match_data[1], &match_length, match_positions) && match_backtrack (c, backtrackCount, backtrack, - lookup_context.funcs.match, lookup_context.match_data[0]) + lookup_context.funcs.match, lookup_context.match_data[0], + &start_index) && match_lookahead (c, lookaheadCount, lookahead, lookup_context.funcs.match, lookup_context.match_data[2], - match_length) - && apply_lookup (c, + match_length, &end_index) + && (c->buffer->unsafe_to_break_from_outbuffer (start_index, end_index), + apply_lookup (c, inputCount, match_positions, lookupCount, lookupRecord, - match_length); + match_length)); } struct ChainRule { inline void closure (hb_closure_context_t *c, ChainContextClosureLookupContext &lookup_context) const { TRACE_CLOSURE (this); const HeadlessArrayOf<USHORT> &input = StructAfter<HeadlessArrayOf<USHORT> > (backtrack);
--- a/gfx/harfbuzz/src/hb-ot-layout-private.hh +++ b/gfx/harfbuzz/src/hb-ot-layout-private.hh @@ -192,18 +192,17 @@ HB_INTERNAL void #define unicode_props() var2.u16[0] /* buffer var allocations, used during the GSUB/GPOS processing */ #define glyph_props() var1.u16[0] /* GDEF glyph properties */ #define lig_props() var1.u8[2] /* GSUB/GPOS ligature tracking */ #define syllable() var1.u8[3] /* GSUB/GPOS shaping boundaries */ -/* loop over syllables */ - +/* Loop over syllables. Based on foreach_cluster(). */ #define foreach_syllable(buffer, start, end) \ for (unsigned int \ _count = buffer->len, \ start = 0, end = _count ? _next_syllable (buffer, 0) : 0; \ start < _count; \ start = end, end = _next_syllable (buffer, start)) static inline unsigned int
--- a/gfx/harfbuzz/src/hb-ot-map.cc +++ b/gfx/harfbuzz/src/hb-ot-map.cc @@ -133,17 +133,21 @@ void hb_ot_map_builder_t::add_pause (uns current_stage[table_index]++; } void hb_ot_map_builder_t::compile (hb_ot_map_t &m, const int *coords, unsigned int num_coords) { - m.global_mask = 1; + ASSERT_STATIC (!(HB_GLYPH_FLAG_DEFINED & (HB_GLYPH_FLAG_DEFINED + 1))); + unsigned int global_bit_mask = HB_GLYPH_FLAG_DEFINED + 1; + unsigned int global_bit_shift = _hb_popcount32 (HB_GLYPH_FLAG_DEFINED); + + m.global_mask = global_bit_mask; unsigned int required_feature_index[2]; hb_tag_t required_feature_tag[2]; /* We default to applying required feature in stage 0. If the required * feature has a tag that is known to the shaper, we apply required feature * in the stage for that tag. */ unsigned int required_feature_stage[2] = {0, 0}; @@ -185,17 +189,18 @@ hb_ot_map_builder_t::compile (hb_ot_map_ feature_infos[j].stage[0] = MIN (feature_infos[j].stage[0], feature_infos[i].stage[0]); feature_infos[j].stage[1] = MIN (feature_infos[j].stage[1], feature_infos[i].stage[1]); } feature_infos.shrink (j + 1); } /* Allocate bits now */ - unsigned int next_bit = 1; + unsigned int next_bit = global_bit_shift + 1; + for (unsigned int i = 0; i < feature_infos.len; i++) { const feature_info_t *info = &feature_infos[i]; unsigned int bits_needed; if ((info->flags & F_GLOBAL) && info->max_value == 1) /* Uses the global bit */ @@ -244,18 +249,18 @@ hb_ot_map_builder_t::compile (hb_ot_map_ map->index[0] = feature_index[0]; map->index[1] = feature_index[1]; map->stage[0] = info->stage[0]; map->stage[1] = info->stage[1]; map->auto_zwnj = !(info->flags & F_MANUAL_ZWNJ); map->auto_zwj = !(info->flags & F_MANUAL_ZWJ); if ((info->flags & F_GLOBAL) && info->max_value == 1) { /* Uses the global bit */ - map->shift = 0; - map->mask = 1; + map->shift = global_bit_shift; + map->mask = global_bit_mask; } else { map->shift = next_bit; map->mask = (1u << (next_bit + bits_needed)) - (1u << next_bit); next_bit += bits_needed; m.global_mask |= (info->default_value << map->shift) & map->mask; } map->_1_mask = (1u << map->shift) & map->mask; map->needs_fallback = !found; @@ -282,17 +287,17 @@ hb_ot_map_builder_t::compile (hb_ot_map_ unsigned int last_num_lookups = 0; for (unsigned stage = 0; stage < current_stage[table_index]; stage++) { if (required_feature_index[table_index] != HB_OT_LAYOUT_NO_FEATURE_INDEX && required_feature_stage[table_index] == stage) add_lookups (m, face, table_index, required_feature_index[table_index], variations_index, - 1 /* mask */); + global_bit_mask); for (unsigned i = 0; i < m.features.len; i++) if (m.features[i].stage[table_index] == stage) add_lookups (m, face, table_index, m.features[i].index[table_index], variations_index, m.features[i].mask, m.features[i].auto_zwnj,
--- a/gfx/harfbuzz/src/hb-ot-shape-complex-arabic.cc +++ b/gfx/harfbuzz/src/hb-ot-shape-complex-arabic.cc @@ -316,17 +316,20 @@ arabic_joining (hb_buffer_t *buffer) if (unlikely (this_type == JOINING_TYPE_T)) { info[i].arabic_shaping_action() = NONE; continue; } const arabic_state_table_entry *entry = &arabic_state_table[state][this_type]; if (entry->prev_action != NONE && prev != (unsigned int) -1) + { info[prev].arabic_shaping_action() = entry->prev_action; + buffer->unsafe_to_break (prev, i + 1); + } info[i].arabic_shaping_action() = entry->curr_action; prev = i; state = entry->next_state; } for (unsigned int i = 0; i < buffer->context_len[1]; i++)
--- a/gfx/harfbuzz/src/hb-ot-shape-complex-hangul.cc +++ b/gfx/harfbuzz/src/hb-ot-shape-complex-hangul.cc @@ -197,16 +197,17 @@ preprocess_text_hangul (const hb_ot_shap /* * We could cache the width of the tone marks and the existence of dotted-circle, * but the use of the Hangul tone mark characters seems to be rare enough that * I didn't bother for now. */ if (start < end && end == buffer->out_len) { /* Tone mark follows a valid syllable; move it in front, unless it's zero width. */ + buffer->unsafe_to_break_from_outbuffer (start, buffer->idx); buffer->next_glyph (); if (!is_zero_width_char (font, u)) { buffer->merge_out_clusters (start, end + 1); hb_glyph_info_t *info = buffer->out_info; hb_glyph_info_t tone = info[end]; memmove (&info[start + 1], &info[start], (end - start) * sizeof (hb_glyph_info_t)); info[start] = tone; @@ -253,16 +254,17 @@ preprocess_text_hangul (const hb_ot_shap if (buffer->idx + 2 < count) { t = buffer->cur(+2).codepoint; if (isT (t)) tindex = t - TBase; /* Only used if isCombiningT (t); otherwise invalid. */ else t = 0; /* The next character was not a trailing jamo. */ } + buffer->unsafe_to_break (buffer->idx, buffer->idx + (t ? 3 : 2)); /* We've got a syllable <L,V,T?>; see if it can potentially be composed. */ if (isCombiningL (l) && isCombiningV (v) && (t == 0 || isCombiningT (t))) { /* Try to compose; if this succeeds, end is set to start+1. */ hb_codepoint_t s = SBase + (l - LBase) * NCount + (v - VBase) * TCount + tindex; if (font->has_glyph (s)) { @@ -317,16 +319,18 @@ preprocess_text_hangul (const hb_ot_shap if (font->has_glyph (new_s)) { buffer->replace_glyphs (2, 1, &new_s); if (unlikely (buffer->in_error)) return; end = start + 1; continue; } + else + buffer->unsafe_to_break (buffer->idx, buffer->idx + 2); /* Mark unsafe between LV and T. */ } /* Otherwise, decompose if font doesn't support <LV> or <LVT>, * or if having non-combining <LV,T>. Note that we already handled * combining <LV,T> above. */ if (!has_glyph || (!tindex && buffer->idx + 1 < count && @@ -363,16 +367,18 @@ preprocess_text_hangul (const hb_ot_shap info[i++].hangul_shaping_feature() = LJMO; info[i++].hangul_shaping_feature() = VJMO; if (i < end) info[i++].hangul_shaping_feature() = TJMO; if (buffer->cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES) buffer->merge_out_clusters (start, end); continue; } + else if ((!tindex && buffer->idx + 1 < count && isT (buffer->cur(+1).codepoint))) + buffer->unsafe_to_break (buffer->idx, buffer->idx + 2); /* Mark unsafe between LV and T. */ } if (has_glyph) { /* We didn't decompose the S, so just advance past it. */ end = start + 1; buffer->next_glyph (); continue;
--- a/gfx/harfbuzz/src/hb-ot-shape-complex-indic-private.hh +++ b/gfx/harfbuzz/src/hb-ot-shape-complex-indic-private.hh @@ -116,17 +116,17 @@ enum indic_syllabic_category_t { INDIC_SYLLABIC_CATEGORY_CONSONANT_HEAD_LETTER = OT_C, INDIC_SYLLABIC_CATEGORY_CONSONANT_KILLER = OT_M, /* U+17CD only. */ INDIC_SYLLABIC_CATEGORY_CONSONANT_MEDIAL = OT_CM, INDIC_SYLLABIC_CATEGORY_CONSONANT_PLACEHOLDER = OT_PLACEHOLDER, INDIC_SYLLABIC_CATEGORY_CONSONANT_PRECEDING_REPHA = OT_Repha, INDIC_SYLLABIC_CATEGORY_CONSONANT_PREFIXED = OT_X, /* Don't care. */ INDIC_SYLLABIC_CATEGORY_CONSONANT_SUBJOINED = OT_CM, INDIC_SYLLABIC_CATEGORY_CONSONANT_SUCCEEDING_REPHA = OT_N, - INDIC_SYLLABIC_CATEGORY_CONSONANT_WITH_STACKER = OT_Repha, /* TODO */ + INDIC_SYLLABIC_CATEGORY_CONSONANT_WITH_STACKER = OT_C, INDIC_SYLLABIC_CATEGORY_GEMINATION_MARK = OT_SM, INDIC_SYLLABIC_CATEGORY_INVISIBLE_STACKER = OT_Coeng, INDIC_SYLLABIC_CATEGORY_JOINER = OT_ZWJ, INDIC_SYLLABIC_CATEGORY_MODIFYING_LETTER = OT_X, INDIC_SYLLABIC_CATEGORY_NON_JOINER = OT_ZWNJ, INDIC_SYLLABIC_CATEGORY_NUKTA = OT_N, INDIC_SYLLABIC_CATEGORY_NUMBER = OT_PLACEHOLDER, INDIC_SYLLABIC_CATEGORY_NUMBER_JOINER = OT_PLACEHOLDER, /* Don't care. */
--- a/gfx/harfbuzz/src/hb-ot-shape-complex-indic.cc +++ b/gfx/harfbuzz/src/hb-ot-shape-complex-indic.cc @@ -619,16 +619,18 @@ setup_masks_indic (const hb_ot_shape_pla } static void setup_syllables (const hb_ot_shape_plan_t *plan HB_UNUSED, hb_font_t *font HB_UNUSED, hb_buffer_t *buffer) { find_syllables (buffer); + foreach_syllable (buffer, start, end) + buffer->unsafe_to_break (start, end); } static int compare_indic_order (const hb_glyph_info_t *pa, const hb_glyph_info_t *pb) { int a = pa->indic_position(); int b = pb->indic_position();
--- a/gfx/harfbuzz/src/hb-ot-shape-complex-myanmar.cc +++ b/gfx/harfbuzz/src/hb-ot-shape-complex-myanmar.cc @@ -292,16 +292,18 @@ setup_masks_myanmar (const hb_ot_shape_p } static void setup_syllables (const hb_ot_shape_plan_t *plan HB_UNUSED, hb_font_t *font HB_UNUSED, hb_buffer_t *buffer) { find_syllables (buffer); + foreach_syllable (buffer, start, end) + buffer->unsafe_to_break (start, end); } static int compare_myanmar_order (const hb_glyph_info_t *pa, const hb_glyph_info_t *pb) { int a = pa->myanmar_position(); int b = pb->myanmar_position();
--- a/gfx/harfbuzz/src/hb-ot-shape-complex-thai.cc +++ b/gfx/harfbuzz/src/hb-ot-shape-complex-thai.cc @@ -239,16 +239,17 @@ do_thai_pua_shaping (const hb_ot_shape_p const thai_above_state_machine_edge_t &above_edge = thai_above_state_machine[above_state][mt]; const thai_below_state_machine_edge_t &below_edge = thai_below_state_machine[below_state][mt]; above_state = above_edge.next_state; below_state = below_edge.next_state; /* At least one of the above/below actions is NOP. */ thai_action_t action = above_edge.action != NOP ? above_edge.action : below_edge.action; + buffer->unsafe_to_break (base, i); if (action == RD) info[base].codepoint = thai_pua_shape (info[base].codepoint, action, font); else info[i].codepoint = thai_pua_shape (info[i].codepoint, action, font); } }
--- a/gfx/harfbuzz/src/hb-ot-shape-complex-use.cc +++ b/gfx/harfbuzz/src/hb-ot-shape-complex-use.cc @@ -349,16 +349,18 @@ setup_topographical_masks (const hb_ot_s } static void setup_syllables (const hb_ot_shape_plan_t *plan, hb_font_t *font HB_UNUSED, hb_buffer_t *buffer) { find_syllables (buffer); + foreach_syllable (buffer, start, end) + buffer->unsafe_to_break (start, end); setup_rphf_mask (plan, buffer); setup_topographical_masks (plan, buffer); } static void clear_substitution_flags (const hb_ot_shape_plan_t *plan, hb_font_t *font HB_UNUSED, hb_buffer_t *buffer)
--- a/gfx/harfbuzz/src/hb-ot-shape-fallback.cc +++ b/gfx/harfbuzz/src/hb-ot-shape-fallback.cc @@ -302,16 +302,19 @@ position_mark (const hb_ot_shape_plan_t static inline void position_around_base (const hb_ot_shape_plan_t *plan, hb_font_t *font, hb_buffer_t *buffer, unsigned int base, unsigned int end) { hb_direction_t horiz_dir = HB_DIRECTION_INVALID; + + buffer->unsafe_to_break (base, end); + hb_glyph_extents_t base_extents; if (!font->get_glyph_extents (buffer->info[base].codepoint, &base_extents)) { /* If extents don't work, zero marks and go home. */ zero_mark_advances (buffer, base + 1, end); return; }
--- a/gfx/harfbuzz/src/hb-ot-shape.cc +++ b/gfx/harfbuzz/src/hb-ot-shape.cc @@ -270,34 +270,39 @@ hb_insert_dotted_circle (hb_buffer_t *bu buffer->next_glyph (); buffer->swap_buffers (); } static void hb_form_clusters (hb_buffer_t *buffer) { - if (!(buffer->scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_NON_ASCII) || - buffer->cluster_level != HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES) + if (!(buffer->scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_NON_ASCII)) return; /* Loop duplicated in hb_ensure_native_direction(), and in _hb-coretext.cc */ unsigned int base = 0; unsigned int count = buffer->len; hb_glyph_info_t *info = buffer->info; for (unsigned int i = 1; i < count; i++) { if (likely (!HB_UNICODE_GENERAL_CATEGORY_IS_MARK (_hb_glyph_info_get_general_category (&info[i])) && !_hb_glyph_info_is_joiner (&info[i]))) { - buffer->merge_clusters (base, i); + if (buffer->cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES) + buffer->merge_clusters (base, i); + else + buffer->unsafe_to_break (base, i); base = i; } } - buffer->merge_clusters (base, count); + if (buffer->cluster_level == HB_BUFFER_CLUSTER_LEVEL_MONOTONE_GRAPHEMES) + buffer->merge_clusters (base, count); + else + buffer->unsafe_to_break (base, count); } static void hb_ensure_native_direction (hb_buffer_t *buffer) { hb_direction_t direction = buffer->props.direction; /* TODO vertical: @@ -389,16 +394,18 @@ hb_ot_shape_setup_masks_fraction (hb_ot_ _hb_glyph_info_get_general_category (&info[start - 1]) == HB_UNICODE_GENERAL_CATEGORY_DECIMAL_NUMBER) start--; while (end < count && _hb_glyph_info_get_general_category (&info[end]) == HB_UNICODE_GENERAL_CATEGORY_DECIMAL_NUMBER) end++; + buffer->unsafe_to_break (start, end); + for (unsigned int j = start; j < i; j++) info[j].mask |= pre_mask; info[i].mask |= c->plan->frac_mask; for (unsigned int j = i + 1; j < end; j++) info[j].mask |= post_mask; i = end - 1; } @@ -504,19 +511,20 @@ hb_ot_hide_default_ignorables (hb_ot_sha if (i + 1 < count && cluster == info[i + 1].cluster) continue; /* Cluster survives; do nothing. */ if (j) { /* Merge cluster backward. */ if (cluster < info[j - 1].cluster) { + unsigned int mask = info[i].mask; unsigned int old_cluster = info[j - 1].cluster; for (unsigned k = j; k && info[k - 1].cluster == old_cluster; k--) - info[k - 1].cluster = cluster; + buffer->set_cluster (info[k - 1], cluster, mask); } continue; } if (i + 1 < count) buffer->merge_clusters (i, i + 2); /* Merge cluster forward. */ continue; @@ -572,18 +580,16 @@ hb_synthesize_glyph_classes (hb_ot_shape } } static inline void hb_ot_substitute_default (hb_ot_shape_context_t *c) { hb_buffer_t *buffer = c->buffer; - hb_ot_shape_initialize_masks (c); - hb_ot_mirror_chars (c); HB_BUFFER_ALLOCATE_VAR (buffer, glyph_index); _hb_ot_shape_normalize (c->plan, buffer, c->font); hb_ot_shape_setup_masks (c); @@ -777,16 +783,41 @@ hb_ot_position (hb_ot_shape_context_t *c /* Visual fallback goes here. */ if (c->fallback_positioning) _hb_ot_shape_fallback_kern (c->plan, c->font, c->buffer); _hb_buffer_deallocate_gsubgpos_vars (c->buffer); } +static inline void +hb_propagate_flags (hb_buffer_t *buffer) +{ + /* Propagate cluster-level glyph flags to be the same on all cluster glyphs. + * Simplifies using them. */ + + if (!(buffer->scratch_flags & HB_BUFFER_SCRATCH_FLAG_HAS_UNSAFE_TO_BREAK)) + return; + + hb_glyph_info_t *info = buffer->info; + + foreach_cluster (buffer, start, end) + { + unsigned int mask = 0; + for (unsigned int i = start; i < end; i++) + if (info[i].mask & HB_GLYPH_FLAG_UNSAFE_TO_BREAK) + { + mask = HB_GLYPH_FLAG_UNSAFE_TO_BREAK; + break; + } + if (mask) + for (unsigned int i = start; i < end; i++) + info[i].mask |= mask; + } +} /* Pull it all together! */ static void hb_ot_shape_internal (hb_ot_shape_context_t *c) { c->buffer->deallocate_var_all (); c->buffer->scratch_flags = HB_BUFFER_SCRATCH_FLAG_DEFAULT; @@ -803,33 +834,37 @@ hb_ot_shape_internal (hb_ot_shape_contex /* Save the original direction, we use it later. */ c->target_direction = c->buffer->props.direction; _hb_buffer_allocate_unicode_vars (c->buffer); c->buffer->clear_output (); + hb_ot_shape_initialize_masks (c); hb_set_unicode_props (c->buffer); hb_insert_dotted_circle (c->buffer, c->font); + hb_form_clusters (c->buffer); hb_ensure_native_direction (c->buffer); if (c->plan->shaper->preprocess_text) c->plan->shaper->preprocess_text (c->plan, c->buffer, c->font); hb_ot_substitute (c); hb_ot_position (c); hb_ot_hide_default_ignorables (c); if (c->plan->shaper->postprocess_glyphs) c->plan->shaper->postprocess_glyphs (c->plan, c->buffer, c->font); + hb_propagate_flags (c->buffer); + _hb_buffer_deallocate_unicode_vars (c->buffer); c->buffer->props.direction = c->target_direction; c->buffer->max_len = HB_BUFFER_MAX_LEN_DEFAULT; c->buffer->deallocate_var_all (); }
--- a/gfx/harfbuzz/src/hb-shape-plan.cc +++ b/gfx/harfbuzz/src/hb-shape-plan.cc @@ -155,17 +155,17 @@ hb_shape_plan_create2 (hb_face_t free (coords); free (features); return hb_shape_plan_get_empty (); } assert (props->direction != HB_DIRECTION_INVALID); hb_face_make_immutable (face); - shape_plan->default_shaper_list = shaper_list == NULL; + shape_plan->default_shaper_list = !shaper_list; shape_plan->face_unsafe = face; shape_plan->props = *props; shape_plan->num_user_features = num_user_features; shape_plan->user_features = features; if (num_user_features) memcpy (features, user_features, num_user_features * sizeof (hb_feature_t)); shape_plan->num_coords = num_coords; shape_plan->coords = coords; @@ -418,17 +418,17 @@ hb_shape_plan_coords_match (const hb_sha static hb_bool_t hb_shape_plan_matches (const hb_shape_plan_t *shape_plan, const hb_shape_plan_proposal_t *proposal) { return hb_segment_properties_equal (&shape_plan->props, &proposal->props) && hb_shape_plan_user_features_match (shape_plan, proposal) && hb_shape_plan_coords_match (shape_plan, proposal) && - ((shape_plan->default_shaper_list && proposal->shaper_list == NULL) || + ((shape_plan->default_shaper_list && !proposal->shaper_list) || (shape_plan->shaper_func == proposal->shaper_func)); } static inline hb_bool_t hb_non_global_user_features_present (const hb_feature_t *user_features, unsigned int num_user_features) { while (num_user_features) {
--- a/gfx/harfbuzz/src/hb-unicode-private.hh +++ b/gfx/harfbuzz/src/hb-unicode-private.hh @@ -100,16 +100,20 @@ HB_UNICODE_FUNCS_IMPLEMENT_CALLBACKS_SIM decomposed[ret] = 0; return ret; } inline unsigned int modified_combining_class (hb_codepoint_t unicode) { + /* XXX This hack belongs to the Arabic shaper: + * Put HAMZA ABOVE in the same class as SHADDA. */ + if (unlikely (unicode == 0x0654u)) unicode = 0x0651u; + /* XXX This hack belongs to the Myanmar shaper. */ if (unlikely (unicode == 0x1037u)) unicode = 0x103Au; /* XXX This hack belongs to the SEA shaper (for Tai Tham): * Reorder SAKOT to ensure it comes after any tone marks. */ if (unlikely (unicode == 0x1A60u)) return 254; /* XXX This hack belongs to the Tibetan shaper:
--- a/gfx/harfbuzz/src/hb-uniscribe.cc +++ b/gfx/harfbuzz/src/hb-uniscribe.cc @@ -1019,16 +1019,18 @@ retry: { hb_glyph_info_t *info = &buffer->info[i]; hb_glyph_position_t *pos = &buffer->pos[i]; /* TODO vertical */ pos->x_advance = x_mult * (int32_t) info->mask; pos->x_offset = x_mult * (backward ? -info->var1.i32 : info->var1.i32); pos->y_offset = y_mult * info->var2.i32; + + info->mask = HB_GLYPH_FLAG_UNSAFE_TO_BREAK; } if (backward) hb_buffer_reverse (buffer); /* Wow, done! */ return true; }
--- a/gfx/harfbuzz/src/hb-version.h +++ b/gfx/harfbuzz/src/hb-version.h @@ -32,20 +32,20 @@ #define HB_VERSION_H #include "hb-common.h" HB_BEGIN_DECLS #define HB_VERSION_MAJOR 1 -#define HB_VERSION_MINOR 4 -#define HB_VERSION_MICRO 8 +#define HB_VERSION_MINOR 5 +#define HB_VERSION_MICRO 1 -#define HB_VERSION_STRING "1.4.8" +#define HB_VERSION_STRING "1.5.1" #define HB_VERSION_ATLEAST(major,minor,micro) \ ((major)*10000+(minor)*100+(micro) <= \ HB_VERSION_MAJOR*10000+HB_VERSION_MINOR*100+HB_VERSION_MICRO) HB_EXTERN void hb_version (unsigned int *major,
--- a/gfx/layers/d3d11/TextureD3D11.cpp +++ b/gfx/layers/d3d11/TextureD3D11.cpp @@ -480,16 +480,22 @@ D3D11TextureData::Create(IntSize aSize, if (srcSurf && !DeviceManagerDx::Get()->HasCrashyInitData()) { uploadData.pSysMem = sourceMap.mData; uploadData.SysMemPitch = sourceMap.mStride; uploadData.SysMemSlicePitch = 0; // unused uploadDataPtr = &uploadData; } + // See bug 1397040 + RefPtr<ID3D10Multithread> mt; + device->QueryInterface((ID3D10Multithread**)getter_AddRefs(mt)); + + D3D11MTAutoEnter lock(mt.forget()); + RefPtr<ID3D11Texture2D> texture11; HRESULT hr = device->CreateTexture2D(&newDesc, uploadDataPtr, getter_AddRefs(texture11)); if (FAILED(hr) || !texture11) { gfxCriticalNote << "[D3D11] 2 CreateTexture2D failure Size: " << aSize << "texture11: " << texture11 << " Code: " << gfx::hexa(hr); return nullptr; }
--- a/gfx/layers/d3d11/TextureD3D11.h +++ b/gfx/layers/d3d11/TextureD3D11.h @@ -531,19 +531,25 @@ private: }; class D3D11MTAutoEnter { public: explicit D3D11MTAutoEnter(already_AddRefed<ID3D10Multithread> aMT) : mMT(aMT) { - mMT->Enter(); + if (mMT) { + mMT->Enter(); + } } - ~D3D11MTAutoEnter() { mMT->Leave(); } + ~D3D11MTAutoEnter() { + if (mMT) { + mMT->Leave(); + } + } private: RefPtr<ID3D10Multithread> mMT; }; } // namespace layers } // namespace mozilla
--- a/gfx/layers/ipc/CompositorBridgeChild.cpp +++ b/gfx/layers/ipc/CompositorBridgeChild.cpp @@ -115,47 +115,54 @@ void CompositorBridgeChild::AfterDestroy() { // Note that we cannot rely upon mCanSend here because we already set that to // false to prevent normal IPDL calls from being made after SendWillClose. // The only time we should not issue Send__delete__ is if the actor is already // destroyed, e.g. the compositor process crashed. if (!mActorDestroyed) { Send__delete__(this); + mActorDestroyed = true; } if (sCompositorBridge == this) { sCompositorBridge = nullptr; } } void CompositorBridgeChild::Destroy() { // This must not be called from the destructor! mTexturesWaitingRecycled.Clear(); + // Destroying the layer manager may cause all sorts of things to happen, so + // let's make sure there is still a reference to keep this alive whatever + // happens. + RefPtr<CompositorBridgeChild> selfRef = this; + if (!mCanSend) { + // We may have already called destroy but still have lingering references + // or CompositorBridgeChild::ActorDestroy was called. Ensure that we do our + // post destroy clean up no matter what. It is safe to call multiple times. + MessageLoop::current()->PostTask(NewRunnableMethod( + "CompositorBridgeChild::AfterDestroy", + selfRef, &CompositorBridgeChild::AfterDestroy)); return; } for (size_t i = 0; i < mTexturePools.Length(); i++) { mTexturePools[i]->Destroy(); } if (mSectionAllocator) { delete mSectionAllocator; mSectionAllocator = nullptr; } - // Destroying the layer manager may cause all sorts of things to happen, so - // let's make sure there is still a reference to keep this alive whatever - // happens. - RefPtr<CompositorBridgeChild> selfRef = this; - if (mLayerManager) { mLayerManager->Destroy(); mLayerManager = nullptr; } AutoTArray<PLayerTransactionChild*, 16> transactions; ManagedPLayerTransactionChild(transactions); for (int i = transactions.Length() - 1; i >= 0; --i) {
--- a/js/src/builtin/AtomicsObject.cpp +++ b/js/src/builtin/AtomicsObject.cpp @@ -1124,21 +1124,18 @@ AtomicsObject::initClass(JSContext* cx, if (!JS_DefineFunctions(cx, Atomics, AtomicsMethods)) return nullptr; if (!DefineToStringTag(cx, Atomics, cx->names().Atomics)) return nullptr; RootedValue AtomicsValue(cx, ObjectValue(*Atomics)); // Everything is set up, install Atomics on the global object. - if (!DefineProperty(cx, global, cx->names().Atomics, AtomicsValue, nullptr, nullptr, - JSPROP_RESOLVING)) - { + if (!DefineDataProperty(cx, global, cx->names().Atomics, AtomicsValue, JSPROP_RESOLVING)) return nullptr; - } global->setConstructor(JSProto_Atomics, AtomicsValue); return Atomics; } JSObject* js::InitAtomicsClass(JSContext* cx, HandleObject obj) {
--- a/js/src/builtin/Intl.cpp +++ b/js/src/builtin/Intl.cpp @@ -820,21 +820,18 @@ intl_availableLocales(JSContext* cx, Cou if (!lang) return false; char* p; while ((p = strchr(lang.get(), '_'))) *p = '-'; a = Atomize(cx, lang.get(), strlen(lang.get())); if (!a) return false; - if (!DefineProperty(cx, locales, a->asPropertyName(), TrueHandleValue, nullptr, nullptr, - JSPROP_ENUMERATE)) - { + if (!DefineDataProperty(cx, locales, a->asPropertyName(), TrueHandleValue)) return false; - } } #endif result.setObject(*locales); return true; } /** * Returns the object holding the internal properties for obj. @@ -1065,17 +1062,17 @@ CreateCollatorPrototype(JSContext* cx, H return nullptr; // 10.3.2 and 10.3.3 if (!JS_DefineProperties(cx, proto, collator_properties)) return nullptr; // 8.1 RootedValue ctorValue(cx, ObjectValue(*ctor)); - if (!DefineProperty(cx, Intl, cx->names().Collator, ctorValue, nullptr, nullptr, 0)) + if (!DefineDataProperty(cx, Intl, cx->names().Collator, ctorValue, 0)) return nullptr; return proto; } bool js::intl_Collator_availableLocales(JSContext* cx, unsigned argc, Value* vp) { @@ -1116,17 +1113,17 @@ js::intl_availableCollations(JSContext* RootedObject collations(cx, NewDenseEmptyArray(cx)); if (!collations) return false; uint32_t index = 0; // The first element of the collations array must be |null| per // ES2017 Intl, 10.2.3 Internal Slots. - if (!DefineElement(cx, collations, index++, NullHandleValue)) + if (!DefineDataElement(cx, collations, index++, NullHandleValue)) return false; RootedValue element(cx); for (uint32_t i = 0; i < count; i++) { const char* collation = uenum_next(values, nullptr, &status); if (U_FAILURE(status)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); return false; @@ -1139,17 +1136,17 @@ js::intl_availableCollations(JSContext* if (equal(collation, "standard") || equal(collation, "search")) continue; // ICU returns old-style keyword values; map them to BCP 47 equivalents. JSString* jscollation = JS_NewStringCopyZ(cx, uloc_toUnicodeLocaleType("co", collation)); if (!jscollation) return false; element = StringValue(jscollation); - if (!DefineElement(cx, collations, index++, element)) + if (!DefineDataElement(cx, collations, index++, element)) return false; } args.rval().setObject(*collations); return true; } /** @@ -1644,23 +1641,23 @@ CreateNumberFormatPrototype(JSContext* c HandlePropertyName name = cx->names().formatToParts; if (!GlobalObject::getSelfHostedFunction(cx, cx->global(), cx->names().NumberFormatFormatToParts, name, 1, &ftp)) { return nullptr; } - if (!DefineProperty(cx, proto, cx->names().formatToParts, ftp, nullptr, nullptr, 0)) + if (!DefineDataProperty(cx, proto, cx->names().formatToParts, ftp, 0)) return nullptr; } // 8.1 RootedValue ctorValue(cx, ObjectValue(*ctor)); - if (!DefineProperty(cx, Intl, cx->names().NumberFormat, ctorValue, nullptr, nullptr, 0)) + if (!DefineDataProperty(cx, Intl, cx->names().NumberFormat, ctorValue, 0)) return nullptr; constructor.set(ctor); return proto; } bool js::intl_NumberFormat_availableLocales(JSContext* cx, unsigned argc, Value* vp) @@ -2303,30 +2300,30 @@ intl_FormatNumberToParts(JSContext* cx, MOZ_ASSERT(lastEndIndex < endIndex); singlePart = NewBuiltinClassInstance<PlainObject>(cx); if (!singlePart) return false; propVal.setString(cx->names().*type); - if (!DefineProperty(cx, singlePart, cx->names().type, propVal)) + if (!DefineDataProperty(cx, singlePart, cx->names().type, propVal)) return false; JSLinearString* partSubstr = NewDependentString(cx, overallResult, lastEndIndex, endIndex - lastEndIndex); if (!partSubstr) return false; propVal.setString(partSubstr); - if (!DefineProperty(cx, singlePart, cx->names().value, propVal)) + if (!DefineDataProperty(cx, singlePart, cx->names().value, propVal)) return false; propVal.setObject(*singlePart); - if (!DefineElement(cx, partsArray, partIndex, propVal)) + if (!DefineDataElement(cx, partsArray, partIndex, propVal)) return false; lastEndIndex = endIndex; partIndex++; } while (true); MOZ_ASSERT(lastEndIndex == overallResult->length(), "result array must partition the entire string"); @@ -2524,17 +2521,17 @@ CreateDateTimeFormatPrototype(JSContext* return nullptr; // 12.4.2 and 12.4.3 if (!JS_DefineProperties(cx, proto, dateTimeFormat_properties)) return nullptr; // 8.1 RootedValue ctorValue(cx, ObjectValue(*ctor)); - if (!DefineProperty(cx, Intl, cx->names().DateTimeFormat, ctorValue, nullptr, nullptr, 0)) + if (!DefineDataProperty(cx, Intl, cx->names().DateTimeFormat, ctorValue, 0)) return nullptr; constructor.set(ctor); return proto; } bool js::AddMozDateTimeFormatConstructor(JSContext* cx, JS::Handle<JSObject*> intl) @@ -2611,17 +2608,17 @@ js::intl_availableCalendars(JSContext* c return false; uint32_t index = 0; // We need the default calendar for the locale as the first result. RootedValue element(cx); if (!DefaultCalendar(cx, locale, &element)) return false; - if (!DefineElement(cx, calendars, index++, element)) + if (!DefineDataElement(cx, calendars, index++, element)) return false; // Now get the calendars that "would make a difference", i.e., not the default. UErrorCode status = U_ZERO_ERROR; UEnumeration* values = ucal_getKeywordValuesForLocale("ca", locale.ptr(), false, &status); if (U_FAILURE(status)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); return false; @@ -2643,27 +2640,27 @@ js::intl_availableCalendars(JSContext* c // ICU returns old-style keyword values; map them to BCP 47 equivalents calendar = uloc_toUnicodeLocaleType("ca", calendar); JSString* jscalendar = JS_NewStringCopyZ(cx, calendar); if (!jscalendar) return false; element = StringValue(jscalendar); - if (!DefineElement(cx, calendars, index++, element)) + if (!DefineDataElement(cx, calendars, index++, element)) return false; // ICU doesn't return calendar aliases, append them here. for (const auto& calendarAlias : calendarAliases) { if (equal(calendar, calendarAlias.calendar)) { JSString* jscalendar = JS_NewStringCopyZ(cx, calendarAlias.alias); if (!jscalendar) return false; element = StringValue(jscalendar); - if (!DefineElement(cx, calendars, index++, element)) + if (!DefineDataElement(cx, calendars, index++, element)) return false; } } } args.rval().setObject(*calendars); return true; } @@ -3380,30 +3377,30 @@ intl_FormatToPartsDateTime(JSContext* cx RootedValue val(cx); auto AppendPart = [&](FieldType type, size_t beginIndex, size_t endIndex) { singlePart = NewBuiltinClassInstance<PlainObject>(cx); if (!singlePart) return false; partType = StringValue(cx->names().*type); - if (!DefineProperty(cx, singlePart, cx->names().type, partType)) + if (!DefineDataProperty(cx, singlePart, cx->names().type, partType)) return false; JSLinearString* partSubstr = NewDependentString(cx, overallResult, beginIndex, endIndex - beginIndex); if (!partSubstr) return false; val = StringValue(partSubstr); - if (!DefineProperty(cx, singlePart, cx->names().value, val)) + if (!DefineDataProperty(cx, singlePart, cx->names().value, val)) return false; val = ObjectValue(*singlePart); - if (!DefineElement(cx, partsArray, partIndex, val)) + if (!DefineDataElement(cx, partsArray, partIndex, val)) return false; lastEndIndex = endIndex; partIndex++; return true; }; int32_t fieldInt, beginIndexInt, endIndexInt; @@ -3588,17 +3585,17 @@ CreatePluralRulesPrototype(JSContext* cx if (!JS_DefineFunctions(cx, ctor, pluralRules_static_methods)) return nullptr; if (!JS_DefineFunctions(cx, proto, pluralRules_methods)) return nullptr; RootedValue ctorValue(cx, ObjectValue(*ctor)); - if (!DefineProperty(cx, Intl, cx->names().PluralRules, ctorValue, nullptr, nullptr, 0)) + if (!DefineDataProperty(cx, Intl, cx->names().PluralRules, ctorValue, 0)) return nullptr; return proto; } /* static */ bool js::GlobalObject::addPluralRulesConstructor(JSContext* cx, HandleObject intl) { @@ -3761,17 +3758,17 @@ js::intl_GetPluralCategories(JSContext* break; MOZ_ASSERT(catSize >= 0); JSString* str = NewStringCopyN<CanGC>(cx, cat, catSize); if (!str) return false; element.setString(str); - if (!DefineElement(cx, res, i++, element)) + if (!DefineDataElement(cx, res, i++, element)) return false; } while (true); args.rval().setObject(*res); return true; } @@ -3921,22 +3918,22 @@ js::intl_GetCalendarInfo(JSContext* cx, RootedObject info(cx, NewBuiltinClassInstance<PlainObject>(cx)); if (!info) return false; RootedValue v(cx); int32_t firstDayOfWeek = ucal_getAttribute(cal, UCAL_FIRST_DAY_OF_WEEK); v.setInt32(firstDayOfWeek); - if (!DefineProperty(cx, info, cx->names().firstDayOfWeek, v)) + if (!DefineDataProperty(cx, info, cx->names().firstDayOfWeek, v)) return false; int32_t minDays = ucal_getAttribute(cal, UCAL_MINIMAL_DAYS_IN_FIRST_WEEK); v.setInt32(minDays); - if (!DefineProperty(cx, info, cx->names().minDays, v)) + if (!DefineDataProperty(cx, info, cx->names().minDays, v)) return false; UCalendarWeekdayType prevDayType = ucal_getDayOfWeekType(cal, UCAL_SATURDAY, &status); if (U_FAILURE(status)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INTERNAL_INTL_ERROR); return false; } @@ -3974,20 +3971,20 @@ js::intl_GetCalendarInfo(JSContext* cx, } prevDayType = type; } MOZ_ASSERT(weekendStart.isInt32()); MOZ_ASSERT(weekendEnd.isInt32()); - if (!DefineProperty(cx, info, cx->names().weekendStart, weekendStart)) + if (!DefineDataProperty(cx, info, cx->names().weekendStart, weekendStart)) return false; - if (!DefineProperty(cx, info, cx->names().weekendEnd, weekendEnd)) + if (!DefineDataProperty(cx, info, cx->names().weekendEnd, weekendEnd)) return false; args.rval().setObject(*info); return true; } static void ReportBadKey(JSContext* cx, const Range<const JS::Latin1Char>& range) @@ -4301,17 +4298,17 @@ js::intl_ComputeDisplayNames(JSContext* stablePatternChars.latin1Range()) : ComputeSingleDisplayName(cx, fmt, dtpg, dnStyle, chars, stablePatternChars.twoByteRange()); if (!displayName) return false; // 5.b. Append the result string to result. v.setString(displayName); - if (!DefineElement(cx, result, i, v)) + if (!DefineDataElement(cx, result, i, v)) return false; } // 6. Return result. args.rval().setObject(*result); return true; } @@ -4324,24 +4321,24 @@ js::intl_GetLocaleInfo(JSContext* cx, un JSAutoByteString locale(cx, args[0].toString()); if (!locale) return false; RootedObject info(cx, NewBuiltinClassInstance<PlainObject>(cx)); if (!info) return false; - if (!DefineProperty(cx, info, cx->names().locale, args[0])) + if (!DefineDataProperty(cx, info, cx->names().locale, args[0])) return false; bool rtl = uloc_isRightToLeft(icuLocale(locale.ptr())); RootedValue dir(cx, StringValue(rtl ? cx->names().rtl : cx->names().ltr)); - if (!DefineProperty(cx, info, cx->names().direction, dir)) + if (!DefineDataProperty(cx, info, cx->names().direction, dir)) return false; args.rval().setObject(*info); return true; } const Class js::IntlClass = { js_Object_str, @@ -4398,21 +4395,18 @@ GlobalObject::initIntlObject(JSContext* return false; RootedObject numberFormatProto(cx), numberFormat(cx); numberFormatProto = CreateNumberFormatPrototype(cx, intl, global, &numberFormat); if (!numberFormatProto) return false; // The |Intl| object is fully set up now, so define the global property. RootedValue intlValue(cx, ObjectValue(*intl)); - if (!DefineProperty(cx, global, cx->names().Intl, intlValue, nullptr, nullptr, - JSPROP_RESOLVING)) - { + if (!DefineDataProperty(cx, global, cx->names().Intl, intlValue, JSPROP_RESOLVING)) return false; - } // Now that the |Intl| object is successfully added, we can OOM-safely fill // in all relevant reserved global slots. // Cache the various prototypes, for use in creating instances of these // objects with the proper [[Prototype]] as "the original value of // |Intl.Collator.prototype|" and similar. For builtin classes like // |String.prototype| we have |JSProto_*| that enables
--- a/js/src/builtin/Promise.cpp +++ b/js/src/builtin/Promise.cpp @@ -1984,17 +1984,17 @@ PerformPromiseAll(JSContext *cx, JS::For } // Step h. { // Scope for the JSAutoCompartment we need to work with valuesArray. We // mostly do this for performance; we could go ahead and do the define via // a cross-compartment proxy instead... JSAutoCompartment ac(cx, valuesArray); indexId = INT_TO_JSID(index); - if (!DefineProperty(cx, valuesArray, indexId, UndefinedHandleValue)) + if (!DefineDataProperty(cx, valuesArray, indexId, UndefinedHandleValue)) return false; } // Step i. // Sadly, because someone could have overridden // "resolve" on the canonical Promise constructor. RootedValue nextPromise(cx); RootedValue staticResolve(cx);
--- a/js/src/builtin/Reflect.cpp +++ b/js/src/builtin/Reflect.cpp @@ -235,15 +235,15 @@ js::InitReflect(JSContext* cx, HandleObj RootedObject reflect(cx, NewObjectWithGivenProto<PlainObject>(cx, proto, SingletonObject)); if (!reflect) return nullptr; if (!JS_DefineFunctions(cx, reflect, methods)) return nullptr; RootedValue value(cx, ObjectValue(*reflect)); - if (!DefineProperty(cx, obj, cx->names().Reflect, value, nullptr, nullptr, JSPROP_RESOLVING)) + if (!DefineDataProperty(cx, obj, cx->names().Reflect, value, JSPROP_RESOLVING)) return nullptr; obj->as<GlobalObject>().setConstructor(JSProto_Reflect, value); return reflect; }
--- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -433,17 +433,17 @@ class NodeBuilder * Bug 575416: instead of Atomize, lookup constant atoms in tbl file */ RootedAtom atom(cx, Atomize(cx, name, strlen(name))); if (!atom) return false; /* Represent "no node" as null and ensure users are not exposed to magic values. */ RootedValue optVal(cx, val.isMagic(JS_SERIALIZE_NO_NODE) ? NullValue() : val); - return DefineProperty(cx, obj, atom->asPropertyName(), optVal); + return DefineDataProperty(cx, obj, atom->asPropertyName(), optVal); } MOZ_MUST_USE bool newNodeLoc(TokenPos* pos, MutableHandleValue dst); MOZ_MUST_USE bool setNodeLoc(HandleObject node, TokenPos* pos); public: /* @@ -674,17 +674,17 @@ NodeBuilder::newArray(NodeVector& elts, RootedValue val(cx, elts[i]); MOZ_ASSERT_IF(val.isMagic(), val.whyMagic() == JS_SERIALIZE_NO_NODE); /* Represent "no node" as an array hole by not adding the value. */ if (val.isMagic(JS_SERIALIZE_NO_NODE)) continue; - if (!DefineElement(cx, array, i, val)) + if (!DefineDataElement(cx, array, i, val)) return false; } dst.setObject(*array); return true; } bool
--- a/js/src/builtin/SIMD.cpp +++ b/js/src/builtin/SIMD.cpp @@ -556,21 +556,18 @@ GlobalObject::initSimdObject(JSContext* if (!objProto) return false; globalSimdObject = NewObjectWithGivenProto(cx, &SimdObject::class_, objProto, SingletonObject); if (!globalSimdObject) return false; RootedValue globalSimdValue(cx, ObjectValue(*globalSimdObject)); - if (!DefineProperty(cx, global, cx->names().SIMD, globalSimdValue, nullptr, nullptr, - JSPROP_RESOLVING)) - { + if (!DefineDataProperty(cx, global, cx->names().SIMD, globalSimdValue, JSPROP_RESOLVING)) return false; - } global->setConstructor(JSProto_SIMD, globalSimdValue); return true; } static bool CreateSimdType(JSContext* cx, Handle<GlobalObject*> global, HandlePropertyName stringRepr, SimdType simdType, const JSFunctionSpec* methods) @@ -616,18 +613,18 @@ CreateSimdType(JSContext* cx, Handle<Glo } // Bind type descriptor to the global SIMD object RootedObject globalSimdObject(cx, GlobalObject::getOrCreateSimdGlobalObject(cx, global)); MOZ_ASSERT(globalSimdObject); RootedValue typeValue(cx, ObjectValue(*typeDescr)); if (!JS_DefineFunctions(cx, typeDescr, methods) || - !DefineProperty(cx, globalSimdObject, stringRepr, typeValue, nullptr, nullptr, - JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_RESOLVING)) + !DefineDataProperty(cx, globalSimdObject, stringRepr, typeValue, + JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_RESOLVING)) { return false; } uint32_t slot = uint32_t(typeDescr->type()); MOZ_ASSERT(globalSimdObject->as<NativeObject>().getReservedSlot(slot).isUndefined()); globalSimdObject->as<NativeObject>().setReservedSlot(slot, ObjectValue(*typeDescr)); return !!typeDescr;
--- a/js/src/builtin/TypedObject.cpp +++ b/js/src/builtin/TypedObject.cpp @@ -550,40 +550,40 @@ const JSFunctionSpec ArrayMetaTypeDescr: bool js::CreateUserSizeAndAlignmentProperties(JSContext* cx, HandleTypeDescr descr) { // If data is transparent, also store the public slots. if (descr->transparent()) { // byteLength RootedValue typeByteLength(cx, Int32Value(AssertedCast<int32_t>(descr->size()))); - if (!DefineProperty(cx, descr, cx->names().byteLength, typeByteLength, - nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT)) + if (!DefineDataProperty(cx, descr, cx->names().byteLength, typeByteLength, + JSPROP_READONLY | JSPROP_PERMANENT)) { return false; } // byteAlignment RootedValue typeByteAlignment(cx, Int32Value(descr->alignment())); - if (!DefineProperty(cx, descr, cx->names().byteAlignment, typeByteAlignment, - nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT)) + if (!DefineDataProperty(cx, descr, cx->names().byteAlignment, typeByteAlignment, + JSPROP_READONLY | JSPROP_PERMANENT)) { return false; } } else { // byteLength - if (!DefineProperty(cx, descr, cx->names().byteLength, UndefinedHandleValue, - nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT)) + if (!DefineDataProperty(cx, descr, cx->names().byteLength, UndefinedHandleValue, + JSPROP_READONLY | JSPROP_PERMANENT)) { return false; } // byteAlignment - if (!DefineProperty(cx, descr, cx->names().byteAlignment, UndefinedHandleValue, - nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT)) + if (!DefineDataProperty(cx, descr, cx->names().byteAlignment, UndefinedHandleValue, + JSPROP_READONLY | JSPROP_PERMANENT)) { return false; } } return true; } @@ -608,25 +608,25 @@ ArrayMetaTypeDescr::create(JSContext* cx obj->initReservedSlot(JS_DESCR_SLOT_STRING_REPR, StringValue(stringRepr)); obj->initReservedSlot(JS_DESCR_SLOT_ALIGNMENT, Int32Value(elementType->alignment())); obj->initReservedSlot(JS_DESCR_SLOT_SIZE, Int32Value(size)); obj->initReservedSlot(JS_DESCR_SLOT_OPAQUE, BooleanValue(elementType->opaque())); obj->initReservedSlot(JS_DESCR_SLOT_ARRAY_ELEM_TYPE, ObjectValue(*elementType)); obj->initReservedSlot(JS_DESCR_SLOT_ARRAY_LENGTH, Int32Value(length)); RootedValue elementTypeVal(cx, ObjectValue(*elementType)); - if (!DefineProperty(cx, obj, cx->names().elementType, elementTypeVal, - nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT)) + if (!DefineDataProperty(cx, obj, cx->names().elementType, elementTypeVal, + JSPROP_READONLY | JSPROP_PERMANENT)) { return nullptr; } RootedValue lengthValue(cx, NumberValue(length)); - if (!DefineProperty(cx, obj, cx->names().length, lengthValue, - nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT)) + if (!DefineDataProperty(cx, obj, cx->names().length, lengthValue, + JSPROP_READONLY | JSPROP_PERMANENT)) { return nullptr; } if (!CreateUserSizeAndAlignmentProperties(cx, obj)) return nullptr; // All arrays with the same element type have the same prototype. This @@ -838,18 +838,18 @@ StructMetaTypeDescr::create(JSContext* c // Collect field name and type object RootedValue fieldName(cx, IdToValue(id)); if (!fieldNames.append(fieldName)) return nullptr; if (!fieldTypeObjs.append(ObjectValue(*fieldType))) return nullptr; // userFieldTypes[id] = typeObj - if (!DefineProperty(cx, userFieldTypes, id, fieldTypeObjs[i], nullptr, nullptr, - JSPROP_READONLY | JSPROP_PERMANENT)) + if (!DefineDataProperty(cx, userFieldTypes, id, fieldTypeObjs[i], + JSPROP_READONLY | JSPROP_PERMANENT)) { return nullptr; } // Append "f:Type" to the string repr if (i > 0 && !stringBuffer.append(", ")) return nullptr; if (!stringBuffer.append(JSID_TO_ATOM(id))) @@ -867,18 +867,18 @@ StructMetaTypeDescr::create(JSContext* c return nullptr; } MOZ_ASSERT(offset.value() >= 0); if (!fieldOffsets.append(Int32Value(offset.value()))) return nullptr; // userFieldOffsets[id] = offset RootedValue offsetValue(cx, Int32Value(offset.value())); - if (!DefineProperty(cx, userFieldOffsets, id, offsetValue, nullptr, nullptr, - JSPROP_READONLY | JSPROP_PERMANENT)) + if (!DefineDataProperty(cx, userFieldOffsets, id, offsetValue, + JSPROP_READONLY | JSPROP_PERMANENT)) { return nullptr; } // Add space for this field to the total struct size. sizeSoFar = offset + fieldType->size(); if (!sizeSoFar.isValid()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_TOO_BIG); @@ -956,24 +956,24 @@ StructMetaTypeDescr::create(JSContext* c } // Create data properties fieldOffsets and fieldTypes if (!FreezeObject(cx, userFieldOffsets)) return nullptr; if (!FreezeObject(cx, userFieldTypes)) return nullptr; RootedValue userFieldOffsetsValue(cx, ObjectValue(*userFieldOffsets)); - if (!DefineProperty(cx, descr, cx->names().fieldOffsets, userFieldOffsetsValue, - nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT)) + if (!DefineDataProperty(cx, descr, cx->names().fieldOffsets, userFieldOffsetsValue, + JSPROP_READONLY | JSPROP_PERMANENT)) { return nullptr; } RootedValue userFieldTypesValue(cx, ObjectValue(*userFieldTypes)); - if (!DefineProperty(cx, descr, cx->names().fieldTypes, userFieldTypesValue, - nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT)) + if (!DefineDataProperty(cx, descr, cx->names().fieldTypes, userFieldTypesValue, + JSPROP_READONLY | JSPROP_PERMANENT)) { return nullptr; } if (!CreateUserSizeAndAlignmentProperties(cx, descr)) return nullptr; Rooted<TypedProto*> prototypeObj(cx); @@ -1150,17 +1150,17 @@ DefineSimpleTypeDescr(JSContext* cx, // not being user accessible, but we still create one for consistency. Rooted<TypedProto*> proto(cx); proto = NewObjectWithGivenProto<TypedProto>(cx, objProto, TenuredObject); if (!proto) return false; descr->initReservedSlot(JS_DESCR_SLOT_TYPROTO, ObjectValue(*proto)); RootedValue descrValue(cx, ObjectValue(*descr)); - if (!DefineProperty(cx, module, className, descrValue, nullptr, nullptr, 0)) + if (!DefineDataProperty(cx, module, className, descrValue, 0)) return false; if (!CreateTraceList(cx, descr)) return false; if (!cx->zone()->addTypeDescrObject(cx, descr)) return false; @@ -1197,18 +1197,18 @@ DefineMetaTypeDescr(JSContext* cx, if (!objProto) return nullptr; RootedObject protoProto(cx); protoProto = NewObjectWithGivenProto<PlainObject>(cx, objProto, SingletonObject); if (!protoProto) return nullptr; RootedValue protoProtoValue(cx, ObjectValue(*protoProto)); - if (!DefineProperty(cx, proto, cx->names().prototype, protoProtoValue, - nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT)) + if (!DefineDataProperty(cx, proto, cx->names().prototype, protoProtoValue, + JSPROP_READONLY | JSPROP_PERMANENT)) { return nullptr; } // Create ctor itself const int constructorLength = 2; RootedFunction ctor(cx); @@ -1271,41 +1271,41 @@ GlobalObject::initTypedObjectModule(JSCo RootedObject arrayType(cx); arrayType = DefineMetaTypeDescr<ArrayMetaTypeDescr>( cx, "ArrayType", global, module, TypedObjectModuleObject::ArrayTypePrototype); if (!arrayType) return false; RootedValue arrayTypeValue(cx, ObjectValue(*arrayType)); - if (!DefineProperty(cx, module, cx->names().ArrayType, arrayTypeValue, - nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT)) + if (!DefineDataProperty(cx, module, cx->names().ArrayType, arrayTypeValue, + JSPROP_READONLY | JSPROP_PERMANENT)) { return false; } // StructType. RootedObject structType(cx); structType = DefineMetaTypeDescr<StructMetaTypeDescr>( cx, "StructType", global, module, TypedObjectModuleObject::StructTypePrototype); if (!structType) return false; RootedValue structTypeValue(cx, ObjectValue(*structType)); - if (!DefineProperty(cx, module, cx->names().StructType, structTypeValue, - nullptr, nullptr, JSPROP_READONLY | JSPROP_PERMANENT)) + if (!DefineDataProperty(cx, module, cx->names().StructType, structTypeValue, + JSPROP_READONLY | JSPROP_PERMANENT)) { return false; } // Everything is setup, install module on the global object: RootedValue moduleValue(cx, ObjectValue(*module)); - if (!DefineProperty(cx, global, cx->names().TypedObject, moduleValue, nullptr, nullptr, - JSPROP_RESOLVING)) + if (!DefineDataProperty(cx, global, cx->names().TypedObject, moduleValue, + JSPROP_RESOLVING)) { return false; } global->setConstructor(JSProto_TypedObject, moduleValue); return module; }
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/cacheir/bug1397026.js @@ -0,0 +1,43 @@ +function f1() { + var o = {}; + var values = []; + for (var i = 0; i < 6; ++i) { + var desc = { + value: i, + writable: true, + configurable: true, + enumerable: true + }; + try { + Object.defineProperty(o, "p", desc); + } catch (e) { + } + if (i === 1) { + Object.defineProperty(o, "p", {configurable: false}); + } + values.push(o.p); + } + assertEq(values.toString(), "0,1,1,1,1,1"); +} +f1(); + +function f2() { + var o = {}; + for (var i = 0; i < 6; ++i) { + var desc = { + value: i, + writable: true, + configurable: true, + enumerable: true + }; + try { + Object.defineProperty(o, "p", desc); + } catch (e) { + } + assertEq(Object.getOwnPropertyDescriptor(o, "p").enumerable, true); + if (i > 0) { + Object.defineProperty(o, "p", {enumerable: false}); + } + } +} +f2();
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/ion/array-push-multiple-frozen.js @@ -0,0 +1,87 @@ +// |jit-test| --no-threads + +// This test case check's Ion ability to recover from an allocation failure in +// the inlining of Array.prototype.push, when given multiple arguments. Note, +// that the following are not equivalent in case of failures: +// +// arr = []; +// arr.push(1,2,3); // OOM ---> arr == [] +// +// arr = []; +// arr.push(1); +// arr.push(2); // OOM --> arr == [1] +// arr.push(3); + +function canIoncompile() { + while (true) { + var i = inIon(); + if (i) + return i; + } +} + +if (!("oomAtAllocation" in this)) + quit(); +if (canIoncompile() != true) + quit(); +if ("gczeal" in this) + gczeal(0); + +function pushLimits(limit, offset, arr, freeze) { + arr = arr || []; + arr.push(0,1,2,3,4,5,6,7,8,9); + arr.length = offset; + var l = arr.length; + var was = inIon(); + oomAtAllocation(limit); + try { + for (var i = 0; i < 50; i++) + arr.push(0,1,2,3,4,5,6,7,8,9); + if (freeze) + arr.frozen(); + for (var i = 0; i < 100; i++) + arr.push(0,1,2,3,4,5,6,7,8,9); + } catch (e) { + // Catch OOM. + } + resetOOMFailure(); + assertEq(arr.length % 10, l); + // Check for a bailout. + var is = inIon(); + return was ? is ? 1 : 2 : 0; +} + +// We need this limit to be high enough to be able to OSR in the for-loop of +// pushLimits. +var limit = 1024 * 1024 * 1024; +while(true) { + var res = pushLimits(limit, 0); + + if (res == 0) { + limit = 1024 * 1024 * 1024; + } else if (res == 1) { // Started and finished in Ion. + if (limit == 0) // If we are not in the Jit. + break; + // We want to converge quickly to a state where the memory is limited + // enough to cause failures in array.prototype.push. + limit = (limit / 2) | 0; + } else if (res == 2) { // Started in Ion, and finished in Baseline. + if (limit < 10) { + // This is used to offset the OOM location, such that we can test + // each steps of the Array.push function, when it is jitted. + for (var off = 1; off < 10; off++) { + var arr = []; + try { + pushLimits(limit, off, arr, true); + } catch (e) { + // Cacth OOM produced while generating the error message. + } + if (arr.length > 10) assertEq(arr[arr.length - 1], 9); + else assertEq(arr[arr.length - 1], arr.length - 1); + } + } + if (limit == 1) + break; + limit--; + } +}
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/ion/array-push-multiple.js @@ -0,0 +1,74 @@ +// |jit-test| --no-threads + +// This test case check's Ion ability to recover from an allocation failure in +// the inlining of Array.prototype.push, when given multiple arguments. Note, +// that the following are not equivalent in case of failures: +// +// arr = []; +// arr.push(1,2,3); // OOM ---> arr == [] +// +// arr = []; +// arr.push(1); +// arr.push(2); // OOM --> arr == [1] +// arr.push(3); + +function canIoncompile() { + while (true) { + var i = inIon(); + if (i) + return i; + } +} + +if (!("oomAtAllocation" in this)) + quit(); +if (canIoncompile() != true) + quit(); +if ("gczeal" in this) + gczeal(0); + +function pushLimits(limit, offset) { + var arr = [0,1,2,3,4,5,6,7,8,9]; + arr.length = offset; + var l = arr.length; + var was = inIon(); + oomAtAllocation(limit); + try { + for (var i = 0; i < 100; i++) + arr.push(0,1,2,3,4,5,6,7,8,9); + } catch (e) { + // Catch OOM. + } + resetOOMFailure(); + assertEq(arr.length % 10, l); + // Check for a bailout. + var is = inIon(); + return was ? is ? 1 : 2 : 0; +} + +// We need this limit to be high enough to be able to OSR in the for-loop of +// pushLimits. +var limit = 1024 * 1024 * 1024; +while(true) { + var res = pushLimits(limit, 0); + + if (res == 0) { + limit = 1024 * 1024 * 1024; + } else if (res == 1) { // Started and finished in Ion. + if (limit == 0) // If we are not in the Jit. + break; + // We want to converge quickly to a state where the memory is limited + // enough to cause failures in array.prototype.push. + limit = (limit / 2) | 0; + } else if (res == 2) { // Started in Ion, and finished in Baseline. + if (limit < 10) { + // This is used to offset the OOM location, such that we can test + // each steps of the Array.push function, when it is jitted. + for (var off = 1; off < 10; off++) + pushLimits(limit, off); + } + if (limit == 1) + break; + limit--; + } +}
--- a/js/src/jit/CacheIR.cpp +++ b/js/src/jit/CacheIR.cpp @@ -2707,32 +2707,43 @@ EmitStoreSlotAndReturn(CacheIRWriter& wr } else { size_t offset = nobj->dynamicSlotIndex(shape->slot()) * sizeof(Value); writer.storeDynamicSlot(objId, offset, rhsId); } writer.returnFromIC(); } static Shape* -LookupShapeForSetSlot(NativeObject* obj, jsid id) +LookupShapeForSetSlot(JSOp op, NativeObject* obj, jsid id) { Shape* shape = obj->lookupPure(id); - if (shape && shape->hasSlot() && shape->hasDefaultSetter() && shape->writable()) - return shape; - return nullptr; + if (!shape || !shape->hasSlot() || !shape->hasDefaultSetter() || !shape->writable()) + return nullptr; + + // If this is an op like JSOP_INITELEM / [[DefineOwnProperty]], the + // property's attributes may have to be changed too, so make sure it's a + // simple data property. + if (IsPropertyInitOp(op) && (!shape->configurable() || + !shape->enumerable() || + !shape->hasDefaultGetter())) + { + return nullptr; + } + + return shape; } static bool -CanAttachNativeSetSlot(JSContext* cx, HandleObject obj, HandleId id, +CanAttachNativeSetSlot(JSContext* cx, JSOp op, HandleObject obj, HandleId id, bool* isTemporarilyUnoptimizable, MutableHandleShape propShape) { if (!obj->isNative()) return false; - propShape.set(LookupShapeForSetSlot(&obj->as<NativeObject>(), id)); + propShape.set(LookupShapeForSetSlot(op, &obj->as<NativeObject>(), id)); if (!propShape) return false; ObjectGroup* group = JSObject::getGroup(cx, obj); if (!group) { cx->recoverFromOutOfMemory(); return false; } @@ -2750,17 +2761,17 @@ CanAttachNativeSetSlot(JSContext* cx, Ha return true; } bool SetPropIRGenerator::tryAttachNativeSetSlot(HandleObject obj, ObjOperandId objId, HandleId id, ValOperandId rhsId) { RootedShape propShape(cx_); - if (!CanAttachNativeSetSlot(cx_, obj, id, isTemporarilyUnoptimizable_, &propShape)) + if (!CanAttachNativeSetSlot(cx_, JSOp(*pc_), obj, id, isTemporarilyUnoptimizable_, &propShape)) return false; if (mode_ == ICState::Mode::Megamorphic && cacheKind_ == CacheKind::SetProp) { writer.megamorphicStoreSlot(objId, JSID_TO_ATOM(id)->asPropertyName(), rhsId, typeCheckInfo_.needsTypeBarrier()); writer.returnFromIC(); trackAttached("MegamorphicNativeSlot"); return true; @@ -2794,17 +2805,17 @@ SetPropIRGenerator::tryAttachUnboxedExpa { if (!obj->is<UnboxedPlainObject>()) return false; UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando(); if (!expando) return false; - Shape* propShape = LookupShapeForSetSlot(expando, id); + Shape* propShape = LookupShapeForSetSlot(JSOp(*pc_), expando, id); if (!propShape) return false; maybeEmitIdGuard(id); writer.guardGroup(objId, obj->group()); ObjOperandId expandoId = writer.guardAndLoadUnboxedExpando(objId); writer.guardShape(expandoId, expando->lastProperty()); @@ -3475,17 +3486,19 @@ SetPropIRGenerator::tryAttachDOMProxyExp MOZ_ASSERT(!expandoVal.isUndefined(), "How did a missing expando manage to shadow things?"); auto expandoAndGeneration = static_cast<ExpandoAndGeneration*>(expandoVal.toPrivate()); MOZ_ASSERT(expandoAndGeneration); expandoObj = &expandoAndGeneration->expando.toObject(); } RootedShape propShape(cx_); - if (CanAttachNativeSetSlot(cx_, expandoObj, id, isTemporarilyUnoptimizable_, &propShape)) { + if (CanAttachNativeSetSlot(cx_, JSOp(*pc_), expandoObj, id, isTemporarilyUnoptimizable_, + &propShape)) + { maybeEmitIdGuard(id); ObjOperandId expandoObjId = guardDOMProxyExpandoObjectAndShape(obj, objId, expandoVal, expandoObj); NativeObject* nativeExpandoObj = &expandoObj->as<NativeObject>(); writer.guardGroup(expandoObjId, nativeExpandoObj->group()); typeCheckInfo_.set(nativeExpandoObj->group(), id); @@ -3616,18 +3629,21 @@ SetPropIRGenerator::tryAttachWindowProxy // those. MOZ_ASSERT(obj->getClass() == cx_->runtime()->maybeWindowProxyClass()); MOZ_ASSERT(ToWindowIfWindowProxy(obj) == cx_->global()); // Now try to do the set on the Window (the current global). Handle<GlobalObject*> windowObj = cx_->global(); RootedShape propShape(cx_); - if (!CanAttachNativeSetSlot(cx_, windowObj, id, isTemporarilyUnoptimizable_, &propShape)) + if (!CanAttachNativeSetSlot(cx_, JSOp(*pc_), windowObj, id, isTemporarilyUnoptimizable_, + &propShape)) + { return false; + } maybeEmitIdGuard(id); writer.guardClass(objId, GuardClassKind::WindowProxy); ObjOperandId windowObjId = writer.loadObject(windowObj); writer.guardShape(windowObjId, windowObj->lastProperty()); writer.guardGroup(windowObjId, windowObj->group());
--- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -785,28 +785,31 @@ IonBuilder::inlineArrayJoin(CallInfo& ca MOZ_TRY(resumeAfter(ins)); return InliningStatus_Inlined; } IonBuilder::InliningResult IonBuilder::inlineArrayPush(CallInfo& callInfo) { - if (callInfo.argc() != 1 || callInfo.constructing()) { + const uint32_t inlineArgsLimit = 10; + if (callInfo.argc() < 1 || callInfo.argc() > inlineArgsLimit || callInfo.constructing()) {