From 9bb59e4515c48678e144dbfd667a27cdad7f08ae Mon Sep 17 00:00:00 2001 From: Michael Comella Date: Mon, 3 Feb 2014 15:20:00 -0800 Subject: [PATCH 01/78] Bug 967134 - Add documentation link to UITest. r=margaret --- mobile/android/base/tests/UITest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mobile/android/base/tests/UITest.java b/mobile/android/base/tests/UITest.java index f9d6cfac8829..507851a2ff14 100644 --- a/mobile/android/base/tests/UITest.java +++ b/mobile/android/base/tests/UITest.java @@ -29,6 +29,9 @@ import java.util.HashMap; * provide a framework to improve upon the issues discovered with the previous BaseTest * implementation by providing simple test authorship and framework extension, consistency, * and reliability. + * + * For documentation on writing tests and extending the framework, see + * https://wiki.mozilla.org/Mobile/Fennec/Android/UITest */ abstract class UITest extends ActivityInstrumentationTestCase2 implements UITestContext { From dcaf7dc6cb0b9c65a82a4d45d79fad2c13ed78cb Mon Sep 17 00:00:00 2001 From: Jared Wein Date: Mon, 3 Feb 2014 15:08:39 -0500 Subject: [PATCH 02/78] Bug 882807 - [Australis] Invert the icons for the subview-originating button as well as add the arrow icon. r=Gijs --- browser/themes/linux/jar.mn | 11 ++++---- browser/themes/osx/browser.css | 16 +++++++++++ .../osx/customizableui/panelUIOverlay.css | 6 ++++ browser/themes/osx/jar.mn | 16 ++++++----- .../customizableui/panelUIOverlay.inc.css | 16 +++++++++-- .../subView-arrow-back-inverted.png | Bin 0 -> 307 bytes .../subView-arrow-back-inverted@2x.png | Bin 0 -> 667 bytes browser/themes/shared/menupanel.inc.css | 16 +++++++++++ browser/themes/windows/jar.mn | 26 ++++++++++-------- 9 files changed, 80 insertions(+), 27 deletions(-) create mode 100644 browser/themes/shared/customizableui/subView-arrow-back-inverted.png create mode 100644 browser/themes/shared/customizableui/subView-arrow-back-inverted@2x.png diff --git a/browser/themes/linux/jar.mn b/browser/themes/linux/jar.mn index 37084529a801..05906deac78b 100644 --- a/browser/themes/linux/jar.mn +++ b/browser/themes/linux/jar.mn @@ -23,11 +23,6 @@ browser.jar: * skin/classic/browser/browser.css * skin/classic/browser/browser-lightweightTheme.css skin/classic/browser/click-to-play-warning-stripes.png - skin/classic/browser/customizableui/background-noise-toolbar.png (customizableui/background-noise-toolbar.png) - skin/classic/browser/customizableui/customizeMode-gridTexture.png (customizableui/customizeMode-gridTexture.png) - skin/classic/browser/customizableui/customizeMode-separatorHorizontal.png (customizableui/customizeMode-separatorHorizontal.png) - skin/classic/browser/customizableui/customizeMode-separatorVertical.png (customizableui/customizeMode-separatorVertical.png) - skin/classic/browser/customizableui/customizeFavicon.ico (../shared/customizableui/customizeFavicon.ico) * skin/classic/browser/engineManager.css skin/classic/browser/fullscreen-darknoise.png skin/classic/browser/Geolocation-16.png @@ -73,7 +68,13 @@ browser.jar: skin/classic/browser/webRTC-shareDevice-16.png skin/classic/browser/webRTC-shareDevice-64.png skin/classic/browser/webRTC-sharingDevice-16.png + skin/classic/browser/customizableui/background-noise-toolbar.png (customizableui/background-noise-toolbar.png) + skin/classic/browser/customizableui/customizeMode-gridTexture.png (customizableui/customizeMode-gridTexture.png) + skin/classic/browser/customizableui/customizeMode-separatorHorizontal.png (customizableui/customizeMode-separatorHorizontal.png) + skin/classic/browser/customizableui/customizeMode-separatorVertical.png (customizableui/customizeMode-separatorVertical.png) + skin/classic/browser/customizableui/customizeFavicon.ico (../shared/customizableui/customizeFavicon.ico) * skin/classic/browser/customizableui/panelUIOverlay.css (customizableui/panelUIOverlay.css) + skin/classic/browser/customizableui/subView-arrow-back-inverted.png (../shared/customizableui/subView-arrow-back-inverted.png) skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css) skin/classic/browser/downloads/buttons.png (downloads/buttons.png) skin/classic/browser/downloads/contentAreaDownloadsView.css (downloads/contentAreaDownloadsView.css) diff --git a/browser/themes/osx/browser.css b/browser/themes/osx/browser.css index 79cb8d282f2b..88bcad0b0d67 100644 --- a/browser/themes/osx/browser.css +++ b/browser/themes/osx/browser.css @@ -1024,11 +1024,19 @@ toolbar .toolbarbutton-1:not([type="menu-button"]), -moz-image-region: rect(0px, 320px, 64px, 256px); } + #bookmarks-menu-button[cui-areatype="menu-panel"].panel-multiview-anchor { + -moz-image-region: rect(64px, 320px, 128px, 256px); + } + #history-panelmenu[cui-areatype="menu-panel"], toolbarpaletteitem[place="palette"] > #history-panelmenu { -moz-image-region: rect(0px, 448px, 64px, 384px); } + #history-panelmenu[cui-areatype="menu-panel"].panel-multiview-anchor { + -moz-image-region: rect(64px, 448px, 128px, 384px); + } + #downloads-button[cui-areatype="menu-panel"], toolbarpaletteitem[place="palette"] > #downloads-button { -moz-image-region: rect(0px, 512px, 64px, 448px); @@ -1069,6 +1077,10 @@ toolbar .toolbarbutton-1:not([type="menu-button"]), -moz-image-region: rect(0, 960px, 64px, 896px); } + #characterencoding-button[cui-areatype="menu-panel"].panel-multiview-anchor { + -moz-image-region: rect(64px, 960px, 128px, 896px); + } + #new-window-button[cui-areatype="menu-panel"], toolbarpaletteitem[place="palette"] > #new-window-button { -moz-image-region: rect(0px, 1024px, 64px, 960px); @@ -1109,6 +1121,10 @@ toolbar .toolbarbutton-1:not([type="menu-button"]), -moz-image-region: rect(0px, 1472px, 64px, 1408px); } + #developer-button[cui-areatype="menu-panel"].panel-multiview-anchor { + -moz-image-region: rect(64px, 1472px, 128px, 1408px); + } + #preferences-button[cui-areatype="menu-panel"], toolbarpaletteitem[place="palette"] > #preferences-button { -moz-image-region: rect(0px, 1536px, 64px, 1472px); diff --git a/browser/themes/osx/customizableui/panelUIOverlay.css b/browser/themes/osx/customizableui/panelUIOverlay.css index 521f047bebe4..3b93f5cff889 100644 --- a/browser/themes/osx/customizableui/panelUIOverlay.css +++ b/browser/themes/osx/customizableui/panelUIOverlay.css @@ -5,6 +5,12 @@ %include ../../shared/customizableui/panelUIOverlay.inc.css @media (min-resolution: 2dppx) { + toolbarbutton.panel-multiview-anchor { + background-image: url(chrome://browser/skin/customizableui/subView-arrow-back-inverted@2x.png), + linear-gradient(rgba(255,255,255,0.3), rgba(255,255,255,0)); + background-size: 16px; + } + #PanelUI-customize { list-style-image: url(chrome://browser/skin/menuPanel-customize@2x.png); } diff --git a/browser/themes/osx/jar.mn b/browser/themes/osx/jar.mn index 4b0b95d671a3..51c8546f5e7d 100644 --- a/browser/themes/osx/jar.mn +++ b/browser/themes/osx/jar.mn @@ -23,13 +23,6 @@ browser.jar: * skin/classic/browser/browser.css (browser.css) * skin/classic/browser/browser-lightweightTheme.css skin/classic/browser/click-to-play-warning-stripes.png - skin/classic/browser/customizableui/background-noise-toolbar.png (customizableui/background-noise-toolbar.png) - skin/classic/browser/customizableui/customize-titleBar-toggle.png (customizableui/customize-titleBar-toggle.png) - skin/classic/browser/customizableui/customize-titleBar-toggle@2x.png (customizableui/customize-titleBar-toggle@2x.png) - skin/classic/browser/customizableui/customizeFavicon.ico (../shared/customizableui/customizeFavicon.ico) - skin/classic/browser/customizableui/customizeMode-gridTexture.png (customizableui/customizeMode-gridTexture.png) - skin/classic/browser/customizableui/customizeMode-separatorHorizontal.png (customizableui/customizeMode-separatorHorizontal.png) - skin/classic/browser/customizableui/customizeMode-separatorVertical.png (customizableui/customizeMode-separatorVertical.png) * skin/classic/browser/engineManager.css (engineManager.css) skin/classic/browser/fullscreen-darknoise.png skin/classic/browser/Geolocation-16.png @@ -120,6 +113,15 @@ browser.jar: skin/classic/browser/webRTC-shareDevice-64@2x.png skin/classic/browser/webRTC-sharingDevice-16.png skin/classic/browser/webRTC-sharingDevice-16@2x.png + skin/classic/browser/customizableui/background-noise-toolbar.png (customizableui/background-noise-toolbar.png) + skin/classic/browser/customizableui/customize-titleBar-toggle.png (customizableui/customize-titleBar-toggle.png) + skin/classic/browser/customizableui/customize-titleBar-toggle@2x.png (customizableui/customize-titleBar-toggle@2x.png) + skin/classic/browser/customizableui/customizeFavicon.ico (../shared/customizableui/customizeFavicon.ico) + skin/classic/browser/customizableui/customizeMode-gridTexture.png (customizableui/customizeMode-gridTexture.png) + skin/classic/browser/customizableui/customizeMode-separatorHorizontal.png (customizableui/customizeMode-separatorHorizontal.png) + skin/classic/browser/customizableui/customizeMode-separatorVertical.png (customizableui/customizeMode-separatorVertical.png) + skin/classic/browser/customizableui/subView-arrow-back-inverted.png (../shared/customizableui/subView-arrow-back-inverted.png) + skin/classic/browser/customizableui/subView-arrow-back-inverted@2x.png (../shared/customizableui/subView-arrow-back-inverted@2x.png) * skin/classic/browser/customizableui/panelUIOverlay.css (customizableui/panelUIOverlay.css) skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css) skin/classic/browser/downloads/buttons.png (downloads/buttons.png) diff --git a/browser/themes/shared/customizableui/panelUIOverlay.inc.css b/browser/themes/shared/customizableui/panelUIOverlay.inc.css index d323d0e5a7a3..5c11f54fc688 100644 --- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css +++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css @@ -523,12 +523,22 @@ panelview toolbarseparator, height: 16px; } -#PanelUI-footer.panel-multiview-anchor, -#PanelUI-footer.panel-multiview-anchor > #PanelUI-help, +#PanelUI-footer > #PanelUI-footer-inner.panel-multiview-anchor, toolbarbutton.panel-multiview-anchor { background-color: Highlight; background-image: linear-gradient(rgba(255,255,255,0.3), rgba(255,255,255,0)); - background-repeat: repeat-x; +} + +toolbarbutton.panel-multiview-anchor { + background-image: url(chrome://browser/skin/customizableui/subView-arrow-back-inverted.png), + linear-gradient(rgba(255,255,255,0.3), rgba(255,255,255,0)); + background-position: right 5px center; + background-repeat: no-repeat; +} + +#PanelUI-footer > #PanelUI-footer-inner.panel-multiview-anchor, +toolbarbutton.panel-multiview-anchor, +toolbarbutton.panel-multiview-anchor > .toolbarbutton-menubutton-button { color: HighlightText; } diff --git a/browser/themes/shared/customizableui/subView-arrow-back-inverted.png b/browser/themes/shared/customizableui/subView-arrow-back-inverted.png new file mode 100644 index 0000000000000000000000000000000000000000..86178abfecd2e16d60e721c88cb2e2b57b4dce53 GIT binary patch literal 307 zcmV-30nGl1P))TW;P)835fp!@mrF@0H~Q6NP%qp4ZM}0PI1S)dwD5XiaA zKvK^+37Z;5bQ{!emJhH5%Xv)l*}U;ycPSUM_T&yxTE002ovPDHLk FV1oU1dDj2{ literal 0 HcmV?d00001 diff --git a/browser/themes/shared/customizableui/subView-arrow-back-inverted@2x.png b/browser/themes/shared/customizableui/subView-arrow-back-inverted@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..1c8e86db37d9406c3c64f2194c773b5f0f132dbb GIT binary patch literal 667 zcmV;M0%ZM(P)HwgHvOsDl5Q_jYFA#5l zI*9fT09qspwOkU2|AH{EVEac*+M?J2K+E}o)D|FC1Y(e-|Ni{>`>uD!;(TDjy-T%R zfWvYwAhiaFwV^&|09yXBd-}qn`mSlWfm}wKIRI!m8<1KA#AZmAf9jdOuo!6hO(6Fc z5PzqM1Avw@1F301>;$v?_n$wX`)4jGt0Tj5k{kfE92Be_KOjI;PwJ za&MDrIjJE~3B)m=5Qkd+wRgs%Y9!0Q(WEFw1Ak!*V0Qb%%F6PW63a=-hWx75i|##q zvK1Nve^^+UISNvv>w!_DOqbg5`;VXhja(z#&cLm0LzEhK>95`s$-CkZ*SXsjDDCN55n?vv*(e+EX)2UuxI3}7VGGa|B43aTMNt)SnasDU|vm_{k3 zApozdpf%xdXw)#!D+-XoPjWOdj&@E5+5rFo1^~l(6`1Mj*%bf)002ovPDHLkV1mYH BFbx0z literal 0 HcmV?d00001 diff --git a/browser/themes/shared/menupanel.inc.css b/browser/themes/shared/menupanel.inc.css index 0d560d222b34..ce9df80f0bcf 100644 --- a/browser/themes/shared/menupanel.inc.css +++ b/browser/themes/shared/menupanel.inc.css @@ -15,11 +15,19 @@ toolbarpaletteitem[place="palette"] > #bookmarks-menu-button { -moz-image-region: rect(0px, 160px, 32px, 128px); } +#bookmarks-menu-button[cui-areatype="menu-panel"].panel-multiview-anchor { + -moz-image-region: rect(32px, 160px, 64px, 128px); +} + #history-panelmenu[cui-areatype="menu-panel"], toolbarpaletteitem[place="palette"] > #history-panelmenu { -moz-image-region: rect(0px, 224px, 32px, 192px); } +#history-panelmenu[cui-areatype="menu-panel"].panel-multiview-anchor { + -moz-image-region: rect(32px, 224px, 64px, 192px); +} + #downloads-button[cui-areatype="menu-panel"], toolbarpaletteitem[place="palette"] > #downloads-button { -moz-image-region: rect(0px, 256px, 32px, 224px); @@ -60,6 +68,10 @@ toolbarpaletteitem[place="palette"] > #characterencoding-button { -moz-image-region: rect(0px, 480px, 32px, 448px); } +#characterencoding-button[cui-areatype="menu-panel"].panel-multiview-anchor { + -moz-image-region: rect(32px, 480px, 64px, 448px); +} + #new-window-button[cui-areatype="menu-panel"], toolbarpaletteitem[place="palette"] > #new-window-button { -moz-image-region: rect(0px, 512px, 32px, 480px); @@ -100,6 +112,10 @@ toolbarpaletteitem[place="palette"] > #developer-button { -moz-image-region: rect(0px, 736px, 32px, 704px); } +#developer-button[cui-areatype="menu-panel"].panel-multiview-anchor { + -moz-image-region: rect(32px, 736px, 64px, 704px); +} + #preferences-button[cui-areatype="menu-panel"], toolbarpaletteitem[place="palette"] > #preferences-button { -moz-image-region: rect(0px, 768px, 32px, 736px); diff --git a/browser/themes/windows/jar.mn b/browser/themes/windows/jar.mn index dfcb094e4aa4..93686c1b6595 100644 --- a/browser/themes/windows/jar.mn +++ b/browser/themes/windows/jar.mn @@ -25,12 +25,6 @@ browser.jar: * skin/classic/browser/browser.css * skin/classic/browser/browser-lightweightTheme.css skin/classic/browser/click-to-play-warning-stripes.png - skin/classic/browser/customizableui/background-noise-toolbar.png (customizableui/background-noise-toolbar.png) - skin/classic/browser/customizableui/customizeFavicon.ico (../shared/customizableui/customizeFavicon.ico) - skin/classic/browser/customizableui/customize-titleBar-toggle.png (customizableui/customize-titleBar-toggle.png) - skin/classic/browser/customizableui/customizeMode-gridTexture.png (customizableui/customizeMode-gridTexture.png) - skin/classic/browser/customizableui/customizeMode-separatorHorizontal.png (customizableui/customizeMode-separatorHorizontal.png) - skin/classic/browser/customizableui/customizeMode-separatorVertical.png (customizableui/customizeMode-separatorVertical.png) * skin/classic/browser/engineManager.css skin/classic/browser/fullscreen-darknoise.png skin/classic/browser/Geolocation-16.png @@ -92,7 +86,14 @@ browser.jar: skin/classic/browser/webRTC-shareDevice-16.png skin/classic/browser/webRTC-shareDevice-64.png skin/classic/browser/webRTC-sharingDevice-16.png + skin/classic/browser/customizableui/background-noise-toolbar.png (customizableui/background-noise-toolbar.png) + skin/classic/browser/customizableui/customizeFavicon.ico (../shared/customizableui/customizeFavicon.ico) + skin/classic/browser/customizableui/customize-titleBar-toggle.png (customizableui/customize-titleBar-toggle.png) + skin/classic/browser/customizableui/customizeMode-gridTexture.png (customizableui/customizeMode-gridTexture.png) + skin/classic/browser/customizableui/customizeMode-separatorHorizontal.png (customizableui/customizeMode-separatorHorizontal.png) + skin/classic/browser/customizableui/customizeMode-separatorVertical.png (customizableui/customizeMode-separatorVertical.png) * skin/classic/browser/customizableui/panelUIOverlay.css (customizableui/panelUIOverlay.css) + skin/classic/browser/customizableui/subView-arrow-back-inverted.png (../shared/customizableui/subView-arrow-back-inverted.png) * skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css) skin/classic/browser/downloads/buttons.png (downloads/buttons.png) skin/classic/browser/downloads/contentAreaDownloadsView.css (downloads/contentAreaDownloadsView.css) @@ -335,12 +336,6 @@ browser.jar: * skin/classic/aero/browser/browser.css (browser-aero.css) * skin/classic/aero/browser/browser-lightweightTheme.css skin/classic/aero/browser/click-to-play-warning-stripes.png - skin/classic/aero/browser/customizableui/background-noise-toolbar.png (customizableui/background-noise-toolbar.png) - skin/classic/aero/browser/customizableui/customize-titleBar-toggle.png (customizableui/customize-titleBar-toggle.png) - skin/classic/aero/browser/customizableui/customizeFavicon.ico (../shared/customizableui/customizeFavicon.ico) - skin/classic/aero/browser/customizableui/customizeMode-gridTexture.png (customizableui/customizeMode-gridTexture.png) - skin/classic/aero/browser/customizableui/customizeMode-separatorHorizontal.png (customizableui/customizeMode-separatorHorizontal.png) - skin/classic/aero/browser/customizableui/customizeMode-separatorVertical.png (customizableui/customizeMode-separatorVertical.png) * skin/classic/aero/browser/engineManager.css skin/classic/aero/browser/fullscreen-darknoise.png skin/classic/aero/browser/Geolocation-16.png @@ -401,7 +396,14 @@ browser.jar: skin/classic/aero/browser/webRTC-shareDevice-16.png skin/classic/aero/browser/webRTC-shareDevice-64.png skin/classic/aero/browser/webRTC-sharingDevice-16.png + skin/classic/aero/browser/customizableui/background-noise-toolbar.png (customizableui/background-noise-toolbar.png) + skin/classic/aero/browser/customizableui/customize-titleBar-toggle.png (customizableui/customize-titleBar-toggle.png) + skin/classic/aero/browser/customizableui/customizeFavicon.ico (../shared/customizableui/customizeFavicon.ico) + skin/classic/aero/browser/customizableui/customizeMode-gridTexture.png (customizableui/customizeMode-gridTexture.png) + skin/classic/aero/browser/customizableui/customizeMode-separatorHorizontal.png (customizableui/customizeMode-separatorHorizontal.png) + skin/classic/aero/browser/customizableui/customizeMode-separatorVertical.png (customizableui/customizeMode-separatorVertical.png) * skin/classic/aero/browser/customizableui/panelUIOverlay.css (customizableui/panelUIOverlay.css) + skin/classic/aero/browser/customizableui/subView-arrow-back-inverted.png (../shared/customizableui/subView-arrow-back-inverted.png) * skin/classic/aero/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay-aero.css) skin/classic/aero/browser/downloads/buttons.png (downloads/buttons-aero.png) skin/classic/aero/browser/downloads/contentAreaDownloadsView.css (downloads/contentAreaDownloadsView.css) From 16b025cace6b6478c85c43411d80953f2f941303 Mon Sep 17 00:00:00 2001 From: Darrin Henein Date: Mon, 3 Feb 2014 18:49:24 -0500 Subject: [PATCH 03/78] Bug 931343 - [Australis] Implement Australis Bookmark Animation. Adding missed jar.mn reference for aero skin. r=jaws --- browser/themes/windows/jar.mn | 1 + 1 file changed, 1 insertion(+) diff --git a/browser/themes/windows/jar.mn b/browser/themes/windows/jar.mn index 93686c1b6595..269d3e995d89 100644 --- a/browser/themes/windows/jar.mn +++ b/browser/themes/windows/jar.mn @@ -431,6 +431,7 @@ browser.jar: skin/classic/aero/browser/places/bookmarksMenu.png (places/bookmarksMenu-aero.png) skin/classic/aero/browser/places/bookmarksToolbar.png (places/bookmarksToolbar-aero.png) skin/classic/aero/browser/places/bookmarksToolbar-menuPanel.png (places/bookmarksToolbar-menuPanel-aero.png) + skin/classic/aero/browser/places/bookmarks-notification-finish.png (places/bookmarks-notification-finish.png) skin/classic/aero/browser/places/calendar.png (places/calendar-aero.png) skin/classic/aero/browser/places/toolbarDropMarker.png (places/toolbarDropMarker-aero.png) skin/classic/aero/browser/places/editBookmarkOverlay.css (places/editBookmarkOverlay.css) From 3b71532efcc6018a6c25f5abc80377331c79ec6c Mon Sep 17 00:00:00 2001 From: Erik Vold Date: Sun, 2 Feb 2014 12:38:12 -0800 Subject: [PATCH 04/78] Bug 926264 - Intermittent Jetpack command timed out: 7200 seconds elapsed, attempting to kill starting up private-browsing-supported r=Mossop --- .../test-windows.js | 51 ++++++++----------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/addon-sdk/source/test/addons/private-browsing-supported/test-windows.js b/addon-sdk/source/test/addons/private-browsing-supported/test-windows.js index 768ff61f0bf1..1b6336d8d216 100644 --- a/addon-sdk/source/test/addons/private-browsing-supported/test-windows.js +++ b/addon-sdk/source/test/addons/private-browsing-supported/test-windows.js @@ -6,7 +6,7 @@ const { Cc, Ci } = require('chrome'); const { isPrivate } = require('sdk/private-browsing'); const { isWindowPBSupported } = require('sdk/private-browsing/utils'); -const { onFocus, getMostRecentWindow, getWindowTitle, +const { onFocus, getMostRecentWindow, getWindowTitle, getInnerId, getFrames, windows, open: openWindow, isWindowPrivate } = require('sdk/window/utils'); const { open, close, focus, promise } = require('sdk/window/helpers'); const { browserWindows } = require("sdk/windows"); @@ -25,31 +25,28 @@ function makeEmptyBrowserWindow(options) { chrome: true, private: !!options.private } - }); + }).then(focus); } exports.testWindowTrackerIgnoresPrivateWindows = function(assert, done) { - var myNonPrivateWindow, myPrivateWindow; - var finished = false; - var privateWindow; + var myNonPrivateWindowId, myPrivateWindowId; var privateWindowClosed = false; var privateWindowOpened = false; + var trackedWindowIds = []; let wt = winUtils.WindowTracker({ onTrack: function(window) { - if (window === myPrivateWindow) { - assert.equal(isPrivate(window), isWindowPBSupported); - privateWindowOpened = true; - } + let id = getInnerId(window); + trackedWindowIds.push(id); }, onUntrack: function(window) { - if (window === myPrivateWindow && isWindowPBSupported) { + let id = getInnerId(window); + if (id === myPrivateWindowId) { privateWindowClosed = true; } - if (window === myNonPrivateWindow) { - assert.equal(privateWindowClosed, isWindowPBSupported); - assert.ok(privateWindowOpened); + if (id === myNonPrivateWindowId) { + assert.equal(privateWindowClosed, true, 'private window was untracked'); wt.unload(); done(); } @@ -57,27 +54,23 @@ exports.testWindowTrackerIgnoresPrivateWindows = function(assert, done) { }); // make a new private window - myPrivateWindow = openWindow(BROWSER, { - features: { - private: true - } - }); - promise(myPrivateWindow, 'load').then(function(window) { + makeEmptyBrowserWindow({ private: true }).then(function(window) { + myPrivateWindowId = getInnerId(window); + + assert.ok(trackedWindowIds.indexOf(myPrivateWindowId) >= 0, 'private window was tracked'); assert.equal(isPrivate(window), isWindowPBSupported, 'private window isPrivate'); assert.equal(isWindowPrivate(window), isWindowPBSupported); assert.ok(getFrames(window).length > 1, 'there are frames for private window'); assert.equal(getWindowTitle(window), window.document.title, 'getWindowTitle works'); - close(myPrivateWindow).then(function() { + close(window).then(function() { assert.pass('private window was closed'); + makeEmptyBrowserWindow().then(function(window) { - myNonPrivateWindow = window; - assert.notDeepEqual(myPrivateWindow, myNonPrivateWindow); - assert.pass('opened new window'); - close(myNonPrivateWindow).then(function() { - assert.pass('non private window was closed'); - }) + myNonPrivateWindowId = getInnerId(window); + assert.notEqual(myPrivateWindowId, myNonPrivateWindowId, 'non private window was opened'); + close(window); }); }); }); @@ -95,7 +88,7 @@ exports.testSettingActiveWindowDoesNotIgnorePrivateWindow = function(assert, don // make a new private window makeEmptyBrowserWindow({ private: true - }).then(focus).then(function(window) { + }).then(function(window) { let continueAfterFocus = function(window) onFocus(window).then(nextTest); // PWPB case @@ -172,7 +165,7 @@ exports.testActiveWindowDoesNotIgnorePrivateWindow = function(assert, done) { // make a new private window makeEmptyBrowserWindow({ private: true - }).then(focus).then(function(window) { + }).then(function(window) { // PWPB case if (isWindowPBSupported) { assert.equal(isPrivate(winUtils.activeWindow), true, @@ -212,7 +205,7 @@ exports.testWindowIteratorIgnoresPrivateWindows = function(assert, done) { // make a new private window makeEmptyBrowserWindow({ private: true - }).then(focus).then(function(window) { + }).then(function(window) { assert.equal(isWindowPrivate(window), isWindowPBSupported); assert.ok(toArray(winUtils.windowIterator()).indexOf(window) > -1, "window is in windowIterator()"); From 975bc5e75c252d8aa1de31c76a40e55f87a57194 Mon Sep 17 00:00:00 2001 From: Brian Grinstead Date: Mon, 3 Feb 2014 14:25:30 -0600 Subject: [PATCH 05/78] Bug 966660 - Remove inspect-button.png OVERRIDE HOOK;r=pbrosset --- browser/themes/linux/devtools/inspect-button.png | Bin 1321 -> 0 bytes browser/themes/linux/devtools/inspector.css | 9 --------- browser/themes/linux/jar.mn | 1 - browser/themes/osx/devtools/inspect-button.png | Bin 1321 -> 0 bytes browser/themes/osx/devtools/inspector.css | 9 --------- browser/themes/osx/jar.mn | 1 - .../themes/windows/devtools/inspect-button.png | Bin 1321 -> 0 bytes browser/themes/windows/devtools/inspector.css | 9 --------- browser/themes/windows/jar.mn | 2 -- 9 files changed, 31 deletions(-) delete mode 100644 browser/themes/linux/devtools/inspect-button.png delete mode 100644 browser/themes/osx/devtools/inspect-button.png delete mode 100644 browser/themes/windows/devtools/inspect-button.png diff --git a/browser/themes/linux/devtools/inspect-button.png b/browser/themes/linux/devtools/inspect-button.png deleted file mode 100644 index ebfd9586d201416665b64e4fe57af05c1a967edb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1321 zcmV+^1=jkBP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iyS_ z05usk)smk800gN?L_t(Y$K{l1Y*a-6$A9zQUOjeu&=$I-(1MhZQV@|K5n=%`MvM?t zA|`@dO7J!qFc>BJ$;23=h#YcfIzuKkaEc(bPMg$?RMGSw{PBi;|CV9 z7z})OKEBCs<~RTOzk&bz5CHDHpa&=b@&Wff7zwlk5g84c0IXFykPT!3`aO6XNCFAQ z3)_|cSh#q>m(xc-^<>rkSl4aZa^{tpZ!Q6@0?>LXcW71c^IWfc%Kh*+QgYdzGog2Z z=q-WbQKLqdTecIsp9MyZ8d(k$-x3g!=76GX;?;zGU$N)tVxovN&r=x!Ihv-Y0A8Lp zBeG*>ePcT6%;6p}ytdDZZx)hU{cmLV9m`njOP85HSf=s3l6Km!U3X@!EgW1?vN3L{_2=WPN=Q2_4sK{OP16uU z_(&xk0PSA_Qh;N3gcMNNsi>GYX1>~E_l~VEe*EFwm&!^@ONS1B{OsyA-(5|M-XS2R z1h?0*J3>l?FZ;uZR4<{dd3|!NhHAh1#NTT`$(8-vC&j~8*AQ)q|5pNDcV18jyk4)z zv>j#o{eCfJ^2DMEFO2W}bKRznufJJ*3NYPyK}|+O4uCYx+jq@crLljVH)YDKWD0aa zQMOCGRasUt%_z$IC3Nw^QgZSoYn8}Mzy!=O&y4N|Q~?1^(@elrkg^>W^LV|{=bszr zTC-|d^_*Gnbj>_~DJ7g~b%T}&7 z0cqJzR9g0R%d)+Iv@FY-zhIGOS(Y`t_Az_dqeCnpgd(C8cKSDro6)unQcAAH3@*1P zabgi)x))9=(y~=b>A2kP>in)1-IUwc%ISSEacHA+Gl4)b0Q5l674QM=MV&hNk2LHp zdvDHM&ksMYYo0oJQrSlzyl)srGHd#b*N^?Wd+#0@4J81yAro@&<(A{}l%VS#gdTHO zJseQoYqmPk@N(zGuJ85!Bc_Vng7JkuJ)UyJvk^<*$=kh69YA>fGTrJczy+YGw6u## z8b)B@;-x2096$Q|F{698@-8(seNzH*wrt&g#!1Bt0Ij3V7R_g4qUlttxO}o%v^BN5 z9~kJ7?ku4WZk$ik=?f|zJ*GNW4ltCc%oettjD)waYlReddn#RdK@CL!$OUdFxFMjR zpfJDxxUpkOmo8t~2sB#JQ2cJZ9taBc;1jtdV>w78APx#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iyS_ z05usk)smk800gN?L_t(Y$K{l1Y*a-6$A9zQUOjeu&=$I-(1MhZQV@|K5n=%`MvM?t zA|`@dO7J!qFc>BJ$;23=h#YcfIzuKkaEc(bPMg$?RMGSw{PBi;|CV9 z7z})OKEBCs<~RTOzk&bz5CHDHpa&=b@&Wff7zwlk5g84c0IXFykPT!3`aO6XNCFAQ z3)_|cSh#q>m(xc-^<>rkSl4aZa^{tpZ!Q6@0?>LXcW71c^IWfc%Kh*+QgYdzGog2Z z=q-WbQKLqdTecIsp9MyZ8d(k$-x3g!=76GX;?;zGU$N)tVxovN&r=x!Ihv-Y0A8Lp zBeG*>ePcT6%;6p}ytdDZZx)hU{cmLV9m`njOP85HSf=s3l6Km!U3X@!EgW1?vN3L{_2=WPN=Q2_4sK{OP16uU z_(&xk0PSA_Qh;N3gcMNNsi>GYX1>~E_l~VEe*EFwm&!^@ONS1B{OsyA-(5|M-XS2R z1h?0*J3>l?FZ;uZR4<{dd3|!NhHAh1#NTT`$(8-vC&j~8*AQ)q|5pNDcV18jyk4)z zv>j#o{eCfJ^2DMEFO2W}bKRznufJJ*3NYPyK}|+O4uCYx+jq@crLljVH)YDKWD0aa zQMOCGRasUt%_z$IC3Nw^QgZSoYn8}Mzy!=O&y4N|Q~?1^(@elrkg^>W^LV|{=bszr zTC-|d^_*Gnbj>_~DJ7g~b%T}&7 z0cqJzR9g0R%d)+Iv@FY-zhIGOS(Y`t_Az_dqeCnpgd(C8cKSDro6)unQcAAH3@*1P zabgi)x))9=(y~=b>A2kP>in)1-IUwc%ISSEacHA+Gl4)b0Q5l674QM=MV&hNk2LHp zdvDHM&ksMYYo0oJQrSlzyl)srGHd#b*N^?Wd+#0@4J81yAro@&<(A{}l%VS#gdTHO zJseQoYqmPk@N(zGuJ85!Bc_Vng7JkuJ)UyJvk^<*$=kh69YA>fGTrJczy+YGw6u## z8b)B@;-x2096$Q|F{698@-8(seNzH*wrt&g#!1Bt0Ij3V7R_g4qUlttxO}o%v^BN5 z9~kJ7?ku4WZk$ik=?f|zJ*GNW4ltCc%oettjD)waYlReddn#RdK@CL!$OUdFxFMjR zpfJDxxUpkOmo8t~2sB#JQ2cJZ9taBc;1jtdV>w78APx#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iyS_ z05usk)smk800gN?L_t(Y$K{l1Y*a-6$A9zQUOjeu&=$I-(1MhZQV@|K5n=%`MvM?t zA|`@dO7J!qFc>BJ$;23=h#YcfIzuKkaEc(bPMg$?RMGSw{PBi;|CV9 z7z})OKEBCs<~RTOzk&bz5CHDHpa&=b@&Wff7zwlk5g84c0IXFykPT!3`aO6XNCFAQ z3)_|cSh#q>m(xc-^<>rkSl4aZa^{tpZ!Q6@0?>LXcW71c^IWfc%Kh*+QgYdzGog2Z z=q-WbQKLqdTecIsp9MyZ8d(k$-x3g!=76GX;?;zGU$N)tVxovN&r=x!Ihv-Y0A8Lp zBeG*>ePcT6%;6p}ytdDZZx)hU{cmLV9m`njOP85HSf=s3l6Km!U3X@!EgW1?vN3L{_2=WPN=Q2_4sK{OP16uU z_(&xk0PSA_Qh;N3gcMNNsi>GYX1>~E_l~VEe*EFwm&!^@ONS1B{OsyA-(5|M-XS2R z1h?0*J3>l?FZ;uZR4<{dd3|!NhHAh1#NTT`$(8-vC&j~8*AQ)q|5pNDcV18jyk4)z zv>j#o{eCfJ^2DMEFO2W}bKRznufJJ*3NYPyK}|+O4uCYx+jq@crLljVH)YDKWD0aa zQMOCGRasUt%_z$IC3Nw^QgZSoYn8}Mzy!=O&y4N|Q~?1^(@elrkg^>W^LV|{=bszr zTC-|d^_*Gnbj>_~DJ7g~b%T}&7 z0cqJzR9g0R%d)+Iv@FY-zhIGOS(Y`t_Az_dqeCnpgd(C8cKSDro6)unQcAAH3@*1P zabgi)x))9=(y~=b>A2kP>in)1-IUwc%ISSEacHA+Gl4)b0Q5l674QM=MV&hNk2LHp zdvDHM&ksMYYo0oJQrSlzyl)srGHd#b*N^?Wd+#0@4J81yAro@&<(A{}l%VS#gdTHO zJseQoYqmPk@N(zGuJ85!Bc_Vng7JkuJ)UyJvk^<*$=kh69YA>fGTrJczy+YGw6u## z8b)B@;-x2096$Q|F{698@-8(seNzH*wrt&g#!1Bt0Ij3V7R_g4qUlttxO}o%v^BN5 z9~kJ7?ku4WZk$ik=?f|zJ*GNW4ltCc%oettjD)waYlReddn#RdK@CL!$OUdFxFMjR zpfJDxxUpkOmo8t~2sB#JQ2cJZ9taBc;1jtdV>w78A Date: Mon, 3 Feb 2014 18:59:10 -0600 Subject: [PATCH 06/78] Bug 967033 - DevTools Themes - Active arrow for side menus is missing on windows OVERRIDE HOOK;r=vporof --- browser/themes/windows/jar.mn | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/browser/themes/windows/jar.mn b/browser/themes/windows/jar.mn index 444383285b31..cd3ee4880992 100644 --- a/browser/themes/windows/jar.mn +++ b/browser/themes/windows/jar.mn @@ -226,8 +226,8 @@ browser.jar: skin/classic/browser/devtools/itemToggle-light.png (../shared/devtools/images/itemToggle-light.png) skin/classic/browser/devtools/itemArrow-dark-rtl.png (../shared/devtools/images/itemArrow-dark-rtl.png) skin/classic/browser/devtools/itemArrow-dark-ltr.png (../shared/devtools/images/itemArrow-dark-ltr.png) - skin/classic/browser/devtools/itemArrow-rtl.png (../shared/devtools/images/itemArrow-rtl.svg) - skin/classic/browser/devtools/itemArrow-ltr.png (../shared/devtools/images/itemArrow-ltr.svg) + skin/classic/browser/devtools/itemArrow-rtl.svg (../shared/devtools/images/itemArrow-rtl.svg) + skin/classic/browser/devtools/itemArrow-ltr.svg (../shared/devtools/images/itemArrow-ltr.svg) skin/classic/browser/devtools/background-noise-toolbar.png (devtools/background-noise-toolbar.png) skin/classic/browser/devtools/noise.png (devtools/noise.png) skin/classic/browser/devtools/dropmarker.png (devtools/dropmarker.png) From a587938b110dc757690d811c41ea0bdcd6b307cf Mon Sep 17 00:00:00 2001 From: Tim Taubert Date: Mon, 3 Feb 2014 19:30:47 +0100 Subject: [PATCH 07/78] Bug 967120 - Clean up FxAccounts' public/internal API implementation r=markh --- services/fxaccounts/FxAccounts.jsm | 425 ++++++++---------- services/fxaccounts/FxAccountsUtils.jsm | 49 ++ services/fxaccounts/moz.build | 3 +- .../tests/xpcshell/test_accounts.js | 47 +- 4 files changed, 269 insertions(+), 255 deletions(-) create mode 100644 services/fxaccounts/FxAccountsUtils.jsm diff --git a/services/fxaccounts/FxAccounts.jsm b/services/fxaccounts/FxAccounts.jsm index 56bd86b2baba..d26179a5a96a 100644 --- a/services/fxaccounts/FxAccounts.jsm +++ b/services/fxaccounts/FxAccounts.jsm @@ -17,11 +17,57 @@ Cu.import("resource://gre/modules/Timer.jsm"); Cu.import("resource://gre/modules/Task.jsm"); Cu.import("resource://gre/modules/FxAccountsClient.jsm"); Cu.import("resource://gre/modules/FxAccountsCommon.js"); +Cu.import("resource://gre/modules/FxAccountsUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "jwcrypto", - "resource://gre/modules/identity/jwcrypto.jsm"); + "resource://gre/modules/identity/jwcrypto.jsm"); -InternalMethods = function(mock) { +// All properties exposed by the public FxAccounts API. +let publicProperties = [ + "getAccountsURI", + "getAssertion", + "getKeys", + "getSignedInUser", + "loadAndPoll", + "localtimeOffsetMsec", + "now", + "promiseAccountsForceSigninURI", + "resendVerificationEmail", + "setSignedInUser", + "signOut", + "version", + "whenVerified" +]; + +/** + * The public API's constructor. + */ +this.FxAccounts = function (mockInternal) { + let internal = new FxAccountsInternal(); + let external = {}; + + // Copy all public properties to the 'external' object. + let prototype = FxAccountsInternal.prototype; + let options = {keys: publicProperties, bind: internal}; + FxAccountsUtils.copyObjectProperties(prototype, external, options); + + // Copy all of the mock's properties to the internal object. + if (mockInternal && !mockInternal.onlySetInternal) { + FxAccountsUtils.copyObjectProperties(mockInternal, internal); + } + + if (mockInternal) { + // Exposes the internal object for testing only. + external.internal = internal; + } + + return Object.freeze(external); +} + +/** + * The internal API's constructor. + */ +function FxAccountsInternal() { this.cert = null; this.keyPair = null; this.signedInUser = null; @@ -49,23 +95,23 @@ InternalMethods = function(mock) { this.fxAccountsClient = new FxAccountsClient(); - if (mock) { // Testing. - Object.keys(mock).forEach((prop) => { - log.debug("InternalMethods: mocking: " + prop); - this[prop] = mock[prop]; - }); - } - if (!this.signedInUserStorage) { - // Normal (i.e., non-testing) initialization. - // We don't reference |profileDir| in the top-level module scope - // as we may be imported before we know where it is. - this.signedInUserStorage = new JSONStorage({ - filename: DEFAULT_STORAGE_FILENAME, - baseDir: OS.Constants.Path.profileDir, - }); - } + // We don't reference |profileDir| in the top-level module scope + // as we may be imported before we know where it is. + this.signedInUserStorage = new JSONStorage({ + filename: DEFAULT_STORAGE_FILENAME, + baseDir: OS.Constants.Path.profileDir, + }); } -InternalMethods.prototype = { + +/** + * The internal API's prototype. + */ +FxAccountsInternal.prototype = { + + /** + * The current data format's version number. + */ + version: DATA_FORMAT_VERSION, /** * Return the current time in milliseconds as an integer. Allows tests to @@ -102,6 +148,125 @@ InternalMethods.prototype = { return this.fxAccountsClient.accountKeys(keyFetchToken); }, + // set() makes sure that polling is happening, if necessary. + // get() does not wait for verification, and returns an object even if + // unverified. The caller of get() must check .verified . + // The "fxaccounts:onverified" event will fire only when the verified + // state goes from false to true, so callers must register their observer + // and then call get(). In particular, it will not fire when the account + // was found to be verified in a previous boot: if our stored state says + // the account is verified, the event will never fire. So callers must do: + // register notification observer (go) + // userdata = get() + // if (userdata.verified()) {go()} + + /** + * Get the user currently signed in to Firefox Accounts. + * + * @return Promise + * The promise resolves to the credentials object of the signed-in user: + * { + * email: The user's email address + * uid: The user's unique id + * sessionToken: Session for the FxA server + * kA: An encryption key from the FxA server + * kB: An encryption key derived from the user's FxA password + * verified: email verification status + * } + * or null if no user is signed in. + */ + getSignedInUser: function getSignedInUser() { + return this.getUserAccountData().then(data => { + if (!data) { + return null; + } + if (!this.isUserEmailVerified(data)) { + // If the email is not verified, start polling for verification, + // but return null right away. We don't want to return a promise + // that might not be fulfilled for a long time. + this.startVerifiedCheck(data); + } + return data; + }); + }, + + /** + * Set the current user signed in to Firefox Accounts. + * + * @param credentials + * The credentials object obtained by logging in or creating + * an account on the FxA server: + * { + * email: The users email address + * uid: The user's unique id + * sessionToken: Session for the FxA server + * keyFetchToken: an unused keyFetchToken + * verified: true/false + * } + * @return Promise + * The promise resolves to null when the data is saved + * successfully and is rejected on error. + */ + setSignedInUser: function setSignedInUser(credentials) { + log.debug("setSignedInUser - aborting any existing flows"); + this.abortExistingFlow(); + + let record = {version: this.version, accountData: credentials}; + // Cache a clone of the credentials object. + this.signedInUser = JSON.parse(JSON.stringify(record)); + + // This promise waits for storage, but not for verification. + // We're telling the caller that this is durable now. + return this.signedInUserStorage.set(record).then(() => { + this.notifyObservers(ONLOGIN_NOTIFICATION); + if (!this.isUserEmailVerified(credentials)) { + this.startVerifiedCheck(credentials); + } + }); + }, + + /** + * returns a promise that fires with the assertion. If there is no verified + * signed-in user, fires with null. + */ + getAssertion: function getAssertion(audience) { + log.debug("enter getAssertion()"); + let mustBeValidUntil = this.now() + ASSERTION_LIFETIME; + return this.getUserAccountData().then(data => { + if (!data) { + // No signed-in user + return null; + } + if (!this.isUserEmailVerified(data)) { + // Signed-in user has not verified email + return null; + } + return this.getKeyPair(mustBeValidUntil).then(keyPair => { + return this.getCertificate(data, keyPair, mustBeValidUntil) + .then(cert => { + return this.getAssertionFromCert(data, keyPair, cert, audience); + }); + }); + }); + }, + + /** + * Resend the verification email fot the currently signed-in user. + * + */ + resendVerificationEmail: function resendVerificationEmail() { + return this.getSignedInUser().then(data => { + // If the caller is asking for verification to be re-sent, and there is + // no signed-in user to begin with, this is probably best regarded as an + // error. + if (data) { + this.pollEmailStatus(data.sessionToken, "start"); + return this.fxAccountsClient.resendVerificationEmail(data.sessionToken); + } + throw new Error("Cannot resend verification email; no signed-in user"); + }); + }, + /* * Reset state such that any previous flow is canceled. */ @@ -179,14 +344,14 @@ InternalMethods.prototype = { return Task.spawn(function* task() { // Sign out if we don't have a key fetch token. if (!keyFetchToken) { - yield internal.signOut(); + yield this.signOut(); return null; } - let myGenerationCount = internal.generationCount; + let myGenerationCount = this.generationCount; - let {kA, wrapKB} = yield internal.fetchKeys(keyFetchToken); + let {kA, wrapKB} = yield this.fetchKeys(keyFetchToken); - let data = yield internal.getUserAccountData(); + let data = yield this.getUserAccountData(); // Sanity check that the user hasn't changed out from under us if (data.keyFetchToken !== keyFetchToken) { @@ -208,16 +373,16 @@ InternalMethods.prototype = { // Before writing any data, ensure that a new flow hasn't been // started behind our backs. - if (internal.generationCount !== myGenerationCount) { + if (this.generationCount !== myGenerationCount) { return null; } - yield internal.setUserAccountData(data); + yield this.setUserAccountData(data); // We are now ready for business. This should only be invoked once // per setSignedInUser(), regardless of whether we've rebooted since // setSignedInUser() was called. - internal.notifyObservers(ONVERIFIED_NOTIFICATION); + this.notifyObservers(ONVERIFIED_NOTIFICATION); return data; }.bind(this)); }, @@ -227,8 +392,8 @@ InternalMethods.prototype = { let payload = {}; let d = Promise.defer(); let options = { - localtimeOffsetMsec: internal.localtimeOffsetMsec, - now: internal.now() + localtimeOffsetMsec: this.localtimeOffsetMsec, + now: this.now() }; // "audience" should look like "http://123done.org". // The generated assertion will expire in two minutes. @@ -252,7 +417,7 @@ InternalMethods.prototype = { return Promise.resolve(this.cert.cert); } // else get our cert signed - let willBeValidUntil = internal.now() + CERT_LIFETIME; + let willBeValidUntil = this.now() + CERT_LIFETIME; return this.getCertificateSigned(data.sessionToken, keyPair.serializedPublicKey, CERT_LIFETIME) @@ -279,7 +444,7 @@ InternalMethods.prototype = { return Promise.resolve(this.keyPair.keyPair); } // Otherwse, create a keypair and set validity limit. - let willBeValidUntil = internal.now() + KEY_LIFETIME; + let willBeValidUntil = this.now() + KEY_LIFETIME; let d = Promise.defer(); jwcrypto.generateKeyPair("DS160", (err, kp) => { if (err) { @@ -368,18 +533,6 @@ InternalMethods.prototype = { return this.whenVerifiedPromise.promise; }, - /** - * Resend the verification email to the logged-in user. - * - * @return Promise - * fulfilled: json data returned from xhr call - * rejected: error - */ - resendVerificationEmail: function(data) { - this.pollEmailStatus(data.sessionToken, "start"); - return this.fxAccountsClient.resendVerificationEmail(data.sessionToken); - }, - notifyObservers: function(topic) { log.debug("Notifying observers of " + topic); Services.obs.notifyObservers(null, topic, null); @@ -445,194 +598,12 @@ InternalMethods.prototype = { }, setUserAccountData: function(accountData) { - return this.signedInUserStorage.get().then((record) => { + return this.signedInUserStorage.get().then(record => { record.accountData = accountData; this.signedInUser = record; return this.signedInUserStorage.set(record) .then(() => accountData); }); - } -}; - -let internal = null; - -/** - * FxAccounts delegates private methods to an instance of InternalMethods, - * which is not exported. The xpcshell tests need two overrides: - * 1) Access to the real internal.signedInUserStorage. - * 2) The ability to mock InternalMethods. - * If mockInternal is undefined, we are live. - * If mockInternal.onlySetInternal is present, we are executing the first - * case by binding internal to the FxAccounts instance. - * Otherwise if we have a mock instance, we are executing the second case. - */ -this.FxAccounts = function(mockInternal) { - let mocks = mockInternal; - if (mocks && mocks.onlySetInternal) { - mocks = null; - } - internal = new InternalMethods(mocks); - if (mockInternal) { - // Exposes the internal object for testing only. - this.internal = internal; - } -} -this.FxAccounts.prototype = Object.freeze({ - version: DATA_FORMAT_VERSION, - - now: function() { - if (this.internal) { - return this.internal.now(); - } - return internal.now(); - }, - - get localtimeOffsetMsec() { - if (this.internal) { - return this.internal.localtimeOffsetMsec; - } - return internal.localtimeOffsetMsec; - }, - - // set() makes sure that polling is happening, if necessary. - // get() does not wait for verification, and returns an object even if - // unverified. The caller of get() must check .verified . - // The "fxaccounts:onverified" event will fire only when the verified - // state goes from false to true, so callers must register their observer - // and then call get(). In particular, it will not fire when the account - // was found to be verified in a previous boot: if our stored state says - // the account is verified, the event will never fire. So callers must do: - // register notification observer (go) - // userdata = get() - // if (userdata.verified()) {go()} - - /** - * Set the current user signed in to Firefox Accounts. - * - * @param credentials - * The credentials object obtained by logging in or creating - * an account on the FxA server: - * { - * email: The users email address - * uid: The user's unique id - * sessionToken: Session for the FxA server - * keyFetchToken: an unused keyFetchToken - * verified: true/false - * } - * @return Promise - * The promise resolves to null when the data is saved - * successfully and is rejected on error. - */ - setSignedInUser: function setSignedInUser(credentials) { - log.debug("setSignedInUser - aborting any existing flows"); - internal.abortExistingFlow(); - - let record = {version: this.version, accountData: credentials}; - // Cache a clone of the credentials object. - internal.signedInUser = JSON.parse(JSON.stringify(record)); - - // This promise waits for storage, but not for verification. - // We're telling the caller that this is durable now. - return internal.signedInUserStorage.set(record) - .then(() => { - internal.notifyObservers(ONLOGIN_NOTIFICATION); - if (!internal.isUserEmailVerified(credentials)) { - internal.startVerifiedCheck(credentials); - } - }); - }, - - /** - * Get the user currently signed in to Firefox Accounts. - * - * @return Promise - * The promise resolves to the credentials object of the signed-in user: - * { - * email: The user's email address - * uid: The user's unique id - * sessionToken: Session for the FxA server - * kA: An encryption key from the FxA server - * kB: An encryption key derived from the user's FxA password - * verified: email verification status - * } - * or null if no user is signed in. - */ - getSignedInUser: function getSignedInUser() { - return internal.getUserAccountData() - .then((data) => { - if (!data) { - return null; - } - if (!internal.isUserEmailVerified(data)) { - // If the email is not verified, start polling for verification, - // but return null right away. We don't want to return a promise - // that might not be fulfilled for a long time. - internal.startVerifiedCheck(data); - } - return data; - }); - }, - - /** - * Resend the verification email fot the currently signed-in user. - * - */ - resendVerificationEmail: function resendVerificationEmail() { - return this.getSignedInUser().then((data) => { - // If the caller is asking for verification to be re-sent, and there is - // no signed-in user to begin with, this is probably best regarded as an - // error. - if (data) { - return internal.resendVerificationEmail(data); - } - throw new Error("Cannot resend verification email; no signed-in user"); - }); - }, - - /** - * returns a promise that fires with the assertion. If there is no verified - * signed-in user, fires with null. - */ - getAssertion: function getAssertion(audience) { - log.debug("enter getAssertion()"); - let mustBeValidUntil = internal.now() + ASSERTION_LIFETIME; - return internal.getUserAccountData() - .then((data) => { - if (!data) { - // No signed-in user - return null; - } - if (!internal.isUserEmailVerified(data)) { - // Signed-in user has not verified email - return null; - } - return internal.getKeyPair(mustBeValidUntil) - .then((keyPair) => { - return internal.getCertificate(data, keyPair, mustBeValidUntil) - .then((cert) => { - return internal.getAssertionFromCert(data, keyPair, - cert, audience) - }); - }); - }); - }, - - getKeys: function() { - return internal.getKeys(); - }, - - whenVerified: function(userData) { - return internal.whenVerified(userData); - }, - - /** - * Sign the current user out. - * - * @return Promise - * The promise is rejected if a storage error occurs. - */ - signOut: function signOut() { - return internal.signOut(); }, // Return the URI of the remote UI flows. @@ -661,8 +632,7 @@ this.FxAccounts.prototype = Object.freeze({ return url + newQueryPortion; }); } - -}); +}; /** * JSONStorage constructor that creates instances that may set/get @@ -697,8 +667,7 @@ XPCOMUtils.defineLazyGetter(this, "fxAccounts", function() { // XXX Bug 947061 - We need a strategy for resuming email verification after // browser restart - internal.loadAndPoll(); + a.loadAndPoll(); return a; }); - diff --git a/services/fxaccounts/FxAccountsUtils.jsm b/services/fxaccounts/FxAccountsUtils.jsm new file mode 100644 index 000000000000..95205083415a --- /dev/null +++ b/services/fxaccounts/FxAccountsUtils.jsm @@ -0,0 +1,49 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public +* License, v. 2.0. If a copy of the MPL was not distributed with this file, +* You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +this.EXPORTED_SYMBOLS = ["FxAccountsUtils"]; + +this.FxAccountsUtils = Object.freeze({ + /** + * Copies properties from a given object to another object. + * + * @param from (object) + * The object we read property descriptors from. + * @param to (object) + * The object that we set property descriptors on. + * @param options (object) (optional) + * {keys: [...]} + * Lets the caller pass the names of all properties they want to be + * copied. Will copy all properties of the given source object by + * default. + * {bind: object} + * Lets the caller specify the object that will be used to .bind() + * all function properties we find to. Will bind to the given target + * object by default. + */ + copyObjectProperties: function (from, to, opts = {}) { + let keys = (opts && opts.keys) || Object.keys(from); + let thisArg = (opts && opts.bind) || to; + + for (let prop of keys) { + let desc = Object.getOwnPropertyDescriptor(from, prop); + + if (typeof(desc.value) == "function") { + desc.value = desc.value.bind(thisArg); + } + + if (desc.get) { + desc.get = desc.get.bind(thisArg); + } + + if (desc.set) { + desc.set = desc.set.bind(thisArg); + } + + Object.defineProperty(to, prop, desc); + } + } +}); diff --git a/services/fxaccounts/moz.build b/services/fxaccounts/moz.build index a2e055171d34..3b77600007d8 100644 --- a/services/fxaccounts/moz.build +++ b/services/fxaccounts/moz.build @@ -11,7 +11,8 @@ TEST_DIRS += ['tests'] EXTRA_JS_MODULES += [ 'FxAccounts.jsm', 'FxAccountsClient.jsm', - 'FxAccountsCommon.js' + 'FxAccountsCommon.js', + 'FxAccountsUtils.jsm' ] # For now, we will only be using the FxA manager in B2G. diff --git a/services/fxaccounts/tests/xpcshell/test_accounts.js b/services/fxaccounts/tests/xpcshell/test_accounts.js index 5a3f09544f08..9f3adb21e89f 100644 --- a/services/fxaccounts/tests/xpcshell/test_accounts.js +++ b/services/fxaccounts/tests/xpcshell/test_accounts.js @@ -102,28 +102,23 @@ MockStorage.prototype = Object.freeze({ * mock the now() method, so that we can simulate the passing of * time and verify that signatures expire correctly. */ -let MockFxAccounts = function() { - this._getCertificateSigned_calls = []; - this._d_signCertificate = Promise.defer(); - this._now_is = new Date(); - - let mockInternal = { +function MockFxAccounts() { + return new FxAccounts({ + _getCertificateSigned_calls: [], + _d_signCertificate: Promise.defer(), + _now_is: new Date(), signedInUserStorage: new MockStorage(), - now: () => { + now: function () { return this._now_is; }, - getCertificateSigned: (sessionToken, serializedPublicKey) => { - _("mock getCerificateSigned\n"); + getCertificateSigned: function (sessionToken, serializedPublicKey) { + _("mock getCertificateSigned\n"); this._getCertificateSigned_calls.push([sessionToken, serializedPublicKey]); return this._d_signCertificate.promise; }, fxAccountsClient: new MockFxAccountsClient() - }; - FxAccounts.apply(this, [mockInternal]); -}; -MockFxAccounts.prototype = { - __proto__: FxAccounts.prototype, -}; + }); +} add_test(function test_non_https_remote_server_uri() { Services.prefs.setCharPref( @@ -394,15 +389,15 @@ add_task(function test_getAssertion() { // the test, we will update 'now', but leave 'start' where it is. let now = Date.parse("Mon, 13 Jan 2014 21:45:06 GMT"); let start = now; - fxa._now_is = now; + fxa.internal._now_is = now; let d = fxa.getAssertion("audience.example.com"); // At this point, a thread has been spawned to generate the keys. _("-- back from fxa.getAssertion\n"); - fxa._d_signCertificate.resolve("cert1"); + fxa.internal._d_signCertificate.resolve("cert1"); let assertion = yield d; - do_check_eq(fxa._getCertificateSigned_calls.length, 1); - do_check_eq(fxa._getCertificateSigned_calls[0][0], "sessionToken"); + do_check_eq(fxa.internal._getCertificateSigned_calls.length, 1); + do_check_eq(fxa.internal._getCertificateSigned_calls[0][0], "sessionToken"); do_check_neq(assertion, null); _("ASSERTION: " + assertion + "\n"); let pieces = assertion.split("~"); @@ -424,18 +419,18 @@ add_task(function test_getAssertion() { do_check_eq(exp, now + TWO_MINUTES_MS); // Reset for next call. - fxa._d_signCertificate = Promise.defer(); + fxa.internal._d_signCertificate = Promise.defer(); // Getting a new assertion "soon" (i.e., w/o incrementing "now"), even for // a new audience, should not provoke key generation or a signing request. assertion = yield fxa.getAssertion("other.example.com"); // There were no additional calls - same number of getcert calls as before - do_check_eq(fxa._getCertificateSigned_calls.length, 1); + do_check_eq(fxa.internal._getCertificateSigned_calls.length, 1); // Wait an hour; assertion expires, but not the certificate now += ONE_HOUR_MS; - fxa._now_is = now; + fxa.internal._now_is = now; // This won't block on anything - will make an assertion, but not get a // new certificate. @@ -462,12 +457,12 @@ add_task(function test_getAssertion() { // Now we wait even longer, and expect both assertion and cert to expire. So // we will have to get a new keypair and cert. now += ONE_DAY_MS; - fxa._now_is = now; + fxa.internal._now_is = now; d = fxa.getAssertion("fourth.example.com"); - fxa._d_signCertificate.resolve("cert2"); + fxa.internal._d_signCertificate.resolve("cert2"); assertion = yield d; - do_check_eq(fxa._getCertificateSigned_calls.length, 2); - do_check_eq(fxa._getCertificateSigned_calls[1][0], "sessionToken"); + do_check_eq(fxa.internal._getCertificateSigned_calls.length, 2); + do_check_eq(fxa.internal._getCertificateSigned_calls[1][0], "sessionToken"); pieces = assertion.split("~"); do_check_eq(pieces[0], "cert2"); p2 = pieces[1].split("."); From dbfeb52f65698c64bc795db27b6302b2f6cf29a6 Mon Sep 17 00:00:00 2001 From: Mark Finkle Date: Mon, 3 Feb 2014 19:12:36 -0800 Subject: [PATCH 08/78] Bug 961952 - HardwareUtils.getMemSize can be expensive during startup. r=rnewman --- mobile/android/base/util/HardwareUtils.java | 110 +++++++++++++------- 1 file changed, 74 insertions(+), 36 deletions(-) diff --git a/mobile/android/base/util/HardwareUtils.java b/mobile/android/base/util/HardwareUtils.java index 0ba85d360ff9..d80d8786435c 100644 --- a/mobile/android/base/util/HardwareUtils.java +++ b/mobile/android/base/util/HardwareUtils.java @@ -12,8 +12,8 @@ import android.os.Build; import android.util.Log; import android.view.ViewConfiguration; +import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.RandomAccessFile; import java.io.IOException; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -28,6 +28,10 @@ public final class HardwareUtils { // where we can't depend on Gecko to be up and running e.g. show/hide // reading list capabilities in HomePager. private static final int LOW_MEMORY_THRESHOLD_MB = 384; + + // Number of bytes of /proc/meminfo to read in one go. + private static final int MEMINFO_BUFFER_SIZE_BYTES = 256; + private static volatile int sTotalRAM = -1; private static Context sContext; @@ -89,9 +93,58 @@ public final class HardwareUtils { return sHasMenuButton; } + /** + * Helper functions used to extract key/value data from /proc/meminfo + * Pulled from: + * http://androidxref.com/4.2_r1/xref/frameworks/base/core/java/com/android/internal/util/MemInfoReader.java + */ + + private static boolean matchMemText(byte[] buffer, int index, int bufferLength, byte[] text) { + final int N = text.length; + if ((index + N) >= bufferLength) { + return false; + } + for (int i = 0; i < N; i++) { + if (buffer[index + i] != text[i]) { + return false; + } + } + return true; + } + + /** + * Parses a line like: + * + * MemTotal: 1605324 kB + * + * into 1605324. + * + * @return the first uninterrupted sequence of digits following the + * specified index, parsed as an integer value in KB. + */ + private static int extractMemValue(byte[] buffer, int offset, int length) { + if (offset >= length) { + return 0; + } + + while (offset < length && buffer[offset] != '\n') { + if (buffer[offset] >= '0' && buffer[offset] <= '9') { + int start = offset++; + while (offset < length && + buffer[offset] >= '0' && + buffer[offset] <= '9') { + ++offset; + } + return Integer.parseInt(new String(buffer, start, offset - start), 10); + } + ++offset; + } + return 0; + } + /** * Fetch the total memory of the device in MB by parsing /proc/meminfo. - * + * * Of course, Android doesn't have a neat and tidy way to find total * RAM, so we do it by parsing /proc/meminfo. * @@ -102,48 +155,33 @@ public final class HardwareUtils { return sTotalRAM; } + // This is the string "MemTotal" that we're searching for in the buffer. + final byte[] MEMTOTAL = {'M', 'e', 'm', 'T', 'o', 't', 'a', 'l'}; try { - RandomAccessFile reader = new RandomAccessFile("/proc/meminfo", "r"); + final byte[] buffer = new byte[MEMINFO_BUFFER_SIZE_BYTES]; + final FileInputStream is = new FileInputStream("/proc/meminfo"); try { - // MemTotal will be one of the first three lines. - int i = 0; - String memTotal = null; - while (i++ < 3) { - memTotal = reader.readLine(); - if (memTotal == null || - memTotal.startsWith("MemTotal: ")) { - break; - } - memTotal = null; - } + final int length = is.read(buffer); - if (memTotal == null) { - return sTotalRAM = 0; - } - - // Parse a line like this: - // MemTotal: 1605324 kB - Matcher m = Pattern.compile("^MemTotal:\\s+([0-9]+) kB\\s*$") - .matcher(memTotal); - if (m.matches()) { - String kb = m.group(1); - if (kb != null) { - sTotalRAM = (Integer.parseInt(kb) / 1024); + for (int i = 0; i < length; i++) { + if (matchMemText(buffer, i, length, MEMTOTAL)) { + i += 8; + sTotalRAM = extractMemValue(buffer, i, length) / 1024; Log.d(LOGTAG, "System memory: " + sTotalRAM + "MB."); return sTotalRAM; } } + } finally { + is.close(); + } - Log.w(LOGTAG, "Got unexpected MemTotal line: " + memTotal); - return sTotalRAM = 0; - } finally { - reader.close(); - } - } catch (FileNotFoundException f) { - return sTotalRAM = 0; - } catch (IOException e) { - return sTotalRAM = 0; - } + Log.w(LOGTAG, "Did not find MemTotal line in /proc/meminfo."); + return sTotalRAM = 0; + } catch (FileNotFoundException f) { + return sTotalRAM = 0; + } catch (IOException e) { + return sTotalRAM = 0; + } } public static boolean isLowMemoryPlatform() { From 4124f6c54ba3b3d9e8efa16d6dd8767f12942ea2 Mon Sep 17 00:00:00 2001 From: Phil Ringnalda Date: Mon, 3 Feb 2014 20:30:45 -0800 Subject: [PATCH 09/78] Back out 2865fd5e5458 (bug 967120) for xpcshell and browser-chrome bustage --- services/fxaccounts/FxAccounts.jsm | 425 ++++++++++-------- services/fxaccounts/FxAccountsUtils.jsm | 49 -- services/fxaccounts/moz.build | 3 +- .../tests/xpcshell/test_accounts.js | 47 +- 4 files changed, 255 insertions(+), 269 deletions(-) delete mode 100644 services/fxaccounts/FxAccountsUtils.jsm diff --git a/services/fxaccounts/FxAccounts.jsm b/services/fxaccounts/FxAccounts.jsm index d26179a5a96a..56bd86b2baba 100644 --- a/services/fxaccounts/FxAccounts.jsm +++ b/services/fxaccounts/FxAccounts.jsm @@ -17,57 +17,11 @@ Cu.import("resource://gre/modules/Timer.jsm"); Cu.import("resource://gre/modules/Task.jsm"); Cu.import("resource://gre/modules/FxAccountsClient.jsm"); Cu.import("resource://gre/modules/FxAccountsCommon.js"); -Cu.import("resource://gre/modules/FxAccountsUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "jwcrypto", - "resource://gre/modules/identity/jwcrypto.jsm"); + "resource://gre/modules/identity/jwcrypto.jsm"); -// All properties exposed by the public FxAccounts API. -let publicProperties = [ - "getAccountsURI", - "getAssertion", - "getKeys", - "getSignedInUser", - "loadAndPoll", - "localtimeOffsetMsec", - "now", - "promiseAccountsForceSigninURI", - "resendVerificationEmail", - "setSignedInUser", - "signOut", - "version", - "whenVerified" -]; - -/** - * The public API's constructor. - */ -this.FxAccounts = function (mockInternal) { - let internal = new FxAccountsInternal(); - let external = {}; - - // Copy all public properties to the 'external' object. - let prototype = FxAccountsInternal.prototype; - let options = {keys: publicProperties, bind: internal}; - FxAccountsUtils.copyObjectProperties(prototype, external, options); - - // Copy all of the mock's properties to the internal object. - if (mockInternal && !mockInternal.onlySetInternal) { - FxAccountsUtils.copyObjectProperties(mockInternal, internal); - } - - if (mockInternal) { - // Exposes the internal object for testing only. - external.internal = internal; - } - - return Object.freeze(external); -} - -/** - * The internal API's constructor. - */ -function FxAccountsInternal() { +InternalMethods = function(mock) { this.cert = null; this.keyPair = null; this.signedInUser = null; @@ -95,23 +49,23 @@ function FxAccountsInternal() { this.fxAccountsClient = new FxAccountsClient(); - // We don't reference |profileDir| in the top-level module scope - // as we may be imported before we know where it is. - this.signedInUserStorage = new JSONStorage({ - filename: DEFAULT_STORAGE_FILENAME, - baseDir: OS.Constants.Path.profileDir, - }); + if (mock) { // Testing. + Object.keys(mock).forEach((prop) => { + log.debug("InternalMethods: mocking: " + prop); + this[prop] = mock[prop]; + }); + } + if (!this.signedInUserStorage) { + // Normal (i.e., non-testing) initialization. + // We don't reference |profileDir| in the top-level module scope + // as we may be imported before we know where it is. + this.signedInUserStorage = new JSONStorage({ + filename: DEFAULT_STORAGE_FILENAME, + baseDir: OS.Constants.Path.profileDir, + }); + } } - -/** - * The internal API's prototype. - */ -FxAccountsInternal.prototype = { - - /** - * The current data format's version number. - */ - version: DATA_FORMAT_VERSION, +InternalMethods.prototype = { /** * Return the current time in milliseconds as an integer. Allows tests to @@ -148,125 +102,6 @@ FxAccountsInternal.prototype = { return this.fxAccountsClient.accountKeys(keyFetchToken); }, - // set() makes sure that polling is happening, if necessary. - // get() does not wait for verification, and returns an object even if - // unverified. The caller of get() must check .verified . - // The "fxaccounts:onverified" event will fire only when the verified - // state goes from false to true, so callers must register their observer - // and then call get(). In particular, it will not fire when the account - // was found to be verified in a previous boot: if our stored state says - // the account is verified, the event will never fire. So callers must do: - // register notification observer (go) - // userdata = get() - // if (userdata.verified()) {go()} - - /** - * Get the user currently signed in to Firefox Accounts. - * - * @return Promise - * The promise resolves to the credentials object of the signed-in user: - * { - * email: The user's email address - * uid: The user's unique id - * sessionToken: Session for the FxA server - * kA: An encryption key from the FxA server - * kB: An encryption key derived from the user's FxA password - * verified: email verification status - * } - * or null if no user is signed in. - */ - getSignedInUser: function getSignedInUser() { - return this.getUserAccountData().then(data => { - if (!data) { - return null; - } - if (!this.isUserEmailVerified(data)) { - // If the email is not verified, start polling for verification, - // but return null right away. We don't want to return a promise - // that might not be fulfilled for a long time. - this.startVerifiedCheck(data); - } - return data; - }); - }, - - /** - * Set the current user signed in to Firefox Accounts. - * - * @param credentials - * The credentials object obtained by logging in or creating - * an account on the FxA server: - * { - * email: The users email address - * uid: The user's unique id - * sessionToken: Session for the FxA server - * keyFetchToken: an unused keyFetchToken - * verified: true/false - * } - * @return Promise - * The promise resolves to null when the data is saved - * successfully and is rejected on error. - */ - setSignedInUser: function setSignedInUser(credentials) { - log.debug("setSignedInUser - aborting any existing flows"); - this.abortExistingFlow(); - - let record = {version: this.version, accountData: credentials}; - // Cache a clone of the credentials object. - this.signedInUser = JSON.parse(JSON.stringify(record)); - - // This promise waits for storage, but not for verification. - // We're telling the caller that this is durable now. - return this.signedInUserStorage.set(record).then(() => { - this.notifyObservers(ONLOGIN_NOTIFICATION); - if (!this.isUserEmailVerified(credentials)) { - this.startVerifiedCheck(credentials); - } - }); - }, - - /** - * returns a promise that fires with the assertion. If there is no verified - * signed-in user, fires with null. - */ - getAssertion: function getAssertion(audience) { - log.debug("enter getAssertion()"); - let mustBeValidUntil = this.now() + ASSERTION_LIFETIME; - return this.getUserAccountData().then(data => { - if (!data) { - // No signed-in user - return null; - } - if (!this.isUserEmailVerified(data)) { - // Signed-in user has not verified email - return null; - } - return this.getKeyPair(mustBeValidUntil).then(keyPair => { - return this.getCertificate(data, keyPair, mustBeValidUntil) - .then(cert => { - return this.getAssertionFromCert(data, keyPair, cert, audience); - }); - }); - }); - }, - - /** - * Resend the verification email fot the currently signed-in user. - * - */ - resendVerificationEmail: function resendVerificationEmail() { - return this.getSignedInUser().then(data => { - // If the caller is asking for verification to be re-sent, and there is - // no signed-in user to begin with, this is probably best regarded as an - // error. - if (data) { - this.pollEmailStatus(data.sessionToken, "start"); - return this.fxAccountsClient.resendVerificationEmail(data.sessionToken); - } - throw new Error("Cannot resend verification email; no signed-in user"); - }); - }, - /* * Reset state such that any previous flow is canceled. */ @@ -344,14 +179,14 @@ FxAccountsInternal.prototype = { return Task.spawn(function* task() { // Sign out if we don't have a key fetch token. if (!keyFetchToken) { - yield this.signOut(); + yield internal.signOut(); return null; } - let myGenerationCount = this.generationCount; + let myGenerationCount = internal.generationCount; - let {kA, wrapKB} = yield this.fetchKeys(keyFetchToken); + let {kA, wrapKB} = yield internal.fetchKeys(keyFetchToken); - let data = yield this.getUserAccountData(); + let data = yield internal.getUserAccountData(); // Sanity check that the user hasn't changed out from under us if (data.keyFetchToken !== keyFetchToken) { @@ -373,16 +208,16 @@ FxAccountsInternal.prototype = { // Before writing any data, ensure that a new flow hasn't been // started behind our backs. - if (this.generationCount !== myGenerationCount) { + if (internal.generationCount !== myGenerationCount) { return null; } - yield this.setUserAccountData(data); + yield internal.setUserAccountData(data); // We are now ready for business. This should only be invoked once // per setSignedInUser(), regardless of whether we've rebooted since // setSignedInUser() was called. - this.notifyObservers(ONVERIFIED_NOTIFICATION); + internal.notifyObservers(ONVERIFIED_NOTIFICATION); return data; }.bind(this)); }, @@ -392,8 +227,8 @@ FxAccountsInternal.prototype = { let payload = {}; let d = Promise.defer(); let options = { - localtimeOffsetMsec: this.localtimeOffsetMsec, - now: this.now() + localtimeOffsetMsec: internal.localtimeOffsetMsec, + now: internal.now() }; // "audience" should look like "http://123done.org". // The generated assertion will expire in two minutes. @@ -417,7 +252,7 @@ FxAccountsInternal.prototype = { return Promise.resolve(this.cert.cert); } // else get our cert signed - let willBeValidUntil = this.now() + CERT_LIFETIME; + let willBeValidUntil = internal.now() + CERT_LIFETIME; return this.getCertificateSigned(data.sessionToken, keyPair.serializedPublicKey, CERT_LIFETIME) @@ -444,7 +279,7 @@ FxAccountsInternal.prototype = { return Promise.resolve(this.keyPair.keyPair); } // Otherwse, create a keypair and set validity limit. - let willBeValidUntil = this.now() + KEY_LIFETIME; + let willBeValidUntil = internal.now() + KEY_LIFETIME; let d = Promise.defer(); jwcrypto.generateKeyPair("DS160", (err, kp) => { if (err) { @@ -533,6 +368,18 @@ FxAccountsInternal.prototype = { return this.whenVerifiedPromise.promise; }, + /** + * Resend the verification email to the logged-in user. + * + * @return Promise + * fulfilled: json data returned from xhr call + * rejected: error + */ + resendVerificationEmail: function(data) { + this.pollEmailStatus(data.sessionToken, "start"); + return this.fxAccountsClient.resendVerificationEmail(data.sessionToken); + }, + notifyObservers: function(topic) { log.debug("Notifying observers of " + topic); Services.obs.notifyObservers(null, topic, null); @@ -598,12 +445,194 @@ FxAccountsInternal.prototype = { }, setUserAccountData: function(accountData) { - return this.signedInUserStorage.get().then(record => { + return this.signedInUserStorage.get().then((record) => { record.accountData = accountData; this.signedInUser = record; return this.signedInUserStorage.set(record) .then(() => accountData); }); + } +}; + +let internal = null; + +/** + * FxAccounts delegates private methods to an instance of InternalMethods, + * which is not exported. The xpcshell tests need two overrides: + * 1) Access to the real internal.signedInUserStorage. + * 2) The ability to mock InternalMethods. + * If mockInternal is undefined, we are live. + * If mockInternal.onlySetInternal is present, we are executing the first + * case by binding internal to the FxAccounts instance. + * Otherwise if we have a mock instance, we are executing the second case. + */ +this.FxAccounts = function(mockInternal) { + let mocks = mockInternal; + if (mocks && mocks.onlySetInternal) { + mocks = null; + } + internal = new InternalMethods(mocks); + if (mockInternal) { + // Exposes the internal object for testing only. + this.internal = internal; + } +} +this.FxAccounts.prototype = Object.freeze({ + version: DATA_FORMAT_VERSION, + + now: function() { + if (this.internal) { + return this.internal.now(); + } + return internal.now(); + }, + + get localtimeOffsetMsec() { + if (this.internal) { + return this.internal.localtimeOffsetMsec; + } + return internal.localtimeOffsetMsec; + }, + + // set() makes sure that polling is happening, if necessary. + // get() does not wait for verification, and returns an object even if + // unverified. The caller of get() must check .verified . + // The "fxaccounts:onverified" event will fire only when the verified + // state goes from false to true, so callers must register their observer + // and then call get(). In particular, it will not fire when the account + // was found to be verified in a previous boot: if our stored state says + // the account is verified, the event will never fire. So callers must do: + // register notification observer (go) + // userdata = get() + // if (userdata.verified()) {go()} + + /** + * Set the current user signed in to Firefox Accounts. + * + * @param credentials + * The credentials object obtained by logging in or creating + * an account on the FxA server: + * { + * email: The users email address + * uid: The user's unique id + * sessionToken: Session for the FxA server + * keyFetchToken: an unused keyFetchToken + * verified: true/false + * } + * @return Promise + * The promise resolves to null when the data is saved + * successfully and is rejected on error. + */ + setSignedInUser: function setSignedInUser(credentials) { + log.debug("setSignedInUser - aborting any existing flows"); + internal.abortExistingFlow(); + + let record = {version: this.version, accountData: credentials}; + // Cache a clone of the credentials object. + internal.signedInUser = JSON.parse(JSON.stringify(record)); + + // This promise waits for storage, but not for verification. + // We're telling the caller that this is durable now. + return internal.signedInUserStorage.set(record) + .then(() => { + internal.notifyObservers(ONLOGIN_NOTIFICATION); + if (!internal.isUserEmailVerified(credentials)) { + internal.startVerifiedCheck(credentials); + } + }); + }, + + /** + * Get the user currently signed in to Firefox Accounts. + * + * @return Promise + * The promise resolves to the credentials object of the signed-in user: + * { + * email: The user's email address + * uid: The user's unique id + * sessionToken: Session for the FxA server + * kA: An encryption key from the FxA server + * kB: An encryption key derived from the user's FxA password + * verified: email verification status + * } + * or null if no user is signed in. + */ + getSignedInUser: function getSignedInUser() { + return internal.getUserAccountData() + .then((data) => { + if (!data) { + return null; + } + if (!internal.isUserEmailVerified(data)) { + // If the email is not verified, start polling for verification, + // but return null right away. We don't want to return a promise + // that might not be fulfilled for a long time. + internal.startVerifiedCheck(data); + } + return data; + }); + }, + + /** + * Resend the verification email fot the currently signed-in user. + * + */ + resendVerificationEmail: function resendVerificationEmail() { + return this.getSignedInUser().then((data) => { + // If the caller is asking for verification to be re-sent, and there is + // no signed-in user to begin with, this is probably best regarded as an + // error. + if (data) { + return internal.resendVerificationEmail(data); + } + throw new Error("Cannot resend verification email; no signed-in user"); + }); + }, + + /** + * returns a promise that fires with the assertion. If there is no verified + * signed-in user, fires with null. + */ + getAssertion: function getAssertion(audience) { + log.debug("enter getAssertion()"); + let mustBeValidUntil = internal.now() + ASSERTION_LIFETIME; + return internal.getUserAccountData() + .then((data) => { + if (!data) { + // No signed-in user + return null; + } + if (!internal.isUserEmailVerified(data)) { + // Signed-in user has not verified email + return null; + } + return internal.getKeyPair(mustBeValidUntil) + .then((keyPair) => { + return internal.getCertificate(data, keyPair, mustBeValidUntil) + .then((cert) => { + return internal.getAssertionFromCert(data, keyPair, + cert, audience) + }); + }); + }); + }, + + getKeys: function() { + return internal.getKeys(); + }, + + whenVerified: function(userData) { + return internal.whenVerified(userData); + }, + + /** + * Sign the current user out. + * + * @return Promise + * The promise is rejected if a storage error occurs. + */ + signOut: function signOut() { + return internal.signOut(); }, // Return the URI of the remote UI flows. @@ -632,7 +661,8 @@ FxAccountsInternal.prototype = { return url + newQueryPortion; }); } -}; + +}); /** * JSONStorage constructor that creates instances that may set/get @@ -667,7 +697,8 @@ XPCOMUtils.defineLazyGetter(this, "fxAccounts", function() { // XXX Bug 947061 - We need a strategy for resuming email verification after // browser restart - a.loadAndPoll(); + internal.loadAndPoll(); return a; }); + diff --git a/services/fxaccounts/FxAccountsUtils.jsm b/services/fxaccounts/FxAccountsUtils.jsm deleted file mode 100644 index 95205083415a..000000000000 --- a/services/fxaccounts/FxAccountsUtils.jsm +++ /dev/null @@ -1,49 +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/. */ - -"use strict"; - -this.EXPORTED_SYMBOLS = ["FxAccountsUtils"]; - -this.FxAccountsUtils = Object.freeze({ - /** - * Copies properties from a given object to another object. - * - * @param from (object) - * The object we read property descriptors from. - * @param to (object) - * The object that we set property descriptors on. - * @param options (object) (optional) - * {keys: [...]} - * Lets the caller pass the names of all properties they want to be - * copied. Will copy all properties of the given source object by - * default. - * {bind: object} - * Lets the caller specify the object that will be used to .bind() - * all function properties we find to. Will bind to the given target - * object by default. - */ - copyObjectProperties: function (from, to, opts = {}) { - let keys = (opts && opts.keys) || Object.keys(from); - let thisArg = (opts && opts.bind) || to; - - for (let prop of keys) { - let desc = Object.getOwnPropertyDescriptor(from, prop); - - if (typeof(desc.value) == "function") { - desc.value = desc.value.bind(thisArg); - } - - if (desc.get) { - desc.get = desc.get.bind(thisArg); - } - - if (desc.set) { - desc.set = desc.set.bind(thisArg); - } - - Object.defineProperty(to, prop, desc); - } - } -}); diff --git a/services/fxaccounts/moz.build b/services/fxaccounts/moz.build index 3b77600007d8..a2e055171d34 100644 --- a/services/fxaccounts/moz.build +++ b/services/fxaccounts/moz.build @@ -11,8 +11,7 @@ TEST_DIRS += ['tests'] EXTRA_JS_MODULES += [ 'FxAccounts.jsm', 'FxAccountsClient.jsm', - 'FxAccountsCommon.js', - 'FxAccountsUtils.jsm' + 'FxAccountsCommon.js' ] # For now, we will only be using the FxA manager in B2G. diff --git a/services/fxaccounts/tests/xpcshell/test_accounts.js b/services/fxaccounts/tests/xpcshell/test_accounts.js index 9f3adb21e89f..5a3f09544f08 100644 --- a/services/fxaccounts/tests/xpcshell/test_accounts.js +++ b/services/fxaccounts/tests/xpcshell/test_accounts.js @@ -102,23 +102,28 @@ MockStorage.prototype = Object.freeze({ * mock the now() method, so that we can simulate the passing of * time and verify that signatures expire correctly. */ -function MockFxAccounts() { - return new FxAccounts({ - _getCertificateSigned_calls: [], - _d_signCertificate: Promise.defer(), - _now_is: new Date(), +let MockFxAccounts = function() { + this._getCertificateSigned_calls = []; + this._d_signCertificate = Promise.defer(); + this._now_is = new Date(); + + let mockInternal = { signedInUserStorage: new MockStorage(), - now: function () { + now: () => { return this._now_is; }, - getCertificateSigned: function (sessionToken, serializedPublicKey) { - _("mock getCertificateSigned\n"); + getCertificateSigned: (sessionToken, serializedPublicKey) => { + _("mock getCerificateSigned\n"); this._getCertificateSigned_calls.push([sessionToken, serializedPublicKey]); return this._d_signCertificate.promise; }, fxAccountsClient: new MockFxAccountsClient() - }); -} + }; + FxAccounts.apply(this, [mockInternal]); +}; +MockFxAccounts.prototype = { + __proto__: FxAccounts.prototype, +}; add_test(function test_non_https_remote_server_uri() { Services.prefs.setCharPref( @@ -389,15 +394,15 @@ add_task(function test_getAssertion() { // the test, we will update 'now', but leave 'start' where it is. let now = Date.parse("Mon, 13 Jan 2014 21:45:06 GMT"); let start = now; - fxa.internal._now_is = now; + fxa._now_is = now; let d = fxa.getAssertion("audience.example.com"); // At this point, a thread has been spawned to generate the keys. _("-- back from fxa.getAssertion\n"); - fxa.internal._d_signCertificate.resolve("cert1"); + fxa._d_signCertificate.resolve("cert1"); let assertion = yield d; - do_check_eq(fxa.internal._getCertificateSigned_calls.length, 1); - do_check_eq(fxa.internal._getCertificateSigned_calls[0][0], "sessionToken"); + do_check_eq(fxa._getCertificateSigned_calls.length, 1); + do_check_eq(fxa._getCertificateSigned_calls[0][0], "sessionToken"); do_check_neq(assertion, null); _("ASSERTION: " + assertion + "\n"); let pieces = assertion.split("~"); @@ -419,18 +424,18 @@ add_task(function test_getAssertion() { do_check_eq(exp, now + TWO_MINUTES_MS); // Reset for next call. - fxa.internal._d_signCertificate = Promise.defer(); + fxa._d_signCertificate = Promise.defer(); // Getting a new assertion "soon" (i.e., w/o incrementing "now"), even for // a new audience, should not provoke key generation or a signing request. assertion = yield fxa.getAssertion("other.example.com"); // There were no additional calls - same number of getcert calls as before - do_check_eq(fxa.internal._getCertificateSigned_calls.length, 1); + do_check_eq(fxa._getCertificateSigned_calls.length, 1); // Wait an hour; assertion expires, but not the certificate now += ONE_HOUR_MS; - fxa.internal._now_is = now; + fxa._now_is = now; // This won't block on anything - will make an assertion, but not get a // new certificate. @@ -457,12 +462,12 @@ add_task(function test_getAssertion() { // Now we wait even longer, and expect both assertion and cert to expire. So // we will have to get a new keypair and cert. now += ONE_DAY_MS; - fxa.internal._now_is = now; + fxa._now_is = now; d = fxa.getAssertion("fourth.example.com"); - fxa.internal._d_signCertificate.resolve("cert2"); + fxa._d_signCertificate.resolve("cert2"); assertion = yield d; - do_check_eq(fxa.internal._getCertificateSigned_calls.length, 2); - do_check_eq(fxa.internal._getCertificateSigned_calls[1][0], "sessionToken"); + do_check_eq(fxa._getCertificateSigned_calls.length, 2); + do_check_eq(fxa._getCertificateSigned_calls[1][0], "sessionToken"); pieces = assertion.split("~"); do_check_eq(pieces[0], "cert2"); p2 = pieces[1].split("."); From 7e7788903b427b1face0d46753a384dfd446dd01 Mon Sep 17 00:00:00 2001 From: Victor Porof Date: Mon, 3 Feb 2014 22:32:59 +0200 Subject: [PATCH 10/78] Bug 964977 - Infer JSON from response, r=rcampbell --- .../devtools/netmonitor/netmonitor-view.js | 32 +++++--- browser/devtools/netmonitor/test/browser.ini | 2 + .../test/browser_net_json_text_mime.js | 81 +++++++++++++++++++ browser/devtools/netmonitor/test/head.js | 1 + .../test/html_json-text-mime-test-page.html | 35 ++++++++ .../test/sjs_content-type-test-server.sjs | 8 ++ 6 files changed, 150 insertions(+), 9 deletions(-) create mode 100644 browser/devtools/netmonitor/test/browser_net_json_text_mime.js create mode 100644 browser/devtools/netmonitor/test/html_json-text-mime-test-page.html diff --git a/browser/devtools/netmonitor/netmonitor-view.js b/browser/devtools/netmonitor/netmonitor-view.js index 31434c7ceabf..01c7492a1904 100644 --- a/browser/devtools/netmonitor/netmonitor-view.js +++ b/browser/devtools/netmonitor/netmonitor-view.js @@ -2126,19 +2126,33 @@ NetworkDetailsView.prototype = { // Handle json, which we tentatively identify by checking the MIME type // for "json" after any word boundary. This works for the standard // "application/json", and also for custom types like "x-bigcorp-json". - // This should be marginally more reliable than just looking for "json". - if (/\bjson/.test(mimeType)) { - let jsonpRegex = /^[a-zA-Z0-9_$]+\(|\)$/g; // JSONP with callback. + // Additionally, we also directly parse the response text content to + // verify whether it's json or not, to handle responses incorrectly + // labeled as text/plain instead. + let jsonMimeType, jsonObject, jsonObjectParseError; + try { + // Test the mime type *and* parse the string, because "JSONP" responses + // (json with callback) aren't actually valid json. + jsonMimeType = /\bjson/.test(mimeType); + jsonObject = JSON.parse(aString); + } catch (e) { + jsonObjectParseError = e; + } + if (jsonMimeType || jsonObject) { + // Extract the actual json substring in case this might be a "JSONP". + let jsonpRegex = /^[a-zA-Z0-9_$]+\(|\)$/g; let sanitizedJSON = aString.replace(jsonpRegex, ""); let callbackPadding = aString.match(jsonpRegex); // Make sure this is a valid JSON object first. If so, nicely display // the parsing results in a variables view. Otherwise, simply show // the contents as plain text. - try { - var jsonObject = JSON.parse(sanitizedJSON); - } catch (e) { - var parsingError = e; + if (sanitizedJSON != aString) { + try { + jsonObject = JSON.parse(sanitizedJSON); + } catch (e) { + jsonObjectParseError = e; + } } // Valid JSON. @@ -2157,8 +2171,8 @@ NetworkDetailsView.prototype = { else { $("#response-content-textarea-box").hidden = false; let infoHeader = $("#response-content-info-header"); - infoHeader.setAttribute("value", parsingError); - infoHeader.setAttribute("tooltiptext", parsingError); + infoHeader.setAttribute("value", jsonObjectParseError); + infoHeader.setAttribute("tooltiptext", jsonObjectParseError); infoHeader.hidden = false; return NetMonitorView.editor("#response-content-textarea").then(aEditor => { aEditor.setMode(Editor.modes.js); diff --git a/browser/devtools/netmonitor/test/browser.ini b/browser/devtools/netmonitor/test/browser.ini index 104f35f75add..872013fda912 100644 --- a/browser/devtools/netmonitor/test/browser.ini +++ b/browser/devtools/netmonitor/test/browser.ini @@ -10,6 +10,7 @@ support-files = html_json-custom-mime-test-page.html html_json-long-test-page.html html_json-malformed-test-page.html + html_json-text-mime-test-page.html html_jsonp-test-page.html html_navigate-test-page.html html_post-data-test-page.html @@ -46,6 +47,7 @@ support-files = [browser_net_json-long.js] [browser_net_json-malformed.js] [browser_net_json_custom_mime.js] +[browser_net_json_text_mime.js] [browser_net_jsonp.js] [browser_net_large-response.js] [browser_net_open_request_in_tab.js] diff --git a/browser/devtools/netmonitor/test/browser_net_json_text_mime.js b/browser/devtools/netmonitor/test/browser_net_json_text_mime.js new file mode 100644 index 000000000000..2057881dea80 --- /dev/null +++ b/browser/devtools/netmonitor/test/browser_net_json_text_mime.js @@ -0,0 +1,81 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests if JSON responses with unusal/custom MIME types are handled correctly. + */ + +function test() { + initNetMonitor(JSON_TEXT_MIME_URL).then(([aTab, aDebuggee, aMonitor]) => { + info("Starting test... "); + + let { document, L10N, NetMonitorView } = aMonitor.panelWin; + let { RequestsMenu } = NetMonitorView; + + RequestsMenu.lazyUpdate = false; + + waitForNetworkEvents(aMonitor, 1).then(() => { + verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0), + "GET", CONTENT_TYPE_SJS + "?fmt=json-text-mime", { + status: 200, + statusText: "OK", + type: "plain", + fullMimeType: "text/plain; charset=utf-8", + size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.04), + time: true + }); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + document.getElementById("details-pane-toggle")); + EventUtils.sendMouseEvent({ type: "mousedown" }, + document.querySelectorAll("#details-pane tab")[3]); + + let RESPONSE_BODY_DISPLAYED = aMonitor.panelWin.EVENTS.RESPONSE_BODY_DISPLAYED; + waitFor(aMonitor.panelWin, RESPONSE_BODY_DISPLAYED) + .then(testResponseTab) + .then(() => teardown(aMonitor)) + .then(finish); + + function testResponseTab() { + let tab = document.querySelectorAll("#details-pane tab")[3]; + let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3]; + + is(tab.getAttribute("selected"), "true", + "The response tab in the network details pane should be selected."); + + is(tabpanel.querySelector("#response-content-info-header") + .hasAttribute("hidden"), true, + "The response info header doesn't have the intended visibility."); + is(tabpanel.querySelector("#response-content-json-box") + .hasAttribute("hidden"), false, + "The response content json box doesn't have the intended visibility."); + is(tabpanel.querySelector("#response-content-textarea-box") + .hasAttribute("hidden"), true, + "The response content textarea box doesn't have the intended visibility."); + is(tabpanel.querySelector("#response-content-image-box") + .hasAttribute("hidden"), true, + "The response content image box doesn't have the intended visibility."); + + is(tabpanel.querySelectorAll(".variables-view-scope").length, 1, + "There should be 1 json scope displayed in this tabpanel."); + is(tabpanel.querySelectorAll(".variables-view-property").length, 2, + "There should be 2 json properties displayed in this tabpanel."); + is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0, + "The empty notice should not be displayed in this tabpanel."); + + let jsonScope = tabpanel.querySelectorAll(".variables-view-scope")[0]; + is(jsonScope.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"), + "greeting", "The first json property name was incorrect."); + is(jsonScope.querySelectorAll(".variables-view-property .value")[0].getAttribute("value"), + "\"Hello third-party JSON!\"", "The first json property value was incorrect."); + + is(jsonScope.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"), + "__proto__", "The second json property name was incorrect."); + is(jsonScope.querySelectorAll(".variables-view-property .value")[1].getAttribute("value"), + "Object", "The second json property value was incorrect."); + } + }); + + aDebuggee.performRequests(); + }); +} diff --git a/browser/devtools/netmonitor/test/head.js b/browser/devtools/netmonitor/test/head.js index bb3ac82edb05..dedd9713bf94 100644 --- a/browser/devtools/netmonitor/test/head.js +++ b/browser/devtools/netmonitor/test/head.js @@ -26,6 +26,7 @@ const JSONP_URL = EXAMPLE_URL + "html_jsonp-test-page.html"; const JSON_LONG_URL = EXAMPLE_URL + "html_json-long-test-page.html"; const JSON_MALFORMED_URL = EXAMPLE_URL + "html_json-malformed-test-page.html"; const JSON_CUSTOM_MIME_URL = EXAMPLE_URL + "html_json-custom-mime-test-page.html"; +const JSON_TEXT_MIME_URL = EXAMPLE_URL + "html_json-text-mime-test-page.html"; const SORTING_URL = EXAMPLE_URL + "html_sorting-test-page.html"; const FILTERING_URL = EXAMPLE_URL + "html_filter-test-page.html"; const INFINITE_GET_URL = EXAMPLE_URL + "html_infinite-get-page.html"; diff --git a/browser/devtools/netmonitor/test/html_json-text-mime-test-page.html b/browser/devtools/netmonitor/test/html_json-text-mime-test-page.html new file mode 100644 index 000000000000..76f7af3c3985 --- /dev/null +++ b/browser/devtools/netmonitor/test/html_json-text-mime-test-page.html @@ -0,0 +1,35 @@ + + + + + + + Network Monitor test page + + + +

JSON text test

+ + + + + diff --git a/browser/devtools/netmonitor/test/sjs_content-type-test-server.sjs b/browser/devtools/netmonitor/test/sjs_content-type-test-server.sjs index 088059d5724c..fd6db3b304ee 100644 --- a/browser/devtools/netmonitor/test/sjs_content-type-test-server.sjs +++ b/browser/devtools/netmonitor/test/sjs_content-type-test-server.sjs @@ -112,6 +112,14 @@ function handleRequest(request, response) { response.finish(); break; } + case "json-text-mime": { + response.setStatusLine(request.httpVersion, status, "OK"); + response.setHeader("Content-Type", "text/plain; charset=utf-8", false); + maybeMakeCached(); + response.write("{ \"greeting\": \"Hello third-party JSON!\" }"); + response.finish(); + break; + } case "json-custom-mime": { response.setStatusLine(request.httpVersion, status, "OK"); response.setHeader("Content-Type", "text/x-bigcorp-json; charset=utf-8", false); From 453d6a09c21878f31d82d93399094ec48ee28e68 Mon Sep 17 00:00:00 2001 From: Mike de Boer Date: Tue, 4 Feb 2014 00:09:28 +0100 Subject: [PATCH 11/78] Bug 966681: improve robustness of error reporting. r=Unfocused --- testing/modules/Assert.jsm | 18 +++++++++++++++--- testing/modules/tests/xpcshell/test_assert.js | 11 +++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/testing/modules/Assert.jsm b/testing/modules/Assert.jsm index 45374b8084da..4972302eb21e 100644 --- a/testing/modules/Assert.jsm +++ b/testing/modules/Assert.jsm @@ -57,9 +57,21 @@ function truncate(text, newLength = kTruncateLength) { } function getMessage(error) { - return truncate(JSON.stringify(error.actual, replacer)) + " " + - (error.operator ? error.operator + " " : "") + - truncate(JSON.stringify(error.expected, replacer)); + let actual, expected; + // Wrap calls to JSON.stringify in try...catch blocks, as they may throw. If + // so, fall back to toString(). + try { + actual = JSON.stringify(error.actual, replacer); + } catch (ex) { + actual = Object.prototype.toString.call(error.actual); + } + try { + expected = JSON.stringify(error.expected, replacer); + } catch (ex) { + expected = Object.prototype.toString.call(error.expected); + } + return truncate(actual) + " " + (error.operator ? error.operator + " " : "") + + truncate(expected); } /** diff --git a/testing/modules/tests/xpcshell/test_assert.js b/testing/modules/tests/xpcshell/test_assert.js index 1782a8e72661..83544e9da882 100644 --- a/testing/modules/tests/xpcshell/test_assert.js +++ b/testing/modules/tests/xpcshell/test_assert.js @@ -288,4 +288,15 @@ function run_test() { deepEqual(/a/igm, /a/igm, "deep equal should work on RegExp"); deepEqual({a: 4, b: "1"}, {b: "1", a: 4}, "deep equal should work on regular Object"); deepEqual(a1, a2, "deep equal should work on Array with Object properties"); + + // Test robustness of reporting: + equal(new ns.Assert.AssertionError({ + actual: { + toJSON: function() { + throw "bam!"; + } + }, + expected: "foo", + operator: "=" + }).message, "[object Object] = \"foo\""); } From 7ce44de3c1ec22b5a25367ca89e6c7b38e84d8a0 Mon Sep 17 00:00:00 2001 From: kushagra singh Date: Tue, 4 Feb 2014 13:04:38 +0200 Subject: [PATCH 12/78] Bug 962070 - Tooltips for sources contain the group URL instead of the file URL, r=vporof --- browser/devtools/debugger/debugger-panes.js | 2 ++ .../debugger/test/browser_dbg_scripts-switching-01.js | 7 +++++++ browser/devtools/shared/widgets/SideMenuWidget.jsm | 1 - 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/browser/devtools/debugger/debugger-panes.js b/browser/devtools/debugger/debugger-panes.js index 27bd35a4df82..36873463f633 100644 --- a/browser/devtools/debugger/debugger-panes.js +++ b/browser/devtools/debugger/debugger-panes.js @@ -128,12 +128,14 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, { let url = aSource.url; let label = SourceUtils.getSourceLabel(url.split(" -> ").pop()); let group = SourceUtils.getSourceGroup(url.split(" -> ").pop()); + let unicodeUrl = NetworkHelper.convertToUnicode(unescape(url)); let contents = document.createElement("label"); contents.className = "plain dbg-source-item"; contents.setAttribute("value", label); contents.setAttribute("crop", "start"); contents.setAttribute("flex", "1"); + contents.setAttribute("tooltiptext", unicodeUrl); // Append a source item to this container. this.push([contents, url], { diff --git a/browser/devtools/debugger/test/browser_dbg_scripts-switching-01.js b/browser/devtools/debugger/test/browser_dbg_scripts-switching-01.js index 9401b0cb9bf2..7f36a5e2575e 100644 --- a/browser/devtools/debugger/test/browser_dbg_scripts-switching-01.js +++ b/browser/devtools/debugger/test/browser_dbg_scripts-switching-01.js @@ -45,6 +45,13 @@ function testSourcesDisplay() { is(gSources.itemCount, 2, "Found the expected number of sources."); + is(gSources.items[0].target.querySelector(".dbg-source-item").getAttribute("tooltiptext"), + EXAMPLE_URL + "code_script-switching-01.js", + "The correct tooltip text is displayed for the first source."); + is(gSources.items[1].target.querySelector(".dbg-source-item").getAttribute("tooltiptext"), + EXAMPLE_URL + "code_script-switching-02.js", + "The correct tooltip text is displayed for the second source."); + ok(gSources.containsValue(EXAMPLE_URL + gLabel1), "First source url is incorrect."); ok(gSources.containsValue(EXAMPLE_URL + gLabel2), diff --git a/browser/devtools/shared/widgets/SideMenuWidget.jsm b/browser/devtools/shared/widgets/SideMenuWidget.jsm index 77ea19d80a7f..3b87006e190a 100644 --- a/browser/devtools/shared/widgets/SideMenuWidget.jsm +++ b/browser/devtools/shared/widgets/SideMenuWidget.jsm @@ -423,7 +423,6 @@ function SideMenuGroup(aWidget, aName, aOptions={}) { let target = this._target = this.document.createElement("vbox"); target.className = "side-menu-widget-group"; target.setAttribute("name", aName); - target.setAttribute("tooltiptext", aName); let list = this._list = this.document.createElement("vbox"); list.className = "side-menu-widget-group-list"; From ccbd64f5f0ce8c2044297d0a57ba17a317e9dfdd Mon Sep 17 00:00:00 2001 From: Brian Grinstead Date: Tue, 4 Feb 2014 07:14:40 -0600 Subject: [PATCH 13/78] Bug 966907 - Light icon for search box in light theme - DevTools OVERRIDE HOOK;r=vporof --- .../linux/devtools/magnifying-glass.png | Bin 275 -> 0 bytes browser/themes/linux/jar.mn | 5 ++++- .../themes/osx/devtools/magnifying-glass.png | Bin 275 -> 0 bytes browser/themes/osx/jar.mn | 5 ++++- .../images/magnifying-glass-light.png | Bin 0 -> 186 bytes .../images/magnifying-glass-light@2x.png | Bin 0 -> 421 bytes .../devtools/images/magnifying-glass.png | Bin 0 -> 192 bytes .../devtools/images/magnifying-glass@2x.png | Bin 0 -> 449 bytes .../themes/shared/devtools/toolbars.inc.css | 20 +++++++++++++++++- .../windows/devtools/magnifying-glass.png | Bin 1111 -> 0 bytes browser/themes/windows/jar.mn | 10 +++++++-- 11 files changed, 35 insertions(+), 5 deletions(-) delete mode 100644 browser/themes/linux/devtools/magnifying-glass.png delete mode 100644 browser/themes/osx/devtools/magnifying-glass.png create mode 100644 browser/themes/shared/devtools/images/magnifying-glass-light.png create mode 100644 browser/themes/shared/devtools/images/magnifying-glass-light@2x.png create mode 100644 browser/themes/shared/devtools/images/magnifying-glass.png create mode 100644 browser/themes/shared/devtools/images/magnifying-glass@2x.png delete mode 100644 browser/themes/windows/devtools/magnifying-glass.png diff --git a/browser/themes/linux/devtools/magnifying-glass.png b/browser/themes/linux/devtools/magnifying-glass.png deleted file mode 100644 index 673c32810029e3121cbbe386b108a2951520783b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 275 zcmeAS@N?(olHy`uVBq!ia0vp^+#t-s1|(OmDOUqhk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5XwtBiahE&{2x>NXqhgskQ<3?$L(}oTQlojr=9+@vOfq`3~ zo1xH1pjfH%HN!?Df%D9dt{XV2DU>y7)i4~{yzmEO$QjO?DHD=ZHL7_9>{}n11{kK6 z9BsLw(e!;`j=jRO#w7a{H)2cp-!Uh#DugjU5;8f=(l|r9WB(x^wx4BI0`bg`^aFU3 z_Om2$D)hBxG0Z;0zNXqhgskQ<3?$L(}oTQlojr=9+@vOfq`3~ zo1xH1pjfH%HN!?Df%D9dt{XV2DU>y7)i4~{yzmEO$QjO?DHD=ZHL7_9>{}n11{kK6 z9BsLw(e!;`j=jRO#w7a{H)2cp-!Uh#DugjU5;8f=(l|r9WB(x^wx4BI0`bg`^aFU3 z_Om2$D)hBxG0Z;0z*^RD zYs$QMar#S%l2gwA|67|b6lmkr<O0?q1ZJ4MCwdhra3_-K(^4zU?91<0_ZF{(Bhpf>sF>RjbXQ%=< zkfv#}!FCQ?Rxgl!BnqA)GQT_V?gtTCqV*%hV<)Vk0-V$iqqdZ-E+Ss!qdmU!eJx6?H*401?xM54~H5L|MbiYGWfja*zc{Mzg03?L`~#dbo1Mn zwDQ$T|0*(!~qdqqZfa>Hgt9op4KsX@R&Y&`AuQu6{1-oD!M_J4@05)q?m)5$G!=4E%+Poxj?kpPWoNF1G zz4CC(R%v<_Q7IZBRBP#KonC*4oGX=HI+s0$aJHrp%APK~z7Zbi09X&T*4mT2fEN!w z7>!1|zk+F0P(`Q2hvTT0(0$LCZcu5_nR~xwzl!FO;$}}Hjb3<5wsT433usgfSSfWB zfOnQe%;j7__hY~oX!_vcaJaoJ$|q_rV5DNe#!PR$9`_4+jJAI}Bee=T9*+-R8ijjI00000NkvXXu0mjf!FS94 literal 0 HcmV?d00001 diff --git a/browser/themes/shared/devtools/toolbars.inc.css b/browser/themes/shared/devtools/toolbars.inc.css index b01f9a0286ad..5da528c46eee 100644 --- a/browser/themes/shared/devtools/toolbars.inc.css +++ b/browser/themes/shared/devtools/toolbars.inc.css @@ -217,12 +217,30 @@ padding-bottom: 3px; -moz-padding-start: 22px; -moz-padding-end: 12px; - background-image: url(magnifying-glass.png); background-position: 8px center; + background-size: 11px 11px; background-repeat: no-repeat; font-size: inherit; } +.theme-dark .devtools-searchinput { + background-image: url(magnifying-glass.png); +} + +.theme-light .devtools-searchinput { + background-image: url(magnifying-glass-light.png); +} + +@media (min-resolution: 2dppx) { + .theme-dark .devtools-searchinput { + background-image: url(magnifying-glass@2x.png); + } + + .theme-light .devtools-searchinput { + background-image: url(magnifying-glass-light@2x.png); + } +} + .devtools-searchinput:-moz-locale-dir(rtl) { background-position: calc(100% - 8px) center; } diff --git a/browser/themes/windows/devtools/magnifying-glass.png b/browser/themes/windows/devtools/magnifying-glass.png deleted file mode 100644 index 236d93d49b03455585d2e76b44d1210ea8ab09a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1111 zcmaJ=S!mQi7>*~}14YrIJ{VF(1vkfbw>Gh{Zj)VIG4}yxvn_#WhW3?h44_;3y2r3j2@p^GmyVVERz+`5A{_p#*Zw@9}R#aEa zt{@1aI=)g);=RgWlP2Q-y4>b>c$+2w%N zu9lS6mDPBiYFb7%dIBae0a8iN+-|voCsM<_0zUg@h9ZYuP*$XVCzVzbqy!y6hG{mS zvGp8TAELPs%ZK>IWRT@ph6^(6k^sjF;Uxmgks}|4qd9uJkd$L1vG7f#G6>lM!{qaM zI$uXar-R{mo@dw~6AT8hN5CyuNc94iTU%m~fvY)&jSOg!KBL+Ob4a9croT%t?NM6G z9cdG87~`on!_ll?Qpr(K{_kp%SEcCfw(MEn1eP9UErIgx_Ukmj)fXz zj*GGpE*6Y71SN@!MnhpvE@5S;aDrr`yc7GUSZSoyUiQ!-Mevn?nj{Ai4|7GPaM5;x~8(&EnGOWDEK97eeHSj zs=GM&rniHd@`EHM*b}&C#a=itpB)p;)f}6VPT$=9di>)4>MM~-)W2np{Om11wx;Ld z^*xEf4Paqi-^qr<(=R{Szw~nI+-zyz_fyrA@2@)aZAkc8QRsUlK3yJFvYE4YGgGcj zI~qCid6#qY$j Date: Tue, 4 Feb 2014 15:51:41 +0200 Subject: [PATCH 14/78] Bug 946601 - Show an "Other" button in the requests footer, r=me --- .../devtools/netmonitor/netmonitor-view.js | 53 +++++++++---------- browser/devtools/netmonitor/netmonitor.xul | 5 ++ 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/browser/devtools/netmonitor/netmonitor-view.js b/browser/devtools/netmonitor/netmonitor-view.js index 01c7492a1904..f063f5024f5e 100644 --- a/browser/devtools/netmonitor/netmonitor-view.js +++ b/browser/devtools/netmonitor/netmonitor-view.js @@ -471,27 +471,6 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, { } }, - /** - * Create a new custom request form populated with the data from - * the currently selected request. - */ - cloneSelectedRequest: function() { - let selected = this.selectedItem.attachment; - - // Create the element node for the network request item. - let menuView = this._createMenuView(selected.method, selected.url); - - // Append a network request item to this container. - let newItem = this.push([menuView], { - attachment: Object.create(selected, { - isCustom: { value: true } - }) - }); - - // Immediately switch to new request pane. - this.selectedItem = newItem; - }, - /** * Opens selected item in a new tab. */ @@ -521,16 +500,37 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, { }); }, + /** + * Create a new custom request form populated with the data from + * the currently selected request. + */ + cloneSelectedRequest: function() { + let selected = this.selectedItem.attachment; + + // Create the element node for the network request item. + let menuView = this._createMenuView(selected.method, selected.url); + + // Append a network request item to this container. + let newItem = this.push([menuView], { + attachment: Object.create(selected, { + isCustom: { value: true } + }) + }); + + // Immediately switch to new request pane. + this.selectedItem = newItem; + }, + /** * Send a new HTTP request using the data in the custom request form. */ sendCustomRequest: function() { let selected = this.selectedItem.attachment; + let data = Object.create(selected); - let data = Object.create(selected, { - headers: { value: selected.requestHeaders.headers } - }); - + if (selected.requestHeaders) { + data.headers = selected.requestHeaders.headers; + } if (selected.requestPostData) { data.body = selected.requestPostData.postData.text; } @@ -548,9 +548,8 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, { */ closeCustomRequest: function() { this.remove(this.selectedItem); - NetMonitorView.Sidebar.toggle(false); - }, + }, /** * Filters all network requests in this container by a specified type. diff --git a/browser/devtools/netmonitor/netmonitor.xul b/browser/devtools/netmonitor/netmonitor.xul index 60a9b9eb65ff..52603057c0d8 100644 --- a/browser/devtools/netmonitor/netmonitor.xul +++ b/browser/devtools/netmonitor/netmonitor.xul @@ -215,6 +215,11 @@ data-key="flash" label="&netmonitorUI.footer.filterFlash;"> + From c9376461c4764803d17aa59b835dd0b95059e0a7 Mon Sep 17 00:00:00 2001 From: Victor Porof Date: Tue, 4 Feb 2014 15:51:41 +0200 Subject: [PATCH 15/78] Bug 967411 - Fix a few widget leftovers in RequestsMenuView after bug 951633, r=past --- browser/devtools/netmonitor/netmonitor-view.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/browser/devtools/netmonitor/netmonitor-view.js b/browser/devtools/netmonitor/netmonitor-view.js index f063f5024f5e..e96629939168 100644 --- a/browser/devtools/netmonitor/netmonitor-view.js +++ b/browser/devtools/netmonitor/netmonitor-view.js @@ -338,8 +338,9 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, { this._summary = $("#requests-menu-network-summary-label"); this._summary.setAttribute("value", L10N.getStr("networkMenu.empty")); + this.sortContents(this._byTiming); this.allowFocusOnRightClick = true; - this.widget.maintainSelectionVisible = false; + this.maintainSelectionVisible = true; this.widget.autoscrollWithAppendedItems = true; this.widget.addEventListener("select", this._onSelect, false); From 7d6fb433f1b8b44b13c120cd8030c02200591836 Mon Sep 17 00:00:00 2001 From: Victor Porof Date: Tue, 4 Feb 2014 16:40:50 +0200 Subject: [PATCH 16/78] Bug 967393 - Make the jsonp regex a bit more relaxed, r=rcampbell --- .../devtools/netmonitor/netmonitor-view.js | 15 +++--- .../netmonitor/test/browser_net_jsonp.js | 47 +++++++++++++------ .../netmonitor/test/html_jsonp-test-page.html | 4 +- .../test/sjs_content-type-test-server.sjs | 9 ++++ 4 files changed, 53 insertions(+), 22 deletions(-) diff --git a/browser/devtools/netmonitor/netmonitor-view.js b/browser/devtools/netmonitor/netmonitor-view.js index e96629939168..a3c36a90cd09 100644 --- a/browser/devtools/netmonitor/netmonitor-view.js +++ b/browser/devtools/netmonitor/netmonitor-view.js @@ -2140,26 +2140,27 @@ NetworkDetailsView.prototype = { } if (jsonMimeType || jsonObject) { // Extract the actual json substring in case this might be a "JSONP". - let jsonpRegex = /^[a-zA-Z0-9_$]+\(|\)$/g; - let sanitizedJSON = aString.replace(jsonpRegex, ""); - let callbackPadding = aString.match(jsonpRegex); + // This regex basically parses a function call and captures the + // function name and arguments in two separate groups. + let jsonpRegex = /^\s*([\w$]+)\s*\(\s*([^]*)\s*\)\s*;?\s*$/; + let [_, callbackPadding, jsonpString] = aString.match(jsonpRegex) || []; // Make sure this is a valid JSON object first. If so, nicely display // the parsing results in a variables view. Otherwise, simply show // the contents as plain text. - if (sanitizedJSON != aString) { + if (callbackPadding && jsonpString) { try { - jsonObject = JSON.parse(sanitizedJSON); + jsonObject = JSON.parse(jsonpString); } catch (e) { jsonObjectParseError = e; } } - // Valid JSON. + // Valid JSON or JSONP. if (jsonObject) { $("#response-content-json-box").hidden = false; let jsonScopeName = callbackPadding - ? L10N.getFormatStr("jsonpScopeName", callbackPadding[0].slice(0, -1)) + ? L10N.getFormatStr("jsonpScopeName", callbackPadding) : L10N.getStr("jsonScopeName"); return this._json.controller.setSingleVariable({ diff --git a/browser/devtools/netmonitor/test/browser_net_jsonp.js b/browser/devtools/netmonitor/test/browser_net_jsonp.js index f7b850d02eda..9d065f31e962 100644 --- a/browser/devtools/netmonitor/test/browser_net_jsonp.js +++ b/browser/devtools/netmonitor/test/browser_net_jsonp.js @@ -10,11 +10,12 @@ function test() { info("Starting test... "); let { document, L10N, NetMonitorView } = aMonitor.panelWin; - let { RequestsMenu } = NetMonitorView; + let { RequestsMenu, NetworkDetails } = NetMonitorView; RequestsMenu.lazyUpdate = false; + NetworkDetails._json.lazyEmpty = false; - waitForNetworkEvents(aMonitor, 1).then(() => { + waitForNetworkEvents(aMonitor, 2).then(() => { verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0), "GET", CONTENT_TYPE_SJS + "?fmt=jsonp&jsonp=$_0123Fun", { status: 200, @@ -24,19 +25,37 @@ function test() { size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.04), time: true }); + verifyRequestItemTarget(RequestsMenu.getItemAtIndex(1), + "GET", CONTENT_TYPE_SJS + "?fmt=jsonp2&jsonp=$_4567Sad", { + status: 200, + statusText: "OK", + type: "json", + fullMimeType: "text/json; charset=utf-8", + size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.05), + time: true + }); - EventUtils.sendMouseEvent({ type: "mousedown" }, - document.getElementById("details-pane-toggle")); - EventUtils.sendMouseEvent({ type: "mousedown" }, - document.querySelectorAll("#details-pane tab")[3]); + Task.spawn(function() { + let RESPONSE_BODY_DISPLAYED = aMonitor.panelWin.EVENTS.RESPONSE_BODY_DISPLAYED; - let RESPONSE_BODY_DISPLAYED = aMonitor.panelWin.EVENTS.RESPONSE_BODY_DISPLAYED; - waitFor(aMonitor.panelWin, RESPONSE_BODY_DISPLAYED) - .then(testResponseTab) - .then(() => teardown(aMonitor)) - .then(finish); + EventUtils.sendMouseEvent({ type: "mousedown" }, + document.getElementById("details-pane-toggle")); + EventUtils.sendMouseEvent({ type: "mousedown" }, + document.querySelectorAll("#details-pane tab")[3]); - function testResponseTab() { + yield waitFor(aMonitor.panelWin, RESPONSE_BODY_DISPLAYED); + testResponseTab("$_0123Fun", "\"Hello JSONP!\""); + + RequestsMenu.selectedIndex = 1; + + yield waitFor(aMonitor.panelWin, RESPONSE_BODY_DISPLAYED); + testResponseTab("$_4567Sad", "\"Hello weird JSONP!\""); + + yield teardown(aMonitor); + finish(); + }); + + function testResponseTab(aFunction, aGreeting) { let tab = document.querySelectorAll("#details-pane tab")[3]; let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3]; @@ -66,13 +85,13 @@ function test() { let jsonScope = tabpanel.querySelectorAll(".variables-view-scope")[0]; is(jsonScope.querySelector(".name").getAttribute("value"), - L10N.getFormatStr("jsonpScopeName", "$_0123Fun"), + L10N.getFormatStr("jsonpScopeName", aFunction), "The json scope doesn't have the correct title."); is(jsonScope.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"), "greeting", "The first json property name was incorrect."); is(jsonScope.querySelectorAll(".variables-view-property .value")[0].getAttribute("value"), - "\"Hello JSONP!\"", "The first json property value was incorrect."); + aGreeting, "The first json property value was incorrect."); is(jsonScope.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"), "__proto__", "The second json property name was incorrect."); diff --git a/browser/devtools/netmonitor/test/html_jsonp-test-page.html b/browser/devtools/netmonitor/test/html_jsonp-test-page.html index 793382a044dc..4be5209c21f4 100644 --- a/browser/devtools/netmonitor/test/html_jsonp-test-page.html +++ b/browser/devtools/netmonitor/test/html_jsonp-test-page.html @@ -26,7 +26,9 @@ function performRequests() { get("sjs_content-type-test-server.sjs?fmt=jsonp&jsonp=$_0123Fun", function() { - // Done. + get("sjs_content-type-test-server.sjs?fmt=jsonp2&jsonp=$_4567Sad", function() { + // Done. + }); }); } diff --git a/browser/devtools/netmonitor/test/sjs_content-type-test-server.sjs b/browser/devtools/netmonitor/test/sjs_content-type-test-server.sjs index fd6db3b304ee..9b7bd76c5fd2 100644 --- a/browser/devtools/netmonitor/test/sjs_content-type-test-server.sjs +++ b/browser/devtools/netmonitor/test/sjs_content-type-test-server.sjs @@ -95,6 +95,15 @@ function handleRequest(request, response) { response.finish(); break; } + case "jsonp2": { + let fun = params.filter((s) => s.contains("jsonp="))[0].split("=")[1]; + response.setStatusLine(request.httpVersion, status, "OK"); + response.setHeader("Content-Type", "text/json; charset=utf-8", false); + maybeMakeCached(); + response.write(" " + fun + " ( { \"greeting\": \"Hello weird JSONP!\" } ) ; "); + response.finish(); + break; + } case "json-long": { let str = "{ \"greeting\": \"Hello long string JSON!\" },"; response.setStatusLine(request.httpVersion, status, "OK"); From fd859288c9698beb7c23b2d47b5a703a738b5f60 Mon Sep 17 00:00:00 2001 From: Victor Porof Date: Tue, 4 Feb 2014 16:57:08 +0200 Subject: [PATCH 17/78] Bug 967378 - Part 1: Network statistics: don't rely on parseFloat to deal with numbers in the charts, r=rcampbell --- .../devtools/netmonitor/netmonitor-view.js | 67 ++++++++++++------- .../netmonitor/test/browser_net_charts-03.js | 15 +++-- .../netmonitor/test/browser_net_charts-04.js | 4 +- .../netmonitor/test/browser_net_charts-05.js | 15 +++-- browser/devtools/shared/widgets/Chart.jsm | 56 ++++++++++------ 5 files changed, 96 insertions(+), 61 deletions(-) diff --git a/browser/devtools/netmonitor/netmonitor-view.js b/browser/devtools/netmonitor/netmonitor-view.js index a3c36a90cd09..cf5ef2e65353 100644 --- a/browser/devtools/netmonitor/netmonitor-view.js +++ b/browser/devtools/netmonitor/netmonitor-view.js @@ -2343,13 +2343,9 @@ PerformanceStatisticsView.prototype = { id: "#primed-cache-chart", title: "charts.cacheEnabled", data: this._sanitizeChartDataSource(aItems), - sorted: true, - totals: { - size: L10N.getStr("charts.totalSize"), - time: L10N.getStr("charts.totalTime2"), - cached: L10N.getStr("charts.totalCached"), - count: L10N.getStr("charts.totalCount") - } + strings: this._commonChartStrings, + totals: this._commonChartTotals, + sorted: true }); window.emit(EVENTS.PRIMED_CACHE_CHART_DISPLAYED); }, @@ -2365,26 +2361,53 @@ PerformanceStatisticsView.prototype = { id: "#empty-cache-chart", title: "charts.cacheDisabled", data: this._sanitizeChartDataSource(aItems, true), - sorted: true, - totals: { - size: L10N.getStr("charts.totalSize"), - time: L10N.getStr("charts.totalTime2"), - cached: L10N.getStr("charts.totalCached"), - count: L10N.getStr("charts.totalCount") - } + strings: this._commonChartStrings, + totals: this._commonChartTotals, + sorted: true }); window.emit(EVENTS.EMPTY_CACHE_CHART_DISPLAYED); }, + /** + * Common stringifier predicates used for items and totals in both the + * "primed" and "empty" cache charts. + */ + _commonChartStrings: { + size: value => { + let string = L10N.numberWithDecimals(value / 1024, CONTENT_SIZE_DECIMALS); + return L10N.getFormatStr("charts.sizeKB", string); + }, + time: value => { + let string = L10N.numberWithDecimals(value / 1000, REQUEST_TIME_DECIMALS); + return L10N.getFormatStr("charts.totalS", string); + } + }, + _commonChartTotals: { + size: total => { + let string = L10N.numberWithDecimals(total / 1024, CONTENT_SIZE_DECIMALS); + return L10N.getFormatStr("charts.totalSize", string); + }, + time: total => { + let string = L10N.numberWithDecimals(total / 1000, REQUEST_TIME_DECIMALS); + return L10N.getFormatStr("charts.totalTime2", string); + }, + cached: total => { + return L10N.getFormatStr("charts.totalCached", total); + }, + count: total => { + return L10N.getFormatStr("charts.totalCount", total); + } + }, + /** * Adds a specific chart to this container. * * @param object * An object containing all or some the following properties: * - id: either "#primed-cache-chart" or "#empty-cache-chart" - * - title/data/sorted/totals: @see Chart.jsm for details + * - title/data/strings/totals/sorted: @see Chart.jsm for details */ - _createChart: function({ id, title, data, sorted, totals }) { + _createChart: function({ id, title, data, strings, totals, sorted }) { let container = $(id); // Nuke all existing charts of the specified type. @@ -2397,8 +2420,9 @@ PerformanceStatisticsView.prototype = { diameter: NETWORK_ANALYSIS_PIE_CHART_DIAMETER, title: L10N.getStr(title), data: data, - sorted: sorted, - totals: totals + strings: strings, + totals: totals, + sorted: sorted }); chart.on("click", (_, item) => { @@ -2463,13 +2487,6 @@ PerformanceStatisticsView.prototype = { data[type].count++; } - for (let chartItem of data) { - let size = L10N.numberWithDecimals(chartItem.size / 1024, CONTENT_SIZE_DECIMALS); - let time = L10N.numberWithDecimals(chartItem.time / 1000, REQUEST_TIME_DECIMALS); - chartItem.size = L10N.getFormatStr("charts.sizeKB", size); - chartItem.time = L10N.getFormatStr("charts.totalS", time); - } - return data.filter(e => e.count > 0); }, }; diff --git a/browser/devtools/netmonitor/test/browser_net_charts-03.js b/browser/devtools/netmonitor/test/browser_net_charts-03.js index a708a6edb40b..2f5ec0a4a918 100644 --- a/browser/devtools/netmonitor/test/browser_net_charts-03.js +++ b/browser/devtools/netmonitor/test/browser_net_charts-03.js @@ -9,24 +9,27 @@ function test() { initNetMonitor(SIMPLE_URL).then(([aTab, aDebuggee, aMonitor]) => { info("Starting test... "); - let { document, Chart } = aMonitor.panelWin; + let { document, L10N, Chart } = aMonitor.panelWin; let container = document.createElement("box"); let table = Chart.Table(document, { title: "Table title", data: [{ label1: 1, - label2: "11.1foo" + label2: 11.1 }, { label1: 2, - label2: "12.2bar" + label2: 12.2 }, { label1: 3, - label2: "13.3baz" + label2: 13.3 }], + strings: { + label2: (value, index) => value + ["foo", "bar", "baz"][index] + }, totals: { - label1: "Hello %S", - label2: "World %S" + label1: value => "Hello " + L10N.numberWithDecimals(value, 2), + label2: value => "World " + L10N.numberWithDecimals(value, 2) } }); diff --git a/browser/devtools/netmonitor/test/browser_net_charts-04.js b/browser/devtools/netmonitor/test/browser_net_charts-04.js index e9d247b33787..2b78aab71d10 100644 --- a/browser/devtools/netmonitor/test/browser_net_charts-04.js +++ b/browser/devtools/netmonitor/test/browser_net_charts-04.js @@ -17,8 +17,8 @@ function test() { title: "Table title", data: null, totals: { - label1: "Hello %S", - label2: "World %S" + label1: value => "Hello " + L10N.numberWithDecimals(value, 2), + label2: value => "World " + L10N.numberWithDecimals(value, 2) } }); diff --git a/browser/devtools/netmonitor/test/browser_net_charts-05.js b/browser/devtools/netmonitor/test/browser_net_charts-05.js index b118d3a3601a..ecbf71e67211 100644 --- a/browser/devtools/netmonitor/test/browser_net_charts-05.js +++ b/browser/devtools/netmonitor/test/browser_net_charts-05.js @@ -9,24 +9,27 @@ function test() { initNetMonitor(SIMPLE_URL).then(([aTab, aDebuggee, aMonitor]) => { info("Starting test... "); - let { document, Chart } = aMonitor.panelWin; + let { document, L10N, Chart } = aMonitor.panelWin; let container = document.createElement("box"); let chart = Chart.PieTable(document, { title: "Table title", data: [{ size: 1, - label: "11.1foo" + label: 11.1 }, { size: 2, - label: "12.2bar" + label: 12.2 }, { size: 3, - label: "13.3baz" + label: 13.3 }], + strings: { + label2: (value, index) => value + ["foo", "bar", "baz"][index] + }, totals: { - size: "Hello %S", - label: "World %S" + size: value => "Hello " + L10N.numberWithDecimals(value, 2), + label: value => "World " + L10N.numberWithDecimals(value, 2) } }); diff --git a/browser/devtools/shared/widgets/Chart.jsm b/browser/devtools/shared/widgets/Chart.jsm index a47e0de85f69..3dd0898ee6a6 100644 --- a/browser/devtools/shared/widgets/Chart.jsm +++ b/browser/devtools/shared/widgets/Chart.jsm @@ -96,18 +96,19 @@ function PieTableChart(node, pie, table) { * - data: an array of items used to display each slice in the pie * and each row in the table; * @see `createPieChart` and `createTableChart` for details. + * - strings: @see `createTableChart` for details. + * - totals: @see `createTableChart` for details. * - sorted: a flag specifying if the `data` should be sorted * ascending by `size`. - * - totals: @see `createTableChart` for details. * @return PieTableChart * A pie+table chart proxy instance, which emits the following events: * - "mouseenter", when the mouse enters a slice or a row * - "mouseleave", when the mouse leaves a slice or a row * - "click", when the mouse enters a slice or a row */ -function createPieTableChart(document, { sorted, title, diameter, data, totals }) { +function createPieTableChart(document, { title, diameter, data, strings, totals, sorted }) { if (sorted) { - data = data.slice().sort((a, b) => +(parseFloat(a.size) < parseFloat(b.size))); + data = data.slice().sort((a, b) => +(a.size < b.size)); } let pie = Chart.Pie(document, { @@ -118,6 +119,7 @@ function createPieTableChart(document, { sorted, title, diameter, data, totals } let table = Chart.Table(document, { title: title, data: data, + strings: strings, totals: totals }); @@ -202,7 +204,7 @@ function createPieChart(document, { data, width, height, centerX, centerY, radiu let isPlaceholder = false; // Filter out very small sizes, as they'll just render invisible slices. - data = data ? data.filter(e => parseFloat(e.size) > EPSILON) : null; + data = data ? data.filter(e => e.size > EPSILON) : null; // If there's no data available, display an empty placeholder. if (!data || !data.length) { @@ -222,10 +224,10 @@ function createPieChart(document, { data, width, height, centerX, centerY, radiu let proxy = new PieChart(container); - let total = data.reduce((acc, e) => acc + parseFloat(e.size), 0); - let angles = data.map(e => parseFloat(e.size) / total * (TAU - EPSILON)); - let largest = data.reduce((a, b) => parseFloat(a.size) > parseFloat(b.size) ? a : b); - let smallest = data.reduce((a, b) => parseFloat(a.size) < parseFloat(b.size) ? a : b); + let total = data.reduce((acc, e) => acc + e.size, 0); + let angles = data.map(e => e.size / total * (TAU - EPSILON)); + let largest = data.reduce((a, b) => a.size > b.size ? a : b); + let smallest = data.reduce((a, b) => a.size < b.size ? a : b); let textDistance = radius / NAMED_SLICE_TEXT_DISTANCE_RATIO; let translateDistance = radius / HOVERED_SLICE_TRANSLATE_DISTANCE_RATIO; @@ -307,19 +309,25 @@ function createPieChart(document, { data, width, height, centerX, centerY, radiu * should be objects representing columns, for which the * properties' values will be displayed in each cell of a row. * e.g: [{ - * size: 1, - * label2: "1foo", - * label3: "2yolo" + * label1: 1, + * label2: 3, + * label3: "foo" * }, { - * size: 2, - * label2: "3bar", - * label3: "4swag" + * label1: 4, + * label2: 6, + * label3: "bar * }]; + * - strings: an object specifying for which rows in the `data` array + * their cell values should be stringified and localized + * based on a predicate function; + * e.g: { + * label1: value => l10n.getFormatStr("...", value) + * } * - totals: an object specifying for which rows in the `data` array * the sum of their cells is to be displayed in the chart; * e.g: { - * label1: "Total size: %S", - * label3: "Total lolz: %S" + * label1: total => l10n.getFormatStr("...", total), // 5 + * label2: total => l10n.getFormatStr("...", total), // 9 * } * @return TableChart * A table chart proxy instance, which emits the following events: @@ -327,7 +335,9 @@ function createPieChart(document, { data, width, height, centerX, centerY, radiu * - "mouseleave", when the mouse leaves a row * - "click", when the mouse clicks a row */ -function createTableChart(document, { data, totals, title }) { +function createTableChart(document, { title, data, strings, totals }) { + strings = strings || {}; + totals = totals || {}; let isPlaceholder = false; // If there's no data available, display an empty placeholder. @@ -365,10 +375,12 @@ function createTableChart(document, { data, totals, title }) { rowNode.appendChild(boxNode); for (let [key, value] in Iterator(rowInfo)) { + let index = data.indexOf(rowInfo); + let stringified = strings[key] ? strings[key](value, index) : value; let labelNode = document.createElement("label"); labelNode.className = "plain table-chart-row-label"; labelNode.setAttribute("name", key); - labelNode.setAttribute("value", value); + labelNode.setAttribute("value", stringified); rowNode.appendChild(labelNode); } @@ -380,13 +392,13 @@ function createTableChart(document, { data, totals, title }) { let totalsNode = document.createElement("vbox"); totalsNode.className = "table-chart-totals"; - for (let [key, value] in Iterator(totals || {})) { - let total = data.reduce((acc, e) => acc + parseFloat(e[key]), 0); - let formatted = !isNaN(total) ? L10N.numberWithDecimals(total, 2) : 0; + for (let [key, value] in Iterator(totals)) { + let total = data.reduce((acc, e) => acc + e[key], 0); + let stringified = totals[key] ? totals[key](total || 0) : total; let labelNode = document.createElement("label"); labelNode.className = "plain table-chart-summary-label"; labelNode.setAttribute("name", key); - labelNode.setAttribute("value", value.replace("%S", formatted)); + labelNode.setAttribute("value", stringified); totalsNode.appendChild(labelNode); } From 78f7c38c8343eac494fa6cb431ddb74b5846ece9 Mon Sep 17 00:00:00 2001 From: Victor Porof Date: Tue, 4 Feb 2014 16:57:09 +0200 Subject: [PATCH 18/78] Bug 967378 - Part 2: Use proper plural form for 'total time' in charts, r=flod, rcampbell --- browser/devtools/netmonitor/netmonitor-view.js | 5 +++-- .../en-US/chrome/browser/devtools/netmonitor.properties | 8 +++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/browser/devtools/netmonitor/netmonitor-view.js b/browser/devtools/netmonitor/netmonitor-view.js index cf5ef2e65353..2adb4b24e42c 100644 --- a/browser/devtools/netmonitor/netmonitor-view.js +++ b/browser/devtools/netmonitor/netmonitor-view.js @@ -2388,8 +2388,9 @@ PerformanceStatisticsView.prototype = { return L10N.getFormatStr("charts.totalSize", string); }, time: total => { - let string = L10N.numberWithDecimals(total / 1000, REQUEST_TIME_DECIMALS); - return L10N.getFormatStr("charts.totalTime2", string); + let seconds = total / 1000; + let string = L10N.numberWithDecimals(seconds, REQUEST_TIME_DECIMALS); + return PluralForm.get(seconds, L10N.getStr("charts.totalSeconds")).replace("#1", string); }, cached: total => { return L10N.getFormatStr("charts.totalCached", total); diff --git a/browser/locales/en-US/chrome/browser/devtools/netmonitor.properties b/browser/locales/en-US/chrome/browser/devtools/netmonitor.properties index 4c1296f5bff7..c0bc8100e77e 100644 --- a/browser/locales/en-US/chrome/browser/devtools/netmonitor.properties +++ b/browser/locales/en-US/chrome/browser/devtools/netmonitor.properties @@ -166,9 +166,11 @@ charts.cacheDisabled=Empty cache # in the performance analysis view for total requests size, in kilobytes. charts.totalSize=Size: %S KB -# LOCALIZATION NOTE (charts.totalTime2): This is the label displayed -# in the performance analysis view for total requests time, in seconds. -charts.totalTime2=Time: %S seconds +# LOCALIZATION NOTE (charts.totalSeconds): Semi-colon list of plural forms. +# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals +# This is the label displayed in the performance analysis view for the +# total requests time, in seconds. +charts.totalSeconds=Time: #1 second;Time: #1 seconds # LOCALIZATION NOTE (charts.totalCached): This is the label displayed # in the performance analysis view for total cached responses. From 95c1583b461be3b0174da0c4930d495b88aa5197 Mon Sep 17 00:00:00 2001 From: Wes Johnston Date: Tue, 4 Feb 2014 07:49:30 -0800 Subject: [PATCH 19/78] Bug 967254 - Move uninstalled apps check to delayed startup. r=rnewman --- mobile/android/base/GeckoApp.java | 9 ++++----- .../base/webapp/UninstallListener.java | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/mobile/android/base/GeckoApp.java b/mobile/android/base/GeckoApp.java index b9a4683c0604..02516a42c343 100644 --- a/mobile/android/base/GeckoApp.java +++ b/mobile/android/base/GeckoApp.java @@ -572,6 +572,8 @@ public abstract class GeckoApp } else if (event.equals("Reader:FaviconRequest")) { final String url = message.getString("url"); handleFaviconRequest(url); + } else if (event.equals("Gecko:DelayedStartup")) { + ThreadUtils.postToBackgroundThread(new UninstallListener.DelayedStartupTask(this)); } else if (event.equals("Gecko:Ready")) { mGeckoReadyStartupTimer.stop(); geckoConnected(); @@ -1325,11 +1327,6 @@ public abstract class GeckoApp GeckoApp.this.onLocaleReady(uiLocale); } }); - - // Perform webapp uninstalls as appropiate. - if (AppConstants.MOZ_ANDROID_SYNTHAPKS) { - UninstallListener.initUninstallPackageScan(getApplicationContext()); - } } }); @@ -1534,6 +1531,7 @@ public abstract class GeckoApp registerEventListener("Menu:Remove"); registerEventListener("Menu:Update"); registerEventListener("Gecko:Ready"); + registerEventListener("Gecko:DelayedStartup"); registerEventListener("Toast:Show"); registerEventListener("DOMFullScreen:Start"); registerEventListener("DOMFullScreen:Stop"); @@ -2063,6 +2061,7 @@ public abstract class GeckoApp unregisterEventListener("Menu:Remove"); unregisterEventListener("Menu:Update"); unregisterEventListener("Gecko:Ready"); + unregisterEventListener("Gecko:DelayedStartup"); unregisterEventListener("Toast:Show"); unregisterEventListener("DOMFullScreen:Start"); unregisterEventListener("DOMFullScreen:Stop"); diff --git a/mobile/android/base/webapp/UninstallListener.java b/mobile/android/base/webapp/UninstallListener.java index ddfd1e8f4da0..a7981540cbb5 100644 --- a/mobile/android/base/webapp/UninstallListener.java +++ b/mobile/android/base/webapp/UninstallListener.java @@ -5,8 +5,11 @@ package org.mozilla.gecko.webapp; +import org.mozilla.gecko.AppConstants; +import org.mozilla.gecko.GeckoApp; import org.mozilla.gecko.GeckoAppShell; import org.mozilla.gecko.GeckoEvent; +import org.mozilla.gecko.util.ThreadUtils; import android.content.BroadcastReceiver; import android.content.Context; @@ -23,6 +26,7 @@ import android.content.pm.PackageManager; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Set; import java.util.ArrayList; @@ -91,4 +95,20 @@ public class UninstallListener extends BroadcastReceiver { } } } + + public static class DelayedStartupTask implements Runnable { + private GeckoApp mApp; + + public DelayedStartupTask(GeckoApp app) { + mApp = app; + } + + @Override + public void run() { + // Perform webapp uninstalls as appropiate. + if (AppConstants.MOZ_ANDROID_SYNTHAPKS) { + UninstallListener.initUninstallPackageScan(mApp.getApplicationContext()); + } + } + } } From ca97745c4b1b277594d34aabe45dcddf0ef875a7 Mon Sep 17 00:00:00 2001 From: Wes Johnston Date: Tue, 28 Jan 2014 09:15:36 -0800 Subject: [PATCH 20/78] Bug 956075 - Add a custom view flipper to avoid Gingerbread bugs with touching ViewGroups. r=lucasr --- mobile/android/base/moz.build | 1 + .../base/resources/layout/gecko_app.xml | 4 +- .../android/base/widget/GeckoViewFlipper.java | 47 +++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 mobile/android/base/widget/GeckoViewFlipper.java diff --git a/mobile/android/base/moz.build b/mobile/android/base/moz.build index 82327d1db2fa..fa6a70b6abcf 100644 --- a/mobile/android/base/moz.build +++ b/mobile/android/base/moz.build @@ -362,6 +362,7 @@ gbjar.sources += [ 'widget/FlowLayout.java', 'widget/GeckoActionProvider.java', 'widget/GeckoPopupMenu.java', + 'widget/GeckoViewFlipper.java', 'widget/IconTabWidget.java', 'widget/SquaredImageView.java', 'widget/TabRow.java', diff --git a/mobile/android/base/resources/layout/gecko_app.xml b/mobile/android/base/resources/layout/gecko_app.xml index e109f9373355..58b6a3fa0891 100644 --- a/mobile/android/base/resources/layout/gecko_app.xml +++ b/mobile/android/base/resources/layout/gecko_app.xml @@ -75,7 +75,7 @@ the root view, BrowserToolbar should be specified as low in the view hierarchy as possible. --> - - + diff --git a/mobile/android/base/widget/GeckoViewFlipper.java b/mobile/android/base/widget/GeckoViewFlipper.java new file mode 100644 index 000000000000..65b5d5154893 --- /dev/null +++ b/mobile/android/base/widget/GeckoViewFlipper.java @@ -0,0 +1,47 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- + * 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/. */ + +package org.mozilla.gecko.widget; + +import org.mozilla.gecko.animation.ViewHelper; + +import android.content.Context; +import android.graphics.Rect; +import android.os.Build; +import android.view.MotionEvent; +import android.widget.ViewFlipper; +import android.util.Log; +import android.util.AttributeSet; + +/* This extends the normal ViewFlipper only to fix bug 956075 on < 3.0 devices. + * i.e. It ignores touch events on the ViewFlipper when its hidden. */ + +public class GeckoViewFlipper extends ViewFlipper { + private static final String LOGTAG = "GeckoViewFlipper"; + private Rect mRect = new Rect(); + + public GeckoViewFlipper(Context context) { + super(context); + } + + public GeckoViewFlipper(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + if (Build.VERSION.SDK_INT < 11) { + // Fix bug 956075. Don't allow touching this View if its hidden. + getHitRect(mRect); + mRect.offset((int) ViewHelper.getTranslationX(this), (int) ViewHelper.getTranslationY(this)); + + if (!mRect.contains((int) ev.getX(), (int) ev.getY())) { + return false; + } + } + + return super.dispatchTouchEvent(ev); + } +} From 2bd7b75fd296d9cb39e1cd3a27c41591f8eee2e1 Mon Sep 17 00:00:00 2001 From: Kartikaya Gupta Date: Mon, 3 Feb 2014 18:22:09 -0500 Subject: [PATCH 21/78] Bug 845690 - Print a warning for developers if we apply our implicit backwards-compatible meta-viewport tag. r=ehsan,f=vingtetun --- content/base/src/nsDocument.cpp | 8 ++++++++ dom/locales/en-US/chrome/dom/dom.properties | 1 + 2 files changed, 9 insertions(+) diff --git a/content/base/src/nsDocument.cpp b/content/base/src/nsDocument.cpp index 3e33d33f259f..91189f9c7c50 100644 --- a/content/base/src/nsDocument.cpp +++ b/content/base/src/nsDocument.cpp @@ -6915,6 +6915,14 @@ nsDocument::GetViewportInfo(const ScreenIntSize& aDisplaySize) // app that does not use it. nsCOMPtr docShell(mDocumentContainer); if (docShell && docShell->GetIsApp()) { + nsString uri; + GetDocumentURI(uri); + if (!uri.EqualsLiteral("about:blank")) { + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("DOM"), this, + nsContentUtils::eDOM_PROPERTIES, + "ImplicitMetaViewportTagFallback"); + } mViewportType = DisplayWidthHeightNoZoom; return nsViewportInfo(aDisplaySize, /* allowZoom */ false); } diff --git a/dom/locales/en-US/chrome/dom/dom.properties b/dom/locales/en-US/chrome/dom/dom.properties index 90dbf837e729..8e0157e7981f 100644 --- a/dom/locales/en-US/chrome/dom/dom.properties +++ b/dom/locales/en-US/chrome/dom/dom.properties @@ -144,3 +144,4 @@ UseOfDOM3LoadMethodWarning=Use of document.load() is deprecated. To upgrade your ShowModalDialogWarning=Use of window.showModalDialog() is deprecated. Use window.open() instead. For more help https://developer.mozilla.org/en-US/docs/Web/API/Window.open # LOCALIZATION NOTE: Do not translate "window._content" or "window.content" Window_ContentWarning=window._content is deprecated. Please use window.content instead. +ImplicitMetaViewportTagFallback=No meta-viewport tag found. Please explicitly specify one to prevent unexpected behavioural changes in future versions. For more help https://developer.mozilla.org/en/docs/Mozilla/Mobile/Viewport_meta_tag From 32e9793b5a17212388151294253ba1702ecd04dd Mon Sep 17 00:00:00 2001 From: Ehsan Akhgari Date: Mon, 3 Feb 2014 17:08:42 -0500 Subject: [PATCH 22/78] Bug 924839 - Reapply the OSX part of the fix to bug 915735 --HG-- extra : rebase_source : 1230fc14e43860cd4d5e4975f08216f6d8d6c7be --- intl/icu/source/config/mh-darwin | 6 +----- intl/update-icu.sh | 1 + 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/intl/icu/source/config/mh-darwin b/intl/icu/source/config/mh-darwin index 97d6bfc2d669..fe1490e9a51c 100644 --- a/intl/icu/source/config/mh-darwin +++ b/intl/icu/source/config/mh-darwin @@ -28,11 +28,7 @@ SHLIB.c= $(CC) -dynamiclib -dynamic $(CFLAGS) $(LDFLAGS) $(LD_SOOPTIONS) SHLIB.cc= $(CXX) -dynamiclib -dynamic $(CXXFLAGS) $(LDFLAGS) $(LD_SOOPTIONS) ## Compiler switches to embed a library name and version information -ifeq ($(ENABLE_RPATH),YES) -LD_SONAME = -Wl,-compatibility_version -Wl,$(SO_TARGET_VERSION_MAJOR) -Wl,-current_version -Wl,$(SO_TARGET_VERSION) -install_name $(libdir)/$(notdir $(MIDDLE_SO_TARGET)) -else -LD_SONAME = -Wl,-compatibility_version -Wl,$(SO_TARGET_VERSION_MAJOR) -Wl,-current_version -Wl,$(SO_TARGET_VERSION) -install_name $(notdir $(MIDDLE_SO_TARGET)) -endif +LD_SONAME = -Wl,-compatibility_version -Wl,$(SO_TARGET_VERSION_MAJOR) -Wl,-current_version -Wl,$(SO_TARGET_VERSION) -install_name @executable_path/$(notdir $(MIDDLE_SO_TARGET)) ## Compiler switch to embed a runtime search path LD_RPATH= diff --git a/intl/update-icu.sh b/intl/update-icu.sh index fccc2e827b2f..6ede56835fd4 100755 --- a/intl/update-icu.sh +++ b/intl/update-icu.sh @@ -52,6 +52,7 @@ svn info $1 | grep -v '^Revision: [[:digit:]]\+$' > ${icu_dir}/SVN-INFO patch -d ${icu_dir}/../../ -p1 < ${icu_dir}/../icu-patches/bug-724533 patch -d ${icu_dir}/../../ -p1 < ${icu_dir}/../icu-patches/bug-899722-4 +patch -d ${icu_dir}/../../ -p1 < ${icu_dir}/../icu-patches/bug-915735 patch -d ${icu_dir}/../../ -p1 < ${icu_dir}/../icu-patches/genrb-omitCollationRules.diff patch -d ${icu_dir}/../../ -p1 < ${icu_dir}/../icu-patches/qualify-uinitonce-windows.diff From f571a8a02d7cc53c4127f876ee00c36999069950 Mon Sep 17 00:00:00 2001 From: Matt Woodrow Date: Tue, 4 Feb 2014 12:25:22 +1300 Subject: [PATCH 23/78] Bug 966543 - Ensure that the MacIOSurfaceTextureHost has taken a ref to the surface before letting the client side object be destroyed. r=nical --- .../opengl/MacIOSurfaceTextureClientOGL.cpp | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.cpp b/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.cpp index fa7422fe2a65..34668a0b3c8d 100644 --- a/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.cpp +++ b/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.cpp @@ -65,15 +65,29 @@ MacIOSurfaceTextureClientOGL::GetSize() const return gfx::IntSize(mSurface->GetDevicePixelWidth(), mSurface->GetDevicePixelHeight()); } +class MacIOSurfaceTextureClientData : public TextureClientData +{ +public: + MacIOSurfaceTextureClientData(MacIOSurface* aSurface) + : mSurface(aSurface) + {} + + virtual void DeallocateSharedData(ISurfaceAllocator*) MOZ_OVERRIDE + { + mSurface = nullptr; + } + +private: + RefPtr mSurface; +}; + TextureClientData* MacIOSurfaceTextureClientOGL::DropTextureData() { - // MacIOSurface has proper cross-process refcounting so we can just drop - // our reference now, and the data will stay alive (at least) until the host - // has also been torn down. + TextureClientData* data = new MacIOSurfaceTextureClientData(mSurface); mSurface = nullptr; MarkInvalid(); - return nullptr; + return data; } } From 5ead3a7c0c99cd45266af0845e89a3928195980a Mon Sep 17 00:00:00 2001 From: Matt Woodrow Date: Tue, 4 Feb 2014 12:25:22 +1300 Subject: [PATCH 24/78] Bug 952011 - Remove some nsLayoutUtils transform functions that are no longer needed. r=roc --- layout/base/nsDisplayList.cpp | 53 +++++++++-------------------------- layout/base/nsDisplayList.h | 10 +------ layout/base/nsLayoutUtils.cpp | 36 ------------------------ layout/base/nsLayoutUtils.h | 8 ------ 4 files changed, 15 insertions(+), 92 deletions(-) diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index 2a0bf6b82ed0..8835b620c26f 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -4459,13 +4459,8 @@ bool nsDisplayTransform::ComputeVisibility(nsDisplayListBuilder *aBuilder, * think that it's painting in its original rectangular coordinate space. * If we can't untransform, take the entire overflow rect */ nsRect untransformedVisibleRect; - // GetTransform always operates in dev pixels. - float factor = mFrame->PresContext()->AppUnitsPerDevPixel(); if (ShouldPrerenderTransformedContent(aBuilder, mFrame) || - !UntransformRectMatrix(mVisibleRect, - GetTransform(), - factor, - &untransformedVisibleRect)) + !UntransformVisibleRect(&untransformedVisibleRect)) { untransformedVisibleRect = mFrame->GetVisualOverflowRectRelativeToSelf(); } @@ -4606,8 +4601,6 @@ nsRegion nsDisplayTransform::GetOpaqueRegion(nsDisplayListBuilder *aBuilder, { *aSnap = false; nsRect untransformedVisible; - // GetTransform always operates in dev pixels. - float factor = mFrame->PresContext()->AppUnitsPerDevPixel(); // If we're going to prerender all our content, pretend like we // don't have opqaue content so that everything under us is rendered // as well. That will increase graphics memory usage if our frame @@ -4615,7 +4608,7 @@ nsRegion nsDisplayTransform::GetOpaqueRegion(nsDisplayListBuilder *aBuilder, // updated extremely cheaply, without invalidating any other // content. if (ShouldPrerenderTransformedContent(aBuilder, mFrame) || - !UntransformRectMatrix(mVisibleRect, GetTransform(), factor, &untransformedVisible)) { + !UntransformVisibleRect(&untransformedVisible)) { return nsRegion(); } @@ -4639,9 +4632,7 @@ nsRegion nsDisplayTransform::GetOpaqueRegion(nsDisplayListBuilder *aBuilder, bool nsDisplayTransform::IsUniform(nsDisplayListBuilder *aBuilder, nscolor* aColor) { nsRect untransformedVisible; - // GetTransform always operates in dev pixels. - float factor = mFrame->PresContext()->AppUnitsPerDevPixel(); - if (!UntransformRectMatrix(mVisibleRect, GetTransform(), factor, &untransformedVisible)) { + if (!UntransformVisibleRect(&untransformedVisible)) { return false; } const gfx3DMatrix& matrix = GetTransform(); @@ -4737,43 +4728,27 @@ nsRect nsDisplayTransform::TransformRectOut(const nsRect &aUntransformedBounds, factor); } -bool nsDisplayTransform::UntransformRectMatrix(const nsRect &aUntransformedBounds, - const gfx3DMatrix& aMatrix, - float aAppUnitsPerPixel, - nsRect *aOutRect) +bool nsDisplayTransform::UntransformVisibleRect(nsRect *aOutRect) { - if (aMatrix.IsSingular()) + const gfx3DMatrix& matrix = GetTransform(); + if (matrix.IsSingular()) return false; - gfxRect result(NSAppUnitsToFloatPixels(aUntransformedBounds.x, aAppUnitsPerPixel), - NSAppUnitsToFloatPixels(aUntransformedBounds.y, aAppUnitsPerPixel), - NSAppUnitsToFloatPixels(aUntransformedBounds.width, aAppUnitsPerPixel), - NSAppUnitsToFloatPixels(aUntransformedBounds.height, aAppUnitsPerPixel)); + // GetTransform always operates in dev pixels. + float factor = mFrame->PresContext()->AppUnitsPerDevPixel(); + gfxRect result(NSAppUnitsToFloatPixels(mVisibleRect.x, factor), + NSAppUnitsToFloatPixels(mVisibleRect.y, factor), + NSAppUnitsToFloatPixels(mVisibleRect.width, factor), + NSAppUnitsToFloatPixels(mVisibleRect.height, factor)); /* We want to untransform the matrix, so invert the transformation first! */ - result = aMatrix.Inverse().ProjectRectBounds(result); + result = matrix.Inverse().ProjectRectBounds(result); - *aOutRect = nsLayoutUtils::RoundGfxRectToAppRect(result, aAppUnitsPerPixel); + *aOutRect = nsLayoutUtils::RoundGfxRectToAppRect(result, factor); return true; } -bool nsDisplayTransform::UntransformRect(const nsRect &aUntransformedBounds, - const nsIFrame* aFrame, - const nsPoint &aOrigin, - nsRect* aOutRect) -{ - NS_PRECONDITION(aFrame, "Can't take the transform based on a null frame!"); - - /* Grab the matrix. If the transform is degenerate, just hand back the - * empty rect. - */ - float factor = aFrame->PresContext()->AppUnitsPerDevPixel(); - gfx3DMatrix matrix = GetResultingTransformMatrix(aFrame, aOrigin, factor); - - return UntransformRectMatrix(aUntransformedBounds, matrix, factor, aOutRect); -} - nsDisplaySVGEffects::nsDisplaySVGEffects(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList) : nsDisplayWrapList(aBuilder, aFrame, aList), diff --git a/layout/base/nsDisplayList.h b/layout/base/nsDisplayList.h index 2cf7118ea43a..9ad2b73cf83b 100644 --- a/layout/base/nsDisplayList.h +++ b/layout/base/nsDisplayList.h @@ -3084,15 +3084,7 @@ public: /* UntransformRect is like TransformRect, except that it inverts the * transform. */ - static bool UntransformRect(const nsRect &aUntransformedBounds, - const nsIFrame* aFrame, - const nsPoint &aOrigin, - nsRect* aOutRect); - - static bool UntransformRectMatrix(const nsRect &aUntransformedBounds, - const gfx3DMatrix& aMatrix, - float aAppUnitsPerPixel, - nsRect* aOutRect); + bool UntransformVisibleRect(nsRect* aOutRect); static gfxPoint3D GetDeltaToTransformOrigin(const nsIFrame* aFrame, float aAppUnitsPerPixel, diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 87bf48fc5c44..a9fa137146ae 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -1847,15 +1847,6 @@ TransformGfxPointFromAncestor(nsIFrame *aFrame, return ctm.Inverse().ProjectPoint(aPoint); } -static gfxRect -TransformGfxRectFromAncestor(nsIFrame *aFrame, - const gfxRect &aRect, - const nsIFrame *aAncestor) -{ - gfx3DMatrix ctm = nsLayoutUtils::GetTransformToAncestor(aFrame, aAncestor); - return ctm.Inverse().ProjectRectBounds(aRect); -} - static gfxRect TransformGfxRectToAncestor(nsIFrame *aFrame, const gfxRect &aRect, @@ -1905,33 +1896,6 @@ nsLayoutUtils::TransformAncestorPointToFrame(nsIFrame* aFrame, NSFloatPixelsToAppUnits(float(result.y), factor)); } -nsRect -nsLayoutUtils::TransformAncestorRectToFrame(nsIFrame* aFrame, - const nsRect &aRect, - const nsIFrame* aAncestor) -{ - SVGTextFrame* text = GetContainingSVGTextFrame(aFrame); - - float srcAppUnitsPerDevPixel = aAncestor->PresContext()->AppUnitsPerDevPixel(); - gfxRect result(NSAppUnitsToFloatPixels(aRect.x, srcAppUnitsPerDevPixel), - NSAppUnitsToFloatPixels(aRect.y, srcAppUnitsPerDevPixel), - NSAppUnitsToFloatPixels(aRect.width, srcAppUnitsPerDevPixel), - NSAppUnitsToFloatPixels(aRect.height, srcAppUnitsPerDevPixel)); - - if (text) { - result = TransformGfxRectFromAncestor(text, result, aAncestor); - result = text->TransformFrameRectToTextChild(result, aFrame); - } else { - result = TransformGfxRectFromAncestor(aFrame, result, aAncestor); - } - - float destAppUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel(); - return nsRect(NSFloatPixelsToAppUnits(float(result.x), destAppUnitsPerDevPixel), - NSFloatPixelsToAppUnits(float(result.y), destAppUnitsPerDevPixel), - NSFloatPixelsToAppUnits(float(result.width), destAppUnitsPerDevPixel), - NSFloatPixelsToAppUnits(float(result.height), destAppUnitsPerDevPixel)); -} - nsRect nsLayoutUtils::TransformFrameRectToAncestor(nsIFrame* aFrame, const nsRect& aRect, diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index 9adc72f22d8b..482433e14b33 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -653,14 +653,6 @@ public: nsTArray &aOutFrames, uint32_t aFlags = 0); - /** - * Transform aRect relative to aAncestor down to the coordinate system of - * aFrame. Computes the bounding-box of the true quadrilateral. - */ - static nsRect TransformAncestorRectToFrame(nsIFrame* aFrame, - const nsRect& aRect, - const nsIFrame* aAncestor); - /** * Transform aRect relative to aFrame up to the coordinate system of * aAncestor. Computes the bounding-box of the true quadrilateral. From 7e18435a4df6c9553dc264c527efc922c2df5b25 Mon Sep 17 00:00:00 2001 From: Matt Woodrow Date: Tue, 4 Feb 2014 12:25:22 +1300 Subject: [PATCH 25/78] Bug 952011 - Add gfx3DMatrix API for untransformed rects and points. r=bjacob --- gfx/thebes/gfx3DMatrix.cpp | 35 +++++++++++++++++++++++++++++++---- gfx/thebes/gfx3DMatrix.h | 19 +++++++++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/gfx/thebes/gfx3DMatrix.cpp b/gfx/thebes/gfx3DMatrix.cpp index 9695ede87348..11b4671e108c 100644 --- a/gfx/thebes/gfx3DMatrix.cpp +++ b/gfx/thebes/gfx3DMatrix.cpp @@ -788,10 +788,9 @@ gfxRect gfx3DMatrix::ProjectRectBounds(const gfxRect& aRect) const gfxPoint points[4]; points[0] = ProjectPoint(aRect.TopLeft()); - points[1] = ProjectPoint(gfxPoint(aRect.X() + aRect.Width(), aRect.Y())); - points[2] = ProjectPoint(gfxPoint(aRect.X(), aRect.Y() + aRect.Height())); - points[3] = ProjectPoint(gfxPoint(aRect.X() + aRect.Width(), - aRect.Y() + aRect.Height())); + points[1] = ProjectPoint(aRect.TopRight()); + points[2] = ProjectPoint(aRect.BottomLeft()); + points[3] = ProjectPoint(aRect.BottomRight()); gfxFloat min_x, max_x; gfxFloat min_y, max_y; @@ -809,6 +808,34 @@ gfxRect gfx3DMatrix::ProjectRectBounds(const gfxRect& aRect) const return gfxRect(min_x, min_y, max_x - min_x, max_y - min_y); } +gfxRect gfx3DMatrix::UntransformBounds(const gfxRect& aRect, const gfxRect& aChildBounds) const +{ + if (Is2D()) { + return Inverse().TransformBounds(aRect); + } + gfxRect bounds = TransformBounds(aChildBounds); + + gfxRect rect = aRect.Intersect(bounds); + + return Inverse().ProjectRectBounds(rect); +} + +bool gfx3DMatrix::UntransformPoint(const gfxPoint& aPoint, const gfxRect& aChildBounds, gfxPoint* aOut) const +{ + if (Is2D()) { + *aOut = Inverse().Transform(aPoint); + return true; + } + gfxRect bounds = TransformBounds(aChildBounds); + + if (!bounds.Contains(aPoint)) { + return false; + } + + *aOut = Inverse().ProjectPoint(aPoint); + return true; +} + gfxPoint3D gfx3DMatrix::GetNormalVector() const { // Define a plane in transformed space as the transformations diff --git a/gfx/thebes/gfx3DMatrix.h b/gfx/thebes/gfx3DMatrix.h index 40b290ddccb2..64a174fdb1a6 100644 --- a/gfx/thebes/gfx3DMatrix.h +++ b/gfx/thebes/gfx3DMatrix.h @@ -250,6 +250,25 @@ public: gfxPoint ProjectPoint(const gfxPoint& aPoint) const; gfxRect ProjectRectBounds(const gfxRect& aRect) const; + /** + * Transforms a point by the inverse of this matrix. In the case of perspective transforms, some screen + * points have no equivalent in the untransformed plane (if they exist past the vanishing point). To + * avoid this, we need to specify the bounds of the untransformed plane to restrict the search area. + * + * @param aPoint Point to untransform. + * @param aChildBounds Bounds of the untransformed plane. + * @param aOut Untransformed point. + * @return Returns true if a point was found within a ChildBounds, false otherwise. + */ + bool UntransformPoint(const gfxPoint& aPoint, const gfxRect& aChildBounds, gfxPoint* aOut) const; + + + /** + * Same as UntransformPoint, but untransforms a rect and returns the bounding rect of the result. + * Returns an empty rect if the result doesn't intersect aChildBounds. + */ + gfxRect UntransformBounds(const gfxRect& aRect, const gfxRect& aChildBounds) const; + /** * Inverts this matrix, if possible. Otherwise, the matrix is left From ce740641cb27fee34278be0d081750c5c5dc5694 Mon Sep 17 00:00:00 2001 From: Matt Woodrow Date: Tue, 4 Feb 2014 12:25:23 +1300 Subject: [PATCH 26/78] Bug 952011 - Use Untransform API to safetly untransform points when we have a projective matrix. r=roc --- layout/base/FrameLayerBuilder.cpp | 5 ++- layout/base/nsDisplayList.cpp | 60 +++++++++++++++++++++++-------- layout/base/nsDisplayList.h | 5 +-- layout/base/nsLayoutUtils.cpp | 22 +++++++++--- 4 files changed, 70 insertions(+), 22 deletions(-) diff --git a/layout/base/FrameLayerBuilder.cpp b/layout/base/FrameLayerBuilder.cpp index 27ce7cd5d3b9..0df2afce2e30 100644 --- a/layout/base/FrameLayerBuilder.cpp +++ b/layout/base/FrameLayerBuilder.cpp @@ -1561,7 +1561,10 @@ SetVisibleRegionForLayer(Layer* aLayer, const nsIntRegion& aLayerVisibleRegion, // for the layer, so it doesn't really matter what we do here gfxRect itemVisible(aRestrictToRect.x, aRestrictToRect.y, aRestrictToRect.width, aRestrictToRect.height); - gfxRect layerVisible = transform.Inverse().ProjectRectBounds(itemVisible); + nsIntRect childBounds = aLayerVisibleRegion.GetBounds(); + gfxRect childGfxBounds(childBounds.x, childBounds.y, + childBounds.width, childBounds.height); + gfxRect layerVisible = transform.UntransformBounds(itemVisible, childGfxBounds); layerVisible.RoundOut(); nsIntRect visibleRect; diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index 8835b620c26f..9b4e4810b25f 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -1400,7 +1400,7 @@ void nsDisplayList::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, if (aRect.width != 1 || aRect.height != 1) { point = aRect.Center(); } - temp.AppendElement(FramesWithDepth(transform->GetHitDepthAtPoint(point))); + temp.AppendElement(FramesWithDepth(transform->GetHitDepthAtPoint(aBuilder, point))); writeFrames = &temp[temp.Length() - 1].mFrames; } } else { @@ -4460,7 +4460,7 @@ bool nsDisplayTransform::ComputeVisibility(nsDisplayListBuilder *aBuilder, * If we can't untransform, take the entire overflow rect */ nsRect untransformedVisibleRect; if (ShouldPrerenderTransformedContent(aBuilder, mFrame) || - !UntransformVisibleRect(&untransformedVisibleRect)) + !UntransformVisibleRect(aBuilder, &untransformedVisibleRect)) { untransformedVisibleRect = mFrame->GetVisualOverflowRectRelativeToSelf(); } @@ -4501,14 +4501,24 @@ void nsDisplayTransform::HitTest(nsDisplayListBuilder *aBuilder, * Thus we have to invert the matrix, which normally does * the reverse operation (e.g. regular->transformed) */ + bool snap; + nsRect childBounds = mStoredList.GetBounds(aBuilder, &snap); + gfxRect childGfxBounds(NSAppUnitsToFloatPixels(childBounds.x, factor), + NSAppUnitsToFloatPixels(childBounds.y, factor), + NSAppUnitsToFloatPixels(childBounds.width, factor), + NSAppUnitsToFloatPixels(childBounds.height, factor)); /* Now, apply the transform and pass it down the channel. */ nsRect resultingRect; if (aRect.width == 1 && aRect.height == 1) { // Magic width/height indicating we're hit testing a point, not a rect - gfxPoint point = matrix.Inverse().ProjectPoint( - gfxPoint(NSAppUnitsToFloatPixels(aRect.x, factor), - NSAppUnitsToFloatPixels(aRect.y, factor))); + gfxPoint point; + if (!matrix.UntransformPoint(gfxPoint(NSAppUnitsToFloatPixels(aRect.x, factor), + NSAppUnitsToFloatPixels(aRect.y, factor)), + childGfxBounds, + &point)) { + return; + } resultingRect = nsRect(NSFloatPixelsToAppUnits(float(point.x), factor), NSFloatPixelsToAppUnits(float(point.y), factor), @@ -4520,7 +4530,7 @@ void nsDisplayTransform::HitTest(nsDisplayListBuilder *aBuilder, NSAppUnitsToFloatPixels(aRect.width, factor), NSAppUnitsToFloatPixels(aRect.height, factor)); - gfxRect rect = matrix.Inverse().ProjectRectBounds(originalRect);; + gfxRect rect = matrix.UntransformBounds(originalRect, childGfxBounds); resultingRect = nsRect(NSFloatPixelsToAppUnits(float(rect.X()), factor), NSFloatPixelsToAppUnits(float(rect.Y()), factor), @@ -4528,6 +4538,10 @@ void nsDisplayTransform::HitTest(nsDisplayListBuilder *aBuilder, NSFloatPixelsToAppUnits(float(rect.Height()), factor)); } + if (resultingRect.IsEmpty()) { + return; + } + #ifdef DEBUG_HIT printf("Frame: %p\n", dynamic_cast(mFrame)); @@ -4547,7 +4561,7 @@ void nsDisplayTransform::HitTest(nsDisplayListBuilder *aBuilder, } float -nsDisplayTransform::GetHitDepthAtPoint(const nsPoint& aPoint) +nsDisplayTransform::GetHitDepthAtPoint(nsDisplayListBuilder* aBuilder, const nsPoint& aPoint) { // GetTransform always operates in dev pixels. float factor = mFrame->PresContext()->AppUnitsPerDevPixel(); @@ -4555,9 +4569,19 @@ nsDisplayTransform::GetHitDepthAtPoint(const nsPoint& aPoint) NS_ASSERTION(IsFrameVisible(mFrame, matrix), "We can't have hit a frame that isn't visible!"); - gfxPoint point = - matrix.Inverse().ProjectPoint(gfxPoint(NSAppUnitsToFloatPixels(aPoint.x, factor), - NSAppUnitsToFloatPixels(aPoint.y, factor))); + bool snap; + nsRect childBounds = mStoredList.GetBounds(aBuilder, &snap); + gfxRect childGfxBounds(NSAppUnitsToFloatPixels(childBounds.x, factor), + NSAppUnitsToFloatPixels(childBounds.y, factor), + NSAppUnitsToFloatPixels(childBounds.width, factor), + NSAppUnitsToFloatPixels(childBounds.height, factor)); + + gfxPoint point; + DebugOnly result = matrix.UntransformPoint(gfxPoint(NSAppUnitsToFloatPixels(aPoint.x, factor), + NSAppUnitsToFloatPixels(aPoint.y, factor)), + childGfxBounds, + &point); + NS_ASSERTION(result, "Why are we trying to get the depth for a point we didn't hit?"); gfxPoint3D transformed = matrix.Transform3D(gfxPoint3D(point.x, point.y, 0)); return transformed.z; @@ -4608,7 +4632,7 @@ nsRegion nsDisplayTransform::GetOpaqueRegion(nsDisplayListBuilder *aBuilder, // updated extremely cheaply, without invalidating any other // content. if (ShouldPrerenderTransformedContent(aBuilder, mFrame) || - !UntransformVisibleRect(&untransformedVisible)) { + !UntransformVisibleRect(aBuilder, &untransformedVisible)) { return nsRegion(); } @@ -4632,7 +4656,7 @@ nsRegion nsDisplayTransform::GetOpaqueRegion(nsDisplayListBuilder *aBuilder, bool nsDisplayTransform::IsUniform(nsDisplayListBuilder *aBuilder, nscolor* aColor) { nsRect untransformedVisible; - if (!UntransformVisibleRect(&untransformedVisible)) { + if (!UntransformVisibleRect(aBuilder, &untransformedVisible)) { return false; } const gfx3DMatrix& matrix = GetTransform(); @@ -4728,7 +4752,8 @@ nsRect nsDisplayTransform::TransformRectOut(const nsRect &aUntransformedBounds, factor); } -bool nsDisplayTransform::UntransformVisibleRect(nsRect *aOutRect) +bool nsDisplayTransform::UntransformVisibleRect(nsDisplayListBuilder* aBuilder, + nsRect *aOutRect) { const gfx3DMatrix& matrix = GetTransform(); if (matrix.IsSingular()) @@ -4741,8 +4766,15 @@ bool nsDisplayTransform::UntransformVisibleRect(nsRect *aOutRect) NSAppUnitsToFloatPixels(mVisibleRect.width, factor), NSAppUnitsToFloatPixels(mVisibleRect.height, factor)); + bool snap; + nsRect childBounds = mStoredList.GetBounds(aBuilder, &snap); + gfxRect childGfxBounds(NSAppUnitsToFloatPixels(childBounds.x, factor), + NSAppUnitsToFloatPixels(childBounds.y, factor), + NSAppUnitsToFloatPixels(childBounds.width, factor), + NSAppUnitsToFloatPixels(childBounds.height, factor)); + /* We want to untransform the matrix, so invert the transformation first! */ - result = matrix.Inverse().ProjectRectBounds(result); + result = matrix.UntransformBounds(result, childGfxBounds); *aOutRect = nsLayoutUtils::RoundGfxRectToAppRect(result, factor); diff --git a/layout/base/nsDisplayList.h b/layout/base/nsDisplayList.h index 9ad2b73cf83b..38859afcac25 100644 --- a/layout/base/nsDisplayList.h +++ b/layout/base/nsDisplayList.h @@ -3051,7 +3051,7 @@ public: const gfx3DMatrix& GetTransform(); - float GetHitDepthAtPoint(const nsPoint& aPoint); + float GetHitDepthAtPoint(nsDisplayListBuilder* aBuilder, const nsPoint& aPoint); /** * TransformRect takes in as parameters a rectangle (in aFrame's coordinate @@ -3084,7 +3084,8 @@ public: /* UntransformRect is like TransformRect, except that it inverts the * transform. */ - bool UntransformVisibleRect(nsRect* aOutRect); + bool UntransformVisibleRect(nsDisplayListBuilder* aBuilder, + nsRect* aOutRect); static gfxPoint3D GetDeltaToTransformOrigin(const nsIFrame* aFrame, float aAppUnitsPerPixel, diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index a9fa137146ae..4326c9498f13 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -1838,13 +1838,21 @@ nsLayoutUtils::GetLayerTransformForFrame(nsIFrame* aFrame, return true; } -static gfxPoint +static bool TransformGfxPointFromAncestor(nsIFrame *aFrame, const gfxPoint &aPoint, - nsIFrame *aAncestor) + nsIFrame *aAncestor, + gfxPoint* aOut) { gfx3DMatrix ctm = nsLayoutUtils::GetTransformToAncestor(aFrame, aAncestor); - return ctm.Inverse().ProjectPoint(aPoint); + + float factor = aFrame->PresContext()->AppUnitsPerDevPixel(); + nsRect childBounds = aFrame->GetVisualOverflowRectRelativeToSelf(); + gfxRect childGfxBounds(NSAppUnitsToFloatPixels(childBounds.x, factor), + NSAppUnitsToFloatPixels(childBounds.y, factor), + NSAppUnitsToFloatPixels(childBounds.width, factor), + NSAppUnitsToFloatPixels(childBounds.height, factor)); + return ctm.UntransformPoint(aPoint, childGfxBounds, aOut); } static gfxRect @@ -1886,10 +1894,14 @@ nsLayoutUtils::TransformAncestorPointToFrame(nsIFrame* aFrame, NSAppUnitsToFloatPixels(aPoint.y, factor)); if (text) { - result = TransformGfxPointFromAncestor(text, result, aAncestor); + if (!TransformGfxPointFromAncestor(text, result, aAncestor, &result)) { + return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); + } result = text->TransformFramePointToTextChild(result, aFrame); } else { - result = TransformGfxPointFromAncestor(aFrame, result, nullptr); + if (!TransformGfxPointFromAncestor(aFrame, result, nullptr, &result)) { + return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); + } } return nsPoint(NSFloatPixelsToAppUnits(float(result.x), factor), From 81d44a083462bd6a16aaaf0efb38da1063382c0e Mon Sep 17 00:00:00 2001 From: William Chen Date: Fri, 31 Jan 2014 15:03:24 -0800 Subject: [PATCH 27/78] Bug 946585 - Change how the form element pointer affects parsing template elements. r=hsivonen --- content/html/content/test/mochitest.ini | 1 + .../test/test_fragment_form_pointer.html | 27 +++++++ parser/html/javasrc/TreeBuilder.java | 66 ++++++++++++----- parser/html/nsHtml5TreeBuilder.cpp | 58 ++++++++++----- parser/html/nsHtml5TreeBuilderCppSupplement.h | 34 +++++++++ parser/html/nsHtml5TreeBuilderHSupplement.h | 2 + .../html5lib_tree_construction/isindex.dat | 20 ++++++ .../html5lib_tree_construction/template.dat | 72 +++++++++++++++++++ 8 files changed, 244 insertions(+), 36 deletions(-) create mode 100644 content/html/content/test/test_fragment_form_pointer.html diff --git a/content/html/content/test/mochitest.ini b/content/html/content/test/mochitest.ini index dd0c928e2088..aa1c6c5442bd 100644 --- a/content/html/content/test/mochitest.ini +++ b/content/html/content/test/mochitest.ini @@ -430,3 +430,4 @@ support-files = [test_video_wakelock.html] [test_input_files_not_nsIFile.html] [test_ignoreuserfocus.html] +[test_fragment_form_pointer.html] diff --git a/content/html/content/test/test_fragment_form_pointer.html b/content/html/content/test/test_fragment_form_pointer.html new file mode 100644 index 000000000000..c9ed9dbc5c84 --- /dev/null +++ b/content/html/content/test/test_fragment_form_pointer.html @@ -0,0 +1,27 @@ + + + + + + Test for Bug 946585 + + + + +Mozilla Bug 946585 +

+ +
+
+ + + diff --git a/parser/html/javasrc/TreeBuilder.java b/parser/html/javasrc/TreeBuilder.java index bb32fe076044..4b62af7e34d4 100644 --- a/parser/html/javasrc/TreeBuilder.java +++ b/parser/html/javasrc/TreeBuilder.java @@ -620,6 +620,7 @@ public abstract class TreeBuilder implements TokenHandler, pushTemplateMode(IN_TEMPLATE); } resetTheInsertionMode(); + formPointer = getFormPointerForContext(contextNode); if ("title" == contextName || "textarea" == contextName) { tokenizer.setStateAndEndTagExpectation(Tokenizer.RCDATA, contextName); } else if ("style" == contextName || "xmp" == contextName @@ -1892,7 +1893,7 @@ public abstract class TreeBuilder implements TokenHandler, attributes = null; // CPP break starttagloop; case FORM: - if (formPointer != null) { + if (formPointer != null || isTemplateContents()) { errFormWhenFormOpen(); break starttagloop; } else { @@ -2078,7 +2079,7 @@ public abstract class TreeBuilder implements TokenHandler, attributes = null; // CPP break starttagloop; case FORM: - if (formPointer != null) { + if (formPointer != null && !isTemplateContents()) { errFormWhenFormOpen(); break starttagloop; } else { @@ -2255,7 +2256,7 @@ public abstract class TreeBuilder implements TokenHandler, break starttagloop; case ISINDEX: errIsindex(); - if (formPointer != null) { + if (formPointer != null && !isTemplateContents()) { break starttagloop; } implicitlyCloseP(); @@ -2319,6 +2320,11 @@ public abstract class TreeBuilder implements TokenHandler, ElementName.HR, HtmlAttributes.EMPTY_ATTRIBUTES); pop(); // form + + if (!isTemplateContents()) { + formPointer = null; + } + selfClosing = false; // Portability.delete(formAttrs); // Portability.delete(inputAttributes); @@ -3574,22 +3580,38 @@ public abstract class TreeBuilder implements TokenHandler, } break endtagloop; case FORM: - if (formPointer == null) { - errStrayEndTag(name); + if (!isTemplateContents()) { + if (formPointer == null) { + errStrayEndTag(name); + break endtagloop; + } + formPointer = null; + eltPos = findLastInScope(name); + if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { + errStrayEndTag(name); + break endtagloop; + } + generateImpliedEndTags(); + if (errorHandler != null && !isCurrent(name)) { + errUnclosedElements(eltPos, name); + } + removeFromStack(eltPos); + break endtagloop; + } else { + eltPos = findLastInScope(name); + if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { + errStrayEndTag(name); + break endtagloop; + } + generateImpliedEndTags(); + if (errorHandler != null && !isCurrent(name)) { + errUnclosedElements(eltPos, name); + } + while (currentPtr >= eltPos) { + pop(); + } break endtagloop; } - formPointer = null; - eltPos = findLastInScope(name); - if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { - errStrayEndTag(name); - break endtagloop; - } - generateImpliedEndTags(); - if (errorHandler != null && !isCurrent(name)) { - errUnclosedElements(eltPos, name); - } - removeFromStack(eltPos); - break endtagloop; case P: eltPos = findLastInButtonScope("p"); if (eltPos == TreeBuilder.NOT_FOUND_ON_STACK) { @@ -5078,7 +5100,11 @@ public abstract class TreeBuilder implements TokenHandler, // ]NOCPP] T elt = createElement("http://www.w3.org/1999/xhtml", "form", attributes); - formPointer = elt; + + if (!isTemplateContents()) { + formPointer = elt; + } + StackNode current = stack[currentPtr]; if (current.isFosterParenting()) { fatal(); @@ -5203,6 +5229,10 @@ public abstract class TreeBuilder implements TokenHandler, T getDocumentFragmentForTemplate(T template) { return template; } + + T getFormPointerForContext(T context) { + return null; + } // ]NOCPP] private boolean annotationXmlEncodingPermitsHtml(HtmlAttributes attributes) { diff --git a/parser/html/nsHtml5TreeBuilder.cpp b/parser/html/nsHtml5TreeBuilder.cpp index f4e1a5ae2a2b..4645b240a458 100644 --- a/parser/html/nsHtml5TreeBuilder.cpp +++ b/parser/html/nsHtml5TreeBuilder.cpp @@ -102,6 +102,7 @@ nsHtml5TreeBuilder::startTokenization(nsHtml5Tokenizer* self) pushTemplateMode(NS_HTML5TREE_BUILDER_IN_TEMPLATE); } resetTheInsertionMode(); + formPointer = getFormPointerForContext(contextNode); if (nsHtml5Atoms::title == contextName || nsHtml5Atoms::textarea == contextName) { tokenizer->setStateAndEndTagExpectation(NS_HTML5TOKENIZER_RCDATA, contextName); } else if (nsHtml5Atoms::style == contextName || nsHtml5Atoms::xmp == contextName || nsHtml5Atoms::iframe == contextName || nsHtml5Atoms::noembed == contextName || nsHtml5Atoms::noframes == contextName || (scriptingEnabled && nsHtml5Atoms::noscript == contextName)) { @@ -868,7 +869,7 @@ nsHtml5TreeBuilder::startTag(nsHtml5ElementName* elementName, nsHtml5HtmlAttribu NS_HTML5_BREAK(starttagloop); } case NS_HTML5TREE_BUILDER_FORM: { - if (formPointer) { + if (!!formPointer || isTemplateContents()) { errFormWhenFormOpen(); NS_HTML5_BREAK(starttagloop); } else { @@ -1055,7 +1056,7 @@ nsHtml5TreeBuilder::startTag(nsHtml5ElementName* elementName, nsHtml5HtmlAttribu NS_HTML5_BREAK(starttagloop); } case NS_HTML5TREE_BUILDER_FORM: { - if (formPointer) { + if (!!formPointer && !isTemplateContents()) { errFormWhenFormOpen(); NS_HTML5_BREAK(starttagloop); } else { @@ -1213,7 +1214,7 @@ nsHtml5TreeBuilder::startTag(nsHtml5ElementName* elementName, nsHtml5HtmlAttribu } case NS_HTML5TREE_BUILDER_ISINDEX: { errIsindex(); - if (formPointer) { + if (!!formPointer && !isTemplateContents()) { NS_HTML5_BREAK(starttagloop); } implicitlyCloseP(); @@ -1247,6 +1248,9 @@ nsHtml5TreeBuilder::startTag(nsHtml5ElementName* elementName, nsHtml5HtmlAttribu pop(); appendVoidElementToCurrentMayFoster(nsHtml5ElementName::ELT_HR, nsHtml5HtmlAttributes::EMPTY_ATTRIBUTES); pop(); + if (!isTemplateContents()) { + formPointer = nullptr; + } selfClosing = false; NS_HTML5_BREAK(starttagloop); } @@ -2508,22 +2512,38 @@ nsHtml5TreeBuilder::endTag(nsHtml5ElementName* elementName) NS_HTML5_BREAK(endtagloop); } case NS_HTML5TREE_BUILDER_FORM: { - if (!formPointer) { - errStrayEndTag(name); + if (!isTemplateContents()) { + if (!formPointer) { + errStrayEndTag(name); + NS_HTML5_BREAK(endtagloop); + } + formPointer = nullptr; + eltPos = findLastInScope(name); + if (eltPos == NS_HTML5TREE_BUILDER_NOT_FOUND_ON_STACK) { + errStrayEndTag(name); + NS_HTML5_BREAK(endtagloop); + } + generateImpliedEndTags(); + if (!!MOZ_UNLIKELY(mViewSource) && !isCurrent(name)) { + errUnclosedElements(eltPos, name); + } + removeFromStack(eltPos); + NS_HTML5_BREAK(endtagloop); + } else { + eltPos = findLastInScope(name); + if (eltPos == NS_HTML5TREE_BUILDER_NOT_FOUND_ON_STACK) { + errStrayEndTag(name); + NS_HTML5_BREAK(endtagloop); + } + generateImpliedEndTags(); + if (!!MOZ_UNLIKELY(mViewSource) && !isCurrent(name)) { + errUnclosedElements(eltPos, name); + } + while (currentPtr >= eltPos) { + pop(); + } NS_HTML5_BREAK(endtagloop); } - formPointer = nullptr; - eltPos = findLastInScope(name); - if (eltPos == NS_HTML5TREE_BUILDER_NOT_FOUND_ON_STACK) { - errStrayEndTag(name); - NS_HTML5_BREAK(endtagloop); - } - generateImpliedEndTags(); - if (!!MOZ_UNLIKELY(mViewSource) && !isCurrent(name)) { - errUnclosedElements(eltPos, name); - } - removeFromStack(eltPos); - NS_HTML5_BREAK(endtagloop); } case NS_HTML5TREE_BUILDER_P: { eltPos = findLastInButtonScope(nsHtml5Atoms::p); @@ -3855,7 +3875,9 @@ void nsHtml5TreeBuilder::appendToCurrentNodeAndPushFormElementMayFoster(nsHtml5HtmlAttributes* attributes) { nsIContent** elt = createElement(kNameSpaceID_XHTML, nsHtml5Atoms::form, attributes); - formPointer = elt; + if (!isTemplateContents()) { + formPointer = elt; + } nsHtml5StackNode* current = stack[currentPtr]; if (current->isFosterParenting()) { diff --git a/parser/html/nsHtml5TreeBuilderCppSupplement.h b/parser/html/nsHtml5TreeBuilderCppSupplement.h index 40fa7ebf0ac8..ae3b0e09018e 100644 --- a/parser/html/nsHtml5TreeBuilderCppSupplement.h +++ b/parser/html/nsHtml5TreeBuilderCppSupplement.h @@ -761,6 +761,40 @@ nsHtml5TreeBuilder::getDocumentFragmentForTemplate(nsIContent** aTemplate) return fragHandle; } +nsIContent** +nsHtml5TreeBuilder::getFormPointerForContext(nsIContent** aContext) +{ + if (!aContext) { + return nullptr; + } + + MOZ_ASSERT(NS_IsMainThread()); + + // aContext must always be a handle to an element that already exists + // in the document. It must never be an empty handle. + nsIContent* contextNode = *aContext; + nsIContent* currentAncestor = contextNode; + + // We traverse the ancestors of the context node to find the nearest + // form pointer. This traversal is why aContext must not be an emtpy handle. + nsIContent* nearestForm = nullptr; + while (currentAncestor) { + if (currentAncestor->IsHTML(nsGkAtoms::form)) { + nearestForm = currentAncestor; + break; + } + currentAncestor = currentAncestor->GetParent(); + } + + if (!nearestForm) { + return nullptr; + } + + nsIContent** formPointer = AllocateContentHandle(); + *formPointer = nearestForm; + return formPointer; +} + // Error reporting void diff --git a/parser/html/nsHtml5TreeBuilderHSupplement.h b/parser/html/nsHtml5TreeBuilderHSupplement.h index c1d9fcd7c051..a15864d9544a 100644 --- a/parser/html/nsHtml5TreeBuilderHSupplement.h +++ b/parser/html/nsHtml5TreeBuilderHSupplement.h @@ -28,6 +28,8 @@ nsIContent** getDocumentFragmentForTemplate(nsIContent** aTemplate); + nsIContent** getFormPointerForContext(nsIContent** aContext); + /** * Using nsIContent** instead of nsIContent* is the parser deals with DOM * nodes in a way that works off the main thread. Non-main-thread code diff --git a/parser/htmlparser/tests/mochitest/html5lib_tree_construction/isindex.dat b/parser/htmlparser/tests/mochitest/html5lib_tree_construction/isindex.dat index 42ef997f7ffe..b187e11b7f0d 100644 --- a/parser/htmlparser/tests/mochitest/html5lib_tree_construction/isindex.dat +++ b/parser/htmlparser/tests/mochitest/html5lib_tree_construction/isindex.dat @@ -45,3 +45,23 @@ | | |
+ +#data + +#errors +6: Start tag seen without seeing a doctype first. Expected “”. +15: “isindex” seen. +21: End of file seen and there were open elements. +21: Unclosed element “form”. +#document +| +| +| +| +|
+|