diff --git a/b2g/chrome/content/shell.js b/b2g/chrome/content/shell.js index ddc1e42f9187..96c7f5f3a495 100644 --- a/b2g/chrome/content/shell.js +++ b/b2g/chrome/content/shell.js @@ -398,14 +398,14 @@ var shell = { } let mediaKeys = { - 'MediaNextTrack': 'media-next-track-button', - 'MediaPreviousTrack': 'media-previous-track-button', + 'MediaTrackNext': 'media-next-track-button', + 'MediaTrackPrevious': 'media-previous-track-button', 'MediaPause': 'media-pause-button', 'MediaPlay': 'media-play-button', 'MediaPlayPause': 'media-play-pause-button', 'MediaStop': 'media-stop-button', 'MediaRewind': 'media-rewind-button', - 'FastFwd': 'media-fast-forward-button' + 'MediaFastForward': 'media-fast-forward-button' }; let isMediaKey = false; diff --git a/browser/devtools/webconsole/test/browser_webconsole_allow_mixedcontent_securityerrors.js b/browser/devtools/webconsole/test/browser_webconsole_allow_mixedcontent_securityerrors.js index 6de4425e66cd..bc9eecc6a6f7 100644 --- a/browser/devtools/webconsole/test/browser_webconsole_allow_mixedcontent_securityerrors.js +++ b/browser/devtools/webconsole/test/browser_webconsole_allow_mixedcontent_securityerrors.js @@ -24,14 +24,14 @@ let test = asyncTest(function* () { messages: [ { name: "Logged mixed active content", - text: "Loading mixed (insecure) active content on a secure page \"http://example.com/\"", + text: "Loading mixed (insecure) active content \"http://example.com/\" on a secure page", category: CATEGORY_SECURITY, severity: SEVERITY_WARNING, objects: true, }, { name: "Logged mixed passive content - image", - text: "Loading mixed (insecure) display content on a secure page \"http://example.com/tests/image/test/mochitest/blue.png\"", + text: "Loading mixed (insecure) display content \"http://example.com/tests/image/test/mochitest/blue.png\" on a secure page", category: CATEGORY_SECURITY, severity: SEVERITY_WARNING, objects: true, diff --git a/browser/devtools/webconsole/test/browser_webconsole_block_mixedcontent_securityerrors.js b/browser/devtools/webconsole/test/browser_webconsole_block_mixedcontent_securityerrors.js index 6b59d3f107d8..a3058999e262 100644 --- a/browser/devtools/webconsole/test/browser_webconsole_block_mixedcontent_securityerrors.js +++ b/browser/devtools/webconsole/test/browser_webconsole_block_mixedcontent_securityerrors.js @@ -95,16 +95,17 @@ function afterNotificationShown(hud, notification, deferred) messages: [ { name: "Logged blocking mixed active content", - text: "Loading mixed (insecure) active content on a secure"+ - " page \"http://example.com/\"", + text: "Loading mixed (insecure) active content \"http://example.com/\"" + + " on a secure page", category: CATEGORY_SECURITY, severity: SEVERITY_WARNING, objects: true, }, { name: "Logged blocking mixed passive content - image", - text: "Loading mixed (insecure) display content on a secure page"+ - " \"http://example.com/tests/image/test/mochitest/blue.png\"", + text: "Loading mixed (insecure) display content" + + " \"http://example.com/tests/image/test/mochitest/blue.png\"" + + " on a secure page", category: CATEGORY_SECURITY, severity: SEVERITY_WARNING, objects: true, diff --git a/build/automation.py.in b/build/automation.py.in index 3248e584622c..f2a2103e2a68 100644 --- a/build/automation.py.in +++ b/build/automation.py.in @@ -18,6 +18,7 @@ import sys import threading import tempfile import sqlite3 +import zipfile from datetime import datetime, timedelta from string import Template @@ -922,7 +923,7 @@ class Automation(object): os.makedirs(extensionsRootDir) if os.path.isfile(extensionSource): - reader = automationutils.ZipFileReader(extensionSource) + reader = zipfile.ZipFile(extensionSource, "r") for filename in reader.namelist(): # Sanity check the zip file. diff --git a/build/automationutils.py b/build/automationutils.py index ea47dd4b9b40..a9ed760bdf4c 100644 --- a/build/automationutils.py +++ b/build/automationutils.py @@ -13,11 +13,9 @@ import signal import subprocess import sys import tempfile -import zipfile import mozinfo __all__ = [ - "ZipFileReader", "dumpLeakLog", "processLeakLog", 'systemMemory', @@ -40,68 +38,6 @@ def setAutomationLog(alt_logger): global log log = alt_logger -class ZipFileReader(object): - """ - Class to read zip files in Python 2.5 and later. Limited to only what we - actually use. - """ - - def __init__(self, filename): - self._zipfile = zipfile.ZipFile(filename, "r") - - def __del__(self): - self._zipfile.close() - - def _getnormalizedpath(self, path): - """ - Gets a normalized path from 'path' (or the current working directory if - 'path' is None). Also asserts that the path exists. - """ - if path is None: - path = os.curdir - path = os.path.normpath(os.path.expanduser(path)) - assert os.path.isdir(path) - return path - - def _extractname(self, name, path): - """ - Extracts a file with the given name from the zip file to the given path. - Also creates any directories needed along the way. - """ - filename = os.path.normpath(os.path.join(path, name)) - if name.endswith("/"): - os.makedirs(filename) - else: - path = os.path.split(filename)[0] - if not os.path.isdir(path): - os.makedirs(path) - with open(filename, "wb") as dest: - dest.write(self._zipfile.read(name)) - - def namelist(self): - return self._zipfile.namelist() - - def read(self, name): - return self._zipfile.read(name) - - def extract(self, name, path = None): - if hasattr(self._zipfile, "extract"): - return self._zipfile.extract(name, path) - - # This will throw if name is not part of the zip file. - self._zipfile.getinfo(name) - - self._extractname(name, self._getnormalizedpath(path)) - - def extractall(self, path = None): - if hasattr(self._zipfile, "extractall"): - return self._zipfile.extractall(path) - - path = self._getnormalizedpath(path) - - for name in self._zipfile.namelist(): - self._extractname(name, path) - # Python does not provide strsignal() even in the very latest 3.x. # This is a reasonable fake. def strsig(n): diff --git a/configure.in b/configure.in index 69acd4fc4319..0c80e6cee4f6 100644 --- a/configure.in +++ b/configure.in @@ -7245,7 +7245,7 @@ dnl ======================================================== if test -z "$MOZ_MEMORY"; then if test -n "$MOZ_JEMALLOC3" -a -z "$MOZ_REPLACE_MALLOC"; then MOZ_NATIVE_JEMALLOC=1 - AC_CHECK_FUNCS(mallctl nallocm,, + AC_CHECK_FUNCS(mallctl nallocx,, [MOZ_NATIVE_JEMALLOC= break]) if test -n "$MOZ_NATIVE_JEMALLOC"; then diff --git a/dom/base/nsDeprecatedOperationList.h b/dom/base/nsDeprecatedOperationList.h index d9865cd8d095..eebc78c96dbe 100644 --- a/dom/base/nsDeprecatedOperationList.h +++ b/dom/base/nsDeprecatedOperationList.h @@ -40,29 +40,4 @@ DEPRECATED_OPERATION(SyncXMLHttpRequest) DEPRECATED_OPERATION(DataContainerEvent) DEPRECATED_OPERATION(SendAsBinary) DEPRECATED_OPERATION(Window_Controllers) -DEPRECATED_OPERATION(KeyNameDown) -DEPRECATED_OPERATION(KeyNameLeft) -DEPRECATED_OPERATION(KeyNameRight) -DEPRECATED_OPERATION(KeyNameUp) -DEPRECATED_OPERATION(KeyNameCrsel) -DEPRECATED_OPERATION(KeyNameDel) -DEPRECATED_OPERATION(KeyNameExsel) -DEPRECATED_OPERATION(KeyNameMenu) -DEPRECATED_OPERATION(KeyNameEsc) -DEPRECATED_OPERATION(KeyNameNonconvert) -DEPRECATED_OPERATION(KeyNameHalfWidth) -DEPRECATED_OPERATION(KeyNameRomanCharacters) -DEPRECATED_OPERATION(KeyNameFullWidth) -DEPRECATED_OPERATION(KeyNameSelectMedia) -DEPRECATED_OPERATION(KeyNameMediaNextTrack) -DEPRECATED_OPERATION(KeyNameMediaPreviousTrack) -DEPRECATED_OPERATION(KeyNameRed) -DEPRECATED_OPERATION(KeyNameGreen) -DEPRECATED_OPERATION(KeyNameYellow) -DEPRECATED_OPERATION(KeyNameBlue) -DEPRECATED_OPERATION(KeyNameLive) -DEPRECATED_OPERATION(KeyNameApps) -DEPRECATED_OPERATION(KeyNameFastFwd) -DEPRECATED_OPERATION(KeyNameZoom) -DEPRECATED_OPERATION(KeyNameDeadKeys) DEPRECATED_OPERATION(ImportXULIntoContent) diff --git a/dom/events/KeyNameList.h b/dom/events/KeyNameList.h index 2cb688ac6dae..b083d5eae207 100644 --- a/dom/events/KeyNameList.h +++ b/dom/events/KeyNameList.h @@ -46,7 +46,7 @@ DEFINE_KEYNAME_WITH_SAME_NAME(OS) DEFINE_KEYNAME_WITH_SAME_NAME(ScrollLock) DEFINE_KEYNAME_WITH_SAME_NAME(Shift) // DEFINE_KEYNAME_WITH_SAME_NAME(Super) -// DEFINE_KEYNAME_WITH_SAME_NAME(Symbol) +DEFINE_KEYNAME_WITH_SAME_NAME(Symbol) // DEFINE_KEYNAME_WITH_SAME_NAME(SymbolLock) /****************************************************************************** @@ -59,10 +59,10 @@ DEFINE_KEYNAME_WITH_SAME_NAME(Tab) /****************************************************************************** * Navigation Keys *****************************************************************************/ -DEFINE_KEYNAME_WITH_SAME_NAME(Down) // Rename to ArrowDown -DEFINE_KEYNAME_WITH_SAME_NAME(Left) // Rename to ArrowLeft -DEFINE_KEYNAME_WITH_SAME_NAME(Right) // Rename to ArrowRight -DEFINE_KEYNAME_WITH_SAME_NAME(Up) // Rename to ArrowUp +DEFINE_KEYNAME_WITH_SAME_NAME(ArrowDown) +DEFINE_KEYNAME_WITH_SAME_NAME(ArrowLeft) +DEFINE_KEYNAME_WITH_SAME_NAME(ArrowRight) +DEFINE_KEYNAME_WITH_SAME_NAME(ArrowUp) DEFINE_KEYNAME_WITH_SAME_NAME(End) DEFINE_KEYNAME_WITH_SAME_NAME(Home) DEFINE_KEYNAME_WITH_SAME_NAME(PageDown) @@ -74,14 +74,14 @@ DEFINE_KEYNAME_WITH_SAME_NAME(PageUp) DEFINE_KEYNAME_WITH_SAME_NAME(Backspace) DEFINE_KEYNAME_WITH_SAME_NAME(Clear) DEFINE_KEYNAME_WITH_SAME_NAME(Copy) -DEFINE_KEYNAME_WITH_SAME_NAME(Crsel) // Rename to CrSel +DEFINE_KEYNAME_WITH_SAME_NAME(CrSel) DEFINE_KEYNAME_WITH_SAME_NAME(Cut) -DEFINE_KEYNAME_WITH_SAME_NAME(Del) // Rename to Delete +DEFINE_KEYNAME_WITH_SAME_NAME(Delete) DEFINE_KEYNAME_WITH_SAME_NAME(EraseEof) -DEFINE_KEYNAME_WITH_SAME_NAME(Exsel) // Rename to ExSel +DEFINE_KEYNAME_WITH_SAME_NAME(ExSel) DEFINE_KEYNAME_WITH_SAME_NAME(Insert) DEFINE_KEYNAME_WITH_SAME_NAME(Paste) -// DEFINE_KEYNAME_WITH_SAME_NAME(Redo) +DEFINE_KEYNAME_WITH_SAME_NAME(Redo) DEFINE_KEYNAME_WITH_SAME_NAME(Undo) /****************************************************************************** @@ -91,8 +91,8 @@ DEFINE_KEYNAME_WITH_SAME_NAME(Accept) // DEFINE_KEYNAME_WITH_SAME_NAME(Again) DEFINE_KEYNAME_WITH_SAME_NAME(Attn) DEFINE_KEYNAME_WITH_SAME_NAME(Cancel) -DEFINE_KEYNAME_WITH_SAME_NAME(Menu) // Rename to ContextMenu -DEFINE_KEYNAME_WITH_SAME_NAME(Esc) // Rename to Escape +DEFINE_KEYNAME_WITH_SAME_NAME(ContextMenu) +DEFINE_KEYNAME_WITH_SAME_NAME(Escape) DEFINE_KEYNAME_WITH_SAME_NAME(Execute) DEFINE_KEYNAME_WITH_SAME_NAME(Find) DEFINE_KEYNAME_WITH_SAME_NAME(Help) @@ -100,8 +100,8 @@ DEFINE_KEYNAME_WITH_SAME_NAME(Pause) DEFINE_KEYNAME_WITH_SAME_NAME(Play) // DEFINE_KEYNAME_WITH_SAME_NAME(Props) DEFINE_KEYNAME_WITH_SAME_NAME(Select) -// DEFINE_KEYNAME_WITH_SAME_NAME(ZoomIn) -// DEFINE_KEYNAME_WITH_SAME_NAME(ZoomOut) +DEFINE_KEYNAME_WITH_SAME_NAME(ZoomIn) +DEFINE_KEYNAME_WITH_SAME_NAME(ZoomOut) /****************************************************************************** * Device Keys @@ -110,13 +110,13 @@ DEFINE_KEYNAME_WITH_SAME_NAME(BrightnessDown) DEFINE_KEYNAME_WITH_SAME_NAME(BrightnessUp) DEFINE_KEYNAME_WITH_SAME_NAME(Camera) DEFINE_KEYNAME_WITH_SAME_NAME(Eject) -// DEFINE_KEYNAME_WITH_SAME_NAME(LogOff) +DEFINE_KEYNAME_WITH_SAME_NAME(LogOff) DEFINE_KEYNAME_WITH_SAME_NAME(Power) -// DEFINE_KEYNAME_WITH_SAME_NAME(PowerOff) +DEFINE_KEYNAME_WITH_SAME_NAME(PowerOff) DEFINE_KEYNAME_WITH_SAME_NAME(PrintScreen) -// DEFINE_KEYNAME_WITH_SAME_NAME(Hibernate) -// DEFINE_KEYNAME_WITH_SAME_NAME(Standby) -// DEFINE_KEYNAME_WITH_SAME_NAME(WakeUp) +DEFINE_KEYNAME_WITH_SAME_NAME(Hibernate) +DEFINE_KEYNAME_WITH_SAME_NAME(Standby) +DEFINE_KEYNAME_WITH_SAME_NAME(WakeUp) /****************************************************************************** * IME and Composition Keys @@ -126,18 +126,18 @@ DEFINE_KEYNAME_WITH_SAME_NAME(Alphanumeric) DEFINE_KEYNAME_WITH_SAME_NAME(CodeInput) DEFINE_KEYNAME_WITH_SAME_NAME(Compose) DEFINE_KEYNAME_WITH_SAME_NAME(Convert) -// DEFINE_KEYNAME_WITH_SAME_NAME(Dead) +DEFINE_KEYNAME_WITH_SAME_NAME(Dead) DEFINE_KEYNAME_WITH_SAME_NAME(FinalMode) -// DEFINE_KEYNAME_WITH_SAME_NAME(GroupFirst) -// DEFINE_KEYNAME_WITH_SAME_NAME(GroupLast) -// DEFINE_KEYNAME_WITH_SAME_NAME(GroupNext) -// DEFINE_KEYNAME_WITH_SAME_NAME(GroupPrevious) +DEFINE_KEYNAME_WITH_SAME_NAME(GroupFirst) +DEFINE_KEYNAME_WITH_SAME_NAME(GroupLast) +DEFINE_KEYNAME_WITH_SAME_NAME(GroupNext) +DEFINE_KEYNAME_WITH_SAME_NAME(GroupPrevious) DEFINE_KEYNAME_WITH_SAME_NAME(ModeChange) // DEFINE_KEYNAME_WITH_SAME_NAME(NextCandidate) -DEFINE_KEYNAME_WITH_SAME_NAME(Nonconvert) // Rename to NonConvert +DEFINE_KEYNAME_WITH_SAME_NAME(NonConvert) DEFINE_KEYNAME_WITH_SAME_NAME(PreviousCandidate) // DEFINE_KEYNAME_WITH_SAME_NAME(Process) -// DEFINE_KEYNAME_WITH_SAME_NAME(SingleCandidate) +DEFINE_KEYNAME_WITH_SAME_NAME(SingleCandidate) /****************************************************************************** * Keys specific to Korean keyboards @@ -149,16 +149,16 @@ DEFINE_KEYNAME_WITH_SAME_NAME(JunjaMode) /****************************************************************************** * Keys specific to Japanese keyboards *****************************************************************************/ -// DEFINE_KEYNAME_WITH_SAME_NAME(Eisu) -DEFINE_KEYNAME_WITH_SAME_NAME(HalfWidth) // Rename to Hankaku +DEFINE_KEYNAME_WITH_SAME_NAME(Eisu) +DEFINE_KEYNAME_WITH_SAME_NAME(Hankaku) DEFINE_KEYNAME_WITH_SAME_NAME(Hiragana) -// DEFINE_KEYNAME_WITH_SAME_NAME(HiraganaKatakana) +DEFINE_KEYNAME_WITH_SAME_NAME(HiraganaKatakana) DEFINE_KEYNAME_WITH_SAME_NAME(KanaMode) DEFINE_KEYNAME_WITH_SAME_NAME(KanjiMode) DEFINE_KEYNAME_WITH_SAME_NAME(Katakana) -DEFINE_KEYNAME_WITH_SAME_NAME(RomanCharacters) // Rename to Romaji -DEFINE_KEYNAME_WITH_SAME_NAME(FullWidth) // Rename to Zenkaku -// DEFINE_KEYNAME_WITH_SAME_NAME(ZenkakuHankaku) +DEFINE_KEYNAME_WITH_SAME_NAME(Romaji) +DEFINE_KEYNAME_WITH_SAME_NAME(Zenkaku) +DEFINE_KEYNAME_WITH_SAME_NAME(ZenkakuHankaku) /****************************************************************************** * General-Purpose Function Keys @@ -206,20 +206,20 @@ DEFINE_KEYNAME_WITH_SAME_NAME(F35) /****************************************************************************** * Multimedia Keys *****************************************************************************/ -// DEFINE_KEYNAME_WITH_SAME_NAME(Close) -// DEFINE_KEYNAME_WITH_SAME_NAME(MailForward) -// DEFINE_KEYNAME_WITH_SAME_NAME(MailReply) -// DEFINE_KEYNAME_WITH_SAME_NAME(MailSend) +DEFINE_KEYNAME_WITH_SAME_NAME(Close) +DEFINE_KEYNAME_WITH_SAME_NAME(MailForward) +DEFINE_KEYNAME_WITH_SAME_NAME(MailReply) +DEFINE_KEYNAME_WITH_SAME_NAME(MailSend) DEFINE_KEYNAME_WITH_SAME_NAME(MediaPlayPause) -DEFINE_KEYNAME_WITH_SAME_NAME(SelectMedia) // Rename to MediaSelect +DEFINE_KEYNAME_WITH_SAME_NAME(MediaSelect) DEFINE_KEYNAME_WITH_SAME_NAME(MediaStop) -DEFINE_KEYNAME_WITH_SAME_NAME(MediaNextTrack) // Rename to MediaTrackNext -DEFINE_KEYNAME_WITH_SAME_NAME(MediaPreviousTrack) // Rename to MediaTrackPrevious -// DEFINE_KEYNAME_WITH_SAME_NAME(New) -// DEFINE_KEYNAME_WITH_SAME_NAME(Open) -// DEFINE_KEYNAME_WITH_SAME_NAME(Print) -// DEFINE_KEYNAME_WITH_SAME_NAME(Save) -// DEFINE_KEYNAME_WITH_SAME_NAME(SpellCheck) +DEFINE_KEYNAME_WITH_SAME_NAME(MediaTrackNext) +DEFINE_KEYNAME_WITH_SAME_NAME(MediaTrackPrevious) +DEFINE_KEYNAME_WITH_SAME_NAME(New) +DEFINE_KEYNAME_WITH_SAME_NAME(Open) +DEFINE_KEYNAME_WITH_SAME_NAME(Print) +DEFINE_KEYNAME_WITH_SAME_NAME(Save) +DEFINE_KEYNAME_WITH_SAME_NAME(SpellCheck) DEFINE_KEYNAME_WITH_SAME_NAME(VolumeDown) DEFINE_KEYNAME_WITH_SAME_NAME(VolumeUp) DEFINE_KEYNAME_WITH_SAME_NAME(VolumeMute) @@ -227,17 +227,17 @@ DEFINE_KEYNAME_WITH_SAME_NAME(VolumeMute) /****************************************************************************** * Application Keys *****************************************************************************/ -// DEFINE_KEYNAME_WITH_SAME_NAME(LaunchCalculator) -// DEFINE_KEYNAME_WITH_SAME_NAME(LaunchCalendar) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchCalculator) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchCalendar) DEFINE_KEYNAME_WITH_SAME_NAME(LaunchMail) -// DEFINE_KEYNAME_WITH_SAME_NAME(LaunchMediaPlayer) -// DEFINE_KEYNAME_WITH_SAME_NAME(LaunchMusicPlayer) -// DEFINE_KEYNAME_WITH_SAME_NAME(LaunchMyComputer) -// DEFINE_KEYNAME_WITH_SAME_NAME(LaunchScreenSaver) -// DEFINE_KEYNAME_WITH_SAME_NAME(LaunchSpreadsheet) -// DEFINE_KEYNAME_WITH_SAME_NAME(LaunchWebBrowser) -// DEFINE_KEYNAME_WITH_SAME_NAME(LaunchWebCam) -// DEFINE_KEYNAME_WITH_SAME_NAME(LaunchWordProcessor) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchMediaPlayer) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchMusicPlayer) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchMyComputer) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchScreenSaver) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchSpreadsheet) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchWebBrowser) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchWebCam) +DEFINE_KEYNAME_WITH_SAME_NAME(LaunchWordProcessor) DEFINE_KEYNAME_WITH_SAME_NAME(LaunchApplication1) DEFINE_KEYNAME_WITH_SAME_NAME(LaunchApplication2) @@ -279,43 +279,43 @@ DEFINE_KEYNAME_WITH_SAME_NAME(AudioBassBoostUp) // DEFINE_KEYNAME_WITH_SAME_NAME(AudioFaderFront) // DEFINE_KEYNAME_WITH_SAME_NAME(AudioFaderRear) // DEFINE_KEYNAME_WITH_SAME_NAME(AudioSurroundModeNext) -// DEFINE_KEYNAME_WITH_SAME_NAME(AVRInput) -// DEFINE_KEYNAME_WITH_SAME_NAME(AVRPower) +DEFINE_KEYNAME_WITH_SAME_NAME(AVRInput) +DEFINE_KEYNAME_WITH_SAME_NAME(AVRPower) DEFINE_KEYNAME_WITH_SAME_NAME(ChannelDown) DEFINE_KEYNAME_WITH_SAME_NAME(ChannelUp) -DEFINE_KEYNAME_WITH_SAME_NAME(Red) // Rename to ColorF0Red -DEFINE_KEYNAME_WITH_SAME_NAME(Green) // Rename to ColorF1Green -DEFINE_KEYNAME_WITH_SAME_NAME(Yellow) // Rename to ColorF2Yellow -DEFINE_KEYNAME_WITH_SAME_NAME(Blue) // Rename to ColorF3Blue -// DEFINE_KEYNAME_WITH_SAME_NAME(Grey) // Rename to ColorF4Grey -// DEFINE_KEYNAME_WITH_SAME_NAME(Brown) // Rename to ColorF5Brown +DEFINE_KEYNAME_WITH_SAME_NAME(ColorF0Red) +DEFINE_KEYNAME_WITH_SAME_NAME(ColorF1Green) +DEFINE_KEYNAME_WITH_SAME_NAME(ColorF2Yellow) +DEFINE_KEYNAME_WITH_SAME_NAME(ColorF3Blue) +// DEFINE_KEYNAME_WITH_SAME_NAME(ColorF4Grey) +// DEFINE_KEYNAME_WITH_SAME_NAME(ColorF5Brown) // DEFINE_KEYNAME_WITH_SAME_NAME(ClosedCaptionToggle) DEFINE_KEYNAME_WITH_SAME_NAME(Dimmer) // DEFINE_KEYNAME_WITH_SAME_NAME(DisplaySwap) -DEFINE_KEYNAME_WITH_SAME_NAME(Exit) -// DEFINE_KEYNAME_WITH_SAME_NAME(ClearFavorite0) // Rename to FavoriteClear0 -// DEFINE_KEYNAME_WITH_SAME_NAME(ClearFavorite1) // Rename to FavoriteClear1 -// DEFINE_KEYNAME_WITH_SAME_NAME(ClearFavorite2) // Rename to FavoriteClear2 -// DEFINE_KEYNAME_WITH_SAME_NAME(ClearFavorite3) // Rename to FavoriteClear3 -// DEFINE_KEYNAME_WITH_SAME_NAME(RecallFavorite0) // Rename to FavoriteRecall0 -// DEFINE_KEYNAME_WITH_SAME_NAME(RecallFavorite1) // Rename to FavoriteRecall1 -// DEFINE_KEYNAME_WITH_SAME_NAME(RecallFavorite2) // Rename to FavoriteRecall2 -// DEFINE_KEYNAME_WITH_SAME_NAME(RecallFavorite3) // Rename to FavoriteRecall3 -// DEFINE_KEYNAME_WITH_SAME_NAME(StoreFavorite0) // Rename to FavoriteStore0 -// DEFINE_KEYNAME_WITH_SAME_NAME(StoreFavorite1) // Rename to FavoriteStore1 -// DEFINE_KEYNAME_WITH_SAME_NAME(StoreFavorite2) // Rename to FavoriteStore2 -// DEFINE_KEYNAME_WITH_SAME_NAME(StoreFavorite3) // Rename to FavoriteStore3 +// DEFINE_KEYNAME_WITH_SAME_NAME(Exit) +// DEFINE_KEYNAME_WITH_SAME_NAME(FavoriteClear0) +// DEFINE_KEYNAME_WITH_SAME_NAME(FavoriteClear1) +// DEFINE_KEYNAME_WITH_SAME_NAME(FavoriteClear2) +// DEFINE_KEYNAME_WITH_SAME_NAME(FavoriteClear3) +// DEFINE_KEYNAME_WITH_SAME_NAME(FavoriteRecall0) +// DEFINE_KEYNAME_WITH_SAME_NAME(FavoriteRecall1) +// DEFINE_KEYNAME_WITH_SAME_NAME(FavoriteRecall2) +// DEFINE_KEYNAME_WITH_SAME_NAME(FavoriteRecall3) +// DEFINE_KEYNAME_WITH_SAME_NAME(FavoriteStore0) +// DEFINE_KEYNAME_WITH_SAME_NAME(FavoriteStore1) +// DEFINE_KEYNAME_WITH_SAME_NAME(FavoriteStore2) +// DEFINE_KEYNAME_WITH_SAME_NAME(FavoriteStore3) DEFINE_KEYNAME_WITH_SAME_NAME(Guide) -// DEFINE_KEYNAME_WITH_SAME_NAME(NextDay) // Rename to GuideNextDay -// DEFINE_KEYNAME_WITH_SAME_NAME(PrevDay) // Rename to GuidePreviousDay +// DEFINE_KEYNAME_WITH_SAME_NAME(GuideNextDay) +// DEFINE_KEYNAME_WITH_SAME_NAME(GuidePreviousDay) DEFINE_KEYNAME_WITH_SAME_NAME(Info) // DEFINE_KEYNAME_WITH_SAME_NAME(InstantReplay) // DEFINE_KEYNAME_WITH_SAME_NAME(Link) -// DEFINE_KEYNAME_WITH_SAME_NAME(List) // Rename to ListProgram -DEFINE_KEYNAME_WITH_SAME_NAME(Live) // Rename to LiveContent +// DEFINE_KEYNAME_WITH_SAME_NAME(ListProgram) +// DEFINE_KEYNAME_WITH_SAME_NAME(LiveContent) // DEFINE_KEYNAME_WITH_SAME_NAME(Lock) -DEFINE_KEYNAME_WITH_SAME_NAME(Apps) // Rename to MediaApps -DEFINE_KEYNAME_WITH_SAME_NAME(FastFwd) // Rename to MediaFastForward +// DEFINE_KEYNAME_WITH_SAME_NAME(MediaApps) +DEFINE_KEYNAME_WITH_SAME_NAME(MediaFastForward) DEFINE_KEYNAME_WITH_SAME_NAME(MediaLast) DEFINE_KEYNAME_WITH_SAME_NAME(MediaPause) DEFINE_KEYNAME_WITH_SAME_NAME(MediaPlay) @@ -340,36 +340,16 @@ DEFINE_KEYNAME_WITH_SAME_NAME(RandomToggle) // DEFINE_KEYNAME_WITH_SAME_NAME(ScreenModeNext) DEFINE_KEYNAME_WITH_SAME_NAME(Settings) // DEFINE_KEYNAME_WITH_SAME_NAME(SplitScreenToggle) -// DEFINE_KEYNAME_WITH_SAME_NAME(STBInput) -// DEFINE_KEYNAME_WITH_SAME_NAME(STBPower) +DEFINE_KEYNAME_WITH_SAME_NAME(STBInput) +DEFINE_KEYNAME_WITH_SAME_NAME(STBPower) DEFINE_KEYNAME_WITH_SAME_NAME(Subtitle) // DEFINE_KEYNAME_WITH_SAME_NAME(Teletext) -// DEFINE_KEYNAME_WITH_SAME_NAME(TV) -// DEFINE_KEYNAME_WITH_SAME_NAME(TVInput) -// DEFINE_KEYNAME_WITH_SAME_NAME(TVPower) -// DEFINE_KEYNAME_WITH_SAME_NAME(VideoModeNext) +DEFINE_KEYNAME_WITH_SAME_NAME(TV) +DEFINE_KEYNAME_WITH_SAME_NAME(TVInput) +DEFINE_KEYNAME_WITH_SAME_NAME(TVPower) +DEFINE_KEYNAME_WITH_SAME_NAME(VideoModeNext) // DEFINE_KEYNAME_WITH_SAME_NAME(Wink) -DEFINE_KEYNAME_WITH_SAME_NAME(Zoom) // Rename to ZoomToggle - -/****************************************************************************** - * Deprecated - ******************************************************************************/ -DEFINE_KEYNAME_WITH_SAME_NAME(DeadGrave) -DEFINE_KEYNAME_WITH_SAME_NAME(DeadAcute) -DEFINE_KEYNAME_WITH_SAME_NAME(DeadCircumflex) -DEFINE_KEYNAME_WITH_SAME_NAME(DeadTilde) -DEFINE_KEYNAME_WITH_SAME_NAME(DeadMacron) -DEFINE_KEYNAME_WITH_SAME_NAME(DeadBreve) -DEFINE_KEYNAME_WITH_SAME_NAME(DeadAboveDot) -DEFINE_KEYNAME_WITH_SAME_NAME(DeadUmlaut) -DEFINE_KEYNAME_WITH_SAME_NAME(DeadAboveRing) -DEFINE_KEYNAME_WITH_SAME_NAME(DeadDoubleacute) -DEFINE_KEYNAME_WITH_SAME_NAME(DeadCaron) -DEFINE_KEYNAME_WITH_SAME_NAME(DeadCedilla) -DEFINE_KEYNAME_WITH_SAME_NAME(DeadOgonek) -DEFINE_KEYNAME_WITH_SAME_NAME(DeadIota) -DEFINE_KEYNAME_WITH_SAME_NAME(DeadVoicedSound) -DEFINE_KEYNAME_WITH_SAME_NAME(DeadSemivoicedSound) +DEFINE_KEYNAME_WITH_SAME_NAME(ZoomToggle) #undef DEFINE_KEYNAME_WITH_SAME_NAME #undef DEFINE_KEYNAME_INTERNAL diff --git a/dom/events/KeyboardEvent.cpp b/dom/events/KeyboardEvent.cpp index 9d02c9a29973..8cb330334392 100644 --- a/dom/events/KeyboardEvent.cpp +++ b/dom/events/KeyboardEvent.cpp @@ -5,7 +5,6 @@ #include "mozilla/dom/KeyboardEvent.h" #include "mozilla/TextEvents.h" -#include "nsIDocument.h" #include "prtime.h" namespace mozilla { @@ -125,110 +124,7 @@ KeyboardEvent::GetModifierState(const nsAString& aKey, NS_IMETHODIMP KeyboardEvent::GetKey(nsAString& aKeyName) { - WidgetKeyboardEvent* keyboardEvent = mEvent->AsKeyboardEvent(); - keyboardEvent->GetDOMKeyName(aKeyName); - - nsIDocument::DeprecatedOperations deprecatedOperation; - switch (keyboardEvent->mKeyNameIndex) { - case KEY_NAME_INDEX_Down: - deprecatedOperation = nsIDocument::eKeyNameDown; - break; - case KEY_NAME_INDEX_Left: - deprecatedOperation = nsIDocument::eKeyNameLeft; - break; - case KEY_NAME_INDEX_Right: - deprecatedOperation = nsIDocument::eKeyNameRight; - break; - case KEY_NAME_INDEX_Up: - deprecatedOperation = nsIDocument::eKeyNameUp; - break; - case KEY_NAME_INDEX_Crsel: - deprecatedOperation = nsIDocument::eKeyNameCrsel; - break; - case KEY_NAME_INDEX_Del: - deprecatedOperation = nsIDocument::eKeyNameDel; - break; - case KEY_NAME_INDEX_Exsel: - deprecatedOperation = nsIDocument::eKeyNameExsel; - break; - case KEY_NAME_INDEX_Menu: - deprecatedOperation = nsIDocument::eKeyNameMenu; - break; - case KEY_NAME_INDEX_Esc: - deprecatedOperation = nsIDocument::eKeyNameEsc; - break; - case KEY_NAME_INDEX_Nonconvert: - deprecatedOperation = nsIDocument::eKeyNameNonconvert; - break; - case KEY_NAME_INDEX_HalfWidth: - deprecatedOperation = nsIDocument::eKeyNameHalfWidth; - break; - case KEY_NAME_INDEX_RomanCharacters: - deprecatedOperation = nsIDocument::eKeyNameRomanCharacters; - break; - case KEY_NAME_INDEX_FullWidth: - deprecatedOperation = nsIDocument::eKeyNameFullWidth; - break; - case KEY_NAME_INDEX_SelectMedia: - deprecatedOperation = nsIDocument::eKeyNameSelectMedia; - break; - case KEY_NAME_INDEX_MediaNextTrack: - deprecatedOperation = nsIDocument::eKeyNameMediaNextTrack; - break; - case KEY_NAME_INDEX_MediaPreviousTrack: - deprecatedOperation = nsIDocument::eKeyNameMediaPreviousTrack; - break; - case KEY_NAME_INDEX_Red: - deprecatedOperation = nsIDocument::eKeyNameRed; - break; - case KEY_NAME_INDEX_Green: - deprecatedOperation = nsIDocument::eKeyNameGreen; - break; - case KEY_NAME_INDEX_Yellow: - deprecatedOperation = nsIDocument::eKeyNameYellow; - break; - case KEY_NAME_INDEX_Blue: - deprecatedOperation = nsIDocument::eKeyNameBlue; - break; - case KEY_NAME_INDEX_Live: - deprecatedOperation = nsIDocument::eKeyNameLive; - break; - case KEY_NAME_INDEX_Apps: - deprecatedOperation = nsIDocument::eKeyNameApps; - break; - case KEY_NAME_INDEX_FastFwd: - deprecatedOperation = nsIDocument::eKeyNameFastFwd; - break; - case KEY_NAME_INDEX_Zoom: - deprecatedOperation = nsIDocument::eKeyNameZoom; - break; - case KEY_NAME_INDEX_DeadGrave: - case KEY_NAME_INDEX_DeadAcute: - case KEY_NAME_INDEX_DeadCircumflex: - case KEY_NAME_INDEX_DeadTilde: - case KEY_NAME_INDEX_DeadMacron: - case KEY_NAME_INDEX_DeadBreve: - case KEY_NAME_INDEX_DeadAboveDot: - case KEY_NAME_INDEX_DeadUmlaut: - case KEY_NAME_INDEX_DeadAboveRing: - case KEY_NAME_INDEX_DeadDoubleacute: - case KEY_NAME_INDEX_DeadCaron: - case KEY_NAME_INDEX_DeadCedilla: - case KEY_NAME_INDEX_DeadOgonek: - case KEY_NAME_INDEX_DeadIota: - case KEY_NAME_INDEX_DeadVoicedSound: - case KEY_NAME_INDEX_DeadSemivoicedSound: - deprecatedOperation = nsIDocument::eKeyNameDeadKeys; - break; - default: - return NS_OK; - } - - nsIDocument* doc = mOwner ? mOwner->GetExtantDoc() : nullptr; - if (NS_WARN_IF(!doc)) { - return NS_OK; - } - doc->WarnOnceAbout(deprecatedOperation); + mEvent->AsKeyboardEvent()->GetDOMKeyName(aKeyName); return NS_OK; } diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index d98def2487ca..a895a040701b 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -951,7 +951,7 @@ mozilla::plugins::PPluginModuleParent* ContentChild::AllocPPluginModuleParent(mozilla::ipc::Transport* aTransport, base::ProcessId aOtherProcess) { - return plugins::PluginModuleContentParent::Create(aTransport, aOtherProcess); + return plugins::PluginModuleContentParent::Initialize(aTransport, aOtherProcess); } PContentBridgeChild* @@ -2474,6 +2474,21 @@ ContentChild::RecvGetProfile(nsCString* aProfile) return true; } +bool +ContentChild::RecvLoadPluginResult(const uint32_t& aPluginId, const bool& aResult) +{ + plugins::PluginModuleContentParent::OnLoadPluginResult(aPluginId, aResult); + return true; +} + +bool +ContentChild::RecvAssociatePluginId(const uint32_t& aPluginId, + const base::ProcessId& aProcessId) +{ + plugins::PluginModuleContentParent::AssociatePluginId(aPluginId, aProcessId); + return true; +} + PBrowserOrId ContentChild::GetBrowserOrId(TabChild* aTabChild) { diff --git a/dom/ipc/ContentChild.h b/dom/ipc/ContentChild.h index d38478b92bd7..9d0ef654067a 100644 --- a/dom/ipc/ContentChild.h +++ b/dom/ipc/ContentChild.h @@ -362,6 +362,11 @@ public: virtual bool RecvOnAppThemeChanged() MOZ_OVERRIDE; + virtual bool RecvAssociatePluginId(const uint32_t& aPluginId, + const base::ProcessId& aProcessId) MOZ_OVERRIDE; + virtual bool RecvLoadPluginResult(const uint32_t& aPluginId, + const bool& aResult) MOZ_OVERRIDE; + virtual bool RecvStartProfiler(const uint32_t& aEntries, const double& aInterval, const nsTArray& aFeatures, diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl index 5c370b9d0ce1..dea19e602c6f 100644 --- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -59,6 +59,7 @@ using struct ChromePackage from "mozilla/chrome/RegistryMessageUtils.h"; using struct ResourceMapping from "mozilla/chrome/RegistryMessageUtils.h"; using struct OverrideMapping from "mozilla/chrome/RegistryMessageUtils.h"; using base::ChildPrivileges from "base/process_util.h"; +using base::ProcessId from "base/process.h"; using struct IPC::Permission from "mozilla/net/NeckoMessageUtils.h"; using class IPC::Principal from "mozilla/dom/PermissionMessageUtils.h"; using struct mozilla::null_t from "ipc/IPCMessageUtils.h"; @@ -518,6 +519,19 @@ child: */ OnAppThemeChanged(); + /** + * Called during plugin initialization to map a plugin id to a child process + * id. + */ + async AssociatePluginId(uint32_t aPluginId, ProcessId aProcessId); + + /** + * This call is used by async plugin initialization to notify the + * PluginModuleContentParent that the PluginModuleChromeParent's async + * init has completed. + */ + async LoadPluginResult(uint32_t aPluginId, bool aResult); + /** * Control the Gecko Profiler in the child process. */ diff --git a/dom/locales/en-US/chrome/dom/dom.properties b/dom/locales/en-US/chrome/dom/dom.properties index 79cd391347a0..a8fb45cd41c3 100644 --- a/dom/locales/en-US/chrome/dom/dom.properties +++ b/dom/locales/en-US/chrome/dom/dom.properties @@ -157,56 +157,6 @@ DataContainerEventWarning=Use of DataContainerEvent is deprecated. Use CustomEve SendAsBinaryWarning=The non-standard sendAsBinary method is deprecated and will soon be removed. Use the standard send(Blob data) method instead. # LOCALIZATION NOTE: Do not translate "window.controllers" Window_ControllersWarning=window.controllers is deprecated. Do not use it for UA detection. -# LOCALIZATION NOTE: Do not translate "KeyboardEvent.key", "Down" and "ArrowDown". -KeyNameDownWarning=KeyboardEvent.key value "Down" is obsolete and will be renamed to "ArrowDown". For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key -# LOCALIZATION NOTE: Do not translate "KeyboardEvent.key", "Left" and "ArrowLeft". -KeyNameLeftWarning=KeyboardEvent.key value "Left" is obsolete and will be renamed to "ArrowLeft". For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key -# LOCALIZATION NOTE: Do not translate "KeyboardEvent.key", "Right" and "ArrowRight". -KeyNameRightWarning=KeyboardEvent.key value "Right" is obsolete and will be renamed to "ArrowRight". For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key -# LOCALIZATION NOTE: Do not translate "KeyboardEvent.key", "Up" and "ArrowUp". -KeyNameUpWarning=KeyboardEvent.key value "Up" is obsolete and will be renamed to "ArrowUp". For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key -# LOCALIZATION NOTE: Do not translate "KeyboardEvent.key", "Crsel" and "CrSel". -KeyNameCrselWarning=KeyboardEvent.key value "Crsel" is obsolete and will be renamed to "CrSel". For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key -# LOCALIZATION NOTE: Do not translate "KeyboardEvent.key", "Del" and "Delete". -KeyNameDelWarning=KeyboardEvent.key value "Del" is obsolete and will be renamed to "Delete". For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key -# LOCALIZATION NOTE: Do not translate "KeyboardEvent.key", "Exsel" and "ExSel". -KeyNameExselWarning=KeyboardEvent.key value "Exsel" is obsolete and will be renamed to "ExSel". For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key -# LOCALIZATION NOTE: Do not translate "KeyboardEvent.key", "Menu" and "ContextMenu". -KeyNameMenuWarning=KeyboardEvent.key value "Menu" is obsolete and will be renamed to "ContextMenu". For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key -# LOCALIZATION NOTE: Do not translate "KeyboardEvent.key", "Esc" and "Escape". -KeyNameEscWarning=KeyboardEvent.key value "Esc" is obsolete and will be renamed to "Escape". For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key -# LOCALIZATION NOTE: Do not translate "KeyboardEvent.key", "Nonconvert" and "NonConvert". -KeyNameNonconvertWarning=KeyboardEvent.key value "Nonconvert" is obsolete and will be renamed to "NonConvert". For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key -# LOCALIZATION NOTE: Do not translate "KeyboardEvent.key", "HalfWidth" and "Hankaku". -KeyNameHalfWidthWarning=KeyboardEvent.key value "HalfWidth" is obsolete and will be renamed to "Hankaku". For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key -# LOCALIZATION NOTE: Do not translate "KeyboardEvent.key", "RomanCharacters", "Romaji" and "Eisu". -KeyNameRomanCharactersWarning=KeyboardEvent.key value "RomanCharacters" is obsolete and will be renamed to "Romaji" or remapped to "Eisu". For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key -# LOCALIZATION NOTE: Do not translate "KeyboardEvent.key", "FullWith" and "Zenkaku". -KeyNameFullWidthWarning=KeyboardEvent.key value "FullWidth" is obsolete and will be renamed to "Zenkaku". For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key -# LOCALIZATION NOTE: Do not translate "KeyboardEvent.key", "SelectMedia" and "MediaSelect". -KeyNameSelectMediaWarning=KeyboardEvent.key value "SelectMedia" is obsolete and will be renamed to "MediaSelect". For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key -# LOCALIZATION NOTE: Do not translate "KeyboardEvent.key", "MediaNextTrack" and "MediaTrackNext". -KeyNameMediaNextTrackWarning=KeyboardEvent.key value "MediaNextTrack" is obsolete and will be renamed to "MediaTrackNext". For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key -# LOCALIZATION NOTE: Do not translate "KeyboardEvent.key", "MediaPreviousTrack" and "MediaTrackPrevious". -KeyNameMediaPreviousTrackWarning=KeyboardEvent.key value "MediaPreviousTrack" is obsolete and will be renamed to "MediaTrackPrevious". For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key -# LOCALIZATION NOTE: Do not translate "KeyboardEvent.key", "Red" and "ColorF0Red". -KeyNameRedWarning=KeyboardEvent.key value "Red" is obsolete and will be renamed to "ColorF0Red". For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key -# LOCALIZATION NOTE: Do not translate "KeyboardEvent.key", "Green" and "ColorF1Green". -KeyNameGreenWarning=KeyboardEvent.key value "Green" is obsolete and will be renamed to "ColorF1Green". For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key -# LOCALIZATION NOTE: Do not translate "KeyboardEvent.key", "Yellow" and "ColorF2Yellow". -KeyNameYellowWarning=KeyboardEvent.key value "Yellow" is obsolete and will be renamed to "ColorF2Yellow". For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key -# LOCALIZATION NOTE: Do not translate "KeyboardEvent.key", "Blue" and "ColorF3Blue". -KeyNameBlueWarning=KeyboardEvent.key value "Blue" is obsolete and will be renamed to "ColorF3Blue". For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key -# LOCALIZATION NOTE: Do not translate "KeyboardEvent.key", "Live". -KeyNameLiveWarning=KeyboardEvent.key value "Live" is obsolete and will be removed. For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key -# LOCALIZATION NOTE: Do not translate "KeyboardEvent.key", "Apps". -KeyNameAppsWarning=KeyboardEvent.key value "Apps" is obsolete and will be removed. For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key -# LOCALIZATION NOTE: Do not translate "KeyboardEvent.key", "FastFwd" and "MediaFastForward". -KeyNameFastFwdWarning=KeyboardEvent.key value "FastFwd" is obsolete and will be renamed to "MediaFastForward". For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key -# LOCALIZATION NOTE: Do not translate "KeyboardEvent.key", "Zoom" and "ZoomToggle". -KeyNameZoomWarning=KeyboardEvent.key value "Zoom" is obsolete and will be renamed to "ZoomToggle". For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key -# LOCALIZATION NOTE: Do not translate "KeyboardEvent.key" and "Dead". -KeyNameDeadKeysWarning=KeyboardEvent.key values starting with "Dead" are obsolete and will be merged into just "Dead". For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key ImportXULIntoContentWarning=Importing XUL nodes into a content document is deprecated. This functionality may be removed soon. XMLDocumentLoadPrincipalMismatch=Use of document.load forbidden on Documents that come from other Windows. Only the Window in which a Document was created is allowed to call .load on that Document. Preferably, use XMLHttpRequest instead. # LOCALIZATION NOTE: Do not translate "IndexedDB". diff --git a/dom/locales/en-US/chrome/security/security.properties b/dom/locales/en-US/chrome/security/security.properties index 076229c0c71b..3c6db0a80542 100644 --- a/dom/locales/en-US/chrome/security/security.properties +++ b/dom/locales/en-US/chrome/security/security.properties @@ -15,8 +15,9 @@ SHA1Sig=This site makes use of a SHA-1 Certificate; it's recommended you use cer InsecurePasswordsPresentOnPage=Password fields present on an insecure (http://) page. This is a security risk that allows user login credentials to be stolen. InsecureFormActionPasswordsPresent=Password fields present in a form with an insecure (http://) form action. This is a security risk that allows user login credentials to be stolen. InsecurePasswordsPresentOnIframe=Password fields present on an insecure (http://) iframe. This is a security risk that allows user login credentials to be stolen. -LoadingMixedActiveContent=Loading mixed (insecure) active content on a secure page "%1$S" -LoadingMixedDisplayContent=Loading mixed (insecure) display content on a secure page "%1$S" +# LOCALIZATION NOTE: "%1$S" is the URI of the insecure mixed content resource +LoadingMixedActiveContent2=Loading mixed (insecure) active content "%1$S" on a secure page +LoadingMixedDisplayContent2=Loading mixed (insecure) display content "%1$S" on a secure page # LOCALIZATION NOTE: Do not translate "allow-scripts", "allow-same-origin", "sandbox" or "iframe" BothAllowScriptsAndSameOriginPresent=An iframe which has both allow-scripts and allow-same-origin for its sandbox attribute can remove its sandboxing. diff --git a/dom/media/MediaDecoderStateMachine.cpp b/dom/media/MediaDecoderStateMachine.cpp index ed16ab5e9e38..bad8ae588b11 100644 --- a/dom/media/MediaDecoderStateMachine.cpp +++ b/dom/media/MediaDecoderStateMachine.cpp @@ -232,9 +232,6 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder, mBufferingWait = mScheduler->IsRealTime() ? 0 : 30; mLowDataThresholdUsecs = mScheduler->IsRealTime() ? 0 : LOW_DATA_THRESHOLD_USECS; - mVideoPrerollFrames = mScheduler->IsRealTime() ? 0 : mAmpleVideoFrames / 2; - mAudioPrerollUsecs = mScheduler->IsRealTime() ? 0 : LOW_AUDIO_USECS * 2; - #ifdef XP_WIN // Ensure high precision timers are enabled on Windows, otherwise the state // machine thread isn't woken up at reliable intervals to set the next frame, @@ -633,18 +630,7 @@ MediaDecoderStateMachine::DecodeVideo() return; } - // We don't want to consider skipping to the next keyframe if we've - // only just started up the decode loop, so wait until we've decoded - // some frames before enabling the keyframe skip logic on video. - if (mIsVideoPrerolling && - (static_cast(VideoQueue().GetSize()) - >= mVideoPrerollFrames * mPlaybackRate)) - { - mIsVideoPrerolling = false; - } - skipToNextKeyFrame = NeedToSkipToNextKeyframe(); - currentTime = mState == DECODER_STATE_SEEKING ? 0 : GetMediaTime(); // Time the video decode, so that if it's slow, we can increase our low @@ -693,14 +679,6 @@ MediaDecoderStateMachine::DecodeAudio() mon.NotifyAll(); return; } - - // We don't want to consider skipping to the next keyframe if we've - // only just started up the decode loop, so wait until we've decoded - // some audio data before enabling the keyframe skip logic on audio. - if (mIsAudioPrerolling && - GetDecodedAudioDuration() >= mAudioPrerollUsecs * mPlaybackRate) { - mIsAudioPrerolling = false; - } } SAMPLE_LOG("DecodeAudio() queued=%i, decoder-queued=%o", @@ -758,12 +736,19 @@ MediaDecoderStateMachine::OnAudioDecoded(AudioData* aAudioSample) return; } - case DECODER_STATE_BUFFERING: + case DECODER_STATE_BUFFERING: { // If we're buffering, this may be the sample we need to stop buffering. - // Schedule the state machine and then fall through. + // Save it and schedule the state machine. + Push(audio); ScheduleStateMachine(); + return; + } + case DECODER_STATE_DECODING: { Push(audio); + if (mIsAudioPrerolling && DonePrerollingAudio()) { + StopPrerollingAudio(); + } return; } @@ -894,7 +879,13 @@ MediaDecoderStateMachine::OnNotDecoded(MediaData::Type aType, VideoQueue().Push(mFirstVideoFrameAfterSeek); mFirstVideoFrameAfterSeek = nullptr; } - isAudio ? AudioQueue().Finish() : VideoQueue().Finish(); + if (isAudio) { + AudioQueue().Finish(); + StopPrerollingAudio(); + } else { + VideoQueue().Finish(); + StopPrerollingVideo(); + } switch (mState) { case DECODER_STATE_DECODING_FIRSTFRAME: { MaybeFinishDecodeFirstFrame(); @@ -973,12 +964,20 @@ MediaDecoderStateMachine::OnVideoDecoded(VideoData* aVideoSample) return; } - case DECODER_STATE_BUFFERING: + case DECODER_STATE_BUFFERING: { // If we're buffering, this may be the sample we need to stop buffering. - // Schedule the state machine and then fall through. + // Save it and schedule the state machine. + Push(video); ScheduleStateMachine(); + return; + } + case DECODER_STATE_DECODING: { Push(video); + if (mIsVideoPrerolling && DonePrerollingVideo()) { + StopPrerollingVideo(); + } + // If the requested video sample was slow to arrive, increase the // amount of audio we buffer to ensure that we don't run out of audio. // TODO: Detect when we're truly async, and don't do this if so, as @@ -1197,22 +1196,34 @@ int64_t MediaDecoderStateMachine::GetCurrentTimeViaMediaStreamSync() const return mSyncPointInDecodedStream + streamDelta; } -void MediaDecoderStateMachine::StartPlayback() +void MediaDecoderStateMachine::MaybeStartPlayback() { - DECODER_LOG("StartPlayback()"); - - NS_ASSERTION(!IsPlaying(), "Shouldn't be playing when StartPlayback() is called"); AssertCurrentThreadInMonitor(); + if (IsPlaying()) { + // Logging this case is really spammy - don't do it. + return; + } + + bool playStatePermits = mDecoder->GetState() == MediaDecoder::PLAY_STATE_PLAYING; + bool decodeStatePermits = mState == DECODER_STATE_DECODING || mState == DECODER_STATE_COMPLETED; + if (!playStatePermits || !decodeStatePermits || mIsAudioPrerolling || mIsVideoPrerolling) { + DECODER_LOG("Not starting playback [playStatePermits: %d, decodeStatePermits: %d, " + "mIsAudioPrerolling: %d, mIsVideoPrerolling: %d]", (int) playStatePermits, + (int) decodeStatePermits, (int) mIsAudioPrerolling, (int) mIsVideoPrerolling); + return; + } if (mDecoder->CheckDecoderCanOffloadAudio()) { DECODER_LOG("Offloading playback"); return; } + DECODER_LOG("MaybeStartPlayback() starting playback"); + mDecoder->NotifyPlaybackStarted(); SetPlayStartTime(TimeStamp::Now()); + MOZ_ASSERT(IsPlaying()); - NS_ASSERTION(IsPlaying(), "Should report playing by end of StartPlayback()"); nsresult rv = StartAudioThread(); NS_ENSURE_SUCCESS_VOID(rv); @@ -1460,8 +1471,8 @@ void MediaDecoderStateMachine::StartDecoding() } // Reset other state to pristine values before starting decode. - mIsAudioPrerolling = true; - mIsVideoPrerolling = true; + mIsAudioPrerolling = !DonePrerollingAudio(); + mIsVideoPrerolling = !DonePrerollingVideo(); // Ensure that we've got tasks enqueued to decode data if we need to. DispatchDecodeTasksIfNeeded(); @@ -1502,25 +1513,45 @@ void MediaDecoderStateMachine::DoNotifyWaitingForResourcesStatusChanged() ScheduleStateMachine(); } -void MediaDecoderStateMachine::Play() +void MediaDecoderStateMachine::PlayInternal() { - NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); + NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread."); + ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); + + // Once we start playing, we don't want to minimize our prerolling, as we + // assume the user is likely to want to keep playing in future. This needs to + // happen before we invoke StartDecoding(). + if (mMinimizePreroll) { + mMinimizePreroll = false; + DispatchDecodeTasksIfNeeded(); + } + + // Some state transitions still happen synchronously on the main thread. So + // if the main thread invokes Play() and then Seek(), the seek will initiate + // synchronously on the main thread, and the asynchronous PlayInternal task + // will arrive when it's no longer valid. The proper thing to do is to move + // all state transitions to the state machine thread, but for now we just + // make sure that none of the possible main-thread state transitions (Seek(), + // SetDormant(), and Shutdown()) have not occurred. + if (mState != DECODER_STATE_DECODING && mState != DECODER_STATE_BUFFERING && + mState != DECODER_STATE_COMPLETED) + { + DECODER_LOG("Unexpected state - Bailing out of PlayInternal()"); + return; + } + // When asked to play, switch to decoding state only if // we are currently buffering. In other cases, we'll start playing anyway // when the state machine notices the decoder's state change to PLAYING. - ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); if (mState == DECODER_STATE_BUFFERING) { - DECODER_LOG("Changed state from BUFFERING to DECODING"); - SetState(DECODER_STATE_DECODING); - mDecodeStartTime = TimeStamp::Now(); + StartDecoding(); } + if (mDecodingFrozenAtStateDecoding) { mDecodingFrozenAtStateDecoding = false; DispatchDecodeTasksIfNeeded(); } - // Once we start playing, we don't want to minimize our prerolling, as we - // assume the user is likely to want to keep playing in future. - mMinimizePreroll = false; + ScheduleStateMachine(); } @@ -2259,13 +2290,7 @@ MediaDecoderStateMachine::FinishDecodeFirstFrame() // So we need to check if this has occurred, else our decode pipeline won't // run (since it doesn't need to) and we won't detect end of stream. CheckIfDecodeComplete(); - - if ((mState == DECODER_STATE_DECODING || mState == DECODER_STATE_COMPLETED) && - mDecoder->GetState() == MediaDecoder::PLAY_STATE_PLAYING && - !IsPlaying()) - { - StartPlayback(); - } + MaybeStartPlayback(); if (mQueuedSeekTarget.IsValid()) { EnqueueStartQueuedSeekTask(); @@ -2642,12 +2667,8 @@ nsresult MediaDecoderStateMachine::RunStateMachine() StopPlayback(); } - if (mDecoder->GetState() == MediaDecoder::PLAY_STATE_PLAYING && - !IsPlaying()) { - // We are playing, but the state machine does not know it yet. Tell it - // that it is, so that the clock can be properly queried. - StartPlayback(); - } + // Start playback if necessary so that the clock can be properly queried. + MaybeStartPlayback(); AdvanceFrame(); NS_ASSERTION(mDecoder->GetState() != MediaDecoder::PLAY_STATE_PLAYING || @@ -2682,8 +2703,8 @@ nsresult MediaDecoderStateMachine::RunStateMachine() MOZ_ASSERT(mReader->IsWaitForDataSupported(), "Don't yet have a strategy for non-heuristic + non-WaitForData"); DispatchDecodeTasksIfNeeded(); - MOZ_ASSERT_IF(OutOfDecodedAudio(), mAudioRequestStatus != RequestStatus::Idle); - MOZ_ASSERT_IF(OutOfDecodedVideo(), mVideoRequestStatus != RequestStatus::Idle); + MOZ_ASSERT_IF(!mMinimizePreroll && OutOfDecodedAudio(), mAudioRequestStatus != RequestStatus::Idle); + MOZ_ASSERT_IF(!mMinimizePreroll && OutOfDecodedVideo(), mVideoRequestStatus != RequestStatus::Idle); DECODER_LOG("In buffering mode, waiting to be notified: outOfAudio: %d, " "mAudioStatus: %d, outOfVideo: %d, mVideoStatus: %d", OutOfDecodedAudio(), mAudioRequestStatus, @@ -2698,11 +2719,7 @@ nsresult MediaDecoderStateMachine::RunStateMachine() // Notify to allow blocked decoder thread to continue mDecoder->GetReentrantMonitor().NotifyAll(); UpdateReadyState(); - if (mDecoder->GetState() == MediaDecoder::PLAY_STATE_PLAYING && - !IsPlaying()) - { - StartPlayback(); - } + MaybeStartPlayback(); NS_ASSERTION(IsStateMachineScheduled(), "Must have timer scheduled"); return NS_OK; } @@ -2976,8 +2993,8 @@ void MediaDecoderStateMachine::AdvanceFrame() // We've got enough data to keep playing until at least the next frame. // Start playing now if need be. - if (!IsPlaying() && ((mFragmentEndTime >= 0 && clock_time < mFragmentEndTime) || mFragmentEndTime < 0)) { - StartPlayback(); + if ((mFragmentEndTime >= 0 && clock_time < mFragmentEndTime) || mFragmentEndTime < 0) { + MaybeStartPlayback(); } if (currentFrame) { @@ -3363,6 +3380,7 @@ void MediaDecoderStateMachine::SetMinimizePrerollUntilPlaybackStarts() { AssertCurrentThreadInMonitor(); + DECODER_LOG("SetMinimizePrerollUntilPlaybackStarts()"); mMinimizePreroll = true; } diff --git a/dom/media/MediaDecoderStateMachine.h b/dom/media/MediaDecoderStateMachine.h index cc2966ac90af..a079df9dbf04 100644 --- a/dom/media/MediaDecoderStateMachine.h +++ b/dom/media/MediaDecoderStateMachine.h @@ -89,6 +89,7 @@ hardware (via AudioStream). #include "MediaDecoderReader.h" #include "MediaDecoderOwner.h" #include "MediaMetadataManager.h" +#include "MediaDecoderStateMachineScheduler.h" class nsITimer; @@ -197,7 +198,18 @@ public: // Cause state transitions. These methods obtain the decoder monitor // to synchronise the change of state, and to notify other threads // that the state has changed. - void Play(); + void Play() + { + MOZ_ASSERT(NS_IsMainThread()); + nsRefPtr r = NS_NewRunnableMethod(this, &MediaDecoderStateMachine::PlayInternal); + GetStateMachineThread()->Dispatch(r, NS_DISPATCH_NORMAL); + } + +private: + // The actual work for the above, which happens asynchronously on the state + // machine thread. + void PlayInternal(); +public: // Seeks to the decoder to aTarget asynchronously. // Must be called from the main thread. @@ -580,9 +592,10 @@ protected: // The decoder monitor must be held. void StopPlayback(); - // Sets internal state which causes playback of media to begin or resume. + // If the conditions are right, sets internal state which causes playback + // of media to begin or resume. // Must be called with the decode monitor held. - void StartPlayback(); + void MaybeStartPlayback(); // Moves the decoder into decoding state. Called on the state machine // thread. The decoder monitor must be held. @@ -931,8 +944,48 @@ protected: // unneccessarily if we start playing as soon as the first sample is // decoded. These two fields store how many video frames and audio // samples we must consume before are considered to be finished prerolling. - uint32_t mAudioPrerollUsecs; - uint32_t mVideoPrerollFrames; + uint32_t AudioPrerollUsecs() const + { + if (mScheduler->IsRealTime()) { + return 0; + } + + uint32_t result = mLowAudioThresholdUsecs * 2; + MOZ_ASSERT(result <= mAmpleAudioThresholdUsecs, "Prerolling will never finish"); + return result; + } + uint32_t VideoPrerollFrames() const { return mScheduler->IsRealTime() ? 0 : mAmpleVideoFrames / 2; } + + bool DonePrerollingAudio() + { + AssertCurrentThreadInMonitor(); + return !IsAudioDecoding() || GetDecodedAudioDuration() >= AudioPrerollUsecs() * mPlaybackRate; + } + + bool DonePrerollingVideo() + { + AssertCurrentThreadInMonitor(); + return !IsVideoDecoding() || + static_cast(VideoQueue().GetSize()) >= VideoPrerollFrames() * mPlaybackRate; + } + + void StopPrerollingAudio() + { + AssertCurrentThreadInMonitor(); + if (mIsAudioPrerolling) { + mIsAudioPrerolling = false; + ScheduleStateMachine(); + } + } + + void StopPrerollingVideo() + { + AssertCurrentThreadInMonitor(); + if (mIsVideoPrerolling) { + mIsVideoPrerolling = false; + ScheduleStateMachine(); + } + } // This temporarily stores the first frame we decode after we seek. // This is so that if we hit end of stream while we're decoding to reach diff --git a/dom/media/fmp4/MP4Reader.cpp b/dom/media/fmp4/MP4Reader.cpp index 20199640618e..d7c6ae4d8486 100644 --- a/dom/media/fmp4/MP4Reader.cpp +++ b/dom/media/fmp4/MP4Reader.cpp @@ -72,7 +72,7 @@ MP4Reader::MP4Reader(AbstractMediaDecoder* aDecoder) , mDemuxerInitialized(false) , mIsEncrypted(false) , mIndexReady(false) - , mIndexMonitor("MP4 index") + , mDemuxerMonitor("MP4 Demuxer") { MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); MOZ_COUNT_CTOR(MP4Reader); @@ -157,7 +157,7 @@ MP4Reader::Init(MediaDecoderReader* aCloneDonor) { MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); PlatformDecoderModule::Init(); - mDemuxer = new MP4Demuxer(new MP4Stream(mDecoder->GetResource(), &mIndexMonitor), &mIndexMonitor); + mDemuxer = new MP4Demuxer(new MP4Stream(mDecoder->GetResource(), &mDemuxerMonitor), &mDemuxerMonitor); InitLayersBackendType(); @@ -294,12 +294,10 @@ MP4Reader::ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags) { if (!mDemuxerInitialized) { - { - MonitorAutoLock mon(mIndexMonitor); - bool ok = mDemuxer->Init(); - NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); - mIndexReady = true; - } + MonitorAutoLock mon(mDemuxerMonitor); + bool ok = mDemuxer->Init(); + NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); + mIndexReady = true; // To decode, we need valid video and a place to put it. mInfo.mVideo.mHasVideo = mVideo.mActive = mDemuxer->HasValidVideo() && @@ -308,6 +306,7 @@ MP4Reader::ReadMetadata(MediaInfo* aInfo, mInfo.mAudio.mHasAudio = mAudio.mActive = mDemuxer->HasValidAudio(); { + MonitorAutoUnlock unlock(mDemuxerMonitor); ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); mIsEncrypted = mDemuxer->Crypto().valid; } @@ -411,7 +410,11 @@ MP4Reader::ReadMetadata(MediaInfo* aInfo, } // Get the duration, and report it to the decoder if we have it. - Microseconds duration = mDemuxer->Duration(); + Microseconds duration; + { + MonitorAutoLock lock(mDemuxerMonitor); + duration = mDemuxer->Duration(); + } if (duration != -1) { ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor()); mDecoder->SetMediaDuration(duration); @@ -420,7 +423,7 @@ MP4Reader::ReadMetadata(MediaInfo* aInfo, *aInfo = mInfo; *aTags = nullptr; - MonitorAutoLock mon(mIndexMonitor); + MonitorAutoLock mon(mDemuxerMonitor); UpdateIndex(); return NS_OK; @@ -437,6 +440,7 @@ MP4Reader::IsMediaSeekable() { // We can seek if we get a duration *and* the reader reports that it's // seekable. + MonitorAutoLock mon(mDemuxerMonitor); return mDecoder->GetResource()->IsTransportSeekable() && mDemuxer->CanSeek(); } @@ -631,7 +635,14 @@ MP4Reader::ReturnOutput(MediaData* aData, TrackType aTrack) MP4Sample* MP4Reader::PopSample(TrackType aTrack) { - MonitorAutoLock mon(mIndexMonitor); + MonitorAutoLock mon(mDemuxerMonitor); + return PopSampleLocked(aTrack); +} + +MP4Sample* +MP4Reader::PopSampleLocked(TrackType aTrack) +{ + mDemuxerMonitor.AssertCurrentThreadOwns(); switch (aTrack) { case kAudio: return mDemuxer->DemuxAudioSample(); @@ -673,12 +684,12 @@ MP4Reader::ResetDecode() MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn()); Flush(kVideo); { - MonitorAutoLock mon(mIndexMonitor); + MonitorAutoLock mon(mDemuxerMonitor); mDemuxer->SeekVideo(0); } Flush(kAudio); { - MonitorAutoLock mon(mIndexMonitor); + MonitorAutoLock mon(mDemuxerMonitor); mDemuxer->SeekAudio(0); } return MediaDecoderReader::ResetDecode(); @@ -821,6 +832,7 @@ MP4Reader::Seek(int64_t aTime, { LOG("MP4Reader::Seek(%lld)", aTime); MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn()); + MonitorAutoLock mon(mDemuxerMonitor); if (!mDecoder->GetResource()->IsTransportSeekable() || !mDemuxer->CanSeek()) { VLOG("Seek() END (Unseekable)"); return SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); @@ -829,7 +841,7 @@ MP4Reader::Seek(int64_t aTime, mQueuedVideoSample = nullptr; if (mDemuxer->HasValidVideo()) { mDemuxer->SeekVideo(aTime); - mQueuedVideoSample = PopSample(kVideo); + mQueuedVideoSample = PopSampleLocked(kVideo); } if (mDemuxer->HasValidAudio()) { mDemuxer->SeekAudio( @@ -856,7 +868,7 @@ MP4Reader::UpdateIndex() int64_t MP4Reader::GetEvictionOffset(double aTime) { - MonitorAutoLock mon(mIndexMonitor); + MonitorAutoLock mon(mDemuxerMonitor); if (!mIndexReady) { return 0; } @@ -867,7 +879,7 @@ MP4Reader::GetEvictionOffset(double aTime) nsresult MP4Reader::GetBuffered(dom::TimeRanges* aBuffered) { - MonitorAutoLock mon(mIndexMonitor); + MonitorAutoLock mon(mDemuxerMonitor); if (!mIndexReady) { return NS_OK; } diff --git a/dom/media/fmp4/MP4Reader.h b/dom/media/fmp4/MP4Reader.h index fa288c83f0a5..5ec252d57082 100644 --- a/dom/media/fmp4/MP4Reader.h +++ b/dom/media/fmp4/MP4Reader.h @@ -102,6 +102,7 @@ private: // Blocks until the demuxer produces an sample of specified type. // Returns nullptr on error on EOS. Caller must delete sample. mp4_demuxer::MP4Sample* PopSample(mp4_demuxer::TrackType aTrack); + mp4_demuxer::MP4Sample* PopSampleLocked(mp4_demuxer::TrackType aTrack); bool SkipVideoDemuxToNextKeyFrame(int64_t aTimeThreshold, uint32_t& parsed); @@ -259,7 +260,7 @@ private: bool mIsEncrypted; bool mIndexReady; - Monitor mIndexMonitor; + Monitor mDemuxerMonitor; nsRefPtr mSharedDecoderManager; }; diff --git a/dom/media/fmp4/apple/AppleVDADecoder.cpp b/dom/media/fmp4/apple/AppleVDADecoder.cpp index cd5e3bfee9a8..74958c489c51 100644 --- a/dom/media/fmp4/apple/AppleVDADecoder.cpp +++ b/dom/media/fmp4/apple/AppleVDADecoder.cpp @@ -289,10 +289,8 @@ AppleVDADecoder::OutputFrame(CVPixelBufferRef aImage, // Frames come out in DTS order but we need to output them // in composition order. mReorderQueue.Push(data); - // Assume a frame with a PTS <= current DTS is ready. while (mReorderQueue.Length() > mMaxRefFrames) { - nsRefPtr readyData = mReorderQueue.Pop(); - mCallback->Output(readyData); + mCallback->Output(mReorderQueue.Pop()); } LOG("%llu decoded frames queued", static_cast(mReorderQueue.Length())); diff --git a/dom/media/moz.build b/dom/media/moz.build index 1320a3e67d73..3bfd00027b27 100644 --- a/dom/media/moz.build +++ b/dom/media/moz.build @@ -102,6 +102,7 @@ EXPORTS += [ 'MediaDecoderOwner.h', 'MediaDecoderReader.h', 'MediaDecoderStateMachine.h', + 'MediaDecoderStateMachineScheduler.h', 'MediaInfo.h', 'MediaMetadataManager.h', 'MediaPromise.h', diff --git a/dom/media/omx/MediaCodecReader.cpp b/dom/media/omx/MediaCodecReader.cpp index 09244072d57c..81d79c4edeb7 100644 --- a/dom/media/omx/MediaCodecReader.cpp +++ b/dom/media/omx/MediaCodecReader.cpp @@ -37,6 +37,7 @@ #include "ImageContainer.h" #include "SharedThreadPool.h" #include "VideoFrameContainer.h" +#include "VideoUtils.h" using namespace android; @@ -1275,14 +1276,12 @@ MediaCodecReader::CreateTaskQueues() { if (mAudioTrack.mSource != nullptr && mAudioTrack.mCodec != nullptr && !mAudioTrack.mTaskQueue) { - mAudioTrack.mTaskQueue = new MediaTaskQueue( - SharedThreadPool::Get(NS_LITERAL_CSTRING("MediaCodecReader Audio"), 1)); + mAudioTrack.mTaskQueue = CreateMediaDecodeTaskQueue(); NS_ENSURE_TRUE(mAudioTrack.mTaskQueue, false); } - if (mVideoTrack.mSource != nullptr && mVideoTrack.mCodec != nullptr && - !mVideoTrack.mTaskQueue) { - mVideoTrack.mTaskQueue = new MediaTaskQueue( - SharedThreadPool::Get(NS_LITERAL_CSTRING("MediaCodecReader Video"), 1)); + if (mVideoTrack.mSource != nullptr && mVideoTrack.mCodec != nullptr && + !mVideoTrack.mTaskQueue) { + mVideoTrack.mTaskQueue = CreateMediaDecodeTaskQueue(); NS_ENSURE_TRUE(mVideoTrack.mTaskQueue, false); } diff --git a/dom/media/test/test_bug448534.html b/dom/media/test/test_bug448534.html index da43714bc066..6b4894deb10f 100644 --- a/dom/media/test/test_bug448534.html +++ b/dom/media/test/test_bug448534.html @@ -19,6 +19,7 @@ var manager = new MediaTestManager; function loaded(event) { var v = event.target; + info(v.token + ": event=" + event.type); if (v._finished) return; v.play(); @@ -26,19 +27,21 @@ function loaded(event) { function started(event) { var v = event.target; + info(v.token + ": event=" + event.type); if (v._finished) return; - ok(!v.paused, "Video should not be paused while playing"); + ok(!v.paused, v.token + ": Video should not be paused while playing"); v.parentNode.removeChild(v); v._played = true; } function stopped(event) { var v = event.target; + info(v.token + ": event=" + event.type); if (v._finished) return; v._finished = true; - ok(v.paused, "Video should be paused after removing from the Document"); + ok(v.paused, v.token + ": Video should be paused after removing from the Document"); removeNodeAndSource(v); manager.finished(v.token); } diff --git a/dom/plugins/base/nsJSNPRuntime.cpp b/dom/plugins/base/nsJSNPRuntime.cpp index 9d63f0415afd..a50f21bd578f 100644 --- a/dom/plugins/base/nsJSNPRuntime.cpp +++ b/dom/plugins/base/nsJSNPRuntime.cpp @@ -28,7 +28,10 @@ #include "js/HashTable.h" #include "mozilla/HashFunctions.h" #include "mozilla/dom/ScriptSettings.h" +#include "mozilla/plugins/PluginAsyncSurrogate.h" +using mozilla::plugins::AsyncNPObject; +using mozilla::plugins::PluginAsyncSurrogate; #define NPRUNTIME_JSCLASS_NAME "NPObject JS wrapper class" @@ -94,10 +97,24 @@ static nsTArray* sDelayedReleases; namespace { +inline void +CastNPObject(NPObject *aObj, PluginScriptableObjectParent*& aActor, + PluginAsyncSurrogate*& aSurrogate) +{ + aActor = nullptr; + aSurrogate = nullptr; + if (aObj->_class == PluginScriptableObjectParent::GetClass()) { + aActor = static_cast(aObj)->parent; + } else if (aObj->_class == PluginAsyncSurrogate::GetClass()) { + aSurrogate = static_cast(aObj)->mSurrogate; + } +} + inline bool NPObjectIsOutOfProcessProxy(NPObject *obj) { - return obj->_class == PluginScriptableObjectParent::GetClass(); + return obj->_class == PluginScriptableObjectParent::GetClass() || + obj->_class == PluginAsyncSurrogate::GetClass(); } } // anonymous namespace @@ -1389,15 +1406,23 @@ NPObjWrapper_GetProperty(JSContext *cx, JS::Handle obj, JS::Handle(npobj)->parent; + PluginScriptableObjectParent* actor = nullptr; + PluginAsyncSurrogate* surrogate = nullptr; + CastNPObject(npobj, actor, surrogate); - // actor may be null if the plugin crashed. - if (!actor) + // actor and surrogate may be null if the plugin crashed. + if (!actor && !surrogate) return false; - bool success = actor->GetPropertyHelper(identifier, &hasProperty, - &hasMethod, &npv); + bool success = false; + if (surrogate) { + success = surrogate->GetPropertyHelper(npobj, identifier, &hasProperty, + &hasMethod, &npv); + } else if (actor) { + success = actor->GetPropertyHelper(identifier, &hasProperty, &hasMethod, + &npv); + } + if (!ReportExceptionIfPending(cx)) { if (success) _releasevariantvalue(&npv); @@ -2253,3 +2278,35 @@ NPObjectMember_Trace(JSTracer *trc, JSObject *obj) "NPObject Member => npobjWrapper"); } } + +// static +bool +nsJSObjWrapper::HasOwnProperty(NPObject *npobj, NPIdentifier npid) +{ + NPP npp = NPPStack::Peek(); + dom::AutoJSAPI jsapi; + if (NS_WARN_IF(!jsapi.InitWithLegacyErrorReporting(GetGlobalObject(npp)))) { + return false; + } + JSContext *cx = jsapi.cx(); + + if (!npobj) { + ThrowJSException(cx, + "Null npobj in nsJSObjWrapper::NP_HasOwnProperty!"); + + return false; + } + + nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; + bool found, ok = false; + + AutoJSExceptionReporter reporter(cx); + JS::Rooted jsobj(cx, npjsobj->mJSObj); + JSAutoCompartment ac(cx, jsobj); + + NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid), + "id must be either string or int!\n"); + JS::Rooted id(cx, NPIdentifierToJSId(npid)); + ok = ::JS_AlreadyHasOwnPropertyById(cx, jsobj, id, &found); + return ok && found; +} diff --git a/dom/plugins/base/nsJSNPRuntime.h b/dom/plugins/base/nsJSNPRuntime.h index 7d1a5f5c197e..e361b94e1879 100644 --- a/dom/plugins/base/nsJSNPRuntime.h +++ b/dom/plugins/base/nsJSNPRuntime.h @@ -44,6 +44,7 @@ public: static NPObject *GetNewOrUsed(NPP npp, JSContext *cx, JS::Handle obj); + static bool HasOwnProperty(NPObject* npobj, NPIdentifier npid); protected: explicit nsJSObjWrapper(NPP npp); diff --git a/dom/plugins/base/nsNPAPIPluginInstance.cpp b/dom/plugins/base/nsNPAPIPluginInstance.cpp index 5d809fea64ea..cf16dce7a330 100644 --- a/dom/plugins/base/nsNPAPIPluginInstance.cpp +++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp @@ -523,7 +523,7 @@ nsNPAPIPluginInstance::Start() return NS_ERROR_FAILURE; } - return NS_OK; + return newResult; } nsresult nsNPAPIPluginInstance::SetWindow(NPWindow* window) diff --git a/dom/plugins/base/nsNPAPIPluginStreamListener.cpp b/dom/plugins/base/nsNPAPIPluginStreamListener.cpp index aa4bfbdeb156..1383f8563c52 100644 --- a/dom/plugins/base/nsNPAPIPluginStreamListener.cpp +++ b/dom/plugins/base/nsNPAPIPluginStreamListener.cpp @@ -331,20 +331,32 @@ nsNPAPIPluginStreamListener::OnStartBinding(nsPluginStreamListenerPeer* streamPe if (error != NPERR_NO_ERROR) return NS_ERROR_FAILURE; - - switch(streamType) + + if (streamType == nsPluginStreamListenerPeer::STREAM_TYPE_UNKNOWN) { + SuspendRequest(); + } else if (!SetStreamType(streamType, false)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +bool +nsNPAPIPluginStreamListener::SetStreamType(uint16_t aType, bool aNeedsResume) +{ + switch(aType) { case NP_NORMAL: - mStreamType = NP_NORMAL; + mStreamType = NP_NORMAL; break; case NP_ASFILEONLY: - mStreamType = NP_ASFILEONLY; + mStreamType = NP_ASFILEONLY; break; case NP_ASFILE: - mStreamType = NP_ASFILE; + mStreamType = NP_ASFILE; break; case NP_SEEK: - mStreamType = NP_SEEK; + mStreamType = NP_SEEK; // Seekable streams should continue to exist even after OnStopRequest // is fired, so we AddRef ourself an extra time and Release when the // plugin calls NPN_DestroyStream (CleanUpStream). If the plugin never @@ -353,11 +365,16 @@ nsNPAPIPluginStreamListener::OnStartBinding(nsPluginStreamListenerPeer* streamPe NS_ADDREF_THIS(); break; default: - return NS_ERROR_FAILURE; + return false; } - mStreamStarted = true; - return NS_OK; + if (aNeedsResume) { + if (mStreamListenerPeer) { + mStreamListenerPeer->OnStreamTypeSet(mStreamType); + } + ResumeRequest(); + } + return true; } void @@ -369,18 +386,20 @@ nsNPAPIPluginStreamListener::SuspendRequest() nsresult rv = StartDataPump(); if (NS_FAILED(rv)) return; - + mIsSuspended = true; if (mStreamListenerPeer) { - mStreamListenerPeer->SuspendRequests(); + mStreamListenerPeer->SuspendRequests(); } } void nsNPAPIPluginStreamListener::ResumeRequest() { - mStreamListenerPeer->ResumeRequests(); + if (mStreamListenerPeer) { + mStreamListenerPeer->ResumeRequests(); + } mIsSuspended = false; } @@ -390,7 +409,7 @@ nsNPAPIPluginStreamListener::StartDataPump() nsresult rv; mDataPumpTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); NS_ENSURE_SUCCESS(rv, rv); - + // Start pumping data to the plugin every 100ms until it obeys and // eats the data. return mDataPumpTimer->InitWithCallback(this, 100, diff --git a/dom/plugins/base/nsNPAPIPluginStreamListener.h b/dom/plugins/base/nsNPAPIPluginStreamListener.h index 01b19dfe09b4..775bc9afabd1 100644 --- a/dom/plugins/base/nsNPAPIPluginStreamListener.h +++ b/dom/plugins/base/nsNPAPIPluginStreamListener.h @@ -83,6 +83,7 @@ public: nsresult OnStopBinding(nsPluginStreamListenerPeer* streamPeer, nsresult status); nsresult GetStreamType(int32_t *result); + bool SetStreamType(uint16_t aType, bool aNeedsResume = true); bool IsStarted(); nsresult CleanUpStream(NPReason reason); diff --git a/dom/plugins/base/nsPluginHost.cpp b/dom/plugins/base/nsPluginHost.cpp index 7fa3671eb552..b16313650a6f 100644 --- a/dom/plugins/base/nsPluginHost.cpp +++ b/dom/plugins/base/nsPluginHost.cpp @@ -50,6 +50,7 @@ #include "nsPluginStreamListenerPeer.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/LoadInfo.h" +#include "mozilla/plugins/PluginAsyncSurrogate.h" #include "mozilla/plugins/PluginBridge.h" #include "mozilla/plugins/PluginTypes.h" #include "mozilla/Preferences.h" @@ -110,6 +111,7 @@ using namespace mozilla; using mozilla::TimeStamp; using mozilla::plugins::PluginTag; +using mozilla::plugins::PluginAsyncSurrogate; // Null out a strong ref to a linked list iteratively to avoid // exhausting the stack (bug 486349). @@ -830,6 +832,7 @@ nsPluginHost::InstantiatePluginInstance(const char *aMimeType, nsIURI* aURL, if (NS_FAILED(rv)) { return NS_ERROR_FAILURE; } + const bool isAsyncInit = (rv == NS_PLUGIN_INIT_PENDING); nsRefPtr instance; rv = instanceOwner->GetInstance(getter_AddRefs(instance)); @@ -837,11 +840,9 @@ nsPluginHost::InstantiatePluginInstance(const char *aMimeType, nsIURI* aURL, return rv; } - if (instance) { - instanceOwner->CreateWidget(); - - // If we've got a native window, the let the plugin know about it. - instanceOwner->CallSetWindow(); + // Async init plugins will initiate their own widget creation. + if (!isAsyncInit && instance) { + CreateWidget(instanceOwner); } // At this point we consider instantiation to be successful. Do not return an error. @@ -3327,6 +3328,14 @@ nsresult nsPluginHost::NewPluginStreamListener(nsIURI* aURI, return NS_OK; } +void nsPluginHost::CreateWidget(nsPluginInstanceOwner* aOwner) +{ + aOwner->CreateWidget(); + + // If we've got a native window, the let the plugin know about it. + aOwner->CallSetWindow(); +} + NS_IMETHODIMP nsPluginHost::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData) @@ -3954,6 +3963,12 @@ PluginDestructionGuard::PluginDestructionGuard(nsNPAPIPluginInstance *aInstance) Init(); } +PluginDestructionGuard::PluginDestructionGuard(PluginAsyncSurrogate *aSurrogate) + : mInstance(static_cast(aSurrogate->GetNPP()->ndata)) +{ + InitAsync(); +} + PluginDestructionGuard::PluginDestructionGuard(NPP npp) : mInstance(npp ? static_cast(npp->ndata) : nullptr) { diff --git a/dom/plugins/base/nsPluginHost.h b/dom/plugins/base/nsPluginHost.h index 092b8a9033bf..8124dde36d07 100644 --- a/dom/plugins/base/nsPluginHost.h +++ b/dom/plugins/base/nsPluginHost.h @@ -30,6 +30,12 @@ #include "nsCRT.h" #include "mozilla/plugins/PluginTypes.h" +namespace mozilla { +namespace plugins { +class PluginAsyncSurrogate; +} // namespace mozilla +} // namespace plugins + class nsNPAPIPlugin; class nsIComponentManager; class nsIFile; @@ -203,6 +209,8 @@ public: nsNPAPIPluginInstance* aInstance, nsIStreamListener **aStreamListener); + void CreateWidget(nsPluginInstanceOwner* aOwner); + private: friend class nsPluginUnloadRunnable; @@ -333,11 +341,11 @@ private: static nsPluginHost* sInst; }; -class MOZ_STACK_CLASS PluginDestructionGuard : protected PRCList +class PluginDestructionGuard : protected PRCList { public: explicit PluginDestructionGuard(nsNPAPIPluginInstance *aInstance); - + explicit PluginDestructionGuard(mozilla::plugins::PluginAsyncSurrogate *aSurrogate); explicit PluginDestructionGuard(NPP npp); ~PluginDestructionGuard(); @@ -355,6 +363,18 @@ protected: PR_INSERT_BEFORE(this, &sListHead); } + void InitAsync() + { + NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread"); + + mDelayedDestroy = false; + + PR_INIT_CLIST(this); + // Instances with active surrogates must be inserted *after* sListHead so + // that they appear to be at the bottom of the stack + PR_INSERT_AFTER(this, &sListHead); + } + nsRefPtr mInstance; bool mDelayedDestroy; diff --git a/dom/plugins/base/nsPluginInstanceOwner.cpp b/dom/plugins/base/nsPluginInstanceOwner.cpp index 794fb6076e4e..1e6e3f233f30 100644 --- a/dom/plugins/base/nsPluginInstanceOwner.cpp +++ b/dom/plugins/base/nsPluginInstanceOwner.cpp @@ -1452,6 +1452,28 @@ void nsPluginInstanceOwner::ExitFullScreen(jobject view) { #endif +void +nsPluginInstanceOwner::NotifyHostAsyncInitFailed() +{ + nsCOMPtr content = do_QueryInterface(mContent); + content->StopPluginInstance(); +} + +void +nsPluginInstanceOwner::NotifyHostCreateWidget() +{ + mPluginHost->CreateWidget(this); +#ifdef XP_MACOSX + FixUpPluginWindow(ePluginPaintEnable); +#else + if (mPluginFrame) { + mPluginFrame->InvalidateFrame(); + } else { + CallSetWindow(); + } +#endif +} + nsresult nsPluginInstanceOwner::DispatchFocusToPlugin(nsIDOMEvent* aFocusEvent) { #ifdef MOZ_WIDGET_ANDROID @@ -3169,6 +3191,10 @@ nsPluginInstanceOwner::UpdateDocumentActiveState(bool aIsActive) NS_IMETHODIMP nsPluginInstanceOwner::CallSetWindow() { + if (!mWidgetCreationComplete) { + // No widget yet, we can't run this code + return NS_OK; + } if (mPluginFrame) { mPluginFrame->CallSetWindow(false); } else if (mInstance) { diff --git a/dom/plugins/base/nsPluginInstanceOwner.h b/dom/plugins/base/nsPluginInstanceOwner.h index acebe36ee40b..a3e99d790758 100644 --- a/dom/plugins/base/nsPluginInstanceOwner.h +++ b/dom/plugins/base/nsPluginInstanceOwner.h @@ -255,7 +255,10 @@ public: // Called from AndroidJNI when we removed the fullscreen view. static void ExitFullScreen(jobject view); #endif - + + void NotifyHostAsyncInitFailed(); + void NotifyHostCreateWidget(); + private: virtual ~nsPluginInstanceOwner(); diff --git a/dom/plugins/base/nsPluginStreamListenerPeer.cpp b/dom/plugins/base/nsPluginStreamListenerPeer.cpp index 3a72b31744a8..1a4eb8cc42ce 100644 --- a/dom/plugins/base/nsPluginStreamListenerPeer.cpp +++ b/dom/plugins/base/nsPluginStreamListenerPeer.cpp @@ -266,6 +266,7 @@ nsPluginStreamListenerPeer::nsPluginStreamListenerPeer() mHaveFiredOnStartRequest = false; mDataForwardToRequest = nullptr; + mUseLocalCache = false; mSeekable = false; mModified = 0; mStreamOffset = 0; @@ -533,7 +534,9 @@ nsPluginStreamListenerPeer::OnStartRequest(nsIRequest *request, // Set up the stream listener... rv = SetUpStreamListener(request, aURL); - if (NS_FAILED(rv)) return rv; + if (NS_FAILED(rv)) { + return rv; + } return rv; } @@ -1046,8 +1049,6 @@ nsresult nsPluginStreamListenerPeer::SetUpStreamListener(nsIRequest *request, mPStreamListener->SetStreamListenerPeer(this); - bool useLocalCache = false; - // get httpChannel to retrieve some info we need for nsIPluginStreamInfo setup nsCOMPtr channel = do_QueryInterface(request); nsCOMPtr httpChannel = do_QueryInterface(channel); @@ -1103,7 +1104,7 @@ nsresult nsPluginStreamListenerPeer::SetUpStreamListener(nsIRequest *request, nsAutoCString contentEncoding; if (NS_SUCCEEDED(httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Encoding"), contentEncoding))) { - useLocalCache = true; + mUseLocalCache = true; } else { // set seekability (seekable if the stream has a known length and if the // http server accepts byte ranges). @@ -1132,6 +1133,9 @@ nsresult nsPluginStreamListenerPeer::SetUpStreamListener(nsIRequest *request, } } + MOZ_ASSERT(!mRequest); + mRequest = request; + rv = mPStreamListener->OnStartBinding(this); mStartBinding = true; @@ -1139,23 +1143,37 @@ nsresult nsPluginStreamListenerPeer::SetUpStreamListener(nsIRequest *request, if (NS_FAILED(rv)) return rv; - mPStreamListener->GetStreamType(&mStreamType); + int32_t streamType = NP_NORMAL; + mPStreamListener->GetStreamType(&streamType); - if (!useLocalCache && mStreamType >= NP_ASFILE) { - // check it out if this is not a file channel. - nsCOMPtr fileChannel = do_QueryInterface(request); - if (!fileChannel) { - useLocalCache = true; - } - } - - if (useLocalCache) { - SetupPluginCacheFile(channel); + if (streamType != STREAM_TYPE_UNKNOWN) { + OnStreamTypeSet(streamType); } return NS_OK; } +void +nsPluginStreamListenerPeer::OnStreamTypeSet(const int32_t aStreamType) +{ + MOZ_ASSERT(mRequest); + mStreamType = aStreamType; + if (!mUseLocalCache && mStreamType >= NP_ASFILE) { + // check it out if this is not a file channel. + nsCOMPtr fileChannel = do_QueryInterface(mRequest); + if (!fileChannel) { + mUseLocalCache = true; + } + } + + if (mUseLocalCache) { + nsCOMPtr channel = do_QueryInterface(mRequest); + SetupPluginCacheFile(channel); + } +} + +const int32_t nsPluginStreamListenerPeer::STREAM_TYPE_UNKNOWN = UINT16_MAX; + nsresult nsPluginStreamListenerPeer::OnFileAvailable(nsIFile* aFile) { diff --git a/dom/plugins/base/nsPluginStreamListenerPeer.h b/dom/plugins/base/nsPluginStreamListenerPeer.h index c940329676d4..6a19562b7cd1 100644 --- a/dom/plugins/base/nsPluginStreamListenerPeer.h +++ b/dom/plugins/base/nsPluginStreamListenerPeer.h @@ -128,6 +128,11 @@ public: requestsCopy[i]->Resume(); } + // Called by nsNPAPIPluginStreamListener + void OnStreamTypeSet(const int32_t aStreamType); + + static const int32_t STREAM_TYPE_UNKNOWN; + private: nsresult SetUpStreamListener(nsIRequest* request, nsIURI* aURL); nsresult SetupPluginCacheFile(nsIChannel* channel); @@ -159,6 +164,8 @@ private: nsDataHashtable* mDataForwardToRequest; nsCString mContentType; + bool mUseLocalCache; + nsCOMPtr mRequest; bool mSeekable; uint32_t mModified; nsRefPtr mPluginInstance; diff --git a/dom/plugins/ipc/BrowserStreamChild.cpp b/dom/plugins/ipc/BrowserStreamChild.cpp index fecccf37204e..4a3ff033fab1 100644 --- a/dom/plugins/ipc/BrowserStreamChild.cpp +++ b/dom/plugins/ipc/BrowserStreamChild.cpp @@ -17,11 +17,7 @@ BrowserStreamChild::BrowserStreamChild(PluginInstanceChild* instance, const uint32_t& length, const uint32_t& lastmodified, StreamNotifyChild* notifyData, - const nsCString& headers, - const nsCString& mimeType, - const bool& seekable, - NPError* rv, - uint16_t* stype) + const nsCString& headers) : mInstance(instance) , mStreamStatus(kStreamOpen) , mDestroyPending(NOT_DESTROYED) @@ -34,9 +30,9 @@ BrowserStreamChild::BrowserStreamChild(PluginInstanceChild* instance, , mStreamNotify(notifyData) , mDeliveryTracker(MOZ_THIS_IN_INITIALIZER_LIST()) { - PLUGIN_LOG_DEBUG(("%s (%s, %i, %i, %p, %s, %s)", FULLFUNCTION, + PLUGIN_LOG_DEBUG(("%s (%s, %i, %i, %p, %s)", FULLFUNCTION, url.get(), length, lastmodified, (void*) notifyData, - headers.get(), mimeType.get())); + headers.get())); AssertPluginThread(); @@ -46,8 +42,10 @@ BrowserStreamChild::BrowserStreamChild(PluginInstanceChild* instance, mStream.end = length; mStream.lastmodified = lastmodified; mStream.headers = NullableStringGet(mHeaders); - if (notifyData) + if (notifyData) { mStream.notifyData = notifyData->mClosure; + notifyData->SetAssociatedStream(this); + } } NPError @@ -68,9 +66,6 @@ BrowserStreamChild::StreamConstructed( } else { mState = ALIVE; - - if (mStreamNotify) - mStreamNotify->SetAssociatedStream(this); } return rv; diff --git a/dom/plugins/ipc/BrowserStreamChild.h b/dom/plugins/ipc/BrowserStreamChild.h index d16b29b534c4..56a63943ca7d 100644 --- a/dom/plugins/ipc/BrowserStreamChild.h +++ b/dom/plugins/ipc/BrowserStreamChild.h @@ -23,11 +23,7 @@ public: const uint32_t& length, const uint32_t& lastmodified, StreamNotifyChild* notifyData, - const nsCString& headers, - const nsCString& mimeType, - const bool& seekable, - NPError* rv, - uint16_t* stype); + const nsCString& headers); virtual ~BrowserStreamChild(); virtual bool IsBrowserStream() MOZ_OVERRIDE { return true; } diff --git a/dom/plugins/ipc/BrowserStreamParent.cpp b/dom/plugins/ipc/BrowserStreamParent.cpp index 1dc411916b43..508833d88d3d 100644 --- a/dom/plugins/ipc/BrowserStreamParent.cpp +++ b/dom/plugins/ipc/BrowserStreamParent.cpp @@ -5,6 +5,7 @@ #include "BrowserStreamParent.h" +#include "PluginAsyncSurrogate.h" #include "PluginInstanceParent.h" #include "nsNPAPIPlugin.h" @@ -21,9 +22,15 @@ BrowserStreamParent::BrowserStreamParent(PluginInstanceParent* npp, NPStream* stream) : mNPP(npp) , mStream(stream) - , mState(ALIVE) + , mDeferredDestroyReason(NPRES_DONE) + , mState(INITIALIZING) { mStream->pdata = static_cast(this); + nsNPAPIStreamWrapper* wrapper = + reinterpret_cast(mStream->ndata); + if (wrapper) { + mStreamListener = wrapper->GetStreamListener(); + } } BrowserStreamParent::~BrowserStreamParent() @@ -36,6 +43,43 @@ BrowserStreamParent::ActorDestroy(ActorDestroyReason aWhy) // Implement me! Bug 1005159 } +bool +BrowserStreamParent::RecvAsyncNPP_NewStreamResult(const NPError& rv, + const uint16_t& stype) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + PluginAsyncSurrogate* surrogate = mNPP->GetAsyncSurrogate(); + MOZ_ASSERT(surrogate); + surrogate->AsyncCallArriving(); + nsRefPtr streamListener = mStreamListener.forget(); + if (mState == DEFERRING_DESTROY) { + // We've been asked to destroy ourselves before init was complete. + mState = DYING; + unused << SendNPP_DestroyStream(mDeferredDestroyReason); + return true; + } + + NPError error = rv; + if (error == NPERR_NO_ERROR) { + if (!streamListener) { + return false; + } + if (streamListener->SetStreamType(stype)) { + mState = ALIVE; + } else { + error = NPERR_GENERIC_ERROR; + } + } + + if (error != NPERR_NO_ERROR) { + // We need to clean up the stream + parent::_destroystream(mNPP->GetNPP(), mStream, NPRES_DONE); + unused << PBrowserStreamParent::Send__delete__(this); + } + + return true; +} + bool BrowserStreamParent::AnswerNPN_RequestRead(const IPCByteRanges& ranges, NPError* result) @@ -43,6 +87,11 @@ BrowserStreamParent::AnswerNPN_RequestRead(const IPCByteRanges& ranges, PLUGIN_LOG_DEBUG_FUNCTION; switch (mState) { + case INITIALIZING: + NS_ERROR("Requesting a read before initialization has completed"); + *result = NPERR_GENERIC_ERROR; + return false; + case ALIVE: break; @@ -95,9 +144,16 @@ BrowserStreamParent::RecvNPN_DestroyStream(const NPReason& reason) void BrowserStreamParent::NPP_DestroyStream(NPReason reason) { - NS_ASSERTION(ALIVE == mState, "NPP_DestroyStream called twice?"); - mState = DYING; - unused << SendNPP_DestroyStream(reason); + NS_ASSERTION(ALIVE == mState || INITIALIZING == mState, + "NPP_DestroyStream called twice?"); + bool stillInitializing = INITIALIZING == mState; + if (stillInitializing) { + mState = DEFERRING_DESTROY; + mDeferredDestroyReason = reason; + } else { + mState = DYING; + unused << SendNPP_DestroyStream(reason); + } } bool @@ -117,6 +173,9 @@ BrowserStreamParent::RecvStreamDestroyed() int32_t BrowserStreamParent::WriteReady() { + if (mState == INITIALIZING) { + return 0; + } return kSendDataChunk; } diff --git a/dom/plugins/ipc/BrowserStreamParent.h b/dom/plugins/ipc/BrowserStreamParent.h index 726cc8517cea..79cacbf5d2cc 100644 --- a/dom/plugins/ipc/BrowserStreamParent.h +++ b/dom/plugins/ipc/BrowserStreamParent.h @@ -8,6 +8,8 @@ #include "mozilla/plugins/PBrowserStreamParent.h" #include "mozilla/plugins/AStream.h" +#include "nsNPAPIPluginStreamListener.h" +#include "nsPluginStreamListenerPeer.h" namespace mozilla { namespace plugins { @@ -28,6 +30,10 @@ public: virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE; + virtual bool RecvAsyncNPP_NewStreamResult( + const NPError& rv, + const uint16_t& stype) MOZ_OVERRIDE; + virtual bool AnswerNPN_RequestRead(const IPCByteRanges& ranges, NPError* result) MOZ_OVERRIDE; @@ -41,14 +47,25 @@ public: void NPP_DestroyStream(NPReason reason); + void SetAlive() + { + if (mState == INITIALIZING) { + mState = ALIVE; + } + } + private: using PBrowserStreamParent::SendNPP_DestroyStream; PluginInstanceParent* mNPP; NPStream* mStream; nsCOMPtr mStreamPeer; + nsRefPtr mStreamListener; + NPReason mDeferredDestroyReason; enum { + INITIALIZING, + DEFERRING_DESTROY, ALIVE, DYING, DELETING diff --git a/dom/plugins/ipc/PBrowserStream.ipdl b/dom/plugins/ipc/PBrowserStream.ipdl index e28da3c3c493..8d927d60eedc 100644 --- a/dom/plugins/ipc/PBrowserStream.ipdl +++ b/dom/plugins/ipc/PBrowserStream.ipdl @@ -36,6 +36,7 @@ child: async __delete__(); parent: + async AsyncNPP_NewStreamResult(NPError rv, uint16_t stype); intr NPN_RequestRead(IPCByteRanges ranges) returns (NPError result); async NPN_DestroyStream(NPReason reason); diff --git a/dom/plugins/ipc/PPluginInstance.ipdl b/dom/plugins/ipc/PPluginInstance.ipdl index e289056b873d..659805e99981 100644 --- a/dom/plugins/ipc/PPluginInstance.ipdl +++ b/dom/plugins/ipc/PPluginInstance.ipdl @@ -207,21 +207,29 @@ parent: // them to use the plugin. sync NegotiatedCarbon(); + // Notifies the parent of its NPP_New result code. + async AsyncNPP_NewResult(NPError aResult); + both: async PPluginScriptableObject(); child: /* NPP_NewStream */ - intr PBrowserStream(nsCString url, - uint32_t length, - uint32_t lastmodified, - nullable PStreamNotify notifyData, - nsCString headers, - nsCString mimeType, - bool seekable) + async PBrowserStream(nsCString url, + uint32_t length, + uint32_t lastmodified, + nullable PStreamNotify notifyData, + nsCString headers); + + // Implements the legacy (synchronous) version of NPP_NewStream for when + // async plugin init is preffed off. + intr NPP_NewStream(PBrowserStream actor, nsCString mimeType, bool seekable) returns (NPError rv, uint16_t stype); + // Implements the async plugin init version of NPP_NewStream. + async AsyncNPP_NewStream(PBrowserStream actor, nsCString mimeType, bool seekable); + parent: /* NPN_NewStream */ intr PPluginStream(nsCString mimeType, diff --git a/dom/plugins/ipc/PPluginModule.ipdl b/dom/plugins/ipc/PPluginModule.ipdl index c54a0892ef2b..203986502590 100644 --- a/dom/plugins/ipc/PPluginModule.ipdl +++ b/dom/plugins/ipc/PPluginModule.ipdl @@ -54,12 +54,21 @@ child: intr NP_Initialize(PluginSettings settings) returns (NPError rv); - intr PPluginInstance(nsCString aMimeType, - uint16_t aMode, - nsCString[] aNames, - nsCString[] aValues) + async AsyncNP_Initialize(PluginSettings settings); + + async PPluginInstance(nsCString aMimeType, + uint16_t aMode, + nsCString[] aNames, + nsCString[] aValues); + + // Implements the synchronous version of NPP_New for when async plugin init + // is preffed off. + intr SyncNPP_New(PPluginInstance aActor) returns (NPError rv); + // Implements the async plugin init version of NPP_New. + async AsyncNPP_New(PPluginInstance aActor); + intr NP_Shutdown() returns (NPError rv); @@ -89,12 +98,15 @@ child: async StartProfiler(uint32_t aEntries, double aInterval, nsCString[] aFeatures, nsCString[] aThreadNameFilters); async StopProfiler(); + intr GetProfile() returns (nsCString aProfile); async SettingChanged(PluginSettings settings); parent: + async NP_InitializeResult(NPError aError); + /** * This message is only used on X11 platforms. * diff --git a/dom/plugins/ipc/PluginAsyncSurrogate.cpp b/dom/plugins/ipc/PluginAsyncSurrogate.cpp new file mode 100644 index 000000000000..9f7ab4bbe26f --- /dev/null +++ b/dom/plugins/ipc/PluginAsyncSurrogate.cpp @@ -0,0 +1,882 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PluginAsyncSurrogate.h" + +#include "base/message_loop.h" +#include "base/message_pump_default.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/plugins/PluginInstanceParent.h" +#include "mozilla/plugins/PluginModuleParent.h" +#include "mozilla/plugins/PluginScriptableObjectParent.h" +#include "mozilla/Telemetry.h" +#include "nsJSNPRuntime.h" +#include "nsNPAPIPlugin.h" +#include "nsNPAPIPluginInstance.h" +#include "nsNPAPIPluginStreamListener.h" +#include "nsPluginInstanceOwner.h" +#include "nsPluginStreamListenerPeer.h" +#include "npruntime.h" +#include "nsThreadUtils.h" +#include "PluginMessageUtils.h" + +namespace mozilla { +namespace plugins { + +AsyncNPObject::AsyncNPObject(PluginAsyncSurrogate* aSurrogate) + : NPObject() + , mSurrogate(aSurrogate) + , mRealObject(nullptr) +{ +} + +AsyncNPObject::~AsyncNPObject() +{ + if (mRealObject) { + parent::_releaseobject(mRealObject); + mRealObject = nullptr; + } +} + +NPObject* +AsyncNPObject::GetRealObject() +{ + if (mRealObject) { + return mRealObject; + } + PluginInstanceParent* instance = PluginInstanceParent::Cast(mSurrogate->GetNPP()); + if (!instance) { + return nullptr; + } + NPError err = instance->NPP_GetValue(NPPVpluginScriptableNPObject, + &mRealObject); + if (err != NPERR_NO_ERROR) { + return nullptr; + } + return mRealObject; +} + +class MOZ_STACK_CLASS RecursionGuard +{ +public: + RecursionGuard() + : mIsRecursive(sHasEntered) + { + if (!mIsRecursive) { + sHasEntered = true; + } + } + + ~RecursionGuard() + { + if (!mIsRecursive) { + sHasEntered = false; + } + } + + inline bool + IsRecursive() + { + return mIsRecursive; + } + +private: + bool mIsRecursive; + static bool sHasEntered; +}; + +bool RecursionGuard::sHasEntered = false; + +PluginAsyncSurrogate::PluginAsyncSurrogate(PluginModuleParent* aParent) + : mParent(aParent) + , mInstance(nullptr) + , mMode(0) + , mWindow(nullptr) + , mAcceptCalls(false) + , mInstantiated(false) + , mAsyncSetWindow(false) + , mInitCancelled(false) + , mAsyncCallsInFlight(0) +{ + MOZ_ASSERT(aParent); +} + +PluginAsyncSurrogate::~PluginAsyncSurrogate() +{ +} + +bool +PluginAsyncSurrogate::Init(NPMIMEType aPluginType, NPP aInstance, uint16_t aMode, + int16_t aArgc, char* aArgn[], char* aArgv[]) +{ + mMimeType = aPluginType; + mInstance = aInstance; + mMode = aMode; + for (int i = 0; i < aArgc; ++i) { + mNames.AppendElement(NullableString(aArgn[i])); + mValues.AppendElement(NullableString(aArgv[i])); + } + return true; +} + +/* static */ bool +PluginAsyncSurrogate::Create(PluginModuleParent* aParent, NPMIMEType aPluginType, + NPP aInstance, uint16_t aMode, int16_t aArgc, + char* aArgn[], char* aArgv[]) +{ + nsRefPtr surrogate(new PluginAsyncSurrogate(aParent)); + if (!surrogate->Init(aPluginType, aInstance, aMode, aArgc, aArgn, aArgv)) { + return false; + } + PluginAsyncSurrogate* rawSurrogate = nullptr; + surrogate.forget(&rawSurrogate); + aInstance->pdata = static_cast(rawSurrogate); + return true; +} + +/* static */ PluginAsyncSurrogate* +PluginAsyncSurrogate::Cast(NPP aInstance) +{ + MOZ_ASSERT(aInstance); + PluginDataResolver* resolver = + reinterpret_cast(aInstance->pdata); + if (!resolver) { + return nullptr; + } + return resolver->GetAsyncSurrogate(); +} + +nsresult +PluginAsyncSurrogate::NPP_New(NPError* aError) +{ + nsresult rv = mParent->NPP_NewInternal(mMimeType.BeginWriting(), mInstance, + mMode, mNames, mValues, nullptr, + aError); + if (NS_FAILED(rv)) { + return rv; + } + return NS_OK; +} + +void +PluginAsyncSurrogate::NP_GetEntryPoints(NPPluginFuncs* aFuncs) +{ + aFuncs->version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR; + aFuncs->destroy = &NPP_Destroy; + aFuncs->getvalue = &NPP_GetValue; + aFuncs->setvalue = &NPP_SetValue; + aFuncs->newstream = &NPP_NewStream; + aFuncs->setwindow = &NPP_SetWindow; + aFuncs->writeready = &NPP_WriteReady; + aFuncs->event = &NPP_HandleEvent; + // We need to set these so that content code doesn't make assumptions + // about these operations not being supported + aFuncs->write = &PluginModuleParent::NPP_Write; + aFuncs->asfile = &PluginModuleParent::NPP_StreamAsFile; + aFuncs->destroystream = &PluginModuleParent::NPP_DestroyStream; +} + +NPError +PluginAsyncSurrogate::NPP_Destroy(NPSavedData** aSave) +{ + if (!WaitForInit()) { + return NPERR_GENERIC_ERROR; + } + return PluginModuleParent::NPP_Destroy(mInstance, aSave); +} + +NPError +PluginAsyncSurrogate::NPP_GetValue(NPPVariable aVariable, void* aRetval) +{ + if (aVariable != NPPVpluginScriptableNPObject) { + if (!WaitForInit()) { + return NPERR_GENERIC_ERROR; + } + PluginInstanceParent* instance = PluginInstanceParent::Cast(mInstance); + MOZ_ASSERT(instance); + return instance->NPP_GetValue(aVariable, aRetval); + } + + NPObject* npobject = parent::_createobject(mInstance, + const_cast(GetClass())); + MOZ_ASSERT(npobject); + MOZ_ASSERT(npobject->_class == GetClass()); + MOZ_ASSERT(npobject->referenceCount == 1); + *(NPObject**)aRetval = npobject; + return npobject ? NPERR_NO_ERROR : NPERR_GENERIC_ERROR; +} + +NPError +PluginAsyncSurrogate::NPP_SetValue(NPNVariable aVariable, void* aValue) +{ + if (!WaitForInit()) { + return NPERR_GENERIC_ERROR; + } + return PluginModuleParent::NPP_SetValue(mInstance, aVariable, aValue); +} + +NPError +PluginAsyncSurrogate::NPP_NewStream(NPMIMEType aType, NPStream* aStream, + NPBool aSeekable, uint16_t* aStype) +{ + mPendingNewStreamCalls.AppendElement(PendingNewStreamCall(aType, aStream, + aSeekable)); + if (aStype) { + *aStype = nsPluginStreamListenerPeer::STREAM_TYPE_UNKNOWN; + } + return NPERR_NO_ERROR; +} + +NPError +PluginAsyncSurrogate::NPP_SetWindow(NPWindow* aWindow) +{ + mWindow = aWindow; + mAsyncSetWindow = false; + return NPERR_NO_ERROR; +} + +nsresult +PluginAsyncSurrogate::AsyncSetWindow(NPWindow* aWindow) +{ + mWindow = aWindow; + mAsyncSetWindow = true; + return NS_OK; +} + +void +PluginAsyncSurrogate::NPP_Print(NPPrint* aPrintInfo) +{ + // Do nothing, we've got nothing to print right now +} + +int16_t +PluginAsyncSurrogate::NPP_HandleEvent(void* event) +{ + // Drop the event -- the plugin isn't around to handle it + return false; +} + +int32_t +PluginAsyncSurrogate::NPP_WriteReady(NPStream* aStream) +{ + // We'll tell the browser to retry in a bit. Eventually NPP_WriteReady + // will resolve to the plugin's NPP_WriteReady and this should all just work. + return 0; +} + +/* static */ NPError +PluginAsyncSurrogate::NPP_Destroy(NPP aInstance, NPSavedData** aSave) +{ + PluginAsyncSurrogate* rawSurrogate = Cast(aInstance); + MOZ_ASSERT(rawSurrogate); + PluginModuleParent* module = rawSurrogate->GetParent(); + if (module && !module->IsInitialized()) { + // Take ownership of pdata's surrogate since we're going to release it + nsRefPtr surrogate(dont_AddRef(rawSurrogate)); + aInstance->pdata = nullptr; + // We haven't actually called NPP_New yet, so we should remove the + // surrogate for this instance. + bool removeOk = module->RemovePendingSurrogate(surrogate); + MOZ_ASSERT(removeOk); + if (!removeOk) { + return NPERR_GENERIC_ERROR; + } + surrogate->mInitCancelled = true; + return NPERR_NO_ERROR; + } + return rawSurrogate->NPP_Destroy(aSave); +} + +/* static */ NPError +PluginAsyncSurrogate::NPP_GetValue(NPP aInstance, NPPVariable aVariable, + void* aRetval) +{ + PluginAsyncSurrogate* surrogate = Cast(aInstance); + MOZ_ASSERT(surrogate); + return surrogate->NPP_GetValue(aVariable, aRetval); +} + +/* static */ NPError +PluginAsyncSurrogate::NPP_SetValue(NPP aInstance, NPNVariable aVariable, + void* aValue) +{ + PluginAsyncSurrogate* surrogate = Cast(aInstance); + MOZ_ASSERT(surrogate); + return surrogate->NPP_SetValue(aVariable, aValue); +} + +/* static */ NPError +PluginAsyncSurrogate::NPP_NewStream(NPP aInstance, NPMIMEType aType, + NPStream* aStream, NPBool aSeekable, + uint16_t* aStype) +{ + PluginAsyncSurrogate* surrogate = Cast(aInstance); + MOZ_ASSERT(surrogate); + return surrogate->NPP_NewStream(aType, aStream, aSeekable, aStype); +} + +/* static */ NPError +PluginAsyncSurrogate::NPP_SetWindow(NPP aInstance, NPWindow* aWindow) +{ + PluginAsyncSurrogate* surrogate = Cast(aInstance); + MOZ_ASSERT(surrogate); + return surrogate->NPP_SetWindow(aWindow); +} + +/* static */ void +PluginAsyncSurrogate::NPP_Print(NPP aInstance, NPPrint* aPrintInfo) +{ + PluginAsyncSurrogate* surrogate = Cast(aInstance); + MOZ_ASSERT(surrogate); + surrogate->NPP_Print(aPrintInfo); +} + +/* static */ int16_t +PluginAsyncSurrogate::NPP_HandleEvent(NPP aInstance, void* aEvent) +{ + PluginAsyncSurrogate* surrogate = Cast(aInstance); + MOZ_ASSERT(surrogate); + return surrogate->NPP_HandleEvent(aEvent); +} + +/* static */ int32_t +PluginAsyncSurrogate::NPP_WriteReady(NPP aInstance, NPStream* aStream) +{ + PluginAsyncSurrogate* surrogate = Cast(aInstance); + MOZ_ASSERT(surrogate); + return surrogate->NPP_WriteReady(aStream); +} + +PluginAsyncSurrogate::PendingNewStreamCall::PendingNewStreamCall( + NPMIMEType aType, NPStream* aStream, NPBool aSeekable) + : mType(NullableString(aType)) + , mStream(aStream) + , mSeekable(aSeekable) +{ +} + +/* static */ bool +PluginAsyncSurrogate::SetStreamType(NPStream* aStream, uint16_t aStreamType) +{ + nsNPAPIStreamWrapper* wrapper = + reinterpret_cast(aStream->ndata); + if (!wrapper) { + return false; + } + nsNPAPIPluginStreamListener* streamListener = wrapper->GetStreamListener(); + if (!streamListener) { + return false; + } + return streamListener->SetStreamType(aStreamType); +} + +void +PluginAsyncSurrogate::OnInstanceCreated(PluginInstanceParent* aInstance) +{ + for (PRUint32 i = 0, len = mPendingNewStreamCalls.Length(); i < len; ++i) { + PendingNewStreamCall& curPendingCall = mPendingNewStreamCalls[i]; + uint16_t streamType = NP_NORMAL; + NPError curError = aInstance->NPP_NewStream( + const_cast(NullableStringGet(curPendingCall.mType)), + curPendingCall.mStream, curPendingCall.mSeekable, + &streamType); + if (curError != NPERR_NO_ERROR) { + // If we failed here then the send failed and we need to clean up + parent::_destroystream(mInstance, curPendingCall.mStream, NPRES_DONE); + } + } + mPendingNewStreamCalls.Clear(); + mInstantiated = true; +} + +/** + * During asynchronous initialization it might be necessary to wait for the + * plugin to complete its initialization. This typically occurs when the result + * of a plugin call depends on the plugin being fully instantiated. For example, + * if some JS calls into the plugin, the call must be executed synchronously to + * preserve correctness. + * + * This function works by pumping the plugin's IPC channel for events until + * initialization has completed. + */ +bool +PluginAsyncSurrogate::WaitForInit() +{ + if (mInitCancelled) { + return false; + } + if (mAcceptCalls) { + return true; + } + Telemetry::AutoTimer + timer(mParent->GetHistogramKey()); + bool result = false; + MOZ_ASSERT(mParent); + if (mParent->IsChrome()) { + PluginProcessParent* process = static_cast(mParent)->Process(); + MOZ_ASSERT(process); + process->SetCallRunnableImmediately(true); + if (!process->WaitUntilConnected()) { + return false; + } + } + if (!mParent->WaitForIPCConnection()) { + return false; + } + if (!mParent->IsChrome()) { + // For e10s content processes, we need to spin the content channel until the + // protocol bridging has occurred. + dom::ContentChild* cp = dom::ContentChild::GetSingleton(); + mozilla::ipc::MessageChannel* contentChannel = cp->GetIPCChannel(); + MOZ_ASSERT(contentChannel); + while (!mParent->mNPInitialized) { + result = contentChannel->WaitForIncomingMessage(); + if (!result) { + return result; + } + } + } + mozilla::ipc::MessageChannel* channel = mParent->GetIPCChannel(); + MOZ_ASSERT(channel); + while (!mAcceptCalls) { + result = channel->WaitForIncomingMessage(); + if (!result) { + break; + } + } + return result; +} + +void +PluginAsyncSurrogate::AsyncCallDeparting() +{ + ++mAsyncCallsInFlight; + if (!mPluginDestructionGuard) { + mPluginDestructionGuard = MakeUnique(this); + } +} + +void +PluginAsyncSurrogate::AsyncCallArriving() +{ + MOZ_ASSERT(mAsyncCallsInFlight > 0); + if (--mAsyncCallsInFlight == 0) { + mPluginDestructionGuard.reset(nullptr); + } +} + +void +PluginAsyncSurrogate::NotifyAsyncInitFailed() +{ + // Clean up any pending NewStream requests + for (uint32_t i = 0, len = mPendingNewStreamCalls.Length(); i < len; ++i) { + PendingNewStreamCall& curPendingCall = mPendingNewStreamCalls[i]; + parent::_destroystream(mInstance, curPendingCall.mStream, NPRES_DONE); + } + mPendingNewStreamCalls.Clear(); + + nsNPAPIPluginInstance* inst = + static_cast(mInstance->ndata); + if (!inst) { + return; + } + nsPluginInstanceOwner* owner = inst->GetOwner(); + if (!owner) { + return; + } + owner->NotifyHostAsyncInitFailed(); +} + +// static +NPObject* +PluginAsyncSurrogate::ScriptableAllocate(NPP aInstance, NPClass* aClass) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + if (aClass != GetClass()) { + NS_ERROR("Huh?! Wrong class!"); + return nullptr; + } + + return new AsyncNPObject(Cast(aInstance)); +} + +// static +void +PluginAsyncSurrogate::ScriptableInvalidate(NPObject* aObject) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return; + } + + AsyncNPObject* object = static_cast(aObject); + if (!object->mSurrogate->WaitForInit()) { + return; + } + NPObject* realObject = object->GetRealObject(); + if (!realObject) { + return; + } + realObject->_class->invalidate(realObject); +} + +// static +void +PluginAsyncSurrogate::ScriptableDeallocate(NPObject* aObject) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return; + } + + AsyncNPObject* object = static_cast(aObject); + delete object; +} + +// static +bool +PluginAsyncSurrogate::ScriptableHasMethod(NPObject* aObject, + NPIdentifier aName) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + RecursionGuard guard; + if (guard.IsRecursive()) { + return false; + } + + AsyncNPObject* object = static_cast(aObject); + MOZ_ASSERT(object); + bool checkPluginObject = !object->mSurrogate->mInstantiated && + !object->mSurrogate->mAcceptCalls; + + if (!object->mSurrogate->WaitForInit()) { + return false; + } + NPObject* realObject = object->GetRealObject(); + if (!realObject) { + return false; + } + bool result = realObject->_class->hasMethod(realObject, aName); + if (!result && checkPluginObject) { + // We may be calling into this object because properties in the WebIDL + // object hadn't been set yet. Now that we're further along in + // initialization, we should try again. + const NPNetscapeFuncs* npn = object->mSurrogate->mParent->GetNetscapeFuncs(); + NPObject* pluginObject = nullptr; + NPError nperror = npn->getvalue(object->mSurrogate->mInstance, + NPNVPluginElementNPObject, + (void*)&pluginObject); + if (nperror == NPERR_NO_ERROR) { + NPPAutoPusher nppPusher(object->mSurrogate->mInstance); + result = pluginObject->_class->hasMethod(pluginObject, aName); + npn->releaseobject(pluginObject); + NPUTF8* idstr = npn->utf8fromidentifier(aName); + npn->memfree(idstr); + } + } + return result; +} + +bool +PluginAsyncSurrogate::GetPropertyHelper(NPObject* aObject, NPIdentifier aName, + bool* aHasProperty, bool* aHasMethod, + NPVariant* aResult) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + + if (!aObject) { + return false; + } + + RecursionGuard guard; + if (guard.IsRecursive()) { + return false; + } + + WaitForInit(); + + AsyncNPObject* object = static_cast(aObject); + NPObject* realObject = object->GetRealObject(); + if (realObject->_class != PluginScriptableObjectParent::GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + PluginScriptableObjectParent* actor = + static_cast(realObject)->parent; + bool success = actor->GetPropertyHelper(aName, aHasProperty, aHasMethod, aResult); + if (!success) { + const NPNetscapeFuncs* npn = mParent->GetNetscapeFuncs(); + NPObject* pluginObject = nullptr; + NPError nperror = npn->getvalue(mInstance, NPNVPluginElementNPObject, + (void*)&pluginObject); + if (nperror == NPERR_NO_ERROR) { + NPPAutoPusher nppPusher(mInstance); + bool hasProperty = nsJSObjWrapper::HasOwnProperty(pluginObject, aName); + NPUTF8* idstr = npn->utf8fromidentifier(aName); + npn->memfree(idstr); + bool hasMethod = false; + if (hasProperty) { + hasMethod = pluginObject->_class->hasMethod(pluginObject, aName); + success = pluginObject->_class->getProperty(pluginObject, aName, aResult); + idstr = npn->utf8fromidentifier(aName); + npn->memfree(idstr); + } + *aHasProperty = hasProperty; + *aHasMethod = hasMethod; + npn->releaseobject(pluginObject); + } + } + return success; +} + +// static +bool +PluginAsyncSurrogate::ScriptableInvoke(NPObject* aObject, + NPIdentifier aName, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + AsyncNPObject* object = static_cast(aObject); + if (!object->mSurrogate->WaitForInit()) { + return false; + } + NPObject* realObject = object->GetRealObject(); + if (!realObject) { + return false; + } + return realObject->_class->invoke(realObject, aName, aArgs, aArgCount, aResult); +} + +// static +bool +PluginAsyncSurrogate::ScriptableInvokeDefault(NPObject* aObject, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + AsyncNPObject* object = static_cast(aObject); + if (!object->mSurrogate->WaitForInit()) { + return false; + } + NPObject* realObject = object->GetRealObject(); + if (!realObject) { + return false; + } + return realObject->_class->invokeDefault(realObject, aArgs, aArgCount, aResult); +} + +// static +bool +PluginAsyncSurrogate::ScriptableHasProperty(NPObject* aObject, + NPIdentifier aName) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + RecursionGuard guard; + if (guard.IsRecursive()) { + return false; + } + + AsyncNPObject* object = static_cast(aObject); + MOZ_ASSERT(object); + bool checkPluginObject = !object->mSurrogate->mInstantiated && + !object->mSurrogate->mAcceptCalls; + + if (!object->mSurrogate->WaitForInit()) { + return false; + } + NPObject* realObject = object->GetRealObject(); + if (!realObject) { + return false; + } + bool result = realObject->_class->hasProperty(realObject, aName); + const NPNetscapeFuncs* npn = object->mSurrogate->mParent->GetNetscapeFuncs(); + NPUTF8* idstr = npn->utf8fromidentifier(aName); + npn->memfree(idstr); + if (!result && checkPluginObject) { + // We may be calling into this object because properties in the WebIDL + // object hadn't been set yet. Now that we're further along in + // initialization, we should try again. + NPObject* pluginObject = nullptr; + NPError nperror = npn->getvalue(object->mSurrogate->mInstance, + NPNVPluginElementNPObject, + (void*)&pluginObject); + if (nperror == NPERR_NO_ERROR) { + NPPAutoPusher nppPusher(object->mSurrogate->mInstance); + result = nsJSObjWrapper::HasOwnProperty(pluginObject, aName); + npn->releaseobject(pluginObject); + idstr = npn->utf8fromidentifier(aName); + npn->memfree(idstr); + } + } + return result; +} + +// static +bool +PluginAsyncSurrogate::ScriptableGetProperty(NPObject* aObject, + NPIdentifier aName, + NPVariant* aResult) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + // See GetPropertyHelper below. + NS_NOTREACHED("Shouldn't ever call this directly!"); + return false; +} + +// static +bool +PluginAsyncSurrogate::ScriptableSetProperty(NPObject* aObject, + NPIdentifier aName, + const NPVariant* aValue) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + AsyncNPObject* object = static_cast(aObject); + if (!object->mSurrogate->WaitForInit()) { + return false; + } + NPObject* realObject = object->GetRealObject(); + if (!realObject) { + return false; + } + return realObject->_class->setProperty(realObject, aName, aValue); +} + +// static +bool +PluginAsyncSurrogate::ScriptableRemoveProperty(NPObject* aObject, + NPIdentifier aName) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + AsyncNPObject* object = static_cast(aObject); + if (!object->mSurrogate->WaitForInit()) { + return false; + } + NPObject* realObject = object->GetRealObject(); + if (!realObject) { + return false; + } + return realObject->_class->removeProperty(realObject, aName); +} + +// static +bool +PluginAsyncSurrogate::ScriptableEnumerate(NPObject* aObject, + NPIdentifier** aIdentifiers, + uint32_t* aCount) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + AsyncNPObject* object = static_cast(aObject); + if (!object->mSurrogate->WaitForInit()) { + return false; + } + NPObject* realObject = object->GetRealObject(); + if (!realObject) { + return false; + } + return realObject->_class->enumerate(realObject, aIdentifiers, aCount); +} + +// static +bool +PluginAsyncSurrogate::ScriptableConstruct(NPObject* aObject, + const NPVariant* aArgs, + uint32_t aArgCount, + NPVariant* aResult) +{ + PLUGIN_LOG_DEBUG_FUNCTION; + if (aObject->_class != GetClass()) { + NS_ERROR("Don't know what kind of object this is!"); + return false; + } + + AsyncNPObject* object = static_cast(aObject); + if (!object->mSurrogate->WaitForInit()) { + return false; + } + NPObject* realObject = object->GetRealObject(); + if (!realObject) { + return false; + } + return realObject->_class->construct(realObject, aArgs, aArgCount, aResult); +} + +const NPClass PluginAsyncSurrogate::sNPClass = { + NP_CLASS_STRUCT_VERSION, + PluginAsyncSurrogate::ScriptableAllocate, + PluginAsyncSurrogate::ScriptableDeallocate, + PluginAsyncSurrogate::ScriptableInvalidate, + PluginAsyncSurrogate::ScriptableHasMethod, + PluginAsyncSurrogate::ScriptableInvoke, + PluginAsyncSurrogate::ScriptableInvokeDefault, + PluginAsyncSurrogate::ScriptableHasProperty, + PluginAsyncSurrogate::ScriptableGetProperty, + PluginAsyncSurrogate::ScriptableSetProperty, + PluginAsyncSurrogate::ScriptableRemoveProperty, + PluginAsyncSurrogate::ScriptableEnumerate, + PluginAsyncSurrogate::ScriptableConstruct +}; + +PushSurrogateAcceptCalls::PushSurrogateAcceptCalls(PluginInstanceParent* aInstance) + : mSurrogate(nullptr) + , mPrevAcceptCallsState(false) +{ + MOZ_ASSERT(aInstance); + mSurrogate = aInstance->GetAsyncSurrogate(); + if (mSurrogate) { + mPrevAcceptCallsState = mSurrogate->SetAcceptingCalls(true); + } +} + +PushSurrogateAcceptCalls::~PushSurrogateAcceptCalls() +{ + if (mSurrogate) { + mSurrogate->SetAcceptingCalls(mPrevAcceptCallsState); + } +} + +} // namespace plugins +} // namespace mozilla diff --git a/dom/plugins/ipc/PluginAsyncSurrogate.h b/dom/plugins/ipc/PluginAsyncSurrogate.h new file mode 100644 index 000000000000..2734183deee0 --- /dev/null +++ b/dom/plugins/ipc/PluginAsyncSurrogate.h @@ -0,0 +1,179 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef dom_plugins_ipc_PluginAsyncSurrogate_h +#define dom_plugins_ipc_PluginAsyncSurrogate_h + +#include "mozilla/UniquePtr.h" +#include "npapi.h" +#include "npfunctions.h" +#include "npruntime.h" +#include "nsAutoPtr.h" +#include "nsISupportsImpl.h" +#include "nsPluginHost.h" +#include "nsString.h" +#include "nsTArray.h" +#include "PluginDataResolver.h" + +namespace mozilla { +namespace plugins { + +class PluginInstanceParent; +class PluginModuleParent; + +class PluginAsyncSurrogate : public PluginDataResolver +{ +public: + NS_INLINE_DECL_REFCOUNTING(PluginAsyncSurrogate) + + bool Init(NPMIMEType aPluginType, NPP aInstance, uint16_t aMode, + int16_t aArgc, char* aArgn[], char* aArgv[]); + nsresult NPP_New(NPError* aError); + NPError NPP_Destroy(NPSavedData** aSave); + NPError NPP_GetValue(NPPVariable aVariable, void* aRetval); + NPError NPP_SetValue(NPNVariable aVariable, void* aValue); + NPError NPP_NewStream(NPMIMEType aType, NPStream* aStream, NPBool aSeekable, + uint16_t* aStype); + NPError NPP_SetWindow(NPWindow* aWindow); + nsresult AsyncSetWindow(NPWindow* aWindow); + void NPP_Print(NPPrint* aPrintInfo); + int16_t NPP_HandleEvent(void* aEvent); + int32_t NPP_WriteReady(NPStream* aStream); + void OnInstanceCreated(PluginInstanceParent* aInstance); + static bool Create(PluginModuleParent* aParent, NPMIMEType aPluginType, + NPP aInstance, uint16_t aMode, int16_t aArgc, + char* aArgn[], char* aArgv[]); + static const NPClass* GetClass() { return &sNPClass; } + static void NP_GetEntryPoints(NPPluginFuncs* aFuncs); + static PluginAsyncSurrogate* Cast(NPP aInstance); + + virtual PluginAsyncSurrogate* + GetAsyncSurrogate() { return this; } + + virtual PluginInstanceParent* + GetInstance() { return nullptr; } + + NPP GetNPP() { return mInstance; } + + bool GetPropertyHelper(NPObject* aObject, NPIdentifier aName, + bool* aHasProperty, bool* aHasMethod, + NPVariant* aResult); + + PluginModuleParent* + GetParent() { return mParent; } + + bool SetAcceptingCalls(bool aAccept) + { + bool prevState = mAcceptCalls; + if (mInstantiated) { + aAccept = true; + } + mAcceptCalls = aAccept; + return prevState; + } + + void AsyncCallDeparting(); + void AsyncCallArriving(); + + void NotifyAsyncInitFailed(); + +private: + explicit PluginAsyncSurrogate(PluginModuleParent* aParent); + virtual ~PluginAsyncSurrogate(); + + bool WaitForInit(); + + static bool SetStreamType(NPStream* aStream, uint16_t aStreamType); + + static NPError NPP_Destroy(NPP aInstance, NPSavedData** aSave); + static NPError NPP_GetValue(NPP aInstance, NPPVariable aVariable, void* aRetval); + static NPError NPP_SetValue(NPP aInstance, NPNVariable aVariable, void* aValue); + static NPError NPP_NewStream(NPP aInstance, NPMIMEType aType, NPStream* aStream, + NPBool aSeekable, uint16_t* aStype); + static NPError NPP_SetWindow(NPP aInstance, NPWindow* aWindow); + static void NPP_Print(NPP aInstance, NPPrint* aPrintInfo); + static int16_t NPP_HandleEvent(NPP aInstance, void* aEvent); + static int32_t NPP_WriteReady(NPP aInstance, NPStream* aStream); + + static NPObject* ScriptableAllocate(NPP aInstance, NPClass* aClass); + static void ScriptableInvalidate(NPObject* aObject); + static void ScriptableDeallocate(NPObject* aObject); + static bool ScriptableHasMethod(NPObject* aObject, NPIdentifier aName); + static bool ScriptableInvoke(NPObject* aObject, NPIdentifier aName, + const NPVariant* aArgs, uint32_t aArgCount, + NPVariant* aResult); + static bool ScriptableInvokeDefault(NPObject* aObject, const NPVariant* aArgs, + uint32_t aArgCount, NPVariant* aResult); + static bool ScriptableHasProperty(NPObject* aObject, NPIdentifier aName); + static bool ScriptableGetProperty(NPObject* aObject, NPIdentifier aName, + NPVariant* aResult); + static bool ScriptableSetProperty(NPObject* aObject, NPIdentifier aName, + const NPVariant* aValue); + static bool ScriptableRemoveProperty(NPObject* aObject, NPIdentifier aName); + static bool ScriptableEnumerate(NPObject* aObject, NPIdentifier** aIdentifiers, + uint32_t* aCount); + static bool ScriptableConstruct(NPObject* aObject, const NPVariant* aArgs, + uint32_t aArgCount, NPVariant* aResult); + +private: + struct PendingNewStreamCall + { + PendingNewStreamCall(NPMIMEType aType, NPStream* aStream, NPBool aSeekable); + ~PendingNewStreamCall() {} + nsCString mType; + NPStream* mStream; + NPBool mSeekable; + }; + +private: + PluginModuleParent* mParent; + // These values are used to construct the plugin instance + nsCString mMimeType; + NPP mInstance; + uint16_t mMode; + InfallibleTArray mNames; + InfallibleTArray mValues; + // This is safe to store as a pointer because the spec says it will remain + // valid until destruction or a subsequent NPP_SetWindow call. + NPWindow* mWindow; + nsTArray mPendingNewStreamCalls; + UniquePtr mPluginDestructionGuard; + + bool mAcceptCalls; + bool mInstantiated; + bool mAsyncSetWindow; + bool mInitCancelled; + int32_t mAsyncCallsInFlight; + + static const NPClass sNPClass; +}; + +struct AsyncNPObject : NPObject +{ + explicit AsyncNPObject(PluginAsyncSurrogate* aSurrogate); + ~AsyncNPObject(); + + NPObject* GetRealObject(); + + nsRefPtr mSurrogate; + NPObject* mRealObject; +}; + +class MOZ_STACK_CLASS PushSurrogateAcceptCalls +{ +public: + explicit PushSurrogateAcceptCalls(PluginInstanceParent* aInstance); + ~PushSurrogateAcceptCalls(); + +private: + PluginAsyncSurrogate* mSurrogate; + bool mPrevAcceptCallsState; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // dom_plugins_ipc_PluginAsyncSurrogate_h diff --git a/dom/plugins/ipc/PluginDataResolver.h b/dom/plugins/ipc/PluginDataResolver.h new file mode 100644 index 000000000000..8371c4df7456 --- /dev/null +++ b/dom/plugins/ipc/PluginDataResolver.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef dom_plugins_ipc_PluginDataResolver_h +#define dom_plugins_ipc_PluginDataResolver_h + +namespace mozilla { +namespace plugins { + +class PluginAsyncSurrogate; +class PluginInstanceParent; + +class PluginDataResolver +{ +public: + virtual PluginAsyncSurrogate* GetAsyncSurrogate() = 0; + virtual PluginInstanceParent* GetInstance() = 0; +}; + +} // namespace plugins +} // namespace mozilla + +#endif // dom_plugins_ipc_PluginDataResolver_h diff --git a/dom/plugins/ipc/PluginInstanceChild.cpp b/dom/plugins/ipc/PluginInstanceChild.cpp index 4ba8c5448fc3..1adc1a1935fa 100644 --- a/dom/plugins/ipc/PluginInstanceChild.cpp +++ b/dom/plugins/ipc/PluginInstanceChild.cpp @@ -119,8 +119,16 @@ CreateDrawTargetForSurface(gfxASurface *aSurface) return drawTarget; } -PluginInstanceChild::PluginInstanceChild(const NPPluginFuncs* aPluginIface) +PluginInstanceChild::PluginInstanceChild(const NPPluginFuncs* aPluginIface, + const nsCString& aMimeType, + const uint16_t& aMode, + const InfallibleTArray& aNames, + const InfallibleTArray& aValues) : mPluginIface(aPluginIface) + , mMimeType(aMimeType) + , mMode(aMode) + , mNames(aNames) + , mValues(aValues) #if defined(XP_MACOSX) , mContentsScaleFactor(1.0) #endif @@ -210,6 +218,54 @@ PluginInstanceChild::~PluginInstanceChild() #endif } +NPError +PluginInstanceChild::DoNPP_New() +{ + // unpack the arguments into a C format + int argc = mNames.Length(); + NS_ASSERTION(argc == (int) mValues.Length(), + "argn.length != argv.length"); + + nsAutoArrayPtr argn(new char*[1 + argc]); + nsAutoArrayPtr argv(new char*[1 + argc]); + argn[argc] = 0; + argv[argc] = 0; + + for (int i = 0; i < argc; ++i) { + argn[i] = const_cast(NullableStringGet(mNames[i])); + argv[i] = const_cast(NullableStringGet(mValues[i])); + } + + NPP npp = GetNPP(); + + NPError rv = mPluginIface->newp((char*)NullableStringGet(mMimeType), npp, + mMode, argc, argn, argv, 0); + if (NPERR_NO_ERROR != rv) { + return rv; + } + + Initialize(); + +#if defined(XP_MACOSX) && defined(__i386__) + // If an i386 Mac OS X plugin has selected the Carbon event model then + // we have to fail. We do not support putting Carbon event model plugins + // out of process. Note that Carbon is the default model so out of process + // plugins need to actively negotiate something else in order to work + // out of process. + if (EventModel() == NPEventModelCarbon) { + // Send notification that a plugin tried to negotiate Carbon NPAPI so that + // users can be notified that restarting the browser in i386 mode may allow + // them to use the plugin. + SendNegotiatedCarbon(); + + // Fail to instantiate. + rv = NPERR_MODULE_LOAD_FAILED_ERROR; + } +#endif + + return rv; +} + int PluginInstanceChild::GetQuirks() { @@ -2234,21 +2290,86 @@ PluginInstanceChild::RecvPPluginScriptableObjectConstructor( } bool -PluginInstanceChild::AnswerPBrowserStreamConstructor( +PluginInstanceChild::RecvPBrowserStreamConstructor( PBrowserStreamChild* aActor, const nsCString& url, const uint32_t& length, const uint32_t& lastmodified, PStreamNotifyChild* notifyData, - const nsCString& headers, - const nsCString& mimeType, - const bool& seekable, - NPError* rv, - uint16_t* stype) + const nsCString& headers) +{ + return true; +} + +NPError +PluginInstanceChild::DoNPP_NewStream(BrowserStreamChild* actor, + const nsCString& mimeType, + const bool& seekable, + uint16_t* stype) { AssertPluginThread(); - *rv = static_cast(aActor) - ->StreamConstructed(mimeType, seekable, stype); + NPError rv = actor->StreamConstructed(mimeType, seekable, stype); + return rv; +} + +bool +PluginInstanceChild::AnswerNPP_NewStream(PBrowserStreamChild* actor, + const nsCString& mimeType, + const bool& seekable, + NPError* rv, + uint16_t* stype) +{ + *rv = DoNPP_NewStream(static_cast(actor), mimeType, + seekable, stype); + return true; +} + +class NewStreamAsyncCall : public ChildAsyncCall +{ +public: + NewStreamAsyncCall(PluginInstanceChild* aInstance, + BrowserStreamChild* aBrowserStreamChild, + const nsCString& aMimeType, + const bool aSeekable) + : ChildAsyncCall(aInstance, nullptr, nullptr) + , mBrowserStreamChild(aBrowserStreamChild) + , mMimeType(aMimeType) + , mSeekable(aSeekable) + { + } + + void Run() MOZ_OVERRIDE + { + RemoveFromAsyncList(); + + uint16_t stype = NP_NORMAL; + NPError rv = mInstance->DoNPP_NewStream(mBrowserStreamChild, mMimeType, + mSeekable, &stype); + DebugOnly sendOk = + mBrowserStreamChild->SendAsyncNPP_NewStreamResult(rv, stype); + MOZ_ASSERT(sendOk); + } + +private: + BrowserStreamChild* mBrowserStreamChild; + const nsCString mMimeType; + const bool mSeekable; +}; + +bool +PluginInstanceChild::RecvAsyncNPP_NewStream(PBrowserStreamChild* actor, + const nsCString& mimeType, + const bool& seekable) +{ + // Reusing ChildAsyncCall so that the task is cancelled properly on Destroy + BrowserStreamChild* child = static_cast(actor); + NewStreamAsyncCall* task = new NewStreamAsyncCall(this, child, mimeType, + seekable); + { + MutexAutoLock lock(mAsyncCallMutex); + mPendingAsyncCalls.AppendElement(task); + } + MessageLoop::current()->PostTask(FROM_HERE, task); return true; } @@ -2257,16 +2378,12 @@ PluginInstanceChild::AllocPBrowserStreamChild(const nsCString& url, const uint32_t& length, const uint32_t& lastmodified, PStreamNotifyChild* notifyData, - const nsCString& headers, - const nsCString& mimeType, - const bool& seekable, - NPError* rv, - uint16_t *stype) + const nsCString& headers) { AssertPluginThread(); return new BrowserStreamChild(this, url, length, lastmodified, static_cast(notifyData), - headers, mimeType, seekable, rv, stype); + headers); } bool diff --git a/dom/plugins/ipc/PluginInstanceChild.h b/dom/plugins/ipc/PluginInstanceChild.h index f9a0b76eee55..91b00cb08719 100644 --- a/dom/plugins/ipc/PluginInstanceChild.h +++ b/dom/plugins/ipc/PluginInstanceChild.h @@ -143,29 +143,31 @@ protected: virtual bool RecvPPluginScriptableObjectConstructor(PPluginScriptableObjectChild* aActor) MOZ_OVERRIDE; + virtual bool + RecvPBrowserStreamConstructor(PBrowserStreamChild* aActor, const nsCString& url, + const uint32_t& length, const uint32_t& lastmodified, + PStreamNotifyChild* notifyData, const nsCString& headers); + + virtual bool + AnswerNPP_NewStream( + PBrowserStreamChild* actor, + const nsCString& mimeType, + const bool& seekable, + NPError* rv, + uint16_t* stype) MOZ_OVERRIDE; + + virtual bool + RecvAsyncNPP_NewStream( + PBrowserStreamChild* actor, + const nsCString& mimeType, + const bool& seekable) MOZ_OVERRIDE; + virtual PBrowserStreamChild* AllocPBrowserStreamChild(const nsCString& url, const uint32_t& length, const uint32_t& lastmodified, PStreamNotifyChild* notifyData, - const nsCString& headers, - const nsCString& mimeType, - const bool& seekable, - NPError* rv, - uint16_t *stype) MOZ_OVERRIDE; - - virtual bool - AnswerPBrowserStreamConstructor( - PBrowserStreamChild* aActor, - const nsCString& url, - const uint32_t& length, - const uint32_t& lastmodified, - PStreamNotifyChild* notifyData, - const nsCString& headers, - const nsCString& mimeType, - const bool& seekable, - NPError* rv, - uint16_t* stype) MOZ_OVERRIDE; + const nsCString& headers) MOZ_OVERRIDE; virtual bool DeallocPBrowserStreamChild(PBrowserStreamChild* stream) MOZ_OVERRIDE; @@ -202,10 +204,22 @@ protected: #endif public: - explicit PluginInstanceChild(const NPPluginFuncs* aPluginIface); + PluginInstanceChild(const NPPluginFuncs* aPluginIface, + const nsCString& aMimeType, + const uint16_t& aMode, + const InfallibleTArray& aNames, + const InfallibleTArray& aValues); virtual ~PluginInstanceChild(); + NPError DoNPP_New(); + + // Common sync+async implementation of NPP_NewStream + NPError DoNPP_NewStream(BrowserStreamChild* actor, + const nsCString& mimeType, + const bool& seekable, + uint16_t* stype); + bool Initialize(); NPP GetNPP() @@ -352,6 +366,10 @@ private: #endif const NPPluginFuncs* mPluginIface; + nsCString mMimeType; + uint16_t mMode; + InfallibleTArray mNames; + InfallibleTArray mValues; NPP_t mData; NPWindow mWindow; #if defined(XP_MACOSX) diff --git a/dom/plugins/ipc/PluginInstanceParent.cpp b/dom/plugins/ipc/PluginInstanceParent.cpp index e10758c24415..5be8e325bec3 100644 --- a/dom/plugins/ipc/PluginInstanceParent.cpp +++ b/dom/plugins/ipc/PluginInstanceParent.cpp @@ -10,6 +10,7 @@ #include "mozilla/Telemetry.h" #include "PluginInstanceParent.h" #include "BrowserStreamParent.h" +#include "PluginAsyncSurrogate.h" #include "PluginBackgroundDestroyer.h" #include "PluginModuleParent.h" #include "PluginStreamParent.h" @@ -21,6 +22,7 @@ #include "gfxPlatform.h" #include "gfxSharedImageSurface.h" #include "nsNPAPIPluginInstance.h" +#include "nsPluginInstanceOwner.h" #ifdef MOZ_X11 #include "gfxXlibSurface.h" #endif @@ -75,7 +77,9 @@ PluginInstanceParent::PluginInstanceParent(PluginModuleParent* parent, NPP npp, const nsCString& aMimeType, const NPNetscapeFuncs* npniface) - : mParent(parent) + : mParent(parent) + , mSurrogate(PluginAsyncSurrogate::Cast(npp)) + , mUseSurrogate(true) , mNPP(npp) , mNPNIface(npniface) , mWindowType(NPWindowTypeWindow) @@ -167,11 +171,7 @@ PluginInstanceParent::AllocPBrowserStreamParent(const nsCString& url, const uint32_t& length, const uint32_t& lastmodified, PStreamNotifyParent* notifyData, - const nsCString& headers, - const nsCString& mimeType, - const bool& seekable, - NPError* rv, - uint16_t *stype) + const nsCString& headers) { NS_RUNTIMEABORT("Not reachable"); return nullptr; @@ -783,6 +783,12 @@ PluginInstanceParent::EndUpdateBackground(gfxContext* aCtx, return NS_OK; } +PluginAsyncSurrogate* +PluginInstanceParent::GetAsyncSurrogate() +{ + return mSurrogate; +} + bool PluginInstanceParent::CreateBackground(const nsIntSize& aSize) { @@ -1309,24 +1315,36 @@ PluginInstanceParent::NPP_NewStream(NPMIMEType type, NPStream* stream, BrowserStreamParent* bs = new BrowserStreamParent(this, stream); - NPError err; - { // Scope for timer - Telemetry::AutoTimer - timer(Module()->GetHistogramKey()); - if (!CallPBrowserStreamConstructor(bs, - NullableString(stream->url), - stream->end, - stream->lastmodified, - static_cast(stream->notifyData), - NullableString(stream->headers), - NullableString(type), seekable, - &err, stype)) { - return NPERR_GENERIC_ERROR; - } + if (!SendPBrowserStreamConstructor(bs, + NullableString(stream->url), + stream->end, + stream->lastmodified, + static_cast(stream->notifyData), + NullableString(stream->headers))) { + return NPERR_GENERIC_ERROR; } - if (NPERR_NO_ERROR != err) - unused << PBrowserStreamParent::Send__delete__(bs); + Telemetry::AutoTimer + timer(Module()->GetHistogramKey()); + + NPError err = NPERR_NO_ERROR; + if (mParent->IsStartingAsync()) { + MOZ_ASSERT(mSurrogate); + mSurrogate->AsyncCallDeparting(); + if (SendAsyncNPP_NewStream(bs, NullableString(type), seekable)) { + *stype = UINT16_MAX; + } else { + err = NPERR_GENERIC_ERROR; + } + } else { + bs->SetAlive(); + if (!CallNPP_NewStream(bs, NullableString(type), seekable, &err, stype)) { + err = NPERR_GENERIC_ERROR; + } + if (NPERR_NO_ERROR != err) { + unused << PBrowserStreamParent::Send__delete__(bs); + } + } return err; } @@ -1636,6 +1654,49 @@ PluginInstanceParent::RecvNegotiatedCarbon() return true; } +nsPluginInstanceOwner* +PluginInstanceParent::GetOwner() +{ + nsNPAPIPluginInstance* inst = static_cast(mNPP->ndata); + if (!inst) { + return nullptr; + } + return inst->GetOwner(); +} + +bool +PluginInstanceParent::RecvAsyncNPP_NewResult(const NPError& aResult) +{ + // NB: mUseSurrogate must be cleared before doing anything else, especially + // calling NPP_SetWindow! + mUseSurrogate = false; + + mSurrogate->AsyncCallArriving(); + if (aResult == NPERR_NO_ERROR) { + mSurrogate->SetAcceptingCalls(true); + } + + nsPluginInstanceOwner* owner = GetOwner(); + if (!owner) { + // This is possible in async plugin land; the instance may outlive + // the owner + return true; + } + + if (aResult != NPERR_NO_ERROR) { + owner->NotifyHostAsyncInitFailed(); + return true; + } + + // Now we need to do a bunch of exciting post-NPP_New housekeeping. + owner->NotifyHostCreateWidget(); + + MOZ_ASSERT(mSurrogate); + mSurrogate->OnInstanceCreated(this); + + return true; +} + #if defined(OS_WIN) /* @@ -1875,3 +1936,29 @@ PluginInstanceParent::AnswerPluginFocusChange(const bool& gotFocus) return false; #endif } + +PluginInstanceParent* +PluginInstanceParent::Cast(NPP aInstance, PluginAsyncSurrogate** aSurrogate) +{ + PluginDataResolver* resolver = + static_cast(aInstance->pdata); + + // If the plugin crashed and the PluginInstanceParent was deleted, + // aInstance->pdata will be nullptr. + if (!resolver) { + return nullptr; + } + + PluginInstanceParent* instancePtr = resolver->GetInstance(); + + if (instancePtr && aInstance != instancePtr->mNPP) { + NS_RUNTIMEABORT("Corrupted plugin data."); + } + + if (aSurrogate) { + *aSurrogate = resolver->GetAsyncSurrogate(); + } + + return instancePtr; +} + diff --git a/dom/plugins/ipc/PluginInstanceParent.h b/dom/plugins/ipc/PluginInstanceParent.h index f38dcfea0ef1..31448da8b9eb 100644 --- a/dom/plugins/ipc/PluginInstanceParent.h +++ b/dom/plugins/ipc/PluginInstanceParent.h @@ -22,6 +22,7 @@ #include "nsDataHashtable.h" #include "nsHashKeys.h" #include "nsRect.h" +#include "PluginDataResolver.h" #ifdef MOZ_X11 class gfxXlibSurface; @@ -30,6 +31,7 @@ class gfxXlibSurface; class gfxASurface; class gfxContext; +class nsPluginInstanceOwner; namespace mozilla { namespace layers { @@ -42,6 +44,7 @@ class PBrowserStreamParent; class PluginModuleParent; class PluginInstanceParent : public PPluginInstanceParent + , public PluginDataResolver { friend class PluginModuleParent; friend class BrowserStreamParent; @@ -74,11 +77,7 @@ public: const uint32_t& length, const uint32_t& lastmodified, PStreamNotifyParent* notifyData, - const nsCString& headers, - const nsCString& mimeType, - const bool& seekable, - NPError* rv, - uint16_t *stype) MOZ_OVERRIDE; + const nsCString& headers) MOZ_OVERRIDE; virtual bool DeallocPBrowserStreamParent(PBrowserStreamParent* stream) MOZ_OVERRIDE; @@ -210,6 +209,9 @@ public: virtual bool RecvNegotiatedCarbon() MOZ_OVERRIDE; + virtual bool + RecvAsyncNPP_NewResult(const NPError& aResult) MOZ_OVERRIDE; + NPError NPP_SetWindow(const NPWindow* aWindow); NPError NPP_GetValue(NPPVariable variable, void* retval); @@ -254,6 +256,12 @@ public: return mNPP; } + bool + UseSurrogate() const + { + return mUseSurrogate; + } + virtual bool AnswerPluginFocusChange(const bool& gotFocus) MOZ_OVERRIDE; @@ -271,6 +279,13 @@ public: const nsIntRect& aRect); void DidComposite() { unused << SendNPP_DidComposite(); } + virtual PluginAsyncSurrogate* GetAsyncSurrogate(); + + virtual PluginInstanceParent* GetInstance() { return this; } + + static PluginInstanceParent* Cast(NPP instance, + PluginAsyncSurrogate** aSurrogate = nullptr); + private: // Create an appropriate platform surface for a background of size // |aSize|. Return true if successful. @@ -291,8 +306,12 @@ private: PPluginScriptableObjectParent** aValue, NPError* aResult); + nsPluginInstanceOwner* GetOwner(); + private: PluginModuleParent* mParent; + nsRefPtr mSurrogate; + bool mUseSurrogate; NPP mNPP; const NPNetscapeFuncs* mNPNIface; NPWindowType mWindowType; diff --git a/dom/plugins/ipc/PluginModuleChild.cpp b/dom/plugins/ipc/PluginModuleChild.cpp index ac51826d4c9a..034db619d28e 100644 --- a/dom/plugins/ipc/PluginModuleChild.cpp +++ b/dom/plugins/ipc/PluginModuleChild.cpp @@ -95,6 +95,13 @@ static GetWindowInfoPtr sGetWindowInfoPtrStub = nullptr; static HWND sBrowserHwnd = nullptr; #endif +template<> +struct RunnableMethodTraits +{ + static void RetainCallee(PluginModuleChild* obj) { } + static void ReleaseCallee(PluginModuleChild* obj) { } +}; + /* static */ PluginModuleChild* PluginModuleChild::CreateForContentProcess(mozilla::ipc::Transport* aTransport, @@ -1881,7 +1888,21 @@ PluginModuleChild::AnswerNP_GetEntryPoints(NPError* _retval) } bool -PluginModuleChild::AnswerNP_Initialize(const PluginSettings& aSettings, NPError* _retval) +PluginModuleChild::AnswerNP_Initialize(const PluginSettings& aSettings, NPError* rv) +{ + *rv = DoNP_Initialize(aSettings); + return true; +} + +bool +PluginModuleChild::RecvAsyncNP_Initialize(const PluginSettings& aSettings) +{ + NPError error = DoNP_Initialize(aSettings); + return SendNP_InitializeResult(error); +} + +NPError +PluginModuleChild::DoNP_Initialize(const PluginSettings& aSettings) { PLUGIN_LOG_DEBUG_METHOD; AssertPluginThread(); @@ -1900,12 +1921,11 @@ PluginModuleChild::AnswerNP_Initialize(const PluginSettings& aSettings, NPError* SendBackUpXResources(FileDescriptor(xSocketFd)); #endif + NPError result; #if defined(OS_LINUX) || defined(OS_BSD) - *_retval = mInitializeFunc(&sBrowserFuncs, &mFunctions); - return true; + result = mInitializeFunc(&sBrowserFuncs, &mFunctions); #elif defined(OS_WIN) || defined(OS_MACOSX) - *_retval = mInitializeFunc(&sBrowserFuncs); - return true; + result = mInitializeFunc(&sBrowserFuncs); #else # error Please implement me for your platform #endif @@ -1913,6 +1933,8 @@ PluginModuleChild::AnswerNP_Initialize(const PluginSettings& aSettings, NPError* #ifdef XP_WIN CleanupProtectedModeHook(); #endif + + return result; } #if defined(XP_WIN) @@ -2035,8 +2057,7 @@ PPluginInstanceChild* PluginModuleChild::AllocPPluginInstanceChild(const nsCString& aMimeType, const uint16_t& aMode, const InfallibleTArray& aNames, - const InfallibleTArray& aValues, - NPError* rv) + const InfallibleTArray& aValues) { PLUGIN_LOG_DEBUG_METHOD; AssertPluginThread(); @@ -2052,7 +2073,8 @@ PluginModuleChild::AllocPPluginInstanceChild(const nsCString& aMimeType, } #endif - return new PluginInstanceChild(&mFunctions); + return new PluginInstanceChild(&mFunctions, aMimeType, aMode, aNames, + aValues); } void @@ -2105,68 +2127,39 @@ PluginModuleChild::InitQuirksModes(const nsCString& aMimeType) } bool -PluginModuleChild::AnswerPPluginInstanceConstructor(PPluginInstanceChild* aActor, - const nsCString& aMimeType, - const uint16_t& aMode, - const InfallibleTArray& aNames, - const InfallibleTArray& aValues, - NPError* rv) +PluginModuleChild::RecvPPluginInstanceConstructor(PPluginInstanceChild* aActor, + const nsCString& aMimeType, + const uint16_t& aMode, + const InfallibleTArray& aNames, + const InfallibleTArray& aValues) { PLUGIN_LOG_DEBUG_METHOD; AssertPluginThread(); + NS_ASSERTION(aActor, "Null actor!"); + return true; +} + +bool +PluginModuleChild::AnswerSyncNPP_New(PPluginInstanceChild* aActor, NPError* rv) +{ + PLUGIN_LOG_DEBUG_METHOD; PluginInstanceChild* childInstance = reinterpret_cast(aActor); - NS_ASSERTION(childInstance, "Null actor!"); - - // unpack the arguments into a C format - int argc = aNames.Length(); - NS_ASSERTION(argc == (int) aValues.Length(), - "argn.length != argv.length"); - - nsAutoArrayPtr argn(new char*[1 + argc]); - nsAutoArrayPtr argv(new char*[1 + argc]); - argn[argc] = 0; - argv[argc] = 0; - - for (int i = 0; i < argc; ++i) { - argn[i] = const_cast(NullableStringGet(aNames[i])); - argv[i] = const_cast(NullableStringGet(aValues[i])); - } - - NPP npp = childInstance->GetNPP(); - - // FIXME/cjones: use SAFE_CALL stuff - *rv = mFunctions.newp((char*)NullableStringGet(aMimeType), - npp, - aMode, - argc, - argn, - argv, - 0); - if (NPERR_NO_ERROR != *rv) { - return true; - } - - childInstance->Initialize(); - -#if defined(XP_MACOSX) && defined(__i386__) - // If an i386 Mac OS X plugin has selected the Carbon event model then - // we have to fail. We do not support putting Carbon event model plugins - // out of process. Note that Carbon is the default model so out of process - // plugins need to actively negotiate something else in order to work - // out of process. - if (childInstance->EventModel() == NPEventModelCarbon) { - // Send notification that a plugin tried to negotiate Carbon NPAPI so that - // users can be notified that restarting the browser in i386 mode may allow - // them to use the plugin. - childInstance->SendNegotiatedCarbon(); - - // Fail to instantiate. - *rv = NPERR_MODULE_LOAD_FAILED_ERROR; - } -#endif + AssertPluginThread(); + *rv = childInstance->DoNPP_New(); + return true; +} +bool +PluginModuleChild::RecvAsyncNPP_New(PPluginInstanceChild* aActor) +{ + PLUGIN_LOG_DEBUG_METHOD; + PluginInstanceChild* childInstance = + reinterpret_cast(aActor); + AssertPluginThread(); + NPError rv = childInstance->DoNPP_New(); + childInstance->SendAsyncNPP_NewResult(rv); return true; } diff --git a/dom/plugins/ipc/PluginModuleChild.h b/dom/plugins/ipc/PluginModuleChild.h index 16ab97900978..bf0770dfe1c1 100644 --- a/dom/plugins/ipc/PluginModuleChild.h +++ b/dom/plugins/ipc/PluginModuleChild.h @@ -78,6 +78,10 @@ protected: virtual bool RecvDisableFlashProtectedMode() MOZ_OVERRIDE; virtual bool AnswerNP_GetEntryPoints(NPError* rv) MOZ_OVERRIDE; virtual bool AnswerNP_Initialize(const PluginSettings& aSettings, NPError* rv) MOZ_OVERRIDE; + virtual bool RecvAsyncNP_Initialize(const PluginSettings& aSettings) MOZ_OVERRIDE; + virtual bool AnswerSyncNPP_New(PPluginInstanceChild* aActor, NPError* rv) + MOZ_OVERRIDE; + virtual bool RecvAsyncNPP_New(PPluginInstanceChild* aActor) MOZ_OVERRIDE; virtual PPluginModuleChild* AllocPPluginModuleChild(mozilla::ipc::Transport* aTransport, @@ -87,19 +91,19 @@ protected: AllocPPluginInstanceChild(const nsCString& aMimeType, const uint16_t& aMode, const InfallibleTArray& aNames, - const InfallibleTArray& aValues, - NPError* rv) MOZ_OVERRIDE; + const InfallibleTArray& aValues) + MOZ_OVERRIDE; virtual bool DeallocPPluginInstanceChild(PPluginInstanceChild* aActor) MOZ_OVERRIDE; virtual bool - AnswerPPluginInstanceConstructor(PPluginInstanceChild* aActor, - const nsCString& aMimeType, - const uint16_t& aMode, - const InfallibleTArray& aNames, - const InfallibleTArray& aValues, - NPError* rv) MOZ_OVERRIDE; + RecvPPluginInstanceConstructor(PPluginInstanceChild* aActor, + const nsCString& aMimeType, + const uint16_t& aMode, + const InfallibleTArray& aNames, + const InfallibleTArray& aValues) + MOZ_OVERRIDE; virtual bool AnswerNP_Shutdown(NPError *rv) MOZ_OVERRIDE; @@ -286,6 +290,7 @@ public: const PluginSettings& Settings() const { return mCachedSettings; } private: + NPError DoNP_Initialize(const PluginSettings& aSettings); void AddQuirk(PluginQuirks quirk) { if (mQuirks == QUIRKS_NOT_INITIALIZED) mQuirks = 0; diff --git a/dom/plugins/ipc/PluginModuleParent.cpp b/dom/plugins/ipc/PluginModuleParent.cpp index 9ed9a9ab6e2f..e0f1c9facc16 100755 --- a/dom/plugins/ipc/PluginModuleParent.cpp +++ b/dom/plugins/ipc/PluginModuleParent.cpp @@ -17,6 +17,7 @@ #include "mozilla/dom/PCrashReporterParent.h" #include "mozilla/ipc/MessageChannel.h" #include "mozilla/plugins/BrowserStreamParent.h" +#include "mozilla/plugins/PluginAsyncSurrogate.h" #include "mozilla/plugins/PluginBridge.h" #include "mozilla/plugins/PluginInstanceParent.h" #include "mozilla/Preferences.h" @@ -70,6 +71,7 @@ using namespace CrashReporter; static const char kChildTimeoutPref[] = "dom.ipc.plugins.timeoutSecs"; static const char kParentTimeoutPref[] = "dom.ipc.plugins.parentTimeoutSecs"; static const char kLaunchTimeoutPref[] = "dom.ipc.plugins.processLaunchTimeoutSecs"; +static const char kAsyncInitPref[] = "dom.ipc.plugins.asyncInit"; #ifdef XP_WIN static const char kHangUITimeoutPref[] = "dom.ipc.plugins.hangUITimeoutSecs"; static const char kHangUIMinDisplayPref[] = "dom.ipc.plugins.hangUIMinDisplaySecs"; @@ -95,61 +97,266 @@ mozilla::plugins::SetupBridge(uint32_t aPluginId, dom::ContentParent* aContentPa if (NS_FAILED(rv)) { return false; } - PluginModuleParent* chromeParent = static_cast(plugin->GetLibrary()); + PluginModuleChromeParent* chromeParent = static_cast(plugin->GetLibrary()); + chromeParent->SetContentParent(aContentParent); + if (chromeParent->IsStartingAsync()) { + // We'll handle the bridging asynchronously + return true; + } return PPluginModule::Bridge(aContentParent, chromeParent); } -PluginModuleContentParent* PluginModuleContentParent::sSavedModuleParent; +/** + * Objects of this class remain linked until either an error occurs in the + * plugin initialization sequence, or until + * PluginModuleContentParent::OnLoadPluginResult has completed executing. + */ +class PluginModuleMapping : public PRCList +{ +public: + explicit PluginModuleMapping(uint32_t aPluginId) + : mPluginId(aPluginId) + , mProcessIdValid(false) + , mModule(nullptr) + , mChannelOpened(false) + { + MOZ_COUNT_CTOR(PluginModuleMapping); + PR_INIT_CLIST(this); + PR_APPEND_LINK(this, &sModuleListHead); + } + + ~PluginModuleMapping() + { + PR_REMOVE_LINK(this); + MOZ_COUNT_DTOR(PluginModuleMapping); + } + + bool + IsChannelOpened() const + { + return mChannelOpened; + } + + void + SetChannelOpened() + { + mChannelOpened = true; + } + + PluginModuleContentParent* + GetModule() + { + if (!mModule) { + mModule = new PluginModuleContentParent(); + } + return mModule; + } + + static PluginModuleMapping* + AssociateWithProcessId(uint32_t aPluginId, base::ProcessId aProcessId) + { + PluginModuleMapping* mapping = + static_cast(PR_NEXT_LINK(&sModuleListHead)); + while (mapping != &sModuleListHead) { + if (mapping->mPluginId == aPluginId) { + mapping->AssociateWithProcessId(aProcessId); + return mapping; + } + mapping = static_cast(PR_NEXT_LINK(mapping)); + } + return nullptr; + } + + static PluginModuleMapping* + Resolve(base::ProcessId aProcessId) + { + PluginModuleMapping* mapping = nullptr; + + if (sIsLoadModuleOnStack) { + // Special case: If loading synchronously, we just need to access + // the tail entry of the list. + mapping = + static_cast(PR_LIST_TAIL(&sModuleListHead)); + MOZ_ASSERT(mapping); + return mapping; + } + + mapping = + static_cast(PR_NEXT_LINK(&sModuleListHead)); + while (mapping != &sModuleListHead) { + if (mapping->mProcessIdValid && mapping->mProcessId == aProcessId) { + return mapping; + } + mapping = static_cast(PR_NEXT_LINK(mapping)); + } + return nullptr; + } + + static PluginModuleMapping* + FindModuleByPluginId(uint32_t aPluginId) + { + PluginModuleMapping* mapping = + static_cast(PR_NEXT_LINK(&sModuleListHead)); + while (mapping != &sModuleListHead) { + if (mapping->mPluginId == aPluginId) { + return mapping; + } + mapping = static_cast(PR_NEXT_LINK(mapping)); + } + return nullptr; + } + + static bool + IsLoadModuleOnStack() + { + return sIsLoadModuleOnStack; + } + + class MOZ_STACK_CLASS NotifyLoadingModule + { + public: + explicit NotifyLoadingModule(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM) + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + PluginModuleMapping::sIsLoadModuleOnStack = true; + } + + ~NotifyLoadingModule() + { + PluginModuleMapping::sIsLoadModuleOnStack = false; + } + + private: + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER + }; + +private: + void + AssociateWithProcessId(base::ProcessId aProcessId) + { + MOZ_ASSERT(!mProcessIdValid); + mProcessId = aProcessId; + mProcessIdValid = true; + } + + uint32_t mPluginId; + bool mProcessIdValid; + base::ProcessId mProcessId; + PluginModuleContentParent* mModule; + bool mChannelOpened; + + friend class NotifyLoadingModule; + + static PRCList sModuleListHead; + static bool sIsLoadModuleOnStack; +}; + +PRCList PluginModuleMapping::sModuleListHead = + PR_INIT_STATIC_CLIST(&PluginModuleMapping::sModuleListHead); + +bool PluginModuleMapping::sIsLoadModuleOnStack = false; /* static */ PluginLibrary* PluginModuleContentParent::LoadModule(uint32_t aPluginId) { - MOZ_ASSERT(!sSavedModuleParent); + PluginModuleMapping::NotifyLoadingModule loadingModule; + nsAutoPtr mapping(new PluginModuleMapping(aPluginId)); + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Content); /* * We send a LoadPlugin message to the chrome process using an intr * message. Before it sends its response, it sends a message to create * PluginModuleParent instance. That message is handled by - * PluginModuleContentParent::Create, which saves the instance in - * sSavedModuleParent. We fetch it from there after LoadPlugin finishes. + * PluginModuleContentParent::Initialize, which saves the instance in + * its module mapping. We fetch it from there after LoadPlugin finishes. */ dom::ContentChild* cp = dom::ContentChild::GetSingleton(); if (!cp->SendLoadPlugin(aPluginId)) { return nullptr; } - PluginModuleContentParent* parent = sSavedModuleParent; + PluginModuleContentParent* parent = mapping->GetModule(); MOZ_ASSERT(parent); - sSavedModuleParent = nullptr; + + if (!mapping->IsChannelOpened()) { + // mapping is linked into PluginModuleMapping::sModuleListHead and is + // needed later, so since this function is returning successfully we + // forget it here. + mapping.forget(); + } return parent; } -/* static */ PluginModuleContentParent* -PluginModuleContentParent::Create(mozilla::ipc::Transport* aTransport, - base::ProcessId aOtherProcess) +/* static */ void +PluginModuleContentParent::AssociatePluginId(uint32_t aPluginId, + base::ProcessId aProcessId) { - nsAutoPtr parent(new PluginModuleContentParent()); + DebugOnly mapping = + PluginModuleMapping::AssociateWithProcessId(aPluginId, aProcessId); + MOZ_ASSERT(mapping); +} + +/* static */ PluginModuleContentParent* +PluginModuleContentParent::Initialize(mozilla::ipc::Transport* aTransport, + base::ProcessId aOtherProcess) +{ + nsAutoPtr moduleMapping( + PluginModuleMapping::Resolve(aOtherProcess)); + MOZ_ASSERT(moduleMapping); + PluginModuleContentParent* parent = moduleMapping->GetModule(); + MOZ_ASSERT(parent); + ProcessHandle handle; if (!base::OpenProcessHandle(aOtherProcess, &handle)) { // Bug 1090578 - need to kill |aOtherProcess|, it's boned. return nullptr; } - MOZ_ASSERT(!sSavedModuleParent); - sSavedModuleParent = parent; - DebugOnly ok = parent->Open(aTransport, handle, XRE_GetIOMessageLoop(), mozilla::ipc::ParentSide); MOZ_ASSERT(ok); + moduleMapping->SetChannelOpened(); + // Request Windows message deferral behavior on our channel. This // applies to the top level and all sub plugin protocols since they // all share the same channel. parent->GetIPCChannel()->SetChannelFlags(MessageChannel::REQUIRE_DEFERRED_MESSAGE_PROTECTION); - return parent.forget(); + // moduleMapping is linked into PluginModuleMapping::sModuleListHead and is + // needed later, so since this function is returning successfully we + // forget it here. + moduleMapping.forget(); + return parent; +} + +/* static */ void +PluginModuleContentParent::OnLoadPluginResult(const uint32_t& aPluginId, + const bool& aResult) +{ + nsAutoPtr moduleMapping( + PluginModuleMapping::FindModuleByPluginId(aPluginId)); + MOZ_ASSERT(moduleMapping); + PluginModuleContentParent* parent = moduleMapping->GetModule(); + MOZ_ASSERT(parent); + parent->RecvNP_InitializeResult(aResult ? NPERR_NO_ERROR + : NPERR_GENERIC_ERROR); +} + +void +PluginModuleChromeParent::SetContentParent(dom::ContentParent* aContentParent) +{ + MOZ_ASSERT(aContentParent); + mContentParent = aContentParent; +} + +bool +PluginModuleChromeParent::SendAssociatePluginId() +{ + MOZ_ASSERT(mContentParent); + return mContentParent->SendAssociatePluginId(mPluginId, OtherSidePID()); } // static @@ -159,49 +366,118 @@ PluginModuleChromeParent::LoadModule(const char* aFilePath, uint32_t aPluginId, { PLUGIN_LOG_DEBUG_FUNCTION; - int32_t prefSecs = Preferences::GetInt(kLaunchTimeoutPref, 0); - - // Block on the child process being launched and initialized. nsAutoPtr parent(new PluginModuleChromeParent(aFilePath, aPluginId)); + UniquePtr onLaunchedRunnable(new LaunchedTask(parent)); + parent->mSubprocess->SetCallRunnableImmediately(!parent->mIsStartingAsync); TimeStamp launchStart = TimeStamp::Now(); - bool launched = parent->mSubprocess->Launch(prefSecs * 1000); + bool launched = parent->mSubprocess->Launch(Move(onLaunchedRunnable)); if (!launched) { // We never reached open parent->mShutdown = true; return nullptr; } + if (!parent->mIsStartingAsync) { + int32_t launchTimeoutSecs = Preferences::GetInt(kLaunchTimeoutPref, 0); + if (!parent->mSubprocess->WaitUntilConnected(launchTimeoutSecs * 1000)) { + parent->mShutdown = true; + return nullptr; + } + } TimeStamp launchEnd = TimeStamp::Now(); parent->mTimeBlocked = (launchEnd - launchStart); - parent->Open(parent->mSubprocess->GetChannel(), - parent->mSubprocess->GetChildProcessHandle()); + parent->mIsFlashPlugin = aPluginTag->mIsFlashPlugin; + return parent.forget(); +} + +void +PluginModuleChromeParent::OnProcessLaunched(const bool aSucceeded) +{ + if (!aSucceeded) { + mShutdown = true; + OnInitFailure(); + return; + } + // We may have already been initialized by another call that was waiting + // for process connect. If so, this function doesn't need to run. + if (mAsyncInitRv != NS_ERROR_NOT_INITIALIZED || mShutdown) { + return; + } + Open(mSubprocess->GetChannel(), mSubprocess->GetChildProcessHandle()); // Request Windows message deferral behavior on our channel. This // applies to the top level and all sub plugin protocols since they // all share the same channel. - parent->GetIPCChannel()->SetChannelFlags(MessageChannel::REQUIRE_DEFERRED_MESSAGE_PROTECTION); + GetIPCChannel()->SetChannelFlags(MessageChannel::REQUIRE_DEFERRED_MESSAGE_PROTECTION); - TimeoutChanged(CHILD_TIMEOUT_PREF, parent); + TimeoutChanged(CHILD_TIMEOUT_PREF, this); + + Preferences::RegisterCallback(TimeoutChanged, kChildTimeoutPref, this); + Preferences::RegisterCallback(TimeoutChanged, kParentTimeoutPref, this); +#ifdef XP_WIN + Preferences::RegisterCallback(TimeoutChanged, kHangUITimeoutPref, this); + Preferences::RegisterCallback(TimeoutChanged, kHangUIMinDisplayPref, this); +#endif #ifdef MOZ_CRASHREPORTER // If this fails, we're having IPC troubles, and we're doomed anyways. - if (!CrashReporterParent::CreateCrashReporter(parent.get())) { - parent->Close(); - return nullptr; + if (!CrashReporterParent::CreateCrashReporter(this)) { + mShutdown = true; + Close(); + OnInitFailure(); + return; } #ifdef XP_WIN - mozilla::MutexAutoLock lock(parent->mCrashReporterMutex); - parent->mCrashReporter = parent->CrashReporter(); + { // Scope for lock + mozilla::MutexAutoLock lock(mCrashReporterMutex); + mCrashReporter = CrashReporter(); + } #endif #endif #ifdef XP_WIN - if (aPluginTag->mIsFlashPlugin && + if (mIsFlashPlugin && Preferences::GetBool("dom.ipc.plugins.flash.disable-protected-mode", false)) { - parent->SendDisableFlashProtectedMode(); + SendDisableFlashProtectedMode(); } #endif - return parent.forget(); + if (mInitOnAsyncConnect) { + mInitOnAsyncConnect = false; +#if defined(XP_WIN) + mAsyncInitRv = NP_GetEntryPoints(mAsyncInitPluginFuncs, + &mAsyncInitError); + if (NS_SUCCEEDED(mAsyncInitRv)) +#endif + { +#if defined(XP_UNIX) && !defined(XP_MACOSX) && !defined(MOZ_WIDGET_GONK) + mAsyncInitRv = NP_Initialize(mNPNIface, + mAsyncInitPluginFuncs, + &mAsyncInitError); +#else + mAsyncInitRv = NP_Initialize(mNPNIface, + &mAsyncInitError); +#endif + } + +#if defined(XP_MACOSX) + if (NS_SUCCEEDED(mAsyncInitRv)) { + mAsyncInitRv = NP_GetEntryPoints(mAsyncInitPluginFuncs, + &mAsyncInitError); + } +#endif + } +} + +bool +PluginModuleChromeParent::WaitForIPCConnection() +{ + PluginProcessParent* process = Process(); + MOZ_ASSERT(process); + process->SetCallRunnableImmediately(true); + if (!process->WaitUntilConnected()) { + return false; + } + return true; } PluginModuleParent::PluginModuleParent(bool aIsChrome) @@ -212,7 +488,14 @@ PluginModuleParent::PluginModuleParent(bool aIsChrome) , mNPNIface(nullptr) , mPlugin(nullptr) , mTaskFactory(MOZ_THIS_IN_INITIALIZER_LIST()) + , mIsStartingAsync(false) + , mNPInitialized(false) + , mAsyncNewRv(NS_ERROR_NOT_INITIALIZED) + , mAsyncInitPluginFuncs(nullptr) { +#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_GTK) + mIsStartingAsync = Preferences::GetBool(kAsyncInitPref, false); +#endif } PluginModuleParent::~PluginModuleParent() @@ -253,16 +536,14 @@ PluginModuleChromeParent::PluginModuleChromeParent(const char* aFilePath, uint32 , mFlashProcess1(0) , mFlashProcess2(0) #endif + , mInitOnAsyncConnect(false) + , mAsyncInitRv(NS_ERROR_NOT_INITIALIZED) + , mAsyncInitError(NPERR_NO_ERROR) + , mContentParent(nullptr) + , mIsFlashPlugin(false) { NS_ASSERTION(mSubprocess, "Out of memory!"); - Preferences::RegisterCallback(TimeoutChanged, kChildTimeoutPref, this); - Preferences::RegisterCallback(TimeoutChanged, kParentTimeoutPref, this); -#ifdef XP_WIN - Preferences::RegisterCallback(TimeoutChanged, kHangUITimeoutPref, this); - Preferences::RegisterCallback(TimeoutChanged, kHangUIMinDisplayPref, this); -#endif - RegisterSettingsCallbacks(); #ifdef MOZ_ENABLE_PROFILER_SPS @@ -947,8 +1228,7 @@ PPluginInstanceParent* PluginModuleParent::AllocPPluginInstanceParent(const nsCString& aMimeType, const uint16_t& aMode, const InfallibleTArray& aNames, - const InfallibleTArray& aValues, - NPError* rv) + const InfallibleTArray& aValues) { NS_ERROR("Not reachable!"); return nullptr; @@ -1000,6 +1280,19 @@ PluginModuleParent::SetPluginFuncs(NPPluginFuncs* aFuncs) } } +#define RESOLVE_AND_CALL(instance, func) \ +NP_BEGIN_MACRO \ + PluginAsyncSurrogate* surrogate = nullptr; \ + PluginInstanceParent* i = PluginInstanceParent::Cast(instance, &surrogate);\ + if (surrogate && (!i || i->UseSurrogate())) { \ + return surrogate->func; \ + } \ + if (!i) { \ + return NPERR_GENERIC_ERROR; \ + } \ + return i->func; \ +NP_END_MACRO + NPError PluginModuleParent::NPP_Destroy(NPP instance, NPSavedData** /*saved*/) @@ -1009,10 +1302,9 @@ PluginModuleParent::NPP_Destroy(NPP instance, // (2) the child shuts down its instance // (3) remove both parent and child IDs from map // (4) free parent - PLUGIN_LOG_DEBUG_FUNCTION; - PluginInstanceParent* parentInstance = - static_cast(instance->pdata); + PLUGIN_LOG_DEBUG_FUNCTION; + PluginInstanceParent* parentInstance = PluginInstanceParent::Cast(instance); if (!parentInstance) return NPERR_NO_ERROR; @@ -1031,23 +1323,13 @@ PluginModuleParent::NPP_NewStream(NPP instance, NPMIMEType type, { PROFILER_LABEL("PluginModuleParent", "NPP_NewStream", js::ProfileEntry::Category::OTHER); - - PluginInstanceParent* i = InstCast(instance); - if (!i) - return NPERR_GENERIC_ERROR; - - return i->NPP_NewStream(type, stream, seekable, - stype); + RESOLVE_AND_CALL(instance, NPP_NewStream(type, stream, seekable, stype)); } NPError PluginModuleParent::NPP_SetWindow(NPP instance, NPWindow* window) { - PluginInstanceParent* i = InstCast(instance); - if (!i) - return NPERR_GENERIC_ERROR; - - return i->NPP_SetWindow(window); + RESOLVE_AND_CALL(instance, NPP_SetWindow(window)); } NPError @@ -1055,7 +1337,7 @@ PluginModuleParent::NPP_DestroyStream(NPP instance, NPStream* stream, NPReason reason) { - PluginInstanceParent* i = InstCast(instance); + PluginInstanceParent* i = PluginInstanceParent::Cast(instance); if (!i) return NPERR_GENERIC_ERROR; @@ -1066,9 +1348,14 @@ int32_t PluginModuleParent::NPP_WriteReady(NPP instance, NPStream* stream) { - BrowserStreamParent* s = StreamCast(instance, stream); - if (!s) + PluginAsyncSurrogate* surrogate = nullptr; + BrowserStreamParent* s = StreamCast(instance, stream, &surrogate); + if (!s) { + if (surrogate) { + return surrogate->NPP_WriteReady(stream); + } return -1; + } return s->WriteReady(); } @@ -1102,26 +1389,22 @@ PluginModuleParent::NPP_StreamAsFile(NPP instance, void PluginModuleParent::NPP_Print(NPP instance, NPPrint* platformPrint) { - PluginInstanceParent* i = InstCast(instance); - if (i) - i->NPP_Print(platformPrint); + + PluginInstanceParent* i = PluginInstanceParent::Cast(instance); + i->NPP_Print(platformPrint); } int16_t PluginModuleParent::NPP_HandleEvent(NPP instance, void* event) { - PluginInstanceParent* i = InstCast(instance); - if (!i) - return false; - - return i->NPP_HandleEvent(event); + RESOLVE_AND_CALL(instance, NPP_HandleEvent(event)); } void PluginModuleParent::NPP_URLNotify(NPP instance, const char* url, NPReason reason, void* notifyData) { - PluginInstanceParent* i = InstCast(instance); + PluginInstanceParent* i = PluginInstanceParent::Cast(instance); if (!i) return; @@ -1132,10 +1415,16 @@ NPError PluginModuleParent::NPP_GetValue(NPP instance, NPPVariable variable, void *ret_value) { - PluginInstanceParent* i = InstCast(instance); - if (!i) + // The rules are slightly different for this function. + // If there is a surrogate, we *always* use it. + PluginAsyncSurrogate* surrogate = nullptr; + PluginInstanceParent* i = PluginInstanceParent::Cast(instance, &surrogate); + if (surrogate) { + return surrogate->NPP_GetValue(variable, ret_value); + } + if (!i) { return NPERR_GENERIC_ERROR; - + } return i->NPP_GetValue(variable, ret_value); } @@ -1143,11 +1432,7 @@ NPError PluginModuleParent::NPP_SetValue(NPP instance, NPNVariable variable, void *value) { - PluginInstanceParent* i = InstCast(instance); - if (!i) - return NPERR_GENERIC_ERROR; - - return i->NPP_SetValue(variable, value); + RESOLVE_AND_CALL(instance, NPP_SetValue(variable, value)); } bool @@ -1170,37 +1455,21 @@ void PluginModuleParent::NPP_URLRedirectNotify(NPP instance, const char* url, int32_t status, void* notifyData) { - PluginInstanceParent* i = InstCast(instance); + PluginInstanceParent* i = PluginInstanceParent::Cast(instance); if (!i) return; i->NPP_URLRedirectNotify(url, status, notifyData); } -PluginInstanceParent* -PluginModuleParent::InstCast(NPP instance) -{ - PluginInstanceParent* ip = - static_cast(instance->pdata); - - // If the plugin crashed and the PluginInstanceParent was deleted, - // instance->pdata will be nullptr. - if (!ip) - return nullptr; - - if (instance != ip->mNPP) { - NS_RUNTIMEABORT("Corrupted plugin data."); - } - return ip; -} - BrowserStreamParent* -PluginModuleParent::StreamCast(NPP instance, - NPStream* s) +PluginModuleParent::StreamCast(NPP instance, NPStream* s, + PluginAsyncSurrogate** aSurrogate) { - PluginInstanceParent* ip = InstCast(instance); - if (!ip) + PluginInstanceParent* ip = PluginInstanceParent::Cast(instance, aSurrogate); + if (!ip || (aSurrogate && *aSurrogate && ip->UseSurrogate())) { return nullptr; + } BrowserStreamParent* sp = static_cast(static_cast(s->pdata)); @@ -1219,10 +1488,13 @@ PluginModuleParent::HasRequiredFunctions() nsresult PluginModuleParent::AsyncSetWindow(NPP instance, NPWindow* window) { - PluginInstanceParent* i = InstCast(instance); - if (!i) + PluginAsyncSurrogate* surrogate = nullptr; + PluginInstanceParent* i = PluginInstanceParent::Cast(instance, &surrogate); + if (surrogate && (!i || i->UseSurrogate())) { + return surrogate->AsyncSetWindow(window); + } else if (!i) { return NS_ERROR_FAILURE; - + } return i->AsyncSetWindow(window); } @@ -1230,7 +1502,7 @@ nsresult PluginModuleParent::GetImageContainer(NPP instance, mozilla::layers::ImageContainer** aContainer) { - PluginInstanceParent* i = InstCast(instance); + PluginInstanceParent* i = PluginInstanceParent::Cast(instance); return !i ? NS_ERROR_FAILURE : i->GetImageContainer(aContainer); } @@ -1238,14 +1510,14 @@ nsresult PluginModuleParent::GetImageSize(NPP instance, nsIntSize* aSize) { - PluginInstanceParent* i = InstCast(instance); + PluginInstanceParent* i = PluginInstanceParent::Cast(instance); return !i ? NS_ERROR_FAILURE : i->GetImageSize(aSize); } nsresult PluginModuleParent::SetBackgroundUnknown(NPP instance) { - PluginInstanceParent* i = InstCast(instance); + PluginInstanceParent* i = PluginInstanceParent::Cast(instance); if (!i) return NS_ERROR_FAILURE; @@ -1257,7 +1529,7 @@ PluginModuleParent::BeginUpdateBackground(NPP instance, const nsIntRect& aRect, gfxContext** aCtx) { - PluginInstanceParent* i = InstCast(instance); + PluginInstanceParent* i = PluginInstanceParent::Cast(instance); if (!i) return NS_ERROR_FAILURE; @@ -1269,13 +1541,28 @@ PluginModuleParent::EndUpdateBackground(NPP instance, gfxContext* aCtx, const nsIntRect& aRect) { - PluginInstanceParent* i = InstCast(instance); + PluginInstanceParent* i = PluginInstanceParent::Cast(instance); if (!i) return NS_ERROR_FAILURE; return i->EndUpdateBackground(aCtx, aRect); } +void +PluginModuleParent::OnInitFailure() +{ + if (GetIPCChannel()->CanSend()) { + Close(); + } + /* If we've failed then we need to enumerate any pending NPP_New calls + and clean them up. */ + uint32_t len = mSurrogateInstances.Length(); + for (uint32_t i = 0; i < len; ++i) { + mSurrogateInstances[i]->NotifyAsyncInitFailed(); + } + mSurrogateInstances.Clear(); +} + class OfflineObserver MOZ_FINAL : public nsIObserver { public: @@ -1390,28 +1677,106 @@ PluginModuleParent::NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs return NS_ERROR_FAILURE; } + SetPluginFuncs(pFuncs); + *error = NPERR_NO_ERROR; - if (IsChrome()) { - PluginSettings settings; - GetSettings(&settings); - TimeStamp callNpInitStart = TimeStamp::Now(); - if (!CallNP_Initialize(settings, error)) { - Close(); + return NS_OK; +} + +nsresult +PluginModuleChromeParent::NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs, NPError* error) +{ + PLUGIN_LOG_DEBUG_METHOD; + + mNPNIface = bFuncs; + + if (mShutdown) { + *error = NPERR_GENERIC_ERROR; + return NS_ERROR_FAILURE; + } + + mAsyncInitPluginFuncs = pFuncs; + + if (!mSubprocess->IsConnected()) { + // The subprocess isn't connected yet. Defer NP_Initialize until + // OnProcessLaunched is invoked. + mInitOnAsyncConnect = true; + *error = NPERR_NO_ERROR; + return NS_OK; + } + + if (mIsStartingAsync) { + PluginAsyncSurrogate::NP_GetEntryPoints(pFuncs); + } + + *error = NPERR_NO_ERROR; + + PluginSettings settings; + GetSettings(&settings); + + TimeStamp callNpInitStart = TimeStamp::Now(); + // Asynchronous case + if (mIsStartingAsync) { + if (!SendAsyncNP_Initialize(settings)) { return NS_ERROR_FAILURE; } - else if (*error != NPERR_NO_ERROR) { - Close(); - return NS_OK; - } TimeStamp callNpInitEnd = TimeStamp::Now(); mTimeBlocked += (callNpInitEnd - callNpInitStart); + return NS_PLUGIN_INIT_PENDING; } - SetPluginFuncs(pFuncs); + // Synchronous case + if (!CallNP_Initialize(settings, error)) { + Close(); + return NS_ERROR_FAILURE; + } + else if (*error != NPERR_NO_ERROR) { + Close(); + return NS_OK; + } + TimeStamp callNpInitEnd = TimeStamp::Now(); + mTimeBlocked += (callNpInitEnd - callNpInitStart); + RecvNP_InitializeResult(*error); return NS_OK; } + +bool +PluginModuleParent::RecvNP_InitializeResult(const NPError& aError) +{ + if (aError != NPERR_NO_ERROR) { + OnInitFailure(); + return true; + } + + SetPluginFuncs(mAsyncInitPluginFuncs); + InitAsyncSurrogates(); + + mNPInitialized = true; + return true; +} + +bool +PluginModuleChromeParent::RecvNP_InitializeResult(const NPError& aError) +{ + if (!mContentParent) { + return PluginModuleParent::RecvNP_InitializeResult(aError); + } + bool initOk = aError == NPERR_NO_ERROR; + if (initOk) { + SetPluginFuncs(mAsyncInitPluginFuncs); + if (SendAssociatePluginId()) { + PPluginModule::Bridge(mContentParent, this); + mNPInitialized = true; + } else { + initOk = false; + } + } + return mContentParent->SendLoadPluginResult(mPluginId, initOk); +} + #else + nsresult PluginModuleParent::NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error) { @@ -1435,39 +1800,123 @@ PluginModuleChromeParent::NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error) if (NS_FAILED(rv)) return rv; +#if defined(XP_MACOSX) + if (!mSubprocess->IsConnected()) { + // The subprocess isn't connected yet. Defer NP_Initialize until + // OnProcessLaunched is invoked. + mInitOnAsyncConnect = true; + *error = NPERR_NO_ERROR; + return NS_OK; + } +#else + if (mInitOnAsyncConnect) { + *error = NPERR_NO_ERROR; + return NS_OK; + } +#endif + PluginSettings settings; GetSettings(&settings); + TimeStamp callNpInitStart = TimeStamp::Now(); + if (mIsStartingAsync) { + if (!SendAsyncNP_Initialize(settings)) { + return NS_ERROR_FAILURE; + } + TimeStamp callNpInitEnd = TimeStamp::Now(); + mTimeBlocked += (callNpInitEnd - callNpInitStart); + return NS_PLUGIN_INIT_PENDING; + } + if (!CallNP_Initialize(settings, error)) { Close(); return NS_ERROR_FAILURE; } - if (*error != NPERR_NO_ERROR) { - Close(); - return NS_OK; - } TimeStamp callNpInitEnd = TimeStamp::Now(); mTimeBlocked += (callNpInitEnd - callNpInitStart); + RecvNP_InitializeResult(*error); + return NS_OK; +} +bool +PluginModuleParent::RecvNP_InitializeResult(const NPError& aError) +{ + if (aError != NPERR_NO_ERROR) { + OnInitFailure(); + return true; + } + + if (mIsStartingAsync) { +#if defined(XP_WIN) + SetPluginFuncs(mAsyncInitPluginFuncs); +#endif + InitAsyncSurrogates(); + } + + mNPInitialized = true; + return true; +} + +bool +PluginModuleChromeParent::RecvNP_InitializeResult(const NPError& aError) +{ + bool ok = true; + if (mContentParent) { + if ((ok = SendAssociatePluginId())) { + PPluginModule::Bridge(mContentParent, this); + ok = mContentParent->SendLoadPluginResult(mPluginId, + aError == NPERR_NO_ERROR); + } + } else if (aError == NPERR_NO_ERROR) { + // Initialization steps when e10s is disabled #if defined XP_WIN - // Send the info needed to join the chrome process's audio session to the - // plugin process - nsID id; - nsString sessionName; - nsString iconPath; + if (mIsStartingAsync) { + SetPluginFuncs(mAsyncInitPluginFuncs); + } - if (NS_SUCCEEDED(mozilla::widget::GetAudioSessionData(id, sessionName, - iconPath))) - unused << SendSetAudioSessionData(id, sessionName, iconPath); + // Send the info needed to join the chrome process's audio session to the + // plugin process + nsID id; + nsString sessionName; + nsString iconPath; + + if (NS_SUCCEEDED(mozilla::widget::GetAudioSessionData(id, sessionName, + iconPath))) { + unused << SendSetAudioSessionData(id, sessionName, iconPath); + } #endif #ifdef MOZ_CRASHREPORTER_INJECTOR - InitializeInjector(); + InitializeInjector(); +#endif + } + + return PluginModuleParent::RecvNP_InitializeResult(aError) && ok; +} + #endif - return NS_OK; +void +PluginModuleParent::InitAsyncSurrogates() +{ + uint32_t len = mSurrogateInstances.Length(); + for (uint32_t i = 0; i < len; ++i) { + NPError err; + mAsyncNewRv = mSurrogateInstances[i]->NPP_New(&err); + if (NS_FAILED(mAsyncNewRv)) { + mSurrogateInstances[i]->NotifyAsyncInitFailed(); + continue; + } + } + mSurrogateInstances.Clear(); +} + +bool +PluginModuleParent::RemovePendingSurrogate( + const nsRefPtr& aSurrogate) +{ + return mSurrogateInstances.RemoveElement(aSurrogate); } -#endif nsresult PluginModuleParent::NP_Shutdown(NPError* error) @@ -1520,25 +1969,54 @@ PluginModuleParent::NP_GetEntryPoints(NPPluginFuncs* pFuncs, NPError* error) { NS_ASSERTION(pFuncs, "Null pointer!"); + *error = NPERR_NO_ERROR; + if (mIsStartingAsync && !IsChrome()) { + PluginAsyncSurrogate::NP_GetEntryPoints(pFuncs); + mAsyncInitPluginFuncs = pFuncs; + } else { + SetPluginFuncs(pFuncs); + } + + return NS_OK; +} + +nsresult +PluginModuleChromeParent::NP_GetEntryPoints(NPPluginFuncs* pFuncs, NPError* error) +{ +#if defined(XP_MACOSX) + if (mInitOnAsyncConnect) { + PluginAsyncSurrogate::NP_GetEntryPoints(pFuncs); + mAsyncInitPluginFuncs = pFuncs; + *error = NPERR_NO_ERROR; + return NS_OK; + } +#else + if (mIsStartingAsync) { + PluginAsyncSurrogate::NP_GetEntryPoints(pFuncs); + } + if (!mSubprocess->IsConnected()) { + mAsyncInitPluginFuncs = pFuncs; + mInitOnAsyncConnect = true; + *error = NPERR_NO_ERROR; + return NS_OK; + } +#endif + // We need to have the plugin process update its function table here by // actually calling NP_GetEntryPoints. The parent's function table will // reflect nullptr entries in the child's table once SetPluginFuncs is // called. - if (IsChrome()) { - if (!CallNP_GetEntryPoints(error)) { - return NS_ERROR_FAILURE; - } - else if (*error != NPERR_NO_ERROR) { - return NS_OK; - } + if (!CallNP_GetEntryPoints(error)) { + return NS_ERROR_FAILURE; + } + else if (*error != NPERR_NO_ERROR) { + return NS_OK; } - *error = NPERR_NO_ERROR; - SetPluginFuncs(pFuncs); - - return NS_OK; + return PluginModuleParent::NP_GetEntryPoints(pFuncs, error); } + #endif nsresult @@ -1554,6 +2032,22 @@ PluginModuleParent::NPP_New(NPMIMEType pluginType, NPP instance, return NS_ERROR_FAILURE; } + if (mIsStartingAsync) { + if (!PluginAsyncSurrogate::Create(this, pluginType, instance, mode, + argc, argn, argv)) { + *error = NPERR_GENERIC_ERROR; + return NS_ERROR_FAILURE; + } + + if (!mNPInitialized) { + nsRefPtr surrogate = + PluginAsyncSurrogate::Cast(instance); + mSurrogateInstances.AppendElement(surrogate); + *error = NPERR_NO_ERROR; + return NS_PLUGIN_INIT_PENDING; + } + } + if (mPluginName.IsEmpty()) { GetPluginDetails(mPluginName, mPluginVersion); /** mTimeBlocked measures the time that the main thread has been blocked @@ -1577,6 +2071,21 @@ PluginModuleParent::NPP_New(NPMIMEType pluginType, NPP instance, values.AppendElement(NullableString(argv[i])); } + nsresult rv = NPP_NewInternal(pluginType, instance, mode, names, values, + saved, error); + if (NS_FAILED(rv) || !mIsStartingAsync) { + return rv; + } + return NS_PLUGIN_INIT_PENDING; +} + +nsresult +PluginModuleParent::NPP_NewInternal(NPMIMEType pluginType, NPP instance, + uint16_t mode, + InfallibleTArray& names, + InfallibleTArray& values, + NPSavedData* saved, NPError* error) +{ PluginInstanceParent* parentInstance = new PluginInstanceParent(this, instance, nsDependentCString(pluginType), mNPNIface); @@ -1586,27 +2095,49 @@ PluginModuleParent::NPP_New(NPMIMEType pluginType, NPP instance, return NS_ERROR_FAILURE; } - instance->pdata = parentInstance; + // Release the surrogate reference that was in pdata + nsRefPtr surrogate( + dont_AddRef(PluginAsyncSurrogate::Cast(instance))); + // Now replace it with the instance + instance->pdata = static_cast(parentInstance); + + if (!SendPPluginInstanceConstructor(parentInstance, + nsDependentCString(pluginType), mode, + names, values)) { + // |parentInstance| is automatically deleted. + instance->pdata = nullptr; + *error = NPERR_GENERIC_ERROR; + return NS_ERROR_FAILURE; + } { // Scope for timer Telemetry::AutoTimer timer(GetHistogramKey()); - if (!CallPPluginInstanceConstructor(parentInstance, - nsDependentCString(pluginType), mode, - names, values, error)) { - // |parentInstance| is automatically deleted. - instance->pdata = nullptr; - // if IPC is down, we'll get an immediate "failed" return, but - // without *error being set. So make sure that the error - // condition is signaled to nsNPAPIPluginInstance - if (NPERR_NO_ERROR == *error) + if (mIsStartingAsync) { + MOZ_ASSERT(surrogate); + surrogate->AsyncCallDeparting(); + if (!SendAsyncNPP_New(parentInstance)) { *error = NPERR_GENERIC_ERROR; - return NS_ERROR_FAILURE; + return NS_ERROR_FAILURE; + } + *error = NPERR_NO_ERROR; + } else { + if (!CallSyncNPP_New(parentInstance, error)) { + // if IPC is down, we'll get an immediate "failed" return, but + // without *error being set. So make sure that the error + // condition is signaled to nsNPAPIPluginInstance + if (NPERR_NO_ERROR == *error) { + *error = NPERR_GENERIC_ERROR; + } + return NS_ERROR_FAILURE; + } } } if (*error != NPERR_NO_ERROR) { - NPP_Destroy(instance, 0); + if (!mIsStartingAsync) { + NPP_Destroy(instance, 0); + } return NS_ERROR_FAILURE; } @@ -1660,7 +2191,7 @@ PluginModuleParent::NPP_GetSitesWithData(InfallibleTArray& result) nsresult PluginModuleParent::IsRemoteDrawingCoreAnimation(NPP instance, bool *aDrawing) { - PluginInstanceParent* i = InstCast(instance); + PluginInstanceParent* i = PluginInstanceParent::Cast(instance); if (!i) return NS_ERROR_FAILURE; @@ -1670,7 +2201,7 @@ PluginModuleParent::IsRemoteDrawingCoreAnimation(NPP instance, bool *aDrawing) nsresult PluginModuleParent::ContentsScaleFactorChanged(NPP instance, double aContentsScaleFactor) { - PluginInstanceParent* i = InstCast(instance); + PluginInstanceParent* i = PluginInstanceParent::Cast(instance); if (!i) return NS_ERROR_FAILURE; diff --git a/dom/plugins/ipc/PluginModuleParent.h b/dom/plugins/ipc/PluginModuleParent.h index 6ecae4907a69..c6cba7d49eb2 100644 --- a/dom/plugins/ipc/PluginModuleParent.h +++ b/dom/plugins/ipc/PluginModuleParent.h @@ -40,6 +40,7 @@ namespace plugins { //----------------------------------------------------------------------------- class BrowserStreamParent; +class PluginAsyncSurrogate; class PluginInstanceParent; #ifdef XP_WIN @@ -79,8 +80,8 @@ protected: AllocPPluginInstanceParent(const nsCString& aMimeType, const uint16_t& aMode, const InfallibleTArray& aNames, - const InfallibleTArray& aValues, - NPError* rv) MOZ_OVERRIDE; + const InfallibleTArray& aValues) + MOZ_OVERRIDE; virtual bool DeallocPPluginInstanceParent(PPluginInstanceParent* aActor) MOZ_OVERRIDE; @@ -89,6 +90,13 @@ public: explicit PluginModuleParent(bool aIsChrome); virtual ~PluginModuleParent(); + bool RemovePendingSurrogate(const nsRefPtr& aSurrogate); + + /** @return the state of the pref that controls async plugin init */ + bool IsStartingAsync() const { return mIsStartingAsync; } + /** @return whether this modules NP_Initialize has successfully completed + executing */ + bool IsInitialized() const { return mNPInitialized; } bool IsChrome() const { return mIsChrome; } virtual void SetPlugin(nsNPAPIPlugin* plugin) MOZ_OVERRIDE @@ -108,6 +116,8 @@ public: void ProcessRemoteNativeEventsInInterruptCall(); + virtual bool WaitForIPCConnection() { return true; } + nsCString GetHistogramKey() const { return mPluginName + mPluginVersion; } @@ -159,8 +169,11 @@ protected: virtual bool RecvNPN_ReloadPlugins(const bool& aReloadPages) MOZ_OVERRIDE; - static PluginInstanceParent* InstCast(NPP instance); - static BrowserStreamParent* StreamCast(NPP instance, NPStream* s); + virtual bool + RecvNP_InitializeResult(const NPError& aError) MOZ_OVERRIDE; + + static BrowserStreamParent* StreamCast(NPP instance, NPStream* s, + PluginAsyncSurrogate** aSurrogate = nullptr); protected: virtual void UpdatePluginTimeout() {} @@ -169,6 +182,11 @@ protected: void SetPluginFuncs(NPPluginFuncs* aFuncs); + nsresult NPP_NewInternal(NPMIMEType pluginType, NPP instance, uint16_t mode, + InfallibleTArray& names, + InfallibleTArray& values, + NPSavedData* saved, NPError* error); + // NPP-like API that Gecko calls are trampolined into. These // messages then get forwarded along to the plugin instance, // and then eventually the child process. @@ -235,8 +253,11 @@ protected: virtual nsresult ContentsScaleFactorChanged(NPP instance, double aContentsScaleFactor); #endif + void InitAsyncSurrogates(); + protected: void NotifyPluginCrashed(); + void OnInitFailure(); bool GetSetting(NPNVariable aVariable); void GetSettings(PluginSettings* aSettings); @@ -245,7 +266,7 @@ protected: bool mShutdown; bool mClearSiteDataSupported; bool mGetSitesWithDataSupported; - const NPNetscapeFuncs* mNPNIface; + NPNetscapeFuncs* mNPNIface; nsNPAPIPlugin* mPlugin; ScopedMethodFactory mTaskFactory; nsString mPluginDumpID; @@ -266,18 +287,29 @@ protected: GetPluginDetails(nsACString& aPluginName, nsACString& aPluginVersion); friend class mozilla::dom::CrashReporterParent; + friend class mozilla::plugins::PluginAsyncSurrogate; + + bool mIsStartingAsync; + bool mNPInitialized; + nsTArray> mSurrogateInstances; + nsresult mAsyncNewRv; + NPPluginFuncs* mAsyncInitPluginFuncs; }; class PluginModuleContentParent : public PluginModuleParent { public: + explicit PluginModuleContentParent(); + static PluginLibrary* LoadModule(uint32_t aPluginId); - static PluginModuleContentParent* Create(mozilla::ipc::Transport* aTransport, - base::ProcessId aOtherProcess); + static PluginModuleContentParent* Initialize(mozilla::ipc::Transport* aTransport, + base::ProcessId aOtherProcess); + + static void OnLoadPluginResult(const uint32_t& aPluginId, const bool& aResult); + static void AssociatePluginId(uint32_t aPluginId, base::ProcessId aProcessId); private: - explicit PluginModuleContentParent(); #ifdef MOZ_CRASHREPORTER_INJECTOR void OnCrash(DWORD processID) MOZ_OVERRIDE {} @@ -313,6 +345,17 @@ class PluginModuleChromeParent OnHangUIContinue(); #endif // XP_WIN + virtual bool WaitForIPCConnection() MOZ_OVERRIDE; + + virtual bool + RecvNP_InitializeResult(const NPError& aError) MOZ_OVERRIDE; + + void + SetContentParent(dom::ContentParent* aContentParent); + + bool + SendAssociatePluginId(); + void CachedSettingChanged(); private: @@ -341,8 +384,14 @@ private: PluginProcessParent* Process() const { return mSubprocess; } base::ProcessHandle ChildProcessHandle() { return mSubprocess->GetChildProcessHandle(); } -#if !defined(XP_UNIX) || defined(XP_MACOSX) || defined(MOZ_WIDGET_GONK) - virtual nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error); +#if defined(XP_UNIX) && !defined(XP_MACOSX) && !defined(MOZ_WIDGET_GONK) + virtual nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPPluginFuncs* pFuncs, NPError* error) MOZ_OVERRIDE; +#else + virtual nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error) MOZ_OVERRIDE; +#endif + +#if defined(XP_WIN) || defined(XP_MACOSX) + virtual nsresult NP_GetEntryPoints(NPPluginFuncs* pFuncs, NPError* error) MOZ_OVERRIDE; #endif virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE; @@ -421,17 +470,45 @@ private: #endif friend class mozilla::dom::CrashReporterParent; + friend class mozilla::plugins::PluginAsyncSurrogate; #ifdef MOZ_CRASHREPORTER_INJECTOR void InitializeInjector(); - + void OnCrash(DWORD processID) MOZ_OVERRIDE; DWORD mFlashProcess1; DWORD mFlashProcess2; #endif + void OnProcessLaunched(const bool aSucceeded); + + class LaunchedTask : public LaunchCompleteTask + { + public: + explicit LaunchedTask(PluginModuleChromeParent* aModule) + : mModule(aModule) + { + MOZ_ASSERT(aModule); + } + + void Run() MOZ_OVERRIDE + { + mModule->OnProcessLaunched(mLaunchSucceeded); + } + + private: + PluginModuleChromeParent* mModule; + }; + + friend class LaunchedTask; + + bool mInitOnAsyncConnect; + nsresult mAsyncInitRv; + NPError mAsyncInitError; + dom::ContentParent* mContentParent; nsCOMPtr mOfflineObserver; + bool mIsFlashPlugin; }; } // namespace plugins diff --git a/dom/plugins/ipc/PluginProcessParent.cpp b/dom/plugins/ipc/PluginProcessParent.cpp index d7fb2fde9743..fe45ca2e5b8d 100644 --- a/dom/plugins/ipc/PluginProcessParent.cpp +++ b/dom/plugins/ipc/PluginProcessParent.cpp @@ -12,12 +12,14 @@ #include "mozilla/ipc/BrowserProcessSubThread.h" #include "mozilla/plugins/PluginMessageUtils.h" #include "mozilla/Telemetry.h" +#include "nsThreadUtils.h" using std::vector; using std::string; using mozilla::ipc::BrowserProcessSubThread; using mozilla::ipc::GeckoChildProcessHost; +using mozilla::plugins::LaunchCompleteTask; using mozilla::plugins::PluginProcessParent; using base::ProcessArchitecture; @@ -30,7 +32,9 @@ struct RunnableMethodTraits PluginProcessParent::PluginProcessParent(const std::string& aPluginFilePath) : GeckoChildProcessHost(GeckoProcessType_Plugin), - mPluginFilePath(aPluginFilePath) + mPluginFilePath(aPluginFilePath), + mMainMsgLoop(MessageLoop::current()), + mRunCompleteTaskImmediately(false) { } @@ -39,7 +43,7 @@ PluginProcessParent::~PluginProcessParent() } bool -PluginProcessParent::Launch(int32_t timeoutMs) +PluginProcessParent::Launch(mozilla::UniquePtr aLaunchCompleteTask) { ProcessArchitecture currentArchitecture = base::GetCurrentProcessArchitecture(); uint32_t containerArchitectures = GetSupportedArchitecturesForProcessType(GeckoProcessType_Plugin); @@ -74,10 +78,16 @@ PluginProcessParent::Launch(int32_t timeoutMs) } } + mLaunchCompleteTask = mozilla::Move(aLaunchCompleteTask); + vector args; args.push_back(MungePluginDsoPath(mPluginFilePath)); - Telemetry::AutoTimer timer; - return SyncLaunch(args, timeoutMs, selectedArchitecture); + + bool result = AsyncLaunch(args, selectedArchitecture); + if (!result) { + mLaunchCompleteTask = nullptr; + } + return result; } void @@ -94,3 +104,50 @@ PluginProcessParent::Delete() ioLoop->PostTask(FROM_HERE, NewRunnableMethod(this, &PluginProcessParent::Delete)); } + +void +PluginProcessParent::SetCallRunnableImmediately(bool aCallImmediately) +{ + mRunCompleteTaskImmediately = aCallImmediately; +} + +bool +PluginProcessParent::WaitUntilConnected(int32_t aTimeoutMs) +{ + bool result = GeckoChildProcessHost::WaitUntilConnected(aTimeoutMs); + if (mRunCompleteTaskImmediately && mLaunchCompleteTask) { + if (result) { + mLaunchCompleteTask->SetLaunchSucceeded(); + } + mLaunchCompleteTask->Run(); + mLaunchCompleteTask = nullptr; + } + return result; +} + +void +PluginProcessParent::OnChannelConnected(int32_t peer_pid) +{ + GeckoChildProcessHost::OnChannelConnected(peer_pid); + if (mLaunchCompleteTask && !mRunCompleteTaskImmediately) { + mLaunchCompleteTask->SetLaunchSucceeded(); + mMainMsgLoop->PostTask(FROM_HERE, mLaunchCompleteTask.release()); + } +} + +void +PluginProcessParent::OnChannelError() +{ + GeckoChildProcessHost::OnChannelError(); + if (mLaunchCompleteTask && !mRunCompleteTaskImmediately) { + mMainMsgLoop->PostTask(FROM_HERE, mLaunchCompleteTask.release()); + } +} + +bool +PluginProcessParent::IsConnected() +{ + mozilla::MonitorAutoLock lock(mMonitor); + return mProcessState == PROCESS_CONNECTED; +} + diff --git a/dom/plugins/ipc/PluginProcessParent.h b/dom/plugins/ipc/PluginProcessParent.h index fab54eda0ec3..d20dad348a90 100644 --- a/dom/plugins/ipc/PluginProcessParent.h +++ b/dom/plugins/ipc/PluginProcessParent.h @@ -11,15 +11,32 @@ #include "base/basictypes.h" #include "base/file_path.h" +#include "base/task.h" #include "base/thread.h" #include "base/waitable_event.h" #include "chrome/common/child_process_host.h" #include "mozilla/ipc/GeckoChildProcessHost.h" +#include "mozilla/UniquePtr.h" +#include "nsCOMPtr.h" +#include "nsIRunnable.h" namespace mozilla { namespace plugins { -//----------------------------------------------------------------------------- + +class LaunchCompleteTask : public Task +{ +public: + LaunchCompleteTask() + : mLaunchSucceeded(false) + { + } + + void SetLaunchSucceeded() { mLaunchSucceeded = true; } + +protected: + bool mLaunchSucceeded; +}; class PluginProcessParent : public mozilla::ipc::GeckoChildProcessHost { @@ -28,10 +45,13 @@ public: ~PluginProcessParent(); /** - * Synchronously launch the plugin process. If the process fails to launch - * after timeoutMs, this method will return false. + * Launch the plugin process. If the process fails to launch, + * this method will return false. + * + * @param aLaunchCompleteTask Task that is executed on the main + * thread once the asynchonous launch has completed. */ - bool Launch(int32_t timeoutMs); + bool Launch(UniquePtr aLaunchCompleteTask = UniquePtr()); void Delete(); @@ -45,8 +65,19 @@ public: using mozilla::ipc::GeckoChildProcessHost::GetShutDownEvent; using mozilla::ipc::GeckoChildProcessHost::GetChannel; + void SetCallRunnableImmediately(bool aCallImmediately); + virtual bool WaitUntilConnected(int32_t aTimeoutMs = 0) MOZ_OVERRIDE; + + virtual void OnChannelConnected(int32_t peer_pid) MOZ_OVERRIDE; + virtual void OnChannelError() MOZ_OVERRIDE; + + bool IsConnected(); + private: std::string mPluginFilePath; + UniquePtr mLaunchCompleteTask; + MessageLoop* mMainMsgLoop; + bool mRunCompleteTaskImmediately; DISALLOW_EVIL_CONSTRUCTORS(PluginProcessParent); }; diff --git a/dom/plugins/ipc/PluginScriptableObjectChild.cpp b/dom/plugins/ipc/PluginScriptableObjectChild.cpp index 471b95e0117d..bd3f61eae2aa 100644 --- a/dom/plugins/ipc/PluginScriptableObjectChild.cpp +++ b/dom/plugins/ipc/PluginScriptableObjectChild.cpp @@ -1281,5 +1281,7 @@ PluginScriptableObjectChild::CollectForInstance(NPObjectData* d, void* userArg) PluginScriptableObjectChild::NotifyOfInstanceShutdown(PluginInstanceChild* aInstance) { AssertPluginThread(); - sObjectMap->EnumerateEntries(CollectForInstance, aInstance); + if (sObjectMap) { + sObjectMap->EnumerateEntries(CollectForInstance, aInstance); + } } diff --git a/dom/plugins/ipc/PluginScriptableObjectParent.cpp b/dom/plugins/ipc/PluginScriptableObjectParent.cpp index 3bbf3bc3e748..60a5186e5689 100644 --- a/dom/plugins/ipc/PluginScriptableObjectParent.cpp +++ b/dom/plugins/ipc/PluginScriptableObjectParent.cpp @@ -12,6 +12,7 @@ #include "mozilla/plugins/PluginTypes.h" #include "mozilla/unused.h" #include "nsNPAPIPlugin.h" +#include "PluginAsyncSurrogate.h" #include "PluginScriptableObjectUtils.h" using namespace mozilla; @@ -110,6 +111,7 @@ inline void ReleaseVariant(NPVariant& aVariant, PluginInstanceParent* aInstance) { + PushSurrogateAcceptCalls acceptCalls(aInstance); const NPNetscapeFuncs* npn = GetNetscapeFuncs(aInstance); if (npn) { npn->releasevariantvalue(&aVariant); @@ -643,6 +645,7 @@ PluginScriptableObjectParent::CreateProxyObject() NS_ASSERTION(mInstance, "Must have an instance!"); NS_ASSERTION(mType == Proxy, "Shouldn't call this for non-proxy object!"); + PushSurrogateAcceptCalls acceptCalls(mInstance); const NPNetscapeFuncs* npn = GetNetscapeFuncs(mInstance); NPObject* npobject = npn->createobject(mInstance->GetNPP(), @@ -761,6 +764,7 @@ PluginScriptableObjectParent::AnswerHasMethod(const PluginIdentifier& aId, return true; } + PushSurrogateAcceptCalls acceptCalls(instance); const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); if (!npn) { NS_ERROR("No netscape funcs?!"); @@ -801,6 +805,7 @@ PluginScriptableObjectParent::AnswerInvoke(const PluginIdentifier& aId, return true; } + PushSurrogateAcceptCalls acceptCalls(instance); const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); if (!npn) { NS_ERROR("No netscape funcs?!"); @@ -890,6 +895,7 @@ PluginScriptableObjectParent::AnswerInvokeDefault(const InfallibleTArray return true; } + PushSurrogateAcceptCalls acceptCalls(instance); const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); if (!npn) { NS_WARNING("No netscape funcs?!"); @@ -1204,6 +1215,7 @@ PluginScriptableObjectParent::AnswerConstruct(const InfallibleTArray& a return true; } + PushSurrogateAcceptCalls acceptCalls(instance); const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); if (!npn) { NS_ERROR("No netscape funcs?!"); @@ -1296,6 +1308,7 @@ PluginScriptableObjectParent::AnswerNPN_Evaluate(const nsCString& aScript, return true; } + PushSurrogateAcceptCalls acceptCalls(instance); const NPNetscapeFuncs* npn = GetNetscapeFuncs(instance); if (!npn) { NS_ERROR("No netscape funcs?!"); diff --git a/dom/plugins/ipc/PluginScriptableObjectParent.h b/dom/plugins/ipc/PluginScriptableObjectParent.h index 1126f3d18663..dfb67e99858c 100644 --- a/dom/plugins/ipc/PluginScriptableObjectParent.h +++ b/dom/plugins/ipc/PluginScriptableObjectParent.h @@ -16,6 +16,7 @@ namespace mozilla { namespace plugins { +class PluginAsyncSurrogate; class PluginInstanceParent; class PluginScriptableObjectParent; diff --git a/dom/plugins/ipc/moz.build b/dom/plugins/ipc/moz.build index bdae35049fd3..f1f171efbf03 100644 --- a/dom/plugins/ipc/moz.build +++ b/dom/plugins/ipc/moz.build @@ -21,7 +21,9 @@ EXPORTS.mozilla.plugins += [ 'NPEventOSX.h', 'NPEventUnix.h', 'NPEventWindows.h', + 'PluginAsyncSurrogate.h', 'PluginBridge.h', + 'PluginDataResolver.h', 'PluginInstanceChild.h', 'PluginInstanceParent.h', 'PluginMessageUtils.h', @@ -80,6 +82,7 @@ UNIFIED_SOURCES += [ 'BrowserStreamParent.cpp', 'ChildAsyncCall.cpp', 'ChildTimer.cpp', + 'PluginAsyncSurrogate.cpp', 'PluginBackgroundDestroyer.cpp', 'PluginInstanceParent.cpp', 'PluginMessageUtils.cpp', diff --git a/dom/security/nsMixedContentBlocker.cpp b/dom/security/nsMixedContentBlocker.cpp index 1bdfc6e364a0..ef68db3343c5 100644 --- a/dom/security/nsMixedContentBlocker.cpp +++ b/dom/security/nsMixedContentBlocker.cpp @@ -178,9 +178,9 @@ LogMixedContentMessage(MixedContentTypes aClassification, severityFlag = nsIScriptError::warningFlag; messageCategory.AssignLiteral("Mixed Content Message"); if (aClassification == eMixedDisplay) { - messageLookupKey.AssignLiteral("LoadingMixedDisplayContent"); + messageLookupKey.AssignLiteral("LoadingMixedDisplayContent2"); } else { - messageLookupKey.AssignLiteral("LoadingMixedActiveContent"); + messageLookupKey.AssignLiteral("LoadingMixedActiveContent2"); } } diff --git a/editor/libeditor/IMETextTxn.cpp b/editor/libeditor/IMETextTxn.cpp index 973573aec2db..0ba3485c156e 100644 --- a/editor/libeditor/IMETextTxn.cpp +++ b/editor/libeditor/IMETextTxn.cpp @@ -42,7 +42,9 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(IMETextTxn, EditTxn, // mRangeList can't lead to cycles NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IMETextTxn) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsITransaction, IMETextTxn) + if (aIID.Equals(NS_GET_IID(IMETextTxn))) { + foundInterface = static_cast(this); + } else NS_INTERFACE_MAP_END_INHERITING(EditTxn) NS_IMPL_ADDREF_INHERITED(IMETextTxn, EditTxn) diff --git a/editor/libeditor/InsertTextTxn.cpp b/editor/libeditor/InsertTextTxn.cpp index 4c896e2738b1..17efc2959b5e 100644 --- a/editor/libeditor/InsertTextTxn.cpp +++ b/editor/libeditor/InsertTextTxn.cpp @@ -38,7 +38,9 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(InsertTextTxn, EditTxn, NS_IMPL_ADDREF_INHERITED(InsertTextTxn, EditTxn) NS_IMPL_RELEASE_INHERITED(InsertTextTxn, EditTxn) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(InsertTextTxn) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsITransaction, InsertTextTxn) + if (aIID.Equals(NS_GET_IID(InsertTextTxn))) { + foundInterface = static_cast(this); + } else NS_INTERFACE_MAP_END_INHERITING(EditTxn) diff --git a/editor/libeditor/tests/mochitest.ini b/editor/libeditor/tests/mochitest.ini index c8bbeb142322..7662492ebcf1 100644 --- a/editor/libeditor/tests/mochitest.ini +++ b/editor/libeditor/tests/mochitest.ini @@ -154,3 +154,4 @@ skip-if = e10s [test_spellcheck_pref.html] skip-if = toolkit == 'android' [test_bug1068979.html] +[test_bug1109465.html] diff --git a/editor/libeditor/tests/test_bug1109465.html b/editor/libeditor/tests/test_bug1109465.html new file mode 100644 index 000000000000..2b6635ae213c --- /dev/null +++ b/editor/libeditor/tests/test_bug1109465.html @@ -0,0 +1,69 @@ + + + + + Test for Bug 1109465 + + + + + +
+ +
+ +
+
+ + + + + diff --git a/ipc/glue/GeckoChildProcessHost.cpp b/ipc/glue/GeckoChildProcessHost.cpp index 1460b07f5ceb..6e7733f24492 100644 --- a/ipc/glue/GeckoChildProcessHost.cpp +++ b/ipc/glue/GeckoChildProcessHost.cpp @@ -315,8 +315,6 @@ GeckoChildProcessHost::SyncLaunch(std::vector aExtraOpts, int aTime { PrepareLaunch(); - PRIntervalTime timeoutTicks = (aTimeoutMs > 0) ? - PR_MillisecondsToInterval(aTimeoutMs) : PR_INTERVAL_NO_TIMEOUT; MessageLoop* ioLoop = XRE_GetIOMessageLoop(); NS_ASSERTION(MessageLoop::current() != ioLoop, "sync launch from the IO thread NYI"); @@ -324,8 +322,40 @@ GeckoChildProcessHost::SyncLaunch(std::vector aExtraOpts, int aTime NewRunnableMethod(this, &GeckoChildProcessHost::RunPerformAsyncLaunch, aExtraOpts, arch)); + + return WaitUntilConnected(aTimeoutMs); +} + +bool +GeckoChildProcessHost::AsyncLaunch(std::vector aExtraOpts, + base::ProcessArchitecture arch) +{ + PrepareLaunch(); + + MessageLoop* ioLoop = XRE_GetIOMessageLoop(); + ioLoop->PostTask(FROM_HERE, + NewRunnableMethod(this, + &GeckoChildProcessHost::RunPerformAsyncLaunch, + aExtraOpts, arch)); + + // This may look like the sync launch wait, but we only delay as + // long as it takes to create the channel. + MonitorAutoLock lock(mMonitor); + while (mProcessState < CHANNEL_INITIALIZED) { + lock.Wait(); + } + + return true; +} + +bool +GeckoChildProcessHost::WaitUntilConnected(int32_t aTimeoutMs) +{ // NB: this uses a different mechanism than the chromium parent // class. + PRIntervalTime timeoutTicks = (aTimeoutMs > 0) ? + PR_MillisecondsToInterval(aTimeoutMs) : PR_INTERVAL_NO_TIMEOUT; + MonitorAutoLock lock(mMonitor); PRIntervalTime waitStart = PR_IntervalNow(); PRIntervalTime current; @@ -354,27 +384,6 @@ GeckoChildProcessHost::SyncLaunch(std::vector aExtraOpts, int aTime return mProcessState == PROCESS_CONNECTED; } -bool -GeckoChildProcessHost::AsyncLaunch(std::vector aExtraOpts) -{ - PrepareLaunch(); - - MessageLoop* ioLoop = XRE_GetIOMessageLoop(); - ioLoop->PostTask(FROM_HERE, - NewRunnableMethod(this, - &GeckoChildProcessHost::RunPerformAsyncLaunch, - aExtraOpts, base::GetCurrentProcessArchitecture())); - - // This may look like the sync launch wait, but we only delay as - // long as it takes to create the channel. - MonitorAutoLock lock(mMonitor); - while (mProcessState < CHANNEL_INITIALIZED) { - lock.Wait(); - } - - return true; -} - bool GeckoChildProcessHost::LaunchAndWaitForProcessHandle(StringVector aExtraOpts) { diff --git a/ipc/glue/GeckoChildProcessHost.h b/ipc/glue/GeckoChildProcessHost.h index b7a8014b06fb..d5917de0197a 100644 --- a/ipc/glue/GeckoChildProcessHost.h +++ b/ipc/glue/GeckoChildProcessHost.h @@ -52,7 +52,10 @@ public: // Block until the IPC channel for our subprocess is initialized, // but no longer. The child process may or may not have been // created when this method returns. - bool AsyncLaunch(StringVector aExtraOpts=StringVector()); + bool AsyncLaunch(StringVector aExtraOpts=StringVector(), + base::ProcessArchitecture arch=base::GetCurrentProcessArchitecture()); + + virtual bool WaitUntilConnected(int32_t aTimeoutMs = 0); // Block until the IPC channel for our subprocess is initialized and // the OS process is created. The subprocess may or may not have diff --git a/ipc/glue/MessageChannel.cpp b/ipc/glue/MessageChannel.cpp index 96c9187dc2c1..93ff5553ff53 100644 --- a/ipc/glue/MessageChannel.cpp +++ b/ipc/glue/MessageChannel.cpp @@ -298,6 +298,7 @@ MessageChannel::MessageChannel(MessageListener *aListener) mRecvdErrors(0), mRemoteStackDepthGuess(false), mSawInterruptOutMsg(false), + mIsWaitingForIncoming(false), mAbortOnError(false), mBlockScripts(false), mFlags(REQUIRE_DEFAULT), @@ -664,7 +665,8 @@ MessageChannel::OnMessageReceivedFromLink(const Message& aMsg) } bool shouldWakeUp = AwaitingInterruptReply() || - (AwaitingSyncReply() && !ShouldDeferMessage(aMsg)); + (AwaitingSyncReply() && !ShouldDeferMessage(aMsg)) || + AwaitingIncomingMessage(); // There are three cases we're concerned about, relating to the state of the // main thread: @@ -987,6 +989,35 @@ MessageChannel::Call(Message* aMsg, Message* aReply) return true; } +bool +MessageChannel::WaitForIncomingMessage() +{ +#ifdef OS_WIN + SyncStackFrame frame(this, true); +#endif + + { // Scope for lock + MonitorAutoLock lock(*mMonitor); + AutoEnterWaitForIncoming waitingForIncoming(*this); + if (mChannelState != ChannelConnected) { + return false; + } + if (!HasPendingEvents()) { + return WaitForInterruptNotify(); + } + } + + return OnMaybeDequeueOne(); +} + +bool +MessageChannel::HasPendingEvents() +{ + AssertWorkerThread(); + mMonitor->AssertCurrentThreadOwns(); + return Connected() && !mPending.empty(); +} + bool MessageChannel::InterruptEventOccurred() { @@ -1546,7 +1577,7 @@ MessageChannel::OnChannelErrorFromLink() if (InterruptStackDepth() > 0) NotifyWorkerThread(); - if (AwaitingSyncReply()) + if (AwaitingSyncReply() || AwaitingIncomingMessage()) NotifyWorkerThread(); if (ChannelClosing != mChannelState) { diff --git a/ipc/glue/MessageChannel.h b/ipc/glue/MessageChannel.h index 3c7def888cfb..894f075696a6 100644 --- a/ipc/glue/MessageChannel.h +++ b/ipc/glue/MessageChannel.h @@ -124,6 +124,9 @@ class MessageChannel : HasResultCodes // Make an Interrupt call to the other side of the channel bool Call(Message* aMsg, Message* aReply); + // Wait until a message is received + bool WaitForIncomingMessage(); + bool CanSend() const; void SetReplyTimeoutMs(int32_t aTimeoutMs); @@ -214,6 +217,7 @@ class MessageChannel : HasResultCodes void DispatchOnChannelConnected(); bool InterruptEventOccurred(); + bool HasPendingEvents(); bool ProcessPendingRequest(const Message &aUrgent); @@ -319,6 +323,30 @@ class MessageChannel : HasResultCodes mMonitor->AssertCurrentThreadOwns(); return !mInterruptStack.empty(); } + bool AwaitingIncomingMessage() const { + mMonitor->AssertCurrentThreadOwns(); + return mIsWaitingForIncoming; + } + + class MOZ_STACK_CLASS AutoEnterWaitForIncoming + { + public: + explicit AutoEnterWaitForIncoming(MessageChannel& aChannel) + : mChannel(aChannel) + { + aChannel.mMonitor->AssertCurrentThreadOwns(); + aChannel.mIsWaitingForIncoming = true; + } + + ~AutoEnterWaitForIncoming() + { + mChannel.mIsWaitingForIncoming = false; + } + + private: + MessageChannel& mChannel; + }; + friend class AutoEnterWaitForIncoming; // Returns true if we're dispatching a sync message's callback. bool DispatchingSyncMessage() const { @@ -639,6 +667,11 @@ class MessageChannel : HasResultCodes // ExitedCxxStack(), from which this variable is reset. bool mSawInterruptOutMsg; + // Are we waiting on this channel for an incoming message? This is used + // to implement WaitForIncomingMessage(). Must only be accessed while owning + // mMonitor. + bool mIsWaitingForIncoming; + // Map of replies received "out of turn", because of Interrupt // in-calls racing with replies to outstanding in-calls. See // https://bugzilla.mozilla.org/show_bug.cgi?id=521929. diff --git a/ipc/glue/WindowsMessageLoop.cpp b/ipc/glue/WindowsMessageLoop.cpp index 8cda66fc8368..59473c8a9800 100644 --- a/ipc/glue/WindowsMessageLoop.cpp +++ b/ipc/glue/WindowsMessageLoop.cpp @@ -966,7 +966,7 @@ MessageChannel::WaitForInterruptNotify() return WaitForSyncNotify(); } - if (!InterruptStackDepth()) { + if (!InterruptStackDepth() && !AwaitingIncomingMessage()) { // There is currently no way to recover from this condition. NS_RUNTIMEABORT("StackDepth() is 0 in call to MessageChannel::WaitForNotify!"); } diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index 726403c63cfa..faf5484aa35f 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -1673,7 +1673,7 @@ static bool EnableTraceLogger(JSContext *cx, unsigned argc, jsval *vp) { CallArgs args = CallArgsFromVp(argc, vp); - TraceLogger *logger = TraceLoggerForMainThread(cx->runtime()); + TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime()); args.rval().setBoolean(TraceLoggerEnable(logger, cx)); return true; @@ -1683,7 +1683,7 @@ static bool DisableTraceLogger(JSContext *cx, unsigned argc, jsval *vp) { CallArgs args = CallArgsFromVp(argc, vp); - TraceLogger *logger = TraceLoggerForMainThread(cx->runtime()); + TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime()); args.rval().setBoolean(TraceLoggerDisable(logger)); return true; diff --git a/js/src/builtin/TypedArray.js b/js/src/builtin/TypedArray.js index 4ba7baa474aa..407d5c8b1a46 100644 --- a/js/src/builtin/TypedArray.js +++ b/js/src/builtin/TypedArray.js @@ -163,6 +163,53 @@ function TypedArrayIndexOf(searchElement, fromIndex = 0) { return -1; } +// ES6 draft rev30 (2014/12/24) 22.2.3.14 %TypedArray%.prototype.join(separator). +function TypedArrayJoin(separator) { + // This function is not generic. + if (!IsObject(this) || !IsTypedArray(this)) { + return callFunction(CallTypedArrayMethodIfWrapped, this, separator, "TypedArrayJoin"); + } + + // Steps 1-2. + var O = this; + + // Steps 3-5. + var len = TypedArrayLength(O); + + // Steps 6-7. + var sep = separator === undefined ? "," : ToString(separator); + + // Step 8. + if (len === 0) + return ""; + + // Step 9. + var element0 = O[0]; + + // Steps 10-11. + // Omit the 'if' clause in step 10, since typed arrays can not have undefined or null elements. + var R = ToString(element0); + + // Steps 12-13. + for (var k = 1; k < len; k++) { + // Step 13.a. + var S = R + sep; + + // Step 13.b. + var element = O[k]; + + // Steps 13.c-13.d. + // Omit the 'if' clause in step 13.c, since typed arrays can not have undefined or null elements. + var next = ToString(element); + + // Step 13.e. + R = S + next; + } + + // Step 14. + return R; +} + // ES6 draft rev29 (2014/12/06) 22.2.3.16 %TypedArray%.prototype.lastIndexOf(searchElement [,fromIndex]). function TypedArrayLastIndexOf(searchElement, fromIndex = undefined) { // This function is not generic. diff --git a/js/src/configure.in b/js/src/configure.in index b6877824aa95..80fd9ad88ced 100644 --- a/js/src/configure.in +++ b/js/src/configure.in @@ -2985,16 +2985,16 @@ AC_SUBST(MOZ_OPTIMIZE_SIZE_TWEAK) AC_SUBST(MOZ_PGO_OPTIMIZE_FLAGS) dnl ======================================================== -dnl = Enable trace logging +dnl = Disable trace logging dnl ======================================================== -MOZ_ARG_ENABLE_BOOL(trace-logging, -[ --enable-trace-logging Enable trace logging], - ENABLE_TRACE_LOGGING=1, +ENABLE_TRACE_LOGGING=1 +MOZ_ARG_DISABLE_BOOL(trace-logging, +[ --disable-trace-logging Disable trace logging], ENABLE_TRACE_LOGGING= ) AC_SUBST(ENABLE_TRACE_LOGGING) -if test "$ENABLE_TRACE_LOGGING"; then +if test -n "$ENABLE_TRACE_LOGGING"; then AC_DEFINE(JS_TRACE_LOGGING) fi diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index 88d8be9d35f0..83ccdc533dbd 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -220,14 +220,14 @@ frontend::CompileScript(ExclusiveContext *cx, LifoAlloc *alloc, HandleObject sco RootedString source(cx, source_); - js::TraceLogger *logger = nullptr; + js::TraceLoggerThread *logger = nullptr; if (cx->isJSContext()) logger = TraceLoggerForMainThread(cx->asJSContext()->runtime()); else logger = TraceLoggerForCurrentThread(); - uint32_t logId = js::TraceLogCreateTextId(logger, options); - js::AutoTraceLog scriptLogger(logger, logId); - js::AutoTraceLog typeLogger(logger, TraceLogger::ParserCompileScript); + js::TraceLoggerEvent event(logger, TraceLogger_AnnotateScripts, options); + js::AutoTraceLog scriptLogger(logger, event); + js::AutoTraceLog typeLogger(logger, TraceLogger_ParserCompileScript); /* * The scripted callerFrame can only be given for compile-and-go scripts @@ -473,10 +473,10 @@ frontend::CompileLazyFunction(JSContext *cx, Handle lazy, const cha .setNoScriptRval(false) .setSelfHostingMode(false); - js::TraceLogger *logger = js::TraceLoggerForMainThread(cx->runtime()); - uint32_t logId = js::TraceLogCreateTextId(logger, options); - js::AutoTraceLog scriptLogger(logger, logId); - js::AutoTraceLog typeLogger(logger, TraceLogger::ParserCompileLazy); + js::TraceLoggerThread *logger = js::TraceLoggerForMainThread(cx->runtime()); + js::TraceLoggerEvent event(logger, TraceLogger_AnnotateScripts, options); + js::AutoTraceLog scriptLogger(logger, event); + js::AutoTraceLog typeLogger(logger, TraceLogger_ParserCompileLazy); Parser parser(cx, &cx->tempLifoAlloc(), options, chars, length, /* foldConstants = */ true, nullptr, lazy); @@ -531,10 +531,10 @@ CompileFunctionBody(JSContext *cx, MutableHandleFunction fun, const ReadOnlyComp const AutoNameVector &formals, SourceBufferHolder &srcBuf, HandleObject enclosingScope, GeneratorKind generatorKind) { - js::TraceLogger *logger = js::TraceLoggerForMainThread(cx->runtime()); - uint32_t logId = js::TraceLogCreateTextId(logger, options); - js::AutoTraceLog scriptLogger(logger, logId); - js::AutoTraceLog typeLogger(logger, TraceLogger::ParserCompileFunction); + js::TraceLoggerThread *logger = js::TraceLoggerForMainThread(cx->runtime()); + js::TraceLoggerEvent event(logger, TraceLogger_AnnotateScripts, options); + js::AutoTraceLog scriptLogger(logger, event); + js::AutoTraceLog typeLogger(logger, TraceLogger_ParserCompileFunction); // FIXME: make Function pass in two strings and parse them as arguments and // ProgramElements respectively. diff --git a/js/src/jit-test/tests/tracelogger/drainTraceLogger.js b/js/src/jit-test/tests/tracelogger/drainTraceLogger.js new file mode 100644 index 000000000000..80b96b341322 --- /dev/null +++ b/js/src/jit-test/tests/tracelogger/drainTraceLogger.js @@ -0,0 +1,88 @@ +function TestDrainTraceLoggerInvariants(obj) { + var scripts = 0; + var stops = 0; + for (var i = 0; i < objs.length; i++) { + if (objs[i].logType == "Scripts") { + scripts++; + assertEq("fileName" in objs[i], true); + assertEq("lineNumber" in objs[i], true); + assertEq("columnNumber" in objs[i], true); + } else if (objs[i].logType == "Stop") { + stops++; + } else { + assertEq(true, false); + } + } + assertEq(scripts, stops); +} + +function GetMaxScriptDepth(obj) { + var max_depth = 0; + for (var i = 0; i < objs.length; i++) { + if (objs[i].logType == "Stop") + depth--; + else { + depth++; + if (depth > max_depth) + max_depth = depth; + } + } + return max_depth; +} + +function foo1() { + foo2(); +} +function foo2() { + +} + +var du = new Debugger(); +if (typeof du.drainTraceLoggerTraces == "function") { +print(1); + // Test normal setup. + du = new Debugger(); + du.setupTraceLoggerForTraces(); + + du.startTraceLogger(); + du.endTraceLogger(); + + var objs = du.drainTraceLoggerTraces(); + TestDrainTraceLoggerInvariants(objs); + var empty_depth = GetMaxScriptDepth(objs); + var empty_length = objs.length; + + // Test basic script. + for (var i=0; i<20; i++) + foo1(); + + du = new Debugger(); + du.setupTraceLoggerTraces(); + + du.startTraceLogger(); + foo1(); + du.endTraceLogger(); + + var objs = du.drainTraceLoggerTraces(); + TestDrainTraceLoggerInvariants(objs); + assertEq(empty_depth + 2 == GetMaxScriptDepth(objs)); + assertEq(empty_length + 4 == GetMaxScriptDepth(objs)); + + // Test basic script. + for (var i=0; i<20; i++) + foo1(); + + du = new Debugger(); + du.setupTraceLoggerForTraces(); + + du.startTraceLogger(); + for (var i=0; i<100; i++) { + foo1(); + } + du.endTraceLogger(); + + var objs = du.drainTraceLoggerTraces(); + TestDrainTraceLoggerInvariants(objs); + assertEq(empty_depth + 2 == GetMaxScriptDepth(objs)); + assertEq(empty_length + 4*100 == GetMaxScriptDepth(objs)); +} diff --git a/js/src/jit-test/tests/tracelogger/setupTraceLogger.js b/js/src/jit-test/tests/tracelogger/setupTraceLogger.js new file mode 100644 index 000000000000..1c409739d866 --- /dev/null +++ b/js/src/jit-test/tests/tracelogger/setupTraceLogger.js @@ -0,0 +1,70 @@ + +var du = new Debugger(); +if (typeof du.setupTraceLogger == "function") { + + // Try enabling. + assertEq(du.setupTraceLogger({ + Scripts: true + }), true); + + // No fail on re-enabling. + assertEq(du.setupTraceLogger({ + Scripts: true + }), true); + + // Try disabling. + assertEq(du.setupTraceLogger({ + Scripts: false + }), true); + + // No fail on re-disabling. + assertEq(du.setupTraceLogger({ + Scripts: false + }), true); + + // Throw exception if TraceLog item to report isn't found. + var success = du.setupTraceLogger({ + Scripts: false, + Test: true + }); + assertEq(success, false); + + // SetupTraceLogger only enables individual items, + // when all items can be toggled. + du.startTraceLogger(); + var obj = du.drainTraceLogger(); + du.setupTraceLogger({ + Scripts: true, + Test: true, + }); + assertEq(du.drainTraceLogger().length, 0); + du.endTraceLogger(); + + // Expects an object as first argument. + succes = du.setupTraceLogger("blaat"); + assertEq(succes, false); + + // Expects an object as first argument. + succes = du.setupTraceLogger("blaat"); + assertEq(succes, false); + + // Expects an object as first argument. + failed = false; + try { + du.setupTraceLogger(); + } catch (e) { + failed = true; + } + assertEq(failed, true); + + // No problem with added to many arguments. + succes = du.setupTraceLogger({}, "test"); + assertEq(succes, true); +} + +var du2 = new Debugger(); +if (typeof du2.setupTraceLoggerForTraces == "function") { + du2.setupTraceLoggerForTraces({}); + du2.setupTraceLoggerForTraces("test"); + du2.setupTraceLoggerForTraces({}, "test"); +} diff --git a/js/src/jit/Bailouts.cpp b/js/src/jit/Bailouts.cpp index 2234c8446373..25fa715d19e7 100644 --- a/js/src/jit/Bailouts.cpp +++ b/js/src/jit/Bailouts.cpp @@ -41,8 +41,8 @@ jit::Bailout(BailoutStack *sp, BaselineBailoutInfo **bailoutInfo) JitFrameIterator iter(jitActivations); MOZ_ASSERT(!iter.ionScript()->invalidated()); - TraceLogger *logger = TraceLoggerForMainThread(cx->runtime()); - TraceLogTimestamp(logger, TraceLogger::Bailout); + TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime()); + TraceLogTimestamp(logger, TraceLogger_Bailout); JitSpew(JitSpew_IonBailouts, "Took bailout! Snapshot offset: %d", iter.snapshotOffset()); @@ -107,8 +107,8 @@ jit::InvalidationBailout(InvalidationBailoutStack *sp, size_t *frameSizeOut, BailoutFrameInfo bailoutData(jitActivations, sp); JitFrameIterator iter(jitActivations); - TraceLogger *logger = TraceLoggerForMainThread(cx->runtime()); - TraceLogTimestamp(logger, TraceLogger::Invalidation); + TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime()); + TraceLogTimestamp(logger, TraceLogger_Invalidation); JitSpew(JitSpew_IonBailouts, "Took invalidation bailout! Snapshot offset: %d", iter.snapshotOffset()); diff --git a/js/src/jit/BaselineBailouts.cpp b/js/src/jit/BaselineBailouts.cpp index d1a1a9b8852f..3e3595579d7a 100644 --- a/js/src/jit/BaselineBailouts.cpp +++ b/js/src/jit/BaselineBailouts.cpp @@ -1381,9 +1381,9 @@ jit::BailoutIonToBaseline(JSContext *cx, JitActivation *activation, JitFrameIter MOZ_ASSERT(poppedLastSPSFrameOut); MOZ_ASSERT(!*poppedLastSPSFrameOut); - TraceLogger *logger = TraceLoggerForMainThread(cx->runtime()); - TraceLogStopEvent(logger, TraceLogger::IonMonkey); - TraceLogStartEvent(logger, TraceLogger::Baseline); + TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime()); + TraceLogStopEvent(logger, TraceLogger_IonMonkey); + TraceLogStartEvent(logger, TraceLogger_Baseline); // The caller of the top frame must be one of the following: // IonJS - Ion calling into Ion. @@ -1494,8 +1494,12 @@ jit::BailoutIonToBaseline(JSContext *cx, JitActivation *activation, JitFrameIter snapIter.settleOnFrame(); if (frameNo > 0) { - TraceLogStartEvent(logger, TraceLogCreateTextId(logger, scr)); - TraceLogStartEvent(logger, TraceLogger::Baseline); + // TraceLogger doesn't create entries for inlined frames. But we + // see them in Baseline. Here we create the start events of those + // entries. So they correspond to what we will see in Baseline. + TraceLoggerEvent scriptEvent(logger, TraceLogger_Scripts, scr); + TraceLogStartEvent(logger, scriptEvent); + TraceLogStartEvent(logger, TraceLogger_Baseline); } JitSpew(JitSpew_BaselineBailouts, " FrameNo %d", frameNo); diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index 0bb33d90eca4..ef0dafd0aa85 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -81,9 +81,10 @@ BaselineCompiler::compile() JitSpew(JitSpew_Codegen, "# Emitting baseline code for script %s:%d", script->filename(), script->lineno()); - TraceLogger *logger = TraceLoggerForMainThread(cx->runtime()); - AutoTraceLog logScript(logger, TraceLogCreateTextId(logger, script)); - AutoTraceLog logCompile(logger, TraceLogger::BaselineCompilation); + TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime()); + TraceLoggerEvent scriptEvent(logger, TraceLogger_AnnotateScripts, script); + AutoTraceLog logScript(logger, scriptEvent); + AutoTraceLog logCompile(logger, TraceLogger_BaselineCompilation); if (!script->ensureHasTypes(cx) || !script->ensureHasAnalyzedArgsUsage(cx)) return Method_Error; @@ -175,6 +176,10 @@ BaselineCompiler::compile() prologueOffset_.fixup(&masm); epilogueOffset_.fixup(&masm); spsPushToggleOffset_.fixup(&masm); +#ifdef JS_TRACE_LOGGING + traceLoggerEnterToggleOffset_.fixup(&masm); + traceLoggerExitToggleOffset_.fixup(&masm); +#endif postDebugPrologueOffset_.fixup(&masm); // Note: There is an extra entry in the bytecode type map for the search hint, see below. @@ -184,6 +189,8 @@ BaselineCompiler::compile() BaselineScript::New(script, prologueOffset_.offset(), epilogueOffset_.offset(), spsPushToggleOffset_.offset(), + traceLoggerEnterToggleOffset_.offset(), + traceLoggerExitToggleOffset_.offset(), postDebugPrologueOffset_.offset(), icEntries_.length(), pcMappingIndexEntries.length(), @@ -239,6 +246,11 @@ BaselineCompiler::compile() if (cx->runtime()->spsProfiler.enabled()) baselineScript->toggleSPS(true); +#ifdef JS_TRACE_LOGGING + // Initialize the tracelogger instrumentation. + baselineScript->initTraceLogger(cx->runtime(), script); +#endif + uint32_t *bytecodeMap = baselineScript->bytecodeTypeMap(); types::FillBytecodeTypeMap(script, bytecodeMap); @@ -375,13 +387,8 @@ BaselineCompiler::emitPrologue() masm.bind(&earlyStackCheckFailed); #ifdef JS_TRACE_LOGGING - TraceLogger *logger = TraceLoggerForMainThread(cx->runtime()); - Register loggerReg = RegisterSet::Volatile().takeGeneral(); - masm.Push(loggerReg); - masm.movePtr(ImmPtr(logger), loggerReg); - masm.tracelogStart(loggerReg, TraceLogCreateTextId(logger, script)); - masm.tracelogStart(loggerReg, TraceLogger::Baseline); - masm.Pop(loggerReg); + if (!emitTraceLoggerEnter()) + return false; #endif // Record the offset of the prologue, because Ion can bailout before @@ -425,15 +432,8 @@ BaselineCompiler::emitEpilogue() masm.bind(&return_); #ifdef JS_TRACE_LOGGING - TraceLogger *logger = TraceLoggerForMainThread(cx->runtime()); - Register loggerReg = RegisterSet::Volatile().takeGeneral(); - masm.Push(loggerReg); - masm.movePtr(ImmPtr(logger), loggerReg); - masm.tracelogStop(loggerReg, TraceLogger::Baseline); - // Stop the script. Using a stop without checking the textId, since we - // we didn't save the textId for the script. - masm.tracelogStop(loggerReg); - masm.Pop(loggerReg); + if (!emitTraceLoggerExit()) + return false; #endif // Pop SPS frame if necessary @@ -773,6 +773,64 @@ BaselineCompiler::emitDebugTrap() return true; } +#ifdef JS_TRACE_LOGGING +bool +BaselineCompiler::emitTraceLoggerEnter() +{ + TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime()); + RegisterSet regs = RegisterSet::Volatile(); + Register loggerReg = regs.takeGeneral(); + Register scriptReg = regs.takeGeneral(); + + Label noTraceLogger; + traceLoggerEnterToggleOffset_ = masm.toggledJump(&noTraceLogger); + + masm.Push(loggerReg); + masm.Push(scriptReg); + + masm.movePtr(ImmPtr(logger), loggerReg); + + // Script start. + masm.movePtr(ImmGCPtr(script), scriptReg); + masm.loadPtr(Address(scriptReg, JSScript::offsetOfBaselineScript()), scriptReg); + Address scriptEvent(scriptReg, BaselineScript::offsetOfTraceLoggerScriptEvent()); + masm.computeEffectiveAddress(scriptEvent, scriptReg); + masm.tracelogStartEvent(loggerReg, scriptReg); + + // Engine start. + masm.tracelogStartId(loggerReg, TraceLogger_Baseline, /* force = */ true); + + masm.Pop(scriptReg); + masm.Pop(loggerReg); + + masm.bind(&noTraceLogger); + + return true; +} + +bool +BaselineCompiler::emitTraceLoggerExit() +{ + TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime()); + Register loggerReg = RegisterSet::Volatile().takeGeneral(); + + Label noTraceLogger; + traceLoggerExitToggleOffset_ = masm.toggledJump(&noTraceLogger); + + masm.Push(loggerReg); + masm.movePtr(ImmPtr(logger), loggerReg); + + masm.tracelogStopId(loggerReg, TraceLogger_Baseline, /* force = */ true); + masm.tracelogStopId(loggerReg, TraceLogger_Scripts, /* force = */ true); + + masm.Pop(loggerReg); + + masm.bind(&noTraceLogger); + + return true; +} +#endif + bool BaselineCompiler::emitSPSPush() { diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index 47a949e72689..067547696160 100644 --- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -254,6 +254,8 @@ class BaselineCompiler : public BaselineCompilerSpecific bool emitArgumentTypeChecks(); bool emitDebugPrologue(); bool emitDebugTrap(); + bool emitTraceLoggerEnter(); + bool emitTraceLoggerExit(); bool emitSPSPush(); void emitSPSPop(); diff --git a/js/src/jit/BaselineJIT.cpp b/js/src/jit/BaselineJIT.cpp index cc5927e58849..9383079d707d 100644 --- a/js/src/jit/BaselineJIT.cpp +++ b/js/src/jit/BaselineJIT.cpp @@ -42,7 +42,9 @@ PCMappingSlotInfo::ToSlotLocation(const StackValue *stackVal) } BaselineScript::BaselineScript(uint32_t prologueOffset, uint32_t epilogueOffset, - uint32_t spsPushToggleOffset, uint32_t postDebugPrologueOffset) + uint32_t spsPushToggleOffset, uint32_t traceLoggerEnterToggleOffset, + uint32_t traceLoggerExitToggleOffset, + uint32_t postDebugPrologueOffset) : method_(nullptr), templateScope_(nullptr), fallbackStubSpace_(), @@ -53,6 +55,15 @@ BaselineScript::BaselineScript(uint32_t prologueOffset, uint32_t epilogueOffset, spsOn_(false), #endif spsPushToggleOffset_(spsPushToggleOffset), +#ifdef JS_TRACE_LOGGING +# ifdef DEBUG + traceLoggerScriptsEnabled_(false), + traceLoggerEngineEnabled_(false), +# endif + traceLoggerEnterToggleOffset_(traceLoggerEnterToggleOffset), + traceLoggerExitToggleOffset_(traceLoggerExitToggleOffset), + traceLoggerScriptEvent_(), +#endif postDebugPrologueOffset_(postDebugPrologueOffset), flags_(0) { } @@ -192,9 +203,9 @@ jit::EnterBaselineAtBranch(JSContext *cx, InterpreterFrame *fp, jsbytecode *pc) data.calleeToken = CalleeToToken(fp->script()); } - TraceLogger *logger = TraceLoggerForMainThread(cx->runtime()); - TraceLogStopEvent(logger, TraceLogger::Interpreter); - TraceLogStartEvent(logger, TraceLogger::Baseline); + TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime()); + TraceLogStopEvent(logger, TraceLogger_Interpreter); + TraceLogStartEvent(logger, TraceLogger_Baseline); JitExecStatus status = EnterBaseline(cx, data); if (status != JitExec_Ok) @@ -341,7 +352,8 @@ jit::CanEnterBaselineMethod(JSContext *cx, RunState &state) BaselineScript * BaselineScript::New(JSScript *jsscript, uint32_t prologueOffset, uint32_t epilogueOffset, - uint32_t spsPushToggleOffset, uint32_t postDebugPrologueOffset, + uint32_t spsPushToggleOffset, uint32_t traceLoggerEnterToggleOffset, + uint32_t traceLoggerExitToggleOffset, uint32_t postDebugPrologueOffset, size_t icEntries, size_t pcMappingIndexEntries, size_t pcMappingSize, size_t bytecodeTypeMapEntries, size_t yieldEntries) { @@ -368,7 +380,8 @@ BaselineScript::New(JSScript *jsscript, uint32_t prologueOffset, uint32_t epilog if (!script) return nullptr; new (script) BaselineScript(prologueOffset, epilogueOffset, - spsPushToggleOffset, postDebugPrologueOffset); + spsPushToggleOffset, traceLoggerEnterToggleOffset, + traceLoggerExitToggleOffset, postDebugPrologueOffset); size_t offsetCursor = sizeof(BaselineScript); MOZ_ASSERT(offsetCursor == AlignBytes(sizeof(BaselineScript), DataAlignment)); @@ -895,6 +908,90 @@ BaselineScript::toggleSPS(bool enable) #endif } +#ifdef JS_TRACE_LOGGING +void +BaselineScript::initTraceLogger(JSRuntime *runtime, JSScript *script) +{ +#ifdef DEBUG + traceLoggerScriptsEnabled_ = TraceLogTextIdEnabled(TraceLogger_Scripts); + traceLoggerEngineEnabled_ = TraceLogTextIdEnabled(TraceLogger_Engine); +#endif + + TraceLoggerThread *logger = TraceLoggerForMainThread(runtime); + if (TraceLogTextIdEnabled(TraceLogger_Scripts)) + traceLoggerScriptEvent_ = TraceLoggerEvent(logger, TraceLogger_Scripts, script); + else + traceLoggerScriptEvent_ = TraceLoggerEvent(logger, TraceLogger_Scripts); + + if (TraceLogTextIdEnabled(TraceLogger_Engine) || TraceLogTextIdEnabled(TraceLogger_Scripts)) { + CodeLocationLabel enter(method_, CodeOffsetLabel(traceLoggerEnterToggleOffset_)); + CodeLocationLabel exit(method_, CodeOffsetLabel(traceLoggerExitToggleOffset_)); + Assembler::ToggleToCmp(enter); + Assembler::ToggleToCmp(exit); + } +} + +void +BaselineScript::toggleTraceLoggerScripts(JSRuntime *runtime, JSScript *script, bool enable) +{ + bool engineEnabled = TraceLogTextIdEnabled(TraceLogger_Engine); + + MOZ_ASSERT(enable == !traceLoggerScriptsEnabled_); + MOZ_ASSERT(engineEnabled == traceLoggerEngineEnabled_); + + // Patch the logging script textId to be correct. + // When logging log the specific textId else the global Scripts textId. + TraceLoggerThread *logger = TraceLoggerForMainThread(runtime); + if (enable) + traceLoggerScriptEvent_ = TraceLoggerEvent(logger, TraceLogger_Scripts, script); + else + traceLoggerScriptEvent_ = TraceLoggerEvent(logger, TraceLogger_Scripts); + + // Enable/Disable the traceLogger prologue and epilogue. + CodeLocationLabel enter(method_, CodeOffsetLabel(traceLoggerEnterToggleOffset_)); + CodeLocationLabel exit(method_, CodeOffsetLabel(traceLoggerExitToggleOffset_)); + if (!engineEnabled) { + if (enable) { + Assembler::ToggleToCmp(enter); + Assembler::ToggleToCmp(exit); + } else { + Assembler::ToggleToJmp(enter); + Assembler::ToggleToJmp(exit); + } + } + +#if DEBUG + traceLoggerScriptsEnabled_ = enable; +#endif +} + +void +BaselineScript::toggleTraceLoggerEngine(bool enable) +{ + bool scriptsEnabled = TraceLogTextIdEnabled(TraceLogger_Scripts); + + MOZ_ASSERT(enable == !traceLoggerEngineEnabled_); + MOZ_ASSERT(scriptsEnabled == traceLoggerScriptsEnabled_); + + // Enable/Disable the traceLogger prologue and epilogue. + CodeLocationLabel enter(method_, CodeOffsetLabel(traceLoggerEnterToggleOffset_)); + CodeLocationLabel exit(method_, CodeOffsetLabel(traceLoggerExitToggleOffset_)); + if (!scriptsEnabled) { + if (enable) { + Assembler::ToggleToCmp(enter); + Assembler::ToggleToCmp(exit); + } else { + Assembler::ToggleToJmp(enter); + Assembler::ToggleToJmp(exit); + } + } + +#if DEBUG + traceLoggerEngineEnabled_ = enable; +#endif +} +#endif + void BaselineScript::purgeOptimizedStubs(Zone *zone) { @@ -1002,6 +1099,34 @@ jit::ToggleBaselineSPS(JSRuntime *runtime, bool enable) } } +#ifdef JS_TRACE_LOGGING +void +jit::ToggleBaselineTraceLoggerScripts(JSRuntime *runtime, bool enable) +{ + for (ZonesIter zone(runtime, SkipAtoms); !zone.done(); zone.next()) { + for (gc::ZoneCellIter i(zone, gc::FINALIZE_SCRIPT); !i.done(); i.next()) { + JSScript *script = i.get(); + if (!script->hasBaselineScript()) + continue; + script->baselineScript()->toggleTraceLoggerScripts(runtime, script, enable); + } + } +} + +void +jit::ToggleBaselineTraceLoggerEngine(JSRuntime *runtime, bool enable) +{ + for (ZonesIter zone(runtime, SkipAtoms); !zone.done(); zone.next()) { + for (gc::ZoneCellIter i(zone, gc::FINALIZE_SCRIPT); !i.done(); i.next()) { + JSScript *script = i.get(); + if (!script->hasBaselineScript()) + continue; + script->baselineScript()->toggleTraceLoggerEngine(enable); + } + } +} +#endif + static void MarkActiveBaselineScripts(JSRuntime *rt, const JitActivationIterator &activation) { diff --git a/js/src/jit/BaselineJIT.h b/js/src/jit/BaselineJIT.h index 06476c38a6da..3f6fbeb83ded 100644 --- a/js/src/jit/BaselineJIT.h +++ b/js/src/jit/BaselineJIT.h @@ -16,6 +16,7 @@ #include "jit/Bailouts.h" #include "jit/IonCode.h" #include "jit/MacroAssembler.h" +#include "vm/TraceLogging.h" namespace js { namespace jit { @@ -144,6 +145,17 @@ struct BaselineScript #endif uint32_t spsPushToggleOffset_; + // The offsets and event used for Tracelogger toggling. +#ifdef JS_TRACE_LOGGING +# ifdef DEBUG + bool traceLoggerScriptsEnabled_; + bool traceLoggerEngineEnabled_; +# endif + uint32_t traceLoggerEnterToggleOffset_; + uint32_t traceLoggerExitToggleOffset_; + TraceLoggerEvent traceLoggerScriptEvent_; +#endif + // Native code offsets right after the debug prologue VM call returns, or // would have returned. This offset is recorded even when debug mode is // off to aid on-stack debug mode recompilation. @@ -202,11 +214,13 @@ struct BaselineScript public: // Do not call directly, use BaselineScript::New. This is public for cx->new_. BaselineScript(uint32_t prologueOffset, uint32_t epilogueOffset, - uint32_t spsPushToggleOffset, uint32_t postDebugPrologueOffset); + uint32_t spsPushToggleOffset, uint32_t traceLoggerEnterToggleOffset, + uint32_t traceLoggerExitToggleOffset, uint32_t postDebugPrologueOffset); static BaselineScript *New(JSScript *jsscript, uint32_t prologueOffset, uint32_t epilogueOffset, uint32_t postDebugPrologueOffset, - uint32_t spsPushToggleOffset, size_t icEntries, + uint32_t spsPushToggleOffset, uint32_t traceLoggerEnterToggleOffset, + uint32_t traceLoggerExitToggleOffset, size_t icEntries, size_t pcMappingIndexEntries, size_t pcMappingSize, size_t bytecodeTypeMapEntries, size_t yieldEntries); @@ -386,6 +400,16 @@ struct BaselineScript void toggleSPS(bool enable); +#ifdef JS_TRACE_LOGGING + void initTraceLogger(JSRuntime *runtime, JSScript *script); + void toggleTraceLoggerScripts(JSRuntime *runtime, JSScript *script, bool enable); + void toggleTraceLoggerEngine(bool enable); + + static size_t offsetOfTraceLoggerScriptEvent() { + return offsetof(BaselineScript, traceLoggerScriptEvent_); + } +#endif + void noteAccessedGetter(uint32_t pcOffset); void noteArrayWriteHole(uint32_t pcOffset); @@ -438,6 +462,11 @@ AddSizeOfBaselineData(JSScript *script, mozilla::MallocSizeOf mallocSizeOf, size void ToggleBaselineSPS(JSRuntime *runtime, bool enable); +void +ToggleBaselineTraceLoggerScripts(JSRuntime *runtime, bool enable); +void +ToggleBaselineTraceLoggerEngine(JSRuntime *runtime, bool enable); + struct BaselineBailoutInfo { // Pointer into the current C stack, where overwriting will start. diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index af6f42b4eadb..279b76fd7024 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -2022,8 +2022,8 @@ CodeGenerator::visitOsrEntry(LOsrEntry *lir) #ifdef JS_TRACE_LOGGING if (gen->info().executionMode() == SequentialExecution) { - emitTracelogStopEvent(TraceLogger::Baseline); - emitTracelogStartEvent(TraceLogger::IonMonkey); + emitTracelogStopEvent(TraceLogger_Baseline); + emitTracelogStartEvent(TraceLogger_IonMonkey); } #endif @@ -7349,10 +7349,8 @@ CodeGenerator::generate() #ifdef JS_TRACE_LOGGING if (!gen->compilingAsmJS() && gen->info().executionMode() == SequentialExecution) { - if (!emitTracelogScriptStart()) - return false; - if (!emitTracelogStartEvent(TraceLogger::IonMonkey)) - return false; + emitTracelogScriptStart(); + emitTracelogStartEvent(TraceLogger_IonMonkey); } #endif @@ -7625,19 +7623,25 @@ CodeGenerator::link(JSContext *cx, types::CompilerConstraintList *constraints) ionScript->copyPatchableBackedges(cx, code, patchableBackedges_.begin(), masm); #ifdef JS_TRACE_LOGGING - TraceLogger *logger = TraceLoggerForMainThread(cx->runtime()); + TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime()); for (uint32_t i = 0; i < patchableTraceLoggers_.length(); i++) { patchableTraceLoggers_[i].fixup(&masm); Assembler::PatchDataWithValueCheck(CodeLocationLabel(code, patchableTraceLoggers_[i]), ImmPtr(logger), ImmPtr(nullptr)); } - uint32_t scriptId = TraceLogCreateTextId(logger, script); - for (uint32_t i = 0; i < patchableTLScripts_.length(); i++) { - patchableTLScripts_[i].fixup(&masm); - Assembler::PatchDataWithValueCheck(CodeLocationLabel(code, patchableTLScripts_[i]), - ImmPtr((void *) uintptr_t(scriptId)), - ImmPtr((void *)0)); + + if (patchableTLScripts_.length() > 0) { + MOZ_ASSERT(TraceLogTextIdEnabled(TraceLogger_Scripts)); + TraceLoggerEvent event(logger, TraceLogger_Scripts, script); + ionScript->setTraceLoggerEvent(event); + uint32_t textId = event.payload()->textId(); + for (uint32_t i = 0; i < patchableTLScripts_.length(); i++) { + patchableTLScripts_[i].fixup(&masm); + Assembler::PatchDataWithValueCheck(CodeLocationLabel(code, patchableTLScripts_[i]), + ImmPtr((void *) uintptr_t(textId)), + ImmPtr((void *)0)); + } } #endif diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index a2d19e690978..e10492b85007 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -548,9 +548,10 @@ jit::LazyLinkTopActivation(JSContext *cx) builder->remove(); if (CodeGenerator *codegen = builder->backgroundCodegen()) { - js::TraceLogger *logger = TraceLoggerForMainThread(cx->runtime()); - AutoTraceLog logScript(logger, TraceLogCreateTextId(logger, script)); - AutoTraceLog logLink(logger, TraceLogger::IonLinking); + js::TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime()); + TraceLoggerEvent event(logger, TraceLogger_AnnotateScripts, script); + AutoTraceLog logScript(logger, event); + AutoTraceLog logLink(logger, TraceLogger_IonLinking); JitContext jctx(cx, &builder->alloc()); @@ -1272,7 +1273,7 @@ bool OptimizeMIR(MIRGenerator *mir) { MIRGraph &graph = mir->graph(); - TraceLogger *logger; + TraceLoggerThread *logger; if (GetJitContext()->runtime->onMainThread()) logger = TraceLoggerForMainThread(GetJitContext()->runtime); else @@ -1290,7 +1291,7 @@ OptimizeMIR(MIRGenerator *mir) return false; if (!mir->compilingAsmJS()) { - AutoTraceLog log(logger, TraceLogger::FoldTests); + AutoTraceLog log(logger, TraceLogger_FoldTests); FoldTests(graph); IonSpewPass("Fold Tests"); AssertBasicGraphCoherency(graph); @@ -1300,7 +1301,7 @@ OptimizeMIR(MIRGenerator *mir) } { - AutoTraceLog log(logger, TraceLogger::SplitCriticalEdges); + AutoTraceLog log(logger, TraceLogger_SplitCriticalEdges); if (!SplitCriticalEdges(graph)) return false; IonSpewPass("Split Critical Edges"); @@ -1311,7 +1312,7 @@ OptimizeMIR(MIRGenerator *mir) } { - AutoTraceLog log(logger, TraceLogger::RenumberBlocks); + AutoTraceLog log(logger, TraceLogger_RenumberBlocks); if (!RenumberBlocks(graph)) return false; IonSpewPass("Renumber Blocks"); @@ -1322,7 +1323,7 @@ OptimizeMIR(MIRGenerator *mir) } { - AutoTraceLog log(logger, TraceLogger::DominatorTree); + AutoTraceLog log(logger, TraceLogger_DominatorTree); if (!BuildDominatorTree(graph)) return false; // No spew: graph not changed. @@ -1332,7 +1333,7 @@ OptimizeMIR(MIRGenerator *mir) } { - AutoTraceLog log(logger, TraceLogger::PhiAnalysis); + AutoTraceLog log(logger, TraceLogger_PhiAnalysis); // Aggressive phi elimination must occur before any code elimination. If the // script contains a try-statement, we only compiled the try block and not // the catch or finally blocks, so in this case it's also invalid to use @@ -1358,7 +1359,7 @@ OptimizeMIR(MIRGenerator *mir) } if (mir->optimizationInfo().scalarReplacementEnabled()) { - AutoTraceLog log(logger, TraceLogger::ScalarReplacement); + AutoTraceLog log(logger, TraceLogger_ScalarReplacement); if (!ScalarReplacement(mir, graph)) return false; IonSpewPass("Scalar Replacement"); @@ -1369,7 +1370,7 @@ OptimizeMIR(MIRGenerator *mir) } if (!mir->compilingAsmJS()) { - AutoTraceLog log(logger, TraceLogger::ApplyTypes); + AutoTraceLog log(logger, TraceLogger_ApplyTypes); if (!ApplyTypeInformation(mir, graph)) return false; IonSpewPass("Apply types"); @@ -1384,7 +1385,7 @@ OptimizeMIR(MIRGenerator *mir) // are not deleted, leaving them dangling. This is ok, since we'll rerun // AliasAnalysis, which recomputes them, before they're needed. if (graph.entryBlock()->info().executionMode() == ParallelExecution) { - AutoTraceLog log(logger, TraceLogger::ParallelSafetyAnalysis); + AutoTraceLog log(logger, TraceLogger_ParallelSafetyAnalysis); ParallelSafetyAnalysis analysis(mir, graph); if (!analysis.analyze()) return false; @@ -1403,7 +1404,7 @@ OptimizeMIR(MIRGenerator *mir) if (mir->optimizationInfo().licmEnabled() || mir->optimizationInfo().gvnEnabled()) { - AutoTraceLog log(logger, TraceLogger::AliasAnalysis); + AutoTraceLog log(logger, TraceLogger_AliasAnalysis); AliasAnalysis analysis(mir, graph); if (!analysis.analyze()) return false; @@ -1426,7 +1427,7 @@ OptimizeMIR(MIRGenerator *mir) } if (mir->optimizationInfo().gvnEnabled()) { - AutoTraceLog log(logger, TraceLogger::GVN); + AutoTraceLog log(logger, TraceLogger_GVN); if (!gvn.run(ValueNumberer::UpdateAliasAnalysis)) return false; IonSpewPass("GVN"); @@ -1437,7 +1438,7 @@ OptimizeMIR(MIRGenerator *mir) } if (mir->optimizationInfo().licmEnabled()) { - AutoTraceLog log(logger, TraceLogger::LICM); + AutoTraceLog log(logger, TraceLogger_LICM); // LICM can hoist instructions from conditional branches and trigger // repeated bailouts. Disable it if this script is known to bailout // frequently. @@ -1454,7 +1455,7 @@ OptimizeMIR(MIRGenerator *mir) } if (mir->optimizationInfo().rangeAnalysisEnabled()) { - AutoTraceLog log(logger, TraceLogger::RangeAnalysis); + AutoTraceLog log(logger, TraceLogger_RangeAnalysis); RangeAnalysis r(mir, graph); if (!r.addBetaNodes()) return false; @@ -1512,7 +1513,7 @@ OptimizeMIR(MIRGenerator *mir) } if (mir->optimizationInfo().loopUnrollingEnabled()) { - AutoTraceLog log(logger, TraceLogger::LoopUnrolling); + AutoTraceLog log(logger, TraceLogger_LoopUnrolling); if (!UnrollLoops(graph, r.loopIterationBounds)) return false; @@ -1523,7 +1524,7 @@ OptimizeMIR(MIRGenerator *mir) } if (mir->optimizationInfo().eaaEnabled()) { - AutoTraceLog log(logger, TraceLogger::EffectiveAddressAnalysis); + AutoTraceLog log(logger, TraceLogger_EffectiveAddressAnalysis); EffectiveAddressAnalysis eaa(graph); if (!eaa.analyze()) return false; @@ -1535,7 +1536,7 @@ OptimizeMIR(MIRGenerator *mir) } { - AutoTraceLog log(logger, TraceLogger::EliminateDeadCode); + AutoTraceLog log(logger, TraceLogger_EliminateDeadCode); if (!EliminateDeadCode(mir, graph)) return false; IonSpewPass("DCE"); @@ -1546,7 +1547,7 @@ OptimizeMIR(MIRGenerator *mir) } { - AutoTraceLog log(logger, TraceLogger::EliminateDeadCode); + AutoTraceLog log(logger, TraceLogger_EliminateDeadCode); if (!Sink(mir, graph)) return false; IonSpewPass("Sink"); @@ -1559,7 +1560,7 @@ OptimizeMIR(MIRGenerator *mir) // Make loops contiguous. We do this after GVN/UCE and range analysis, // which can remove CFG edges, exposing more blocks that can be moved. { - AutoTraceLog log(logger, TraceLogger::MakeLoopsContiguous); + AutoTraceLog log(logger, TraceLogger_MakeLoopsContiguous); if (!MakeLoopsContiguous(graph)) return false; IonSpewPass("Make loops contiguous"); @@ -1573,7 +1574,7 @@ OptimizeMIR(MIRGenerator *mir) // depend on knowing the final order in which instructions will execute. if (mir->optimizationInfo().edgeCaseAnalysisEnabled()) { - AutoTraceLog log(logger, TraceLogger::EdgeCaseAnalysis); + AutoTraceLog log(logger, TraceLogger_EdgeCaseAnalysis); EdgeCaseAnalysis edgeCaseAnalysis(mir, graph); if (!edgeCaseAnalysis.analyzeLate()) return false; @@ -1585,7 +1586,7 @@ OptimizeMIR(MIRGenerator *mir) } if (mir->optimizationInfo().eliminateRedundantChecksEnabled()) { - AutoTraceLog log(logger, TraceLogger::EliminateRedundantChecks); + AutoTraceLog log(logger, TraceLogger_EliminateRedundantChecks); // Note: check elimination has to run after all other passes that move // instructions. Since check uses are replaced with the actual index, // code motion after this pass could incorrectly move a load or store @@ -1604,7 +1605,7 @@ GenerateLIR(MIRGenerator *mir) { MIRGraph &graph = mir->graph(); - TraceLogger *logger; + TraceLoggerThread *logger; if (GetJitContext()->runtime->onMainThread()) logger = TraceLoggerForMainThread(GetJitContext()->runtime); else @@ -1616,7 +1617,7 @@ GenerateLIR(MIRGenerator *mir) LIRGenerator lirgen(mir, graph, *lir); { - AutoTraceLog log(logger, TraceLogger::GenerateLIR); + AutoTraceLog log(logger, TraceLogger_GenerateLIR); if (!lirgen.generate()) return nullptr; IonSpewPass("Generate LIR"); @@ -1628,7 +1629,7 @@ GenerateLIR(MIRGenerator *mir) AllocationIntegrityState integrity(*lir); { - AutoTraceLog log(logger, TraceLogger::RegisterAllocation); + AutoTraceLog log(logger, TraceLogger_RegisterAllocation); switch (mir->optimizationInfo().registerAllocator()) { case RegisterAllocator_LSRA: { @@ -1698,12 +1699,12 @@ GenerateLIR(MIRGenerator *mir) CodeGenerator * GenerateCode(MIRGenerator *mir, LIRGraph *lir) { - TraceLogger *logger; + TraceLoggerThread *logger; if (GetJitContext()->runtime->onMainThread()) logger = TraceLoggerForMainThread(GetJitContext()->runtime); else logger = TraceLoggerForCurrentThread(); - AutoTraceLog log(logger, TraceLogger::GenerateCode); + AutoTraceLog log(logger, TraceLogger_GenerateCode); CodeGenerator *codegen = js_new(mir, lir); if (!codegen) @@ -1745,7 +1746,7 @@ AttachFinishedCompilations(JSContext *cx) GlobalHelperThreadState::IonBuilderVector &finished = HelperThreadState().ionFinishedList(); - TraceLogger *logger = TraceLoggerForMainThread(cx->runtime()); + TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime()); // Incorporate any off thread compilations for the compartment which have // finished, failed or have been cancelled. @@ -1797,8 +1798,9 @@ AttachFinishedCompilations(JSContext *cx) if (CodeGenerator *codegen = builder->backgroundCodegen()) { RootedScript script(cx, builder->script()); JitContext jctx(cx, &builder->alloc()); - AutoTraceLog logScript(logger, TraceLogCreateTextId(logger, script)); - AutoTraceLog logLink(logger, TraceLogger::IonLinking); + TraceLoggerEvent event(logger, TraceLogger_AnnotateScripts, script); + AutoTraceLog logScript(logger, event); + AutoTraceLog logLink(logger, TraceLogger_IonLinking); // Root the assembler until the builder is finished below. As it // was constructed off thread, the assembler has not been rooted @@ -1876,9 +1878,10 @@ IonCompile(JSContext *cx, JSScript *script, ExecutionMode executionMode, bool recompile, OptimizationLevel optimizationLevel) { - TraceLogger *logger = TraceLoggerForMainThread(cx->runtime()); - AutoTraceLog logScript(logger, TraceLogCreateTextId(logger, script)); - AutoTraceLog logCompile(logger, TraceLogger::IonCompilation); + TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime()); + TraceLoggerEvent event(logger, TraceLogger_AnnotateScripts, script); + AutoTraceLog logScript(logger, event); + AutoTraceLog logCompile(logger, TraceLogger_IonCompilation); MOZ_ASSERT(optimizationLevel > Optimization_DontCompile); diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 2aca5fd11ed6..1444f7b8f332 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -21,6 +21,7 @@ #include "vm/ArgumentsObject.h" #include "vm/Opcodes.h" #include "vm/RegExpStatics.h" +#include "vm/TraceLogging.h" #include "jsinferinlines.h" #include "jsobjinlines.h" @@ -342,6 +343,11 @@ IonBuilder::canInlineTarget(JSFunction *target, CallInfo &callInfo) if (!optimizationInfo().inlineInterpreted()) return InliningDecision_DontInline; + if (TraceLogTextIdEnabled(TraceLogger_InlinedScripts)) { + return DontInline(nullptr, "Tracelogging of inlined scripts is enabled" + "but Tracelogger cannot do that yet."); + } + if (!target->isInterpreted()) return DontInline(nullptr, "Non-interpreted target"); @@ -4472,6 +4478,9 @@ IonBuilder::patchInlinedReturn(CallInfo &callInfo, MBasicBlock *exit, MBasicBloc rdef = callInfo.getArg(0); } + if (!callInfo.isSetter()) + rdef = specializeInlinedReturn(rdef, exit); + MGoto *replacement = MGoto::New(alloc(), bottom); exit->end(replacement); if (!bottom->addPredecessorWithoutPhis(exit)) @@ -4480,6 +4489,47 @@ IonBuilder::patchInlinedReturn(CallInfo &callInfo, MBasicBlock *exit, MBasicBloc return rdef; } +MDefinition * +IonBuilder::specializeInlinedReturn(MDefinition *rdef, MBasicBlock *exit) +{ + // Remove types from the return definition that weren't observed. + types::TemporaryTypeSet *types = bytecodeTypes(pc); + + // The observed typeset doesn't contain extra information. + if (types->empty() || types->unknown()) + return rdef; + + // Decide if specializing is needed using the result typeset if available, + // else use the result type. + + if (rdef->resultTypeSet()) { + // Don't specialize if return typeset is a subset of the + // observed typeset. The return typeset is already more specific. + if (rdef->resultTypeSet()->isSubset(types)) + return rdef; + } else { + // Don't specialize if types are inaccordance, except for MIRType_Value + // and MIRType_Object (when not unknown object), since the typeset + // contains more specific information. + MIRType observedType = types->getKnownMIRType(); + if (observedType == rdef->type() && + observedType != MIRType_Value && + (observedType != MIRType_Object || types->unknownObject())) + { + return rdef; + } + } + + setCurrent(exit); + + MTypeBarrier *barrier = nullptr; + rdef = addTypeBarrier(rdef, types, BarrierKind::TypeSet, &barrier); + if (barrier) + barrier->setNotMovable(); + + return rdef; +} + MDefinition * IonBuilder::patchInlinedReturns(CallInfo &callInfo, MIRGraphReturns &returns, MBasicBlock *bottom) { @@ -6818,55 +6868,60 @@ IonBuilder::testSingletonPropertyTypes(MDefinition *obj, JSObject *singleton, Pr return false; } -// Given an observed type set, annotates the IR as much as possible: -// (1) If no type information is provided, the value on the top of the stack is -// left in place. -// (2) If a single type definitely exists, and no type barrier is needed, -// then an infallible unbox instruction replaces the value on the top of -// the stack. -// (3) If a type barrier is needed, but has an unknown type set, leave the -// value at the top of the stack. -// (4) If a type barrier is needed, and has a single type, an unbox -// instruction replaces the top of the stack. -// (5) Lastly, a type barrier instruction replaces the top of the stack. bool IonBuilder::pushTypeBarrier(MDefinition *def, types::TemporaryTypeSet *observed, BarrierKind kind) +{ + MOZ_ASSERT(def == current->peek(-1)); + + MDefinition *replace = addTypeBarrier(current->pop(), observed, kind); + if (!replace) + return false; + + current->push(replace); + return true; +} + +// Given an observed type set, annotates the IR as much as possible: +// (1) If no type information is provided, the given value is returned. +// (2) If a single type definitely exists, and no type barrier is needed, +// then an infallible unbox instruction is returned. +// (3) If a type barrier is needed, but has an unknown type set, the given +// value is returned. +// (4) Lastly, a type barrier instruction is added and returned. +MDefinition * +IonBuilder::addTypeBarrier(MDefinition *def, types::TemporaryTypeSet *observed, BarrierKind kind, + MTypeBarrier **pbarrier) { // Barriers are never needed for instructions whose result will not be used. if (BytecodeIsPopped(pc)) - return true; + return def; // If the instruction has no side effects, we'll resume the entire operation. // The actual type barrier will occur in the interpreter. If the // instruction is effectful, even if it has a singleton type, there // must be a resume point capturing the original def, and resuming // to that point will explicitly monitor the new type. - if (kind == BarrierKind::NoBarrier) { MDefinition *replace = ensureDefiniteType(def, observed->getKnownMIRType()); - if (replace != def) { - current->pop(); - current->push(replace); - } replace->setResultTypeSet(observed); - return true; + return replace; } if (observed->unknown()) - return true; + return def; - current->pop(); - - MInstruction *barrier = MTypeBarrier::New(alloc(), def, observed, kind); + MTypeBarrier *barrier = MTypeBarrier::New(alloc(), def, observed, kind); current->add(barrier); - if (barrier->type() == MIRType_Undefined) - return pushConstant(UndefinedValue()); - if (barrier->type() == MIRType_Null) - return pushConstant(NullValue()); + if (pbarrier) + *pbarrier = barrier; - current->push(barrier); - return true; + if (barrier->type() == MIRType_Undefined) + return constant(UndefinedValue()); + if (barrier->type() == MIRType_Null) + return constant(NullValue()); + + return barrier; } bool diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 9afa4c9a183e..028822b90b3a 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -358,6 +358,8 @@ class IonBuilder // Add a guard which ensure that the set of type which goes through this // generated code correspond to the observed types for the bytecode. + MDefinition *addTypeBarrier(MDefinition *def, types::TemporaryTypeSet *observed, + BarrierKind kind, MTypeBarrier **pbarrier = nullptr); bool pushTypeBarrier(MDefinition *def, types::TemporaryTypeSet *observed, BarrierKind kind); // As pushTypeBarrier, but will compute the needBarrier boolean itself based @@ -841,6 +843,7 @@ class IonBuilder MDefinition *patchInlinedReturn(CallInfo &callInfo, MBasicBlock *exit, MBasicBlock *bottom); MDefinition *patchInlinedReturns(CallInfo &callInfo, MIRGraphReturns &returns, MBasicBlock *bottom); + MDefinition *specializeInlinedReturn(MDefinition *rdef, MBasicBlock *exit); bool objectsHaveCommonPrototype(types::TemporaryTypeSet *types, PropertyName *name, bool isGetter, JSObject *foundProto, bool *guardGlobal); diff --git a/js/src/jit/IonCode.h b/js/src/jit/IonCode.h index 630fde3ada74..5a71f9efcf91 100644 --- a/js/src/jit/IonCode.h +++ b/js/src/jit/IonCode.h @@ -19,6 +19,7 @@ #include "jit/IonOptimizationLevels.h" #include "jit/IonTypes.h" #include "js/UbiNode.h" +#include "vm/TraceLogging.h" namespace js { @@ -285,6 +286,9 @@ struct IonScript // a LOOPENTRY pc other than osrPc_. uint32_t osrPcMismatchCounter_; + // The tracelogger event used to log the start/stop of this IonScript. + TraceLoggerEvent traceLoggerScriptEvent_; + IonBuilder *pendingBuilder_; private: @@ -462,6 +466,9 @@ struct IonScript bool hasSPSInstrumentation() const { return hasSPSInstrumentation_; } + void setTraceLoggerEvent(TraceLoggerEvent &event) { + traceLoggerScriptEvent_ = event; + } const uint8_t *snapshots() const { return reinterpret_cast(this) + snapshots_; } diff --git a/js/src/jit/JitFrames.cpp b/js/src/jit/JitFrames.cpp index a17ea10fe1bb..1762c9ad6d74 100644 --- a/js/src/jit/JitFrames.cpp +++ b/js/src/jit/JitFrames.cpp @@ -714,7 +714,7 @@ void HandleException(ResumeFromException *rfe) { JSContext *cx = GetJSContextFromJitCode(); - TraceLogger *logger = TraceLoggerForMainThread(cx->runtime()); + TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime()); rfe->kind = ResumeFromException::RESUME_ENTRY_FRAME; @@ -781,8 +781,8 @@ HandleException(ResumeFromException *rfe) JSScript *script = frames.script(); probes::ExitScript(cx, script, script->functionNonDelazifying(), popSPSFrame); if (!frames.more()) { - TraceLogStopEvent(logger, TraceLogger::IonMonkey); - TraceLogStopEvent(logger); + TraceLogStopEvent(logger, TraceLogger_IonMonkey); + TraceLogStopEvent(logger, TraceLogger_Scripts); break; } ++frames; @@ -812,8 +812,8 @@ HandleException(ResumeFromException *rfe) if (rfe->kind != ResumeFromException::RESUME_ENTRY_FRAME) return; - TraceLogStopEvent(logger, TraceLogger::Baseline); - TraceLogStopEvent(logger); + TraceLogStopEvent(logger, TraceLogger_Baseline); + TraceLogStopEvent(logger, TraceLogger_Scripts); // Unwind profiler pseudo-stack JSScript *script = iter.script(); diff --git a/js/src/jit/LIR-Common.h b/js/src/jit/LIR-Common.h index d427dd4d9d3b..b73a9de8ffca 100644 --- a/js/src/jit/LIR-Common.h +++ b/js/src/jit/LIR-Common.h @@ -299,12 +299,8 @@ class LSimdShuffle : public LInstructionHelper<1, 2, 1> { public: LIR_HEADER(SimdShuffle); - LSimdShuffle(const LAllocation &lhs, const LAllocation &rhs, const LDefinition &temp) - { - setOperand(0, lhs); - setOperand(1, rhs); - setTemp(0, temp); - } + LSimdShuffle() + {} const LAllocation *lhs() { return getOperand(0); diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index 53b301d5c5e6..d6f08c34aa16 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -3966,14 +3966,12 @@ LIRGenerator::visitSimdShuffle(MSimdShuffle *ins) bool wFromLHS = ins->laneW() < 4; uint32_t lanesFromLHS = (ins->laneX() < 4) + (ins->laneY() < 4) + zFromLHS + wFromLHS; - LUse lhs = useRegisterAtStart(ins->lhs()); - LUse rhs = useRegister(ins->rhs()); + LSimdShuffle *lir = new (alloc()) LSimdShuffle(); + lowerForFPU(lir, ins, ins->lhs(), ins->rhs()); // See codegen for requirements details. LDefinition temp = (lanesFromLHS == 3) ? tempCopy(ins->rhs(), 1) : LDefinition::BogusTemp(); - - LSimdShuffle *lir = new (alloc()) LSimdShuffle(lhs, rhs, temp); - defineReuseInput(lir, ins, 0); + lir->setTemp(0, temp); } void diff --git a/js/src/jit/MacroAssembler.cpp b/js/src/jit/MacroAssembler.cpp index cde9a8fc2699..194c29973f8c 100644 --- a/js/src/jit/MacroAssembler.cpp +++ b/js/src/jit/MacroAssembler.cpp @@ -1669,9 +1669,10 @@ MacroAssembler::printf(const char *output, Register value) #ifdef JS_TRACE_LOGGING void -MacroAssembler::tracelogStart(Register logger, uint32_t textId) +MacroAssembler::tracelogStartId(Register logger, uint32_t textId, bool force) { - void (&TraceLogFunc)(TraceLogger*, uint32_t) = TraceLogStartEvent; + if (!force && !TraceLogTextIdEnabled(textId)) + return; PushRegsInMask(RegisterSet::Volatile()); @@ -1684,16 +1685,14 @@ MacroAssembler::tracelogStart(Register logger, uint32_t textId) passABIArg(logger); move32(Imm32(textId), temp); passABIArg(temp); - callWithABINoProfiling(JS_FUNC_TO_DATA_PTR(void *, TraceLogFunc)); + callWithABINoProfiling(JS_FUNC_TO_DATA_PTR(void *, TraceLogStartEventPrivate)); PopRegsInMask(RegisterSet::Volatile()); } void -MacroAssembler::tracelogStart(Register logger, Register textId) +MacroAssembler::tracelogStartId(Register logger, Register textId) { - void (&TraceLogFunc)(TraceLogger*, uint32_t) = TraceLogStartEvent; - PushRegsInMask(RegisterSet::Volatile()); RegisterSet regs = RegisterSet::Volatile(); @@ -1705,17 +1704,37 @@ MacroAssembler::tracelogStart(Register logger, Register textId) setupUnalignedABICall(2, temp); passABIArg(logger); passABIArg(textId); - callWithABINoProfiling(JS_FUNC_TO_DATA_PTR(void *, TraceLogFunc)); - - regs.add(temp); + callWithABINoProfiling(JS_FUNC_TO_DATA_PTR(void *, TraceLogStartEventPrivate)); PopRegsInMask(RegisterSet::Volatile()); } void -MacroAssembler::tracelogStop(Register logger, uint32_t textId) +MacroAssembler::tracelogStartEvent(Register logger, Register event) { - void (&TraceLogFunc)(TraceLogger*, uint32_t) = TraceLogStopEvent; + void (&TraceLogFunc)(TraceLoggerThread *, const TraceLoggerEvent &) = TraceLogStartEvent; + + PushRegsInMask(RegisterSet::Volatile()); + + RegisterSet regs = RegisterSet::Volatile(); + regs.takeUnchecked(logger); + regs.takeUnchecked(event); + + Register temp = regs.takeGeneral(); + + setupUnalignedABICall(2, temp); + passABIArg(logger); + passABIArg(event); + callWithABINoProfiling(JS_FUNC_TO_DATA_PTR(void *, TraceLogFunc)); + + PopRegsInMask(RegisterSet::Volatile()); +} + +void +MacroAssembler::tracelogStopId(Register logger, uint32_t textId, bool force) +{ + if (!force && !TraceLogTextIdEnabled(textId)) + return; PushRegsInMask(RegisterSet::Volatile()); @@ -1728,23 +1747,19 @@ MacroAssembler::tracelogStop(Register logger, uint32_t textId) passABIArg(logger); move32(Imm32(textId), temp); passABIArg(temp); - callWithABINoProfiling(JS_FUNC_TO_DATA_PTR(void *, TraceLogFunc)); - regs.add(temp); + callWithABINoProfiling(JS_FUNC_TO_DATA_PTR(void *, TraceLogStopEventPrivate)); PopRegsInMask(RegisterSet::Volatile()); } void -MacroAssembler::tracelogStop(Register logger, Register textId) +MacroAssembler::tracelogStopId(Register logger, Register textId) { -#ifdef DEBUG - void (&TraceLogFunc)(TraceLogger*, uint32_t) = TraceLogStopEvent; - PushRegsInMask(RegisterSet::Volatile()); - RegisterSet regs = RegisterSet::Volatile(); regs.takeUnchecked(logger); + regs.takeUnchecked(textId); Register temp = regs.takeGeneral(); @@ -1752,33 +1767,7 @@ MacroAssembler::tracelogStop(Register logger, Register textId) setupUnalignedABICall(2, temp); passABIArg(logger); passABIArg(textId); - callWithABINoProfiling(JS_FUNC_TO_DATA_PTR(void *, TraceLogFunc)); - - regs.add(temp); - - PopRegsInMask(RegisterSet::Volatile()); -#else - tracelogStop(logger); -#endif -} - -void -MacroAssembler::tracelogStop(Register logger) -{ - void (&TraceLogFunc)(TraceLogger*) = TraceLogStopEvent; - - PushRegsInMask(RegisterSet::Volatile()); - - RegisterSet regs = RegisterSet::Volatile(); - regs.takeUnchecked(logger); - - Register temp = regs.takeGeneral(); - - setupUnalignedABICall(1, temp); - passABIArg(logger); - callWithABINoProfiling(JS_FUNC_TO_DATA_PTR(void *, TraceLogFunc)); - - regs.add(temp); + callWithABINoProfiling(JS_FUNC_TO_DATA_PTR(void *, TraceLogStopEventPrivate)); PopRegsInMask(RegisterSet::Volatile()); } diff --git a/js/src/jit/MacroAssembler.h b/js/src/jit/MacroAssembler.h index 8beb323a3406..27ba6aa54940 100644 --- a/js/src/jit/MacroAssembler.h +++ b/js/src/jit/MacroAssembler.h @@ -1207,11 +1207,11 @@ class MacroAssembler : public MacroAssemblerSpecific void printf(const char *output, Register value); #ifdef JS_TRACE_LOGGING - void tracelogStart(Register logger, uint32_t textId); - void tracelogStart(Register logger, Register textId); - void tracelogStop(Register logger, uint32_t textId); - void tracelogStop(Register logger, Register textId); - void tracelogStop(Register logger); + void tracelogStartId(Register logger, uint32_t textId, bool force = false); + void tracelogStartId(Register logger, Register textId); + void tracelogStartEvent(Register logger, Register event); + void tracelogStopId(Register logger, uint32_t textId, bool force = false); + void tracelogStopId(Register logger, Register textId); #endif #define DISPATCH_FLOATING_POINT_OP(method, type, arg1d, arg1f, arg2) \ diff --git a/js/src/jit/TypePolicy.cpp b/js/src/jit/TypePolicy.cpp index 4ff4cdc0f073..98798769be8e 100644 --- a/js/src/jit/TypePolicy.cpp +++ b/js/src/jit/TypePolicy.cpp @@ -278,6 +278,8 @@ TypeBarrierPolicy::adjustInputs(TempAllocator &alloc, MInstruction *def) // Unbox / propagate the right type. MUnbox::Mode mode = MUnbox::TypeBarrier; MInstruction *replace = MUnbox::New(alloc, ins->getOperand(0), ins->type(), mode); + if (!ins->isMovable()) + replace->setNotMovable(); ins->block()->insertBefore(ins, replace); ins->replaceOperand(0, replace); diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index fe6809c543e0..9c2a8122a5af 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -771,9 +771,9 @@ DebugEpilogueOnBaselineReturn(JSContext *cx, BaselineFrame *frame, jsbytecode *p if (!DebugEpilogue(cx, frame, pc, true)) { // DebugEpilogue popped the frame by updating jitTop, so run the stop event // here before we enter the exception handler. - TraceLogger *logger = TraceLoggerForMainThread(cx->runtime()); - TraceLogStopEvent(logger, TraceLogger::Baseline); - TraceLogStopEvent(logger); // Leave script. + TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime()); + TraceLogStopEvent(logger, TraceLogger_Baseline); + TraceLogStopEvent(logger, TraceLogger_Scripts); return false; } diff --git a/js/src/jit/arm/CodeGenerator-arm.cpp b/js/src/jit/arm/CodeGenerator-arm.cpp index a008e791a9b1..1d9ced6cb9cd 100644 --- a/js/src/jit/arm/CodeGenerator-arm.cpp +++ b/js/src/jit/arm/CodeGenerator-arm.cpp @@ -59,10 +59,8 @@ CodeGeneratorARM::generateEpilogue() #ifdef JS_TRACE_LOGGING if (gen->info().executionMode() == SequentialExecution) { - if (!emitTracelogStopEvent(TraceLogger::IonMonkey)) - return false; - if (!emitTracelogScriptStop()) - return false; + emitTracelogStopEvent(TraceLogger_IonMonkey); + emitTracelogScriptStop(); } #endif diff --git a/js/src/jit/mips/CodeGenerator-mips.cpp b/js/src/jit/mips/CodeGenerator-mips.cpp index cba3b2d6cc14..911df69769fd 100644 --- a/js/src/jit/mips/CodeGenerator-mips.cpp +++ b/js/src/jit/mips/CodeGenerator-mips.cpp @@ -57,10 +57,8 @@ CodeGeneratorMIPS::generateEpilogue() #ifdef JS_TRACE_LOGGING if (gen->info().executionMode() == SequentialExecution) { - if (!emitTracelogStopEvent(TraceLogger::IonMonkey)) - return false; - if (!emitTracelogScriptStop()) - return false; + emitTracelogStopEvent(TraceLogger::IonMonkey); + emitTracelogScriptStop(); } #endif diff --git a/js/src/jit/shared/Assembler-x86-shared.h b/js/src/jit/shared/Assembler-x86-shared.h index 3cb6db45d955..cd78616b9aff 100644 --- a/js/src/jit/shared/Assembler-x86-shared.h +++ b/js/src/jit/shared/Assembler-x86-shared.h @@ -2103,10 +2103,42 @@ class AssemblerX86Shared : public AssemblerShared MOZ_ASSERT(HasSSE2()); masm.vunpcklps_rr(src1.code(), src0.code(), dest.code()); } + void vunpcklps(const Operand &src1, FloatRegister src0, FloatRegister dest) { + MOZ_ASSERT(HasSSE2()); + switch (src1.kind()) { + case Operand::FPREG: + masm.vunpcklps_rr(src1.fpu(), src0.code(), dest.code()); + break; + case Operand::MEM_REG_DISP: + masm.vunpcklps_mr(src1.disp(), src1.base(), src0.code(), dest.code()); + break; + case Operand::MEM_ADDRESS32: + masm.vunpcklps_mr(src1.address(), src0.code(), dest.code()); + break; + default: + MOZ_CRASH("unexpected operand kind"); + } + } void vunpckhps(FloatRegister src1, FloatRegister src0, FloatRegister dest) { MOZ_ASSERT(HasSSE2()); masm.vunpckhps_rr(src1.code(), src0.code(), dest.code()); } + void vunpckhps(const Operand &src1, FloatRegister src0, FloatRegister dest) { + MOZ_ASSERT(HasSSE2()); + switch (src1.kind()) { + case Operand::FPREG: + masm.vunpckhps_rr(src1.fpu(), src0.code(), dest.code()); + break; + case Operand::MEM_REG_DISP: + masm.vunpckhps_mr(src1.disp(), src1.base(), src0.code(), dest.code()); + break; + case Operand::MEM_ADDRESS32: + masm.vunpckhps_mr(src1.address(), src0.code(), dest.code()); + break; + default: + MOZ_CRASH("unexpected operand kind"); + } + } void vshufps(uint32_t mask, FloatRegister src1, FloatRegister src0, FloatRegister dest) { MOZ_ASSERT(HasSSE2()); masm.vshufps_irr(mask, src1.code(), src0.code(), dest.code()); @@ -2324,6 +2356,19 @@ class AssemblerX86Shared : public AssemblerShared MOZ_ASSERT(HasSSE41()); masm.vinsertps_irr(mask, src1.code(), src0.code(), dest.code()); } + void vinsertps(uint32_t mask, const Operand &src1, FloatRegister src0, FloatRegister dest) { + MOZ_ASSERT(HasSSE41()); + switch (src1.kind()) { + case Operand::FPREG: + masm.vinsertps_irr(mask, src1.fpu(), src0.code(), dest.code()); + break; + case Operand::MEM_REG_DISP: + masm.vinsertps_imr(mask, src1.disp(), src1.base(), src0.code(), dest.code()); + break; + default: + MOZ_CRASH("unexpected operand kind"); + } + } unsigned blendpsMask(bool x, bool y, bool z, bool w) { return x | (y << 1) | (z << 2) | (w << 3); } diff --git a/js/src/jit/shared/BaseAssembler-x86-shared.h b/js/src/jit/shared/BaseAssembler-x86-shared.h index 0f899e78f0a1..e58ed67fcbed 100644 --- a/js/src/jit/shared/BaseAssembler-x86-shared.h +++ b/js/src/jit/shared/BaseAssembler-x86-shared.h @@ -30,7 +30,8 @@ #ifndef jit_shared_BaseAssembler_x86_shared_h #define jit_shared_BaseAssembler_x86_shared_h -#include +#include "mozilla/IntegerPrintfMacros.h" + #include #include "jit/shared/AssemblerBuffer-x86-shared.h" @@ -2938,11 +2939,27 @@ public: { twoByteOpSimd("vunpcklps", VEX_PS, OP2_UNPCKLPS_VsdWsd, src1, src0, dst); } + void vunpcklps_mr(int32_t offset, RegisterID base, XMMRegisterID src0, XMMRegisterID dst) + { + twoByteOpSimd("vunpcklps", VEX_PS, OP2_UNPCKLPS_VsdWsd, offset, base, src0, dst); + } + void vunpcklps_mr(const void *addr, XMMRegisterID src0, XMMRegisterID dst) + { + twoByteOpSimd("vunpcklps", VEX_PS, OP2_UNPCKLPS_VsdWsd, addr, src0, dst); + } void vunpckhps_rr(XMMRegisterID src1, XMMRegisterID src0, XMMRegisterID dst) { twoByteOpSimd("vunpckhps", VEX_PS, OP2_UNPCKHPS_VsdWsd, src1, src0, dst); } + void vunpckhps_mr(int32_t offset, RegisterID base, XMMRegisterID src0, XMMRegisterID dst) + { + twoByteOpSimd("vunpckhps", VEX_PS, OP2_UNPCKHPS_VsdWsd, offset, base, src0, dst); + } + void vunpckhps_mr(const void *addr, XMMRegisterID src0, XMMRegisterID dst) + { + twoByteOpSimd("vunpckhps", VEX_PS, OP2_UNPCKHPS_VsdWsd, addr, src0, dst); + } void vpand_rr(XMMRegisterID src1, XMMRegisterID src0, XMMRegisterID dst) { @@ -3606,6 +3623,10 @@ public: { threeByteOpImmSimd("vinsertps", VEX_PD, OP3_INSERTPS_VpsUps, ESCAPE_INSERTPS, mask, src1, src0, dst); } + void vinsertps_imr(uint32_t mask, int32_t offset, RegisterID base, XMMRegisterID src0, XMMRegisterID dst) + { + threeByteOpImmSimd("vinsertps", VEX_PD, OP3_INSERTPS_VpsUps, ESCAPE_INSERTPS, mask, offset, base, src0, dst); + } void vpinsrd_irr(unsigned lane, RegisterID src1, XMMRegisterID src0, XMMRegisterID dst) { diff --git a/js/src/jit/shared/BaselineCompiler-shared.cpp b/js/src/jit/shared/BaselineCompiler-shared.cpp index 2e5bba563f3b..3c47c3988091 100644 --- a/js/src/jit/shared/BaselineCompiler-shared.cpp +++ b/js/src/jit/shared/BaselineCompiler-shared.cpp @@ -30,7 +30,10 @@ BaselineCompilerShared::BaselineCompilerShared(JSContext *cx, TempAllocator &all icLoadLabels_(), pushedBeforeCall_(0), inCall_(false), - spsPushToggleOffset_() + spsPushToggleOffset_(), + traceLoggerEnterToggleOffset_(), + traceLoggerExitToggleOffset_(), + traceLoggerScriptTextIdOffset_() { } bool diff --git a/js/src/jit/shared/BaselineCompiler-shared.h b/js/src/jit/shared/BaselineCompiler-shared.h index d54b15e3f76c..e77715a691fb 100644 --- a/js/src/jit/shared/BaselineCompiler-shared.h +++ b/js/src/jit/shared/BaselineCompiler-shared.h @@ -68,6 +68,9 @@ class BaselineCompilerShared mozilla::DebugOnly inCall_; CodeOffsetLabel spsPushToggleOffset_; + CodeOffsetLabel traceLoggerEnterToggleOffset_; + CodeOffsetLabel traceLoggerExitToggleOffset_; + CodeOffsetLabel traceLoggerScriptTextIdOffset_; BaselineCompilerShared(JSContext *cx, TempAllocator &alloc, JSScript *script); diff --git a/js/src/jit/shared/CodeGenerator-shared.cpp b/js/src/jit/shared/CodeGenerator-shared.cpp index 8f2f0bff4fc1..0daa4bd9562d 100644 --- a/js/src/jit/shared/CodeGenerator-shared.cpp +++ b/js/src/jit/shared/CodeGenerator-shared.cpp @@ -1020,7 +1020,7 @@ CodeGeneratorShared::callVM(const VMFunction &fun, LInstruction *ins, const Regi #endif #ifdef JS_TRACE_LOGGING - emitTracelogStartEvent(TraceLogger::VM); + emitTracelogStartEvent(TraceLogger_VM); #endif // Stack is: @@ -1065,7 +1065,7 @@ CodeGeneratorShared::callVM(const VMFunction &fun, LInstruction *ins, const Regi // ... frame ... #ifdef JS_TRACE_LOGGING - emitTracelogStopEvent(TraceLogger::VM); + emitTracelogStopEvent(TraceLogger_VM); #endif } @@ -1398,9 +1398,12 @@ CodeGeneratorShared::computeDivisionConstants(int d) { #ifdef JS_TRACE_LOGGING -bool +void CodeGeneratorShared::emitTracelogScript(bool isStart) { + if (!TraceLogTextIdEnabled(TraceLogger_Scripts)) + return; + Label done; RegisterSet regs = RegisterSet::Volatile(); @@ -1410,36 +1413,33 @@ CodeGeneratorShared::emitTracelogScript(bool isStart) masm.Push(logger); CodeOffsetLabel patchLogger = masm.movWithPatch(ImmPtr(nullptr), logger); - if (!patchableTraceLoggers_.append(patchLogger)) - return false; + masm.propagateOOM(patchableTraceLoggers_.append(patchLogger)); - Address enabledAddress(logger, TraceLogger::offsetOfEnabled()); + Address enabledAddress(logger, TraceLoggerThread::offsetOfEnabled()); masm.branch32(Assembler::Equal, enabledAddress, Imm32(0), &done); masm.Push(script); CodeOffsetLabel patchScript = masm.movWithPatch(ImmWord(0), script); - if (!patchableTLScripts_.append(patchScript)) - return false; + masm.propagateOOM(patchableTLScripts_.append(patchScript)); if (isStart) - masm.tracelogStart(logger, script); + masm.tracelogStartId(logger, script); else - masm.tracelogStop(logger, script); + masm.tracelogStopId(logger, script); masm.Pop(script); masm.bind(&done); masm.Pop(logger); - return true; } -bool +void CodeGeneratorShared::emitTracelogTree(bool isStart, uint32_t textId) { if (!TraceLogTextIdEnabled(textId)) - return true; + return; Label done; RegisterSet regs = RegisterSet::Volatile(); @@ -1448,26 +1448,19 @@ CodeGeneratorShared::emitTracelogTree(bool isStart, uint32_t textId) masm.Push(logger); CodeOffsetLabel patchLocation = masm.movWithPatch(ImmPtr(nullptr), logger); - if (!patchableTraceLoggers_.append(patchLocation)) - return false; + masm.propagateOOM(patchableTraceLoggers_.append(patchLocation)); - Address enabledAddress(logger, TraceLogger::offsetOfEnabled()); + Address enabledAddress(logger, TraceLoggerThread::offsetOfEnabled()); masm.branch32(Assembler::Equal, enabledAddress, Imm32(0), &done); - if (isStart) { - masm.tracelogStart(logger, textId); - } else { -#ifdef DEBUG - masm.tracelogStop(logger, textId); -#else - masm.tracelogStop(logger); -#endif - } + if (isStart) + masm.tracelogStartId(logger, textId); + else + masm.tracelogStopId(logger, textId); masm.bind(&done); masm.Pop(logger); - return true; } #endif diff --git a/js/src/jit/shared/CodeGenerator-x86-shared.cpp b/js/src/jit/shared/CodeGenerator-x86-shared.cpp index 8eca74fa6f21..859284281b23 100644 --- a/js/src/jit/shared/CodeGenerator-x86-shared.cpp +++ b/js/src/jit/shared/CodeGenerator-x86-shared.cpp @@ -58,10 +58,8 @@ CodeGeneratorX86Shared::generateEpilogue() #ifdef JS_TRACE_LOGGING if (gen->info().executionMode() == SequentialExecution) { - if (!emitTracelogStopEvent(TraceLogger::IonMonkey)) - return false; - if (!emitTracelogScriptStop()) - return false; + emitTracelogStopEvent(TraceLogger_IonMonkey); + emitTracelogScriptStop(); } #endif @@ -2145,9 +2143,8 @@ CodeGeneratorX86Shared::visitSimdSplatX4(LSimdSplatX4 *ins) } case MIRType_Float32x4: { FloatRegister r = ToFloatRegister(ins->getOperand(0)); - if (r != output) - masm.moveFloat32x4(r, output); - masm.vshufps(0, output, output, output); + FloatRegister rCopy = masm.reusedInputFloat32x4(r, output); + masm.vshufps(0, rCopy, rCopy, output); break; } default: @@ -2191,7 +2188,12 @@ CodeGeneratorX86Shared::visitSimdExtractElementF(LSimdExtractElementF *ins) uint32_t mask = MacroAssembler::ComputeShuffleMask(lane); masm.shuffleFloat32(mask, input, output); } - masm.canonicalizeFloat(output); + // NaNs contained within SIMD values are not enforced to be canonical, so + // when we extract an element into a "regular" scalar JS value, we have to + // canonicalize. In asm.js code, we can skip this, as asm.js only has to + // canonicalize NaNs at FFI boundaries. + if (!gen->compilingAsmJS()) + masm.canonicalizeFloat(output); } void @@ -2331,9 +2333,8 @@ void CodeGeneratorX86Shared::visitSimdShuffle(LSimdShuffle *ins) { FloatRegister lhs = ToFloatRegister(ins->lhs()); - FloatRegister rhs = ToFloatRegister(ins->rhs()); + Operand rhs = ToOperand(ins->rhs()); FloatRegister out = ToFloatRegister(ins->output()); - MOZ_ASSERT(out == lhs); // define reuse input uint32_t x = ins->laneX(); uint32_t y = ins->laneY(); @@ -2372,8 +2373,8 @@ CodeGeneratorX86Shared::visitSimdShuffle(LSimdShuffle *ins) unsigned firstMask = -1, secondMask = -1; // register-register vmovss preserves the high lanes. - if (ins->lanesMatch(4, 1, 2, 3)) { - masm.vmovss(rhs, lhs, out); + if (ins->lanesMatch(4, 1, 2, 3) && rhs.kind() == Operand::FPREG) { + masm.vmovss(FloatRegister::FromCode(rhs.fpu()), lhs, out); return; } @@ -2396,7 +2397,7 @@ CodeGeneratorX86Shared::visitSimdShuffle(LSimdShuffle *ins) srcLane = SimdLane(w - 4); dstLane = LaneW; } - masm.vinsertps(masm.vinsertpsMask(srcLane, dstLane), rhs, out, out); + masm.vinsertps(masm.vinsertpsMask(srcLane, dstLane), rhs, lhs, out); return; } @@ -2405,21 +2406,21 @@ CodeGeneratorX86Shared::visitSimdShuffle(LSimdShuffle *ins) if (x < 4 && y < 4) { if (w >= 4) { w %= 4; - // T = (Rw Rw Lz Lz) = vshufps(firstMask, lhs, rhs, rhs) + // T = (Rw Rw Lz Lz) = vshufps(firstMask, lhs, rhs, rhsCopy) firstMask = MacroAssembler::ComputeShuffleMask(w, w, z, z); - // (Lx Ly Lz Rw) = (Lx Ly Tz Tx) = vshufps(secondMask, T, lhs, lhs) + // (Lx Ly Lz Rw) = (Lx Ly Tz Tx) = vshufps(secondMask, T, lhs, out) secondMask = MacroAssembler::ComputeShuffleMask(x, y, LaneZ, LaneX); } else { MOZ_ASSERT(z >= 4); z %= 4; - // T = (Rz Rz Lw Lw) = vshufps(firstMask, lhs, rhs, rhs) + // T = (Rz Rz Lw Lw) = vshufps(firstMask, lhs, rhs, rhsCopy) firstMask = MacroAssembler::ComputeShuffleMask(z, z, w, w); - // (Lx Ly Rz Lw) = (Lx Ly Tx Tz) = vshufps(secondMask, T, lhs, lhs) + // (Lx Ly Rz Lw) = (Lx Ly Tx Tz) = vshufps(secondMask, T, lhs, out) secondMask = MacroAssembler::ComputeShuffleMask(x, y, LaneX, LaneZ); } masm.vshufps(firstMask, lhs, rhsCopy, rhsCopy); - masm.vshufps(secondMask, rhsCopy, lhs, lhs); + masm.vshufps(secondMask, rhsCopy, lhs, out); return; } @@ -2427,22 +2428,26 @@ CodeGeneratorX86Shared::visitSimdShuffle(LSimdShuffle *ins) if (y >= 4) { y %= 4; - // T = (Ry Ry Lx Lx) = vshufps(firstMask, lhs, rhs, rhs) + // T = (Ry Ry Lx Lx) = vshufps(firstMask, lhs, rhs, rhsCopy) firstMask = MacroAssembler::ComputeShuffleMask(y, y, x, x); - // (Lx Ry Lz Lw) = (Tz Tx Lz Lw) = vshufps(secondMask, lhs, T, T) + // (Lx Ry Lz Lw) = (Tz Tx Lz Lw) = vshufps(secondMask, lhs, T, out) secondMask = MacroAssembler::ComputeShuffleMask(LaneZ, LaneX, z, w); } else { MOZ_ASSERT(x >= 4); x %= 4; - // T = (Rx Rx Ly Ly) = vshufps(firstMask, lhs, rhs, rhs) + // T = (Rx Rx Ly Ly) = vshufps(firstMask, lhs, rhs, rhsCopy) firstMask = MacroAssembler::ComputeShuffleMask(x, x, y, y); - // (Rx Ly Lz Lw) = (Tx Tz Lz Lw) = vshufps(secondMask, lhs, T, T) + // (Rx Ly Lz Lw) = (Tx Tz Lz Lw) = vshufps(secondMask, lhs, T, out) secondMask = MacroAssembler::ComputeShuffleMask(LaneX, LaneZ, z, w); } masm.vshufps(firstMask, lhs, rhsCopy, rhsCopy); - masm.vshufps(secondMask, lhs, rhsCopy, rhsCopy); - masm.moveFloat32x4(rhsCopy, out); + if (AssemblerX86Shared::HasAVX()) { + masm.vshufps(secondMask, lhs, rhsCopy, out); + } else { + masm.vshufps(secondMask, lhs, rhsCopy, rhsCopy); + masm.moveFloat32x4(rhsCopy, out); + } return; } @@ -2453,9 +2458,10 @@ CodeGeneratorX86Shared::visitSimdShuffle(LSimdShuffle *ins) // but can't be reached because operands would get swapped (bug 1084404). if (ins->lanesMatch(2, 3, 6, 7)) { if (AssemblerX86Shared::HasAVX()) { - masm.vmovhlps(lhs, rhs, out); + FloatRegister rhsCopy = masm.reusedInputAlignedFloat32x4(rhs, ScratchSimdReg); + masm.vmovhlps(lhs, rhsCopy, out); } else { - masm.moveFloat32x4(rhs, ScratchSimdReg); + masm.loadAlignedFloat32x4(rhs, ScratchSimdReg); masm.vmovhlps(lhs, ScratchSimdReg, ScratchSimdReg); masm.moveFloat32x4(ScratchSimdReg, out); } @@ -2463,7 +2469,16 @@ CodeGeneratorX86Shared::visitSimdShuffle(LSimdShuffle *ins) } if (ins->lanesMatch(0, 1, 4, 5)) { - masm.vmovlhps(rhs, lhs, out); + FloatRegister rhsCopy; + if (rhs.kind() == Operand::FPREG) { + // No need to make an actual copy, since the operand is already + // in a register, and it won't be clobbered by the vmovlhps. + rhsCopy = FloatRegister::FromCode(rhs.fpu()); + } else { + masm.loadAlignedFloat32x4(rhs, ScratchSimdReg); + rhsCopy = ScratchSimdReg; + } + masm.vmovlhps(rhsCopy, lhs, out); return; } @@ -2475,9 +2490,10 @@ CodeGeneratorX86Shared::visitSimdShuffle(LSimdShuffle *ins) // TODO swapped case would be better (bug 1084404) if (ins->lanesMatch(4, 0, 5, 1)) { if (AssemblerX86Shared::HasAVX()) { - masm.vunpcklps(lhs, rhs, out); + FloatRegister rhsCopy = masm.reusedInputAlignedFloat32x4(rhs, ScratchSimdReg); + masm.vunpcklps(lhs, rhsCopy, out); } else { - masm.moveFloat32x4(rhs, ScratchSimdReg); + masm.loadAlignedFloat32x4(rhs, ScratchSimdReg); masm.vunpcklps(lhs, ScratchSimdReg, ScratchSimdReg); masm.moveFloat32x4(ScratchSimdReg, out); } @@ -2485,16 +2501,17 @@ CodeGeneratorX86Shared::visitSimdShuffle(LSimdShuffle *ins) } if (ins->lanesMatch(2, 6, 3, 7)) { - masm.vunpckhps(rhs, lhs, lhs); + masm.vunpckhps(rhs, lhs, out); return; } // TODO swapped case would be better (bug 1084404) if (ins->lanesMatch(6, 2, 7, 3)) { if (AssemblerX86Shared::HasAVX()) { - masm.vunpckhps(lhs, rhs, out); + FloatRegister rhsCopy = masm.reusedInputAlignedFloat32x4(rhs, ScratchSimdReg); + masm.vunpckhps(lhs, rhsCopy, out); } else { - masm.moveFloat32x4(rhs, ScratchSimdReg); + masm.loadAlignedFloat32x4(rhs, ScratchSimdReg); masm.vunpckhps(lhs, ScratchSimdReg, ScratchSimdReg); masm.moveFloat32x4(ScratchSimdReg, out); } @@ -2504,7 +2521,7 @@ CodeGeneratorX86Shared::visitSimdShuffle(LSimdShuffle *ins) // In one vshufps if (x < 4 && y < 4) { mask = MacroAssembler::ComputeShuffleMask(x, y, z % 4, w % 4); - masm.vshufps(mask, rhs, out, out); + masm.vshufps(mask, rhs, lhs, out); return; } diff --git a/js/src/jit/shared/Lowering-x86-shared.cpp b/js/src/jit/shared/Lowering-x86-shared.cpp index fb281e47b0a5..1f5759350c66 100644 --- a/js/src/jit/shared/Lowering-x86-shared.cpp +++ b/js/src/jit/shared/Lowering-x86-shared.cpp @@ -155,9 +155,9 @@ LIRGeneratorX86Shared::lowerForBitAndAndBranch(LBitAndAndBranch *baab, MInstruct void LIRGeneratorX86Shared::lowerMulI(MMul *mul, MDefinition *lhs, MDefinition *rhs) { - // Note: lhs is used twice, so that we can restore the original value for the - // negative zero check. - LMulI *lir = new(alloc()) LMulI(useRegisterAtStart(lhs), useOrConstant(rhs), use(lhs)); + // Note: If we need a negative zero check, lhs is used twice. + LAllocation lhsCopy = mul->canBeNegativeZero() ? use(lhs) : LAllocation(); + LMulI *lir = new(alloc()) LMulI(useRegisterAtStart(lhs), useOrConstant(rhs), lhsCopy); if (mul->fallible()) assignSnapshot(lir, Bailout_DoubleOutput); defineReuseInput(lir, mul, 0); diff --git a/js/src/jit/shared/MacroAssembler-x86-shared.h b/js/src/jit/shared/MacroAssembler-x86-shared.h index f40707b17828..cb73ca6cbf39 100644 --- a/js/src/jit/shared/MacroAssembler-x86-shared.h +++ b/js/src/jit/shared/MacroAssembler-x86-shared.h @@ -1011,12 +1011,11 @@ class MacroAssemblerX86Shared : public Assembler // clobber the output with the input and apply the instruction // afterwards. // Note: this is useAtStart-safe because src isn't read afterwards. - if (src != dest) - moveFloat32x4(src, dest); - vshufps(mask, dest, dest, dest); + FloatRegister srcCopy = reusedInputFloat32x4(src, dest); + vshufps(mask, srcCopy, srcCopy, dest); } void shuffleMix(uint32_t mask, const Operand &src, FloatRegister dest) { - // Note this uses vshufps, which is a cross-domain penaly on CPU where it + // Note this uses vshufps, which is a cross-domain penalty on CPU where it // applies, but that's the way clang and gcc do it. vshufps(mask, src, dest, dest); } diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index e4e5f6796d95..88b5275df4bc 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -3560,7 +3560,7 @@ GCHelperState::work() MOZ_ASSERT(!thread); thread = PR_GetCurrentThread(); - TraceLogger *logger = TraceLoggerForCurrentThread(); + TraceLoggerThread *logger = TraceLoggerForCurrentThread(); switch (state()) { @@ -3569,7 +3569,7 @@ GCHelperState::work() break; case SWEEPING: { - AutoTraceLog logSweeping(logger, TraceLogger::GCSweeping); + AutoTraceLog logSweeping(logger, TraceLogger_GCSweeping); doSweep(lock); MOZ_ASSERT(state() == SWEEPING); break; @@ -3593,8 +3593,8 @@ BackgroundAllocTask::BackgroundAllocTask(JSRuntime *rt, ChunkPool &pool) /* virtual */ void BackgroundAllocTask::run() { - TraceLogger *logger = TraceLoggerForCurrentThread(); - AutoTraceLog logAllocation(logger, TraceLogger::GCAllocation); + TraceLoggerThread *logger = TraceLoggerForCurrentThread(); + AutoTraceLog logAllocation(logger, TraceLogger_GCAllocation); AutoLockGC lock(runtime); while (!cancel_ && runtime->gc.wantBackgroundAllocation(lock)) { @@ -6253,8 +6253,8 @@ GCRuntime::collect(bool incremental, SliceBudget &budget, JSGCInvocationKind gck if (rt->mainThread.suppressGC) return; - TraceLogger *logger = TraceLoggerForMainThread(rt); - AutoTraceLog logGC(logger, TraceLogger::GC); + TraceLoggerThread *logger = TraceLoggerForMainThread(rt); + AutoTraceLog logGC(logger, TraceLogger_GC); #ifdef JS_GC_ZEAL if (deterministicOnly && !IsDeterministicGCReason(reason)) @@ -6464,8 +6464,8 @@ void GCRuntime::minorGC(JS::gcreason::Reason reason) { minorGCRequested = false; - TraceLogger *logger = TraceLoggerForMainThread(rt); - AutoTraceLog logMinorGC(logger, TraceLogger::MinorGC); + TraceLoggerThread *logger = TraceLoggerForMainThread(rt); + AutoTraceLog logMinorGC(logger, TraceLogger_MinorGC); nursery.collect(rt, reason, nullptr); MOZ_ASSERT_IF(!rt->mainThread.suppressGC, nursery.isEmpty()); } @@ -6476,8 +6476,8 @@ GCRuntime::minorGC(JSContext *cx, JS::gcreason::Reason reason) // Alternate to the runtime-taking form above which allows marking type // objects as needing pretenuring. minorGCRequested = false; - TraceLogger *logger = TraceLoggerForMainThread(rt); - AutoTraceLog logMinorGC(logger, TraceLogger::MinorGC); + TraceLoggerThread *logger = TraceLoggerForMainThread(rt); + AutoTraceLog logMinorGC(logger, TraceLogger_MinorGC); Nursery::TypeObjectList pretenureTypes; nursery.collect(rt, reason, &pretenureTypes); for (size_t i = 0; i < pretenureTypes.length(); i++) { diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index 73398dac6508..78e039efc384 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -5082,6 +5082,10 @@ template size_t js::PutEscapedStringImpl(char *buffer, size_t bufferSize, FILE *fp, const Latin1Char *chars, size_t length, uint32_t quote); +template size_t +js::PutEscapedStringImpl(char *buffer, size_t bufferSize, FILE *fp, const char *chars, + size_t length, uint32_t quote); + template size_t js::PutEscapedStringImpl(char *buffer, size_t bufferSize, FILE *fp, const char16_t *chars, size_t length, uint32_t quote); diff --git a/js/src/jsstr.h b/js/src/jsstr.h index 318b80151bdc..fcce4babe4d4 100644 --- a/js/src/jsstr.h +++ b/js/src/jsstr.h @@ -400,6 +400,12 @@ FileEscapedString(FILE *fp, JSLinearString *str, uint32_t quote) return PutEscapedStringImpl(nullptr, 0, fp, str, quote) != size_t(-1); } +inline bool +FileEscapedString(FILE *fp, const char *chars, size_t length, uint32_t quote) +{ + return PutEscapedStringImpl(nullptr, 0, fp, chars, length, quote) != size_t(-1); +} + bool str_match(JSContext *cx, unsigned argc, Value *vp); diff --git a/js/src/moz.build b/js/src/moz.build index 8d81b4727c2e..d3124fa6ce0f 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -313,6 +313,8 @@ if CONFIG['MOZ_INSTRUMENTS']: if CONFIG['ENABLE_TRACE_LOGGING']: SOURCES += [ 'vm/TraceLogging.cpp', + 'vm/TraceLoggingGraph.cpp', + 'vm/TraceLoggingTypes.cpp', ] if not CONFIG['ENABLE_ION']: diff --git a/js/src/tests/ecma_6/TypedArray/join.js b/js/src/tests/ecma_6/TypedArray/join.js new file mode 100644 index 000000000000..b1a6104784bc --- /dev/null +++ b/js/src/tests/ecma_6/TypedArray/join.js @@ -0,0 +1,60 @@ +const constructors = [ + Int8Array, + Uint8Array, + Uint8ClampedArray, + Int16Array, + Uint16Array, + Int32Array, + Uint32Array, + Float32Array, + Float64Array +]; + +for (var constructor of constructors) { + assertEq(constructor.prototype.join.length, 1); + + assertEq(new constructor([1, 2, 3]).join(), "1,2,3"); + assertEq(new constructor([1, 2, 3]).join(undefined), "1,2,3"); + assertEq(new constructor([1, 2, 3]).join(null), "1null2null3"); + assertEq(new constructor([1, 2, 3]).join(""), "123"); + assertEq(new constructor([1, 2, 3]).join("+"), "1+2+3"); + assertEq(new constructor([1, 2, 3]).join(.1), "10.120.13"); + assertEq(new constructor([1, 2, 3]).join({toString(){return "foo"}}), "1foo2foo3"); + assertEq(new constructor([1]).join("-"), "1"); + assertEq(new constructor().join(), ""); + assertEq(new constructor().join("*"), ""); + assertEq(new constructor(1).join(), "0"); + assertEq(new constructor(3).join(), "0,0,0"); + + assertThrowsInstanceOf(() => new constructor().join({toString(){throw new TypeError}}), TypeError); + assertThrowsInstanceOf(() => new constructor().join(Symbol()), TypeError); + + // Called from other globals. + if (typeof newGlobal === "function") { + var join = newGlobal()[constructor.name].prototype.join; + assertEq(join.call(new constructor([1, 2, 3]), "\t"), "1\t2\t3"); + } + + // Throws if `this` isn't a TypedArray. + var invalidReceivers = [undefined, null, 1, false, "", Symbol(), [], {}, /./]; + invalidReceivers.forEach(invalidReceiver => { + assertThrowsInstanceOf(() => { + constructor.prototype.join.call(invalidReceiver); + }, TypeError, "Assert that join fails if this value is not a TypedArray"); + }); + // FIXME: Should throw exception if `this` is a proxy, see bug 1115361. + constructor.prototype.join.call(new Proxy(new constructor(), {})); + + // Test that the length getter is never called. + assertEq(Object.defineProperty(new constructor([1, 2, 3]), "length", { + get() { + throw new Error("length accessor called"); + } + }).join("\0"), "1\0002\0003"); +} + +assertDeepEq(new Float32Array([null, , NaN]).join(), "0,NaN,NaN"); +assertDeepEq(new Float64Array([null, , NaN]).join(), "0,NaN,NaN"); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index cb19ed03ffcc..a3da29c78ce3 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -26,6 +26,7 @@ #include "vm/ArgumentsObject.h" #include "vm/DebuggerMemory.h" #include "vm/SPSProfiler.h" +#include "vm/TraceLogging.h" #include "vm/WrapperObject.h" #include "jsgcinlines.h" @@ -366,7 +367,13 @@ Debugger::Debugger(JSContext *cx, NativeObject *dbg) scripts(cx), sources(cx), objects(cx), - environments(cx) + environments(cx), +#ifdef NIGHTLY_BUILD + traceLoggerLastDrainedId(0), + traceLoggerLastDrainedIteration(0), +#endif + traceLoggerScriptedCallsLastDrainedId(0), + traceLoggerScriptedCallsLastDrainedIteration(0) { assertSameCompartment(cx, dbg); @@ -3756,6 +3763,253 @@ Debugger::makeGlobalObjectReference(JSContext *cx, unsigned argc, Value *vp) return dbg->wrapDebuggeeValue(cx, args.rval()); } +static bool +DefineProperty(JSContext *cx, HandleObject obj, HandleId id, const char *value, size_t n) +{ + JSString *text = JS_NewStringCopyN(cx, value, n); + if (!text) + return false; + + RootedValue str(cx, StringValue(text)); + return JS_DefinePropertyById(cx, obj, id, str, JSPROP_ENUMERATE); +} + +#ifdef JS_TRACE_LOGGING +# ifdef NIGHTLY_BUILD +bool +Debugger::setupTraceLogger(JSContext *cx, unsigned argc, Value *vp) +{ + THIS_DEBUGGER(cx, argc, vp, "setupTraceLogger", args, dbg); + if (!args.requireAtLeast(cx, "Debugger.setupTraceLogger", 1)) + return false; + + RootedObject obj(cx, ToObject(cx, args[0])); + if (!obj) + return false; + + AutoIdVector ids(cx); + if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY, &ids)) + return false; + + if (ids.length() == 0) { + args.rval().setBoolean(true); + return true; + } + + Vector textIds(cx); + if (!textIds.reserve(ids.length())) + return false; + + Vector values(cx); + if (!values.reserve(ids.length())) + return false; + + for (size_t i = 0; i < ids.length(); i++) { + if (!JSID_IS_STRING(ids[i])) { + args.rval().setBoolean(false); + return true; + } + + JSString *id = JSID_TO_STRING(ids[i]); + JSLinearString *linear = id->ensureLinear(cx); + if (!linear) + return false; + + uint32_t textId = TLStringToTextId(linear); + + if (!TLTextIdIsToggable(textId)) { + args.rval().setBoolean(false); + return true; + } + + RootedValue v(cx); + if (!JSObject::getGeneric(cx, obj, obj, ids[i], &v)) + return false; + + textIds.append(textId); + values.append(ToBoolean(v)); + } + + MOZ_ASSERT(ids.length() == textIds.length()); + MOZ_ASSERT(textIds.length() == values.length()); + + for (size_t i = 0; i < textIds.length(); i++) { + if (values[i]) + TraceLogEnableTextId(cx, textIds[i]); + else + TraceLogDisableTextId(cx, textIds[i]); + } + + args.rval().setBoolean(true); + return true; +} + +bool +Debugger::drainTraceLogger(JSContext *cx, unsigned argc, Value *vp) +{ + THIS_DEBUGGER(cx, argc, vp, "drainTraceLogger", args, dbg); + if (!args.requireAtLeast(cx, "Debugger.drainTraceLogger", 0)) + return false; + + size_t num; + TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime()); + bool lostEvents = logger->lostEvents(dbg->traceLoggerLastDrainedIteration, + dbg->traceLoggerLastDrainedId); + EventEntry *events = logger->getEventsStartingAt(&dbg->traceLoggerLastDrainedIteration, + &dbg->traceLoggerLastDrainedId, + &num); + + RootedObject array(cx, NewDenseEmptyArray(cx)); + JSAtom *dataAtom = Atomize(cx, "data", strlen("data")); + if (!dataAtom) + return false; + RootedId dataId(cx, AtomToId(dataAtom)); + + /* Add all events to the array. */ + uint32_t index = 0; + for (EventEntry *eventItem = events; eventItem < events + num; eventItem++, index++) { + RootedObject item(cx, NewObjectWithGivenProto(cx, &PlainObject::class_, nullptr, cx->global())); + if (!item) + return false; + + const char *eventText = logger->eventText(eventItem->textId); + if (!DefineProperty(cx, item, dataId, eventText, strlen(eventText))) + return false; + + RootedValue obj(cx, ObjectValue(*item)); + if (!JS_DefineElement(cx, array, index, obj, JSPROP_ENUMERATE)) + return false; + } + + /* Add "lostEvents" indicating if there are events that were lost. */ + RootedValue lost(cx, BooleanValue(lostEvents)); + if (!JS_DefineProperty(cx, array, "lostEvents", lost, JSPROP_ENUMERATE)) + return false; + + args.rval().setObject(*array); + + return true; +} +# endif // NIGHTLY_BUILD + +bool +Debugger::setupTraceLoggerScriptCalls(JSContext *cx, unsigned argc, Value *vp) +{ + THIS_DEBUGGER(cx, argc, vp, "setupTraceLoggerScriptCalls", args, dbg); + if (!args.requireAtLeast(cx, "Debugger.setupTraceLoggerScriptCalls", 0)) + return false; + + TraceLogEnableTextId(cx, TraceLogger_Scripts); + TraceLogEnableTextId(cx, TraceLogger_InlinedScripts); + TraceLogDisableTextId(cx, TraceLogger_AnnotateScripts); + + args.rval().setBoolean(true); + + return true; +} + +bool +Debugger::startTraceLogger(JSContext *cx, unsigned argc, Value *vp) +{ + THIS_DEBUGGER(cx, argc, vp, "startTraceLogger", args, dbg); + if (!args.requireAtLeast(cx, "Debugger.startTraceLogger", 0)) + return false; + + TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime()); + TraceLoggerEnable(logger, cx); + + args.rval().setUndefined(); + + return true; +} + +bool +Debugger::endTraceLogger(JSContext *cx, unsigned argc, Value *vp) +{ + THIS_DEBUGGER(cx, argc, vp, "endTraceLogger", args, dbg); + if (!args.requireAtLeast(cx, "Debugger.endTraceLogger", 0)) + return false; + + TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime()); + TraceLoggerDisable(logger); + + args.rval().setUndefined(); + + return true; +} + +bool +Debugger::drainTraceLoggerScriptCalls(JSContext *cx, unsigned argc, Value *vp) +{ + THIS_DEBUGGER(cx, argc, vp, "drainTraceLoggerScriptCalls", args, dbg); + if (!args.requireAtLeast(cx, "Debugger.drainTraceLoggerScriptCalls", 0)) + return false; + + size_t num; + TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime()); + bool lostEvents = logger->lostEvents(dbg->traceLoggerScriptedCallsLastDrainedIteration, + dbg->traceLoggerScriptedCallsLastDrainedId); + EventEntry *events = logger->getEventsStartingAt( + &dbg->traceLoggerScriptedCallsLastDrainedIteration, + &dbg->traceLoggerScriptedCallsLastDrainedId, + &num); + + RootedObject array(cx, NewDenseEmptyArray(cx)); + RootedId fileNameId(cx, AtomToId(cx->names().fileName)); + RootedId lineNumberId(cx, AtomToId(cx->names().lineNumber)); + RootedId columnNumberId(cx, AtomToId(cx->names().columnNumber)); + JSAtom *logTypeAtom = Atomize(cx, "logType", strlen("logType")); + if (!logTypeAtom) + return false; + RootedId logTypeId(cx, AtomToId(logTypeAtom)); + + /* Add all events to the array. */ + uint32_t index = 0; + for (EventEntry *eventItem = events; eventItem < events + num; eventItem++) { + RootedObject item(cx, NewObjectWithGivenProto(cx, &PlainObject::class_, nullptr, cx->global())); + if (!item) + return false; + + uint32_t textId = eventItem->textId; + if (textId != TraceLogger_Stop && !logger->textIdIsScriptEvent(textId)) + continue; + + const char *type = (textId == TraceLogger_Stop) ? "Stop" : "Script"; + if (!DefineProperty(cx, item, logTypeId, type, strlen(type))) + return false; + + if (textId != TraceLogger_Stop) { + const char *filename, *lineno, *colno; + size_t filename_len, lineno_len, colno_len; + logger->extractScriptDetails(textId, &filename, &filename_len, &lineno, &lineno_len, + &colno, &colno_len); + + if (!DefineProperty(cx, item, fileNameId, filename, filename_len)) + return false; + if (!DefineProperty(cx, item, lineNumberId, lineno, lineno_len)) + return false; + if (!DefineProperty(cx, item, columnNumberId, colno, colno_len)) + return false; + } + + RootedValue obj(cx, ObjectValue(*item)); + if (!JS_DefineElement(cx, array, index, obj, JSPROP_ENUMERATE)) + return false; + + index++; + } + + /* Add "lostEvents" indicating if there are events that were lost. */ + RootedValue lost(cx, BooleanValue(lostEvents)); + if (!JS_DefineProperty(cx, array, "lostEvents", lost, JSPROP_ENUMERATE)) + return false; + + args.rval().setObject(*array); + + return true; +} +#endif + const JSPropertySpec Debugger::properties[] = { JS_PSGS("enabled", Debugger::getEnabled, Debugger::setEnabled, 0), JS_PSGS("onDebuggerStatement", Debugger::getOnDebuggerStatement, @@ -3785,6 +4039,16 @@ const JSFunctionSpec Debugger::methods[] = { JS_FN("findObjects", Debugger::findObjects, 1, 0), JS_FN("findAllGlobals", Debugger::findAllGlobals, 0, 0), JS_FN("makeGlobalObjectReference", Debugger::makeGlobalObjectReference, 1, 0), +#ifdef JS_TRACE_LOGGING + JS_FN("setupTraceLoggerScriptCalls", Debugger::setupTraceLoggerScriptCalls, 0, 0), + JS_FN("drainTraceLoggerScriptCalls", Debugger::drainTraceLoggerScriptCalls, 0, 0), + JS_FN("startTraceLogger", Debugger::startTraceLogger, 0, 0), + JS_FN("endTraceLogger", Debugger::endTraceLogger, 0, 0), +# ifdef NIGHTLY_BUILD + JS_FN("setupTraceLogger", Debugger::setupTraceLogger, 1, 0), + JS_FN("drainTraceLogger", Debugger::drainTraceLogger, 0, 0), +# endif +#endif JS_FS_END }; diff --git a/js/src/vm/Debugger.h b/js/src/vm/Debugger.h index 138b5192a749..07b9b20f2ae1 100644 --- a/js/src/vm/Debugger.h +++ b/js/src/vm/Debugger.h @@ -307,6 +307,17 @@ class Debugger : private mozilla::LinkedListElement /* The map from debuggee Envs to Debugger.Environment instances. */ ObjectWeakMap environments; + /* + * Keep track of tracelogger last drained identifiers to know if there are + * lost events. + */ +#ifdef NIGHTLY_BUILD + uint32_t traceLoggerLastDrainedId; + uint32_t traceLoggerLastDrainedIteration; +#endif + uint32_t traceLoggerScriptedCallsLastDrainedId; + uint32_t traceLoggerScriptedCallsLastDrainedIteration; + class FrameRange; class ScriptQuery; class ObjectQuery; @@ -405,6 +416,14 @@ class Debugger : private mozilla::LinkedListElement static bool findObjects(JSContext *cx, unsigned argc, Value *vp); static bool findAllGlobals(JSContext *cx, unsigned argc, Value *vp); static bool makeGlobalObjectReference(JSContext *cx, unsigned argc, Value *vp); + static bool setupTraceLoggerScriptCalls(JSContext *cx, unsigned argc, Value *vp); + static bool drainTraceLoggerScriptCalls(JSContext *cx, unsigned argc, Value *vp); + static bool startTraceLogger(JSContext *cx, unsigned argc, Value *vp); + static bool endTraceLogger(JSContext *cx, unsigned argc, Value *vp); +#ifdef NIGHTLY_BUILD + static bool setupTraceLogger(JSContext *cx, unsigned argc, Value *vp); + static bool drainTraceLogger(JSContext *cx, unsigned argc, Value *vp); +#endif static bool construct(JSContext *cx, unsigned argc, Value *vp); static const JSPropertySpec properties[]; static const JSFunctionSpec methods[]; diff --git a/js/src/vm/HelperThreads.cpp b/js/src/vm/HelperThreads.cpp index 55e6e8770a4f..e0a30673c508 100644 --- a/js/src/vm/HelperThreads.cpp +++ b/js/src/vm/HelperThreads.cpp @@ -1058,9 +1058,10 @@ HelperThread::handleIonWorkload() ionBuilder = builder; ionBuilder->setPauseFlag(&pause); - TraceLogger *logger = TraceLoggerForCurrentThread(); - AutoTraceLog logScript(logger, TraceLogCreateTextId(logger, ionBuilder->script())); - AutoTraceLog logCompile(logger, TraceLogger::IonCompilation); + TraceLoggerThread *logger = TraceLoggerForCurrentThread(); + TraceLoggerEvent event(logger, TraceLogger_AnnotateScripts, ionBuilder->script()); + AutoTraceLog logScript(logger, event); + AutoTraceLog logCompile(logger, TraceLogger_IonCompilation); JSRuntime *rt = ionBuilder->script()->compartment()->runtimeFromAnyThread(); @@ -1128,8 +1129,8 @@ CurrentHelperThread() void js::PauseCurrentHelperThread() { - TraceLogger *logger = TraceLoggerForCurrentThread(); - AutoTraceLog logPaused(logger, TraceLogger::IonCompilationPaused); + TraceLoggerThread *logger = TraceLoggerForCurrentThread(); + AutoTraceLog logPaused(logger, TraceLogger_IonCompilationPaused); HelperThread *thread = CurrentHelperThread(); diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index b5a63cb014e1..6040668cd8f1 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1482,10 +1482,10 @@ Interpret(JSContext *cx, RunState &state) RootedScript script(cx); SET_SCRIPT(REGS.fp()->script()); - TraceLogger *logger = TraceLoggerForMainThread(cx->runtime()); - uint32_t scriptLogId = TraceLogCreateTextId(logger, script); - TraceLogStartEvent(logger, scriptLogId); - TraceLogStartEvent(logger, TraceLogger::Interpreter); + TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime()); + TraceLoggerEvent scriptEvent(logger, TraceLogger_Scripts, script); + TraceLogStartEvent(logger, scriptEvent); + TraceLogStartEvent(logger, TraceLogger_Interpreter); /* * Pool of rooters for use in this interpreter frame. References to these @@ -1775,9 +1775,8 @@ CASE(JSOP_RETRVAL) if (activation.entryFrame() != REGS.fp()) { // Stop the engine. (No details about which engine exactly, could be // interpreter, Baseline or IonMonkey.) - TraceLogStopEvent(logger); - // Stop the script. (Again no details about which script exactly.) - TraceLogStopEvent(logger); + TraceLogStopEvent(logger, TraceLogger_Engine); + TraceLogStopEvent(logger, TraceLogger_Scripts); interpReturnOK = Debugger::onLeaveFrame(cx, REGS.fp(), interpReturnOK); @@ -2613,9 +2612,11 @@ CASE(JSOP_FUNCALL) SET_SCRIPT(REGS.fp()->script()); - uint32_t scriptLogId = TraceLogCreateTextId(logger, script); - TraceLogStartEvent(logger, scriptLogId); - TraceLogStartEvent(logger, TraceLogger::Interpreter); + { + TraceLoggerEvent event(logger, TraceLogger_Scripts, script); + TraceLogStartEvent(logger, event); + TraceLogStartEvent(logger, TraceLogger_Interpreter); + } if (!REGS.fp()->prologue(cx)) goto error; @@ -3526,8 +3527,8 @@ DEFAULT() gc::MaybeVerifyBarriers(cx, true); - TraceLogStopEvent(logger); - TraceLogStopEvent(logger, scriptLogId); + TraceLogStopEvent(logger, TraceLogger_Engine); + TraceLogStopEvent(logger, scriptEvent); /* * This path is used when it's guaranteed the method can be finished diff --git a/js/src/vm/RegExpObject.cpp b/js/src/vm/RegExpObject.cpp index 35d75bb24b6b..16c838e01c14 100644 --- a/js/src/vm/RegExpObject.cpp +++ b/js/src/vm/RegExpObject.cpp @@ -456,8 +456,8 @@ bool RegExpShared::compile(JSContext *cx, HandleLinearString input, CompilationMode mode, ForceByteCodeEnum force) { - TraceLogger *logger = TraceLoggerForMainThread(cx->runtime()); - AutoTraceLog logCompile(logger, TraceLogger::IrregexpCompile); + TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime()); + AutoTraceLog logCompile(logger, TraceLogger_IrregexpCompile); if (!sticky()) { RootedAtom pattern(cx, source); @@ -543,7 +543,7 @@ RegExpRunStatus RegExpShared::execute(JSContext *cx, HandleLinearString input, size_t start, MatchPairs *matches) { - TraceLogger *logger = TraceLoggerForMainThread(cx->runtime()); + TraceLoggerThread *logger = TraceLoggerForMainThread(cx->runtime()); CompilationMode mode = matches ? Normal : MatchOnly; @@ -599,7 +599,7 @@ RegExpShared::execute(JSContext *cx, HandleLinearString input, size_t start, RegExpRunStatus result; { - AutoTraceLog logJIT(logger, TraceLogger::IrregexpExecute); + AutoTraceLog logJIT(logger, TraceLogger_IrregexpExecute); AutoCheckCannotGC nogc; if (input->hasLatin1Chars()) { const Latin1Char *chars = input->latin1Chars(nogc) + charsOffset; @@ -638,7 +638,7 @@ RegExpShared::execute(JSContext *cx, HandleLinearString input, size_t start, return RegExpRunStatus_Error; uint8_t *byteCode = compilation(mode, input->hasLatin1Chars()).byteCode; - AutoTraceLog logInterpreter(logger, TraceLogger::IrregexpExecute); + AutoTraceLog logInterpreter(logger, TraceLogger_IrregexpExecute); AutoStableStringChars inputChars(cx); if (!inputChars.init(cx, input)) diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index 5e07400d1ed9..6cb6ff500bd1 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -54,7 +54,7 @@ class PerThreadData; struct ThreadSafeContext; class AutoKeepAtoms; #ifdef JS_TRACE_LOGGING -class TraceLogger; +class TraceLoggerThread; #endif /* Thread Local Storage slot for storing the runtime for a thread. */ @@ -540,7 +540,7 @@ class PerThreadData : public PerThreadDataFriendFields irregexp::RegExpStack regexpStack; #ifdef JS_TRACE_LOGGING - TraceLogger *traceLogger; + TraceLoggerThread *traceLogger; #endif private: diff --git a/js/src/vm/TraceLogging.cpp b/js/src/vm/TraceLogging.cpp index f163d9617e64..b731ed6f815f 100644 --- a/js/src/vm/TraceLogging.cpp +++ b/js/src/vm/TraceLogging.cpp @@ -7,29 +7,27 @@ #include "vm/TraceLogging.h" #include "mozilla/DebugOnly.h" -#include "mozilla/Endian.h" #include #include "jsapi.h" +#include "jsprf.h" #include "jsscript.h" +#include "jit/BaselineJIT.h" #include "jit/CompileWrappers.h" #include "vm/Runtime.h" +#include "vm/TraceLoggingGraph.h" #include "jit/JitFrames-inl.h" using namespace js; using namespace js::jit; + +using mozilla::DebugOnly; using mozilla::NativeEndian; -#ifndef TRACE_LOG_DIR -# if defined(_WIN32) -# define TRACE_LOG_DIR "" -# else -# define TRACE_LOG_DIR "/tmp/" -# endif -#endif +TraceLoggerThreadState traceLoggers; #if defined(_WIN32) #include @@ -75,96 +73,106 @@ rdtsc(void) return result; } +#else +static __inline__ uint64_t +rdtsc(void) +{ + return 0; +} #endif -TraceLogging traceLoggers; - -static const char* const text[] = +class AutoTraceLoggerThreadStateLock { - "TraceLogger failed to process text", -#define NAME(x) #x, - TRACELOGGER_TEXT_ID_LIST(NAME) -#undef NAME + TraceLoggerThreadState *logging; + + public: + explicit AutoTraceLoggerThreadStateLock(TraceLoggerThreadState *logging MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : logging(logging) + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + PR_Lock(logging->lock); + } + ~AutoTraceLoggerThreadStateLock() { + PR_Unlock(logging->lock); + } + private: + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; -TraceLogger::TraceLogger() - : enabled(0), - failed(false), - nextTextId(0), - treeOffset(0), - top(nullptr) +TraceLoggerThread::TraceLoggerThread() + : enabled(0), + failed(false), + graph(), + iteration_(0), + top(nullptr) { } bool -TraceLogger::init(uint32_t loggerId) +TraceLoggerThread::init() { if (!pointerMap.init()) return false; - if (!tree.init()) - return false; - if (!stack.init()) + if (!extraTextId.init()) return false; if (!events.init()) return false; - MOZ_ASSERT(loggerId <= 999); - - char dictFilename[sizeof TRACE_LOG_DIR "tl-dict.100.json"]; - sprintf(dictFilename, TRACE_LOG_DIR "tl-dict.%d.json", loggerId); - dictFile = fopen(dictFilename, "w"); - if (!dictFile) + // Minimum amount of capacity needed for operation to allow flushing. + // Flushing requires space for the actual event and two spaces to log the + // start and stop of flushing. + if (!events.ensureSpaceBeforeAdd(3)) return false; - char treeFilename[sizeof TRACE_LOG_DIR "tl-tree.100.tl"]; - sprintf(treeFilename, TRACE_LOG_DIR "tl-tree.%d.tl", loggerId); - treeFile = fopen(treeFilename, "wb"); - if (!treeFile) { - fclose(dictFile); - dictFile = nullptr; - return false; - } - - char eventFilename[sizeof TRACE_LOG_DIR "tl-event.100.tl"]; - sprintf(eventFilename, TRACE_LOG_DIR "tl-event.%d.tl", loggerId); - eventFile = fopen(eventFilename, "wb"); - if (!eventFile) { - fclose(dictFile); - fclose(treeFile); - dictFile = nullptr; - treeFile = nullptr; - return false; - } - - uint64_t start = rdtsc() - traceLoggers.startupTime; - - TreeEntry &treeEntry = tree.pushUninitialized(); - treeEntry.setStart(start); - treeEntry.setStop(0); - treeEntry.setTextId(0); - treeEntry.setHasChildren(false); - treeEntry.setNextId(0); - - StackEntry &stackEntry = stack.pushUninitialized(); - stackEntry.setTreeId(0); - stackEntry.setLastChildId(0); - stackEntry.setActive(true); - - int written = fprintf(dictFile, "["); - if (written < 0) - fprintf(stderr, "TraceLogging: Error while writing.\n"); - - // Eagerly create the default textIds, to match their Tracelogger::TextId. - for (uint32_t i = 0; i < LAST; i++) { - mozilla::DebugOnly textId = createTextId(text[i]); - MOZ_ASSERT(textId == i); - } - enabled = 1; + logTimestamp(TraceLogger_Enable); + return true; } +void +TraceLoggerThread::initGraph() +{ + // Create a graph. I don't like this is called reset, but it locks the + // graph into the UniquePtr. So it gets deleted when TraceLoggerThread + // is destructed. + graph.reset(js_new()); + if (!graph.get()) + return; + + uint64_t start = rdtsc() - traceLoggers.startupTime; + if (!graph->init(start)) { + graph = nullptr; + return; + } + + // Report the textIds to the graph. + for (uint32_t i = 0; i < TraceLogger_LastTreeItem; i++) { + TraceLoggerTextId id = TraceLoggerTextId(i); + graph->addTextId(i, TLTextIdString(id)); + } + graph->addTextId(TraceLogger_LastTreeItem, "TraceLogger internal"); + for (uint32_t i = TraceLogger_LastTreeItem + 1; i < TraceLogger_Last; i++) { + TraceLoggerTextId id = TraceLoggerTextId(i); + graph->addTextId(i, TLTextIdString(id)); + } +} + +TraceLoggerThread::~TraceLoggerThread() +{ + if (graph.get()) { + if (!failed) + graph->log(events); + graph = nullptr; + } + + for (TextIdHashMap::Range r = extraTextId.all(); !r.empty(); r.popFront()) + js_delete(r.front().value()); + extraTextId.finish(); + pointerMap.finish(); +} + bool -TraceLogger::enable() +TraceLoggerThread::enable() { if (enabled > 0) { enabled++; @@ -175,20 +183,21 @@ TraceLogger::enable() return false; enabled = 1; + logTimestamp(TraceLogger_Enable); + return true; } bool -TraceLogger::enable(JSContext *cx) +TraceLoggerThread::enable(JSContext *cx) { if (!enable()) return false; if (enabled == 1) { // Get the top Activation to log the top script/pc (No inlined frames). - Activation *act = cx->mainThread().activation(); - while (act && (act->cx() != cx || (act->isJit() && !act->asJit()->isActive()))) - act = act->prev(); + ActivationIterator iter(cx->runtime()); + Activation *act = iter.activation(); if (!act) { failed = true; @@ -200,37 +209,23 @@ TraceLogger::enable(JSContext *cx) int32_t engine = 0; if (act->isJit()) { - JSRuntime *rt = cx->runtime(); + JitFrameIterator it(iter); - JitFrameIterator it(rt->mainThread.jitTop, SequentialExecution); - - MOZ_ASSERT(it.type() == JitFrame_Exit); - ++it; - - if (it.type() == JitFrame_Rectifier || - it.type() == JitFrame_Unwound_Rectifier) - { + while (!it.isScripted() && !it.done()) ++it; - MOZ_ASSERT(it.type() == JitFrame_BaselineStub || - it.type() == JitFrame_BaselineJS || - it.type() == JitFrame_IonJS); - } - if (it.type() == JitFrame_BaselineStub || - it.type() == JitFrame_Unwound_BaselineStub) - { - ++it; - MOZ_ASSERT(it.type() == JitFrame_BaselineJS); - } + + MOZ_ASSERT(!it.done()); + MOZ_ASSERT(it.isIonJS() || it.isBaselineJS()); script = it.script(); - engine = it.isIonJS() ? IonMonkey : Baseline; + engine = it.isIonJS() ? TraceLogger_IonMonkey : TraceLogger_Baseline; } else { MOZ_ASSERT(act->isInterpreter()); InterpreterFrame *fp = act->asInterpreter()->current(); MOZ_ASSERT(!fp->runningInJit()); script = fp->script(); - engine = Interpreter; + engine = TraceLogger_Interpreter; if (script->compartment() != cx->compartment()) { failed = true; enabled = 0; @@ -238,7 +233,8 @@ TraceLogger::enable(JSContext *cx) } } - startEvent(createTextId(script)); + TraceLoggerEvent event(this, TraceLogger_Scripts, script); + startEvent(event); startEvent(engine); } @@ -246,7 +242,7 @@ TraceLogger::enable(JSContext *cx) } bool -TraceLogger::disable() +TraceLoggerThread::disable() { if (failed) return false; @@ -259,189 +255,282 @@ TraceLogger::disable() return true; } - MOZ_ASSERT(enabled == 1); - while (stack.size() > 1) - stopEvent(); - + logTimestamp(TraceLogger_Disable); enabled = 0; return true; } +const char * +TraceLoggerThread::eventText(uint32_t id) +{ + if (id < TraceLogger_Last) + return TLTextIdString(static_cast(id)); + + TextIdHashMap::Ptr p = extraTextId.lookup(id); + MOZ_ASSERT(p); + + return p->value()->string(); +} + bool -TraceLogger::flush() +TraceLoggerThread::textIdIsScriptEvent(uint32_t id) { - MOZ_ASSERT(!failed); + if (id < TraceLogger_Last) + return false; - if (treeFile) { - // Format data in big endian. - for (size_t i = 0; i < tree.size(); i++) - entryToBigEndian(&tree[i]); - - int success = fseek(treeFile, 0, SEEK_END); - if (success != 0) - return false; - - size_t bytesWritten = fwrite(tree.data(), sizeof(TreeEntry), tree.size(), treeFile); - if (bytesWritten < tree.size()) - return false; - - treeOffset += tree.lastEntryId(); - tree.clear(); - } - - if (eventFile) { - // Format data in big endian - for (size_t i = 0; i < events.size(); i++) { - events[i].time = NativeEndian::swapToBigEndian(events[i].time); - events[i].textId = NativeEndian::swapToBigEndian(events[i].textId); - } - - size_t bytesWritten = fwrite(events.data(), sizeof(EventEntry), events.size(), eventFile); - if (bytesWritten < events.size()) - return false; - events.clear(); - } - - return true; + // Currently this works by checking if text begins with "script". + const char *str = eventText(id); + return EqualChars(str, "script", 6); } -TraceLogger::~TraceLogger() +void +TraceLoggerThread::extractScriptDetails(uint32_t textId, const char **filename, size_t *filename_len, + const char **lineno, size_t *lineno_len, const char **colno, + size_t *colno_len) { - // Write dictionary to disk - if (dictFile) { - int written = fprintf(dictFile, "]"); - if (written < 0) - fprintf(stderr, "TraceLogging: Error while writing.\n"); - fclose(dictFile); + MOZ_ASSERT(textIdIsScriptEvent(textId)); - dictFile = nullptr; + const char *script = eventText(textId); + + // Get the start of filename (remove 'script ' at the start). + MOZ_ASSERT(EqualChars(script, "script ", 7)); + *filename = script + 7; + + // Get the start of lineno and colno. + *lineno = script; + *colno = script; + const char *next = script - 1; + while ((next = strchr(next + 1, ':'))) { + *lineno = *colno; + *colno = next; } - if (!failed && treeFile) { - // Make sure every start entry has a corresponding stop value. - // We temporary enable logging for this. Stop doesn't need any extra data, - // so is safe to do, even when we encountered OOM. - enabled = 1; - while (stack.size() > 1) - stopEvent(); - enabled = 0; - } + MOZ_ASSERT(*lineno && *lineno != script); + MOZ_ASSERT(*colno && *colno != script); - if (!failed && !flush()) { - fprintf(stderr, "TraceLogging: Couldn't write the data to disk.\n"); - enabled = 0; - failed = true; - } + // Remove the ':' at the front. + *lineno = *lineno + 1; + *colno = *colno + 1; - if (treeFile) { - fclose(treeFile); - treeFile = nullptr; - } - - if (eventFile) { - fclose(eventFile); - eventFile = nullptr; - } + *filename_len = *lineno - *filename - 1; + *lineno_len = *colno - *lineno - 1; + *colno_len = strlen(*colno); } -uint32_t -TraceLogger::createTextId(const char *text) +TraceLoggerEventPayload * +TraceLoggerThread::getOrCreateEventPayload(TraceLoggerTextId textId) { - assertNoQuotes(text); + TextIdHashMap::AddPtr p = extraTextId.lookupForAdd(textId); + if (p) + return p->value(); + TraceLoggerEventPayload *payload = js_new(textId, (char *)nullptr); + + if (!extraTextId.add(p, textId, payload)) + return nullptr; + + return payload; +} + +TraceLoggerEventPayload * +TraceLoggerThread::getOrCreateEventPayload(const char *text) +{ PointerHashMap::AddPtr p = pointerMap.lookupForAdd((const void *)text); if (p) return p->value(); - uint32_t textId = nextTextId++; - if (!pointerMap.add(p, text, textId)) - return TraceLogger::TL_Error; + size_t len = strlen(text); + char *str = js_pod_malloc(len + 1); + if (!str) + return nullptr; - int written; - if (textId > 0) - written = fprintf(dictFile, ",\n\"%s\"", text); - else - written = fprintf(dictFile, "\"%s\"", text); + DebugOnly ret = JS_snprintf(str, len + 1, "%s", text); + MOZ_ASSERT(ret == len); + MOZ_ASSERT(strlen(str) == len); - if (written < 0) - return TraceLogger::TL_Error; + uint32_t textId = extraTextId.count() + TraceLogger_Last; - return textId; + TraceLoggerEventPayload *payload = js_new(textId, str); + if (!payload) { + js_free(str); + return nullptr; + } + + if (!extraTextId.putNew(textId, payload)) { + js_delete(payload); + return nullptr; + } + + if (!pointerMap.add(p, text, payload)) + return nullptr; + + if (graph.get()) + graph->addTextId(textId, str); + + return payload; } -uint32_t -TraceLogger::createTextId(JSScript *script) +TraceLoggerEventPayload * +TraceLoggerThread::getOrCreateEventPayload(TraceLoggerTextId type, const char *filename, + size_t lineno, size_t colno, const void *ptr) { - if (!script->filename()) - return createTextId(""); + MOZ_ASSERT(type == TraceLogger_Scripts || type == TraceLogger_AnnotateScripts || + type == TraceLogger_InlinedScripts); - assertNoQuotes(script->filename()); + if (!filename) + filename = ""; - PointerHashMap::AddPtr p = pointerMap.lookupForAdd(script); + // Only log scripts when enabled otherwise return the global Scripts textId, + // which will get filtered out. + if (!traceLoggers.isTextIdEnabled(type)) + return getOrCreateEventPayload(type); + + PointerHashMap::AddPtr p = pointerMap.lookupForAdd(ptr); if (p) return p->value(); - uint32_t textId = nextTextId++; - if (!pointerMap.add(p, script, textId)) - return TraceLogger::TL_Error; + // Compute the length of the string to create. + size_t lenFilename = strlen(filename); + size_t lenLineno = 1; + for (size_t i = lineno; i /= 10; lenLineno++); + size_t lenColno = 1; + for (size_t i = colno; i /= 10; lenColno++); - int written; - if (textId > 0) { - written = fprintf(dictFile, ",\n\"script %s:%u:%u\"", script->filename(), - (unsigned)script->lineno(), (unsigned)script->column()); - } else { - written = fprintf(dictFile, "\"script %s:%u:%u\"", script->filename(), - (unsigned)script->lineno(), (unsigned)script->column()); + size_t len = 7 + lenFilename + 1 + lenLineno + 1 + lenColno; + char *str = js_pod_malloc(len + 1); + if (!str) + return nullptr; + + DebugOnly ret = + JS_snprintf(str, len + 1, "script %s:%u:%u", filename, lineno, colno); + MOZ_ASSERT(ret == len); + MOZ_ASSERT(strlen(str) == len); + + uint32_t textId = extraTextId.count() + TraceLogger_Last; + TraceLoggerEventPayload *payload = js_new(textId, str); + if (!payload) { + js_free(str); + return nullptr; } - if (written < 0) - return TraceLogger::TL_Error; + if (!extraTextId.putNew(textId, payload)) { + js_delete(payload); + return nullptr; + } - return textId; + if (!pointerMap.add(p, ptr, payload)) + return nullptr; + + if (graph.get()) + graph->addTextId(textId, str); + + return payload; } -uint32_t -TraceLogger::createTextId(const JS::ReadOnlyCompileOptions &compileOptions) +TraceLoggerEventPayload * +TraceLoggerThread::getOrCreateEventPayload(TraceLoggerTextId type, JSScript *script) { - if (!compileOptions.filename()) - return createTextId(""); + return getOrCreateEventPayload(type, script->filename(), script->lineno(), script->column(), + script); +} - assertNoQuotes(compileOptions.filename()); - - PointerHashMap::AddPtr p = pointerMap.lookupForAdd(&compileOptions); - if (p) - return p->value(); - - uint32_t textId = nextTextId++; - if (!pointerMap.add(p, &compileOptions, textId)) - return TraceLogger::TL_Error; - - int written; - if (textId > 0) { - written = fprintf(dictFile, ",\n\"script %s:%d:%d\"", compileOptions.filename(), - compileOptions.lineno, compileOptions.column); - } else { - written = fprintf(dictFile, "\"script %s:%d:%d\"", compileOptions.filename(), - compileOptions.lineno, compileOptions.column); - } - - if (written < 0) - return TraceLogger::TL_Error; - - return textId; +TraceLoggerEventPayload * +TraceLoggerThread::getOrCreateEventPayload(TraceLoggerTextId type, + const JS::ReadOnlyCompileOptions &script) +{ + return getOrCreateEventPayload(type, script.filename(), script.lineno, script.column, &script); } void -TraceLogger::logTimestamp(uint32_t id) +TraceLoggerThread::startEvent(TraceLoggerTextId id) { + startEvent(uint32_t(id)); +} + +void +TraceLoggerThread::startEvent(const TraceLoggerEvent &event) { + if (!event.hasPayload()) { + startEvent(TraceLogger_Error); + return; + } + startEvent(event.payload()->textId()); +} + +void +TraceLoggerThread::startEvent(uint32_t id) +{ + MOZ_ASSERT(TLTextIdIsTreeEvent(id) || id == TraceLogger_Error); + if (!traceLoggers.isTextIdEnabled(id)) + return; + + logTimestamp(id); +} + +void +TraceLoggerThread::stopEvent(TraceLoggerTextId id) { + stopEvent(uint32_t(id)); +} + +void +TraceLoggerThread::stopEvent(const TraceLoggerEvent &event) { + if (!event.hasPayload()) { + stopEvent(TraceLogger_Error); + return; + } + stopEvent(event.payload()->textId()); +} + +void +TraceLoggerThread::stopEvent(uint32_t id) +{ + MOZ_ASSERT(TLTextIdIsTreeEvent(id) || id == TraceLogger_Error); + if (!traceLoggers.isTextIdEnabled(id)) + return; + + logTimestamp(TraceLogger_Stop); +} + +void +TraceLoggerThread::logTimestamp(TraceLoggerTextId id) +{ + logTimestamp(uint32_t(id)); +} + +void +TraceLoggerThread::logTimestamp(uint32_t id) { if (enabled == 0) return; if (!events.ensureSpaceBeforeAdd()) { - fprintf(stderr, "TraceLogging: Disabled a tracelogger due to OOM.\n"); - enabled = 0; - return; + uint64_t start = rdtsc() - traceLoggers.startupTime; + + if (graph.get()) + graph->log(events); + + iteration_++; + events.clear(); + + // Log the time it took to flush the events as being from the + // Tracelogger. + if (graph.get()) { + MOZ_ASSERT(events.capacity() > 2); + EventEntry &entryStart = events.pushUninitialized(); + entryStart.time = start; + entryStart.textId = TraceLogger_Internal; + + EventEntry &entryStop = events.pushUninitialized(); + entryStop.time = rdtsc() - traceLoggers.startupTime; + entryStop.textId = TraceLogger_Stop; + } + + // Free all TextEvents that have no uses anymore. + for (TextIdHashMap::Enum e(extraTextId); !e.empty(); e.popFront()) { + if (e.front().value()->uses() == 0) { + js_delete(e.front().value()); + e.removeFront(); + } + } } uint64_t time = rdtsc() - traceLoggers.startupTime; @@ -451,288 +540,29 @@ TraceLogger::logTimestamp(uint32_t id) entry.textId = id; } -void -TraceLogger::entryToBigEndian(TreeEntry *entry) -{ - entry->start_ = NativeEndian::swapToBigEndian(entry->start_); - entry->stop_ = NativeEndian::swapToBigEndian(entry->stop_); - uint32_t data = (entry->u.s.textId_ << 1) + entry->u.s.hasChildren_; - entry->u.value_ = NativeEndian::swapToBigEndian(data); - entry->nextId_ = NativeEndian::swapToBigEndian(entry->nextId_); -} - -void -TraceLogger::entryToSystemEndian(TreeEntry *entry) -{ - entry->start_ = NativeEndian::swapFromBigEndian(entry->start_); - entry->stop_ = NativeEndian::swapFromBigEndian(entry->stop_); - - uint32_t data = NativeEndian::swapFromBigEndian(entry->u.value_); - entry->u.s.textId_ = data >> 1; - entry->u.s.hasChildren_ = data & 0x1; - - entry->nextId_ = NativeEndian::swapFromBigEndian(entry->nextId_); -} - -bool -TraceLogger::getTreeEntry(uint32_t treeId, TreeEntry *entry) -{ - // Entry is still in memory - if (treeId >= treeOffset) { - *entry = tree[treeId]; - return true; - } - - int success = fseek(treeFile, treeId * sizeof(TreeEntry), SEEK_SET); - if (success != 0) - return false; - - size_t itemsRead = fread((void *)entry, sizeof(TreeEntry), 1, treeFile); - if (itemsRead < 1) - return false; - - entryToSystemEndian(entry); - return true; -} - -bool -TraceLogger::saveTreeEntry(uint32_t treeId, TreeEntry *entry) -{ - int success = fseek(treeFile, treeId * sizeof(TreeEntry), SEEK_SET); - if (success != 0) - return false; - - entryToBigEndian(entry); - - size_t itemsWritten = fwrite(entry, sizeof(TreeEntry), 1, treeFile); - if (itemsWritten < 1) - return false; - - return true; -} - -bool -TraceLogger::updateHasChildren(uint32_t treeId, bool hasChildren) -{ - if (treeId < treeOffset) { - TreeEntry entry; - if (!getTreeEntry(treeId, &entry)) - return false; - entry.setHasChildren(hasChildren); - if (!saveTreeEntry(treeId, &entry)) - return false; - return true; - } - - tree[treeId - treeOffset].setHasChildren(hasChildren); - return true; -} - -bool -TraceLogger::updateNextId(uint32_t treeId, uint32_t nextId) -{ - if (treeId < treeOffset) { - TreeEntry entry; - if (!getTreeEntry(treeId, &entry)) - return false; - entry.setNextId(nextId); - if (!saveTreeEntry(treeId, &entry)) - return false; - return true; - } - - tree[treeId - treeOffset].setNextId(nextId); - return true; -} - -bool -TraceLogger::updateStop(uint32_t treeId, uint64_t timestamp) -{ - if (treeId < treeOffset) { - TreeEntry entry; - if (!getTreeEntry(treeId, &entry)) - return false; - entry.setStop(timestamp); - if (!saveTreeEntry(treeId, &entry)) - return false; - return true; - } - - tree[treeId - treeOffset].setStop(timestamp); - return true; -} - -void -TraceLogger::startEvent(uint32_t id) -{ - if (failed || enabled == 0) - return; - - if (!tree.hasSpaceForAdd()){ - uint64_t start = rdtsc() - traceLoggers.startupTime; - if (!tree.ensureSpaceBeforeAdd()) { - if (!flush()) { - fprintf(stderr, "TraceLogging: Couldn't write the data to disk.\n"); - enabled = 0; - failed = true; - return; - } - } - - // Log the time it took to flush the events as being from the - // Tracelogger. - if (!startEvent(TraceLogger::TL, start)) { - fprintf(stderr, "TraceLogging: Failed to start an event.\n"); - enabled = 0; - failed = true; - return; - } - stopEvent(); - } - - uint64_t start = rdtsc() - traceLoggers.startupTime; - if (!startEvent(id, start)) { - fprintf(stderr, "TraceLogging: Failed to start an event.\n"); - enabled = 0; - failed = true; - return; - } -} - -TraceLogger::StackEntry & -TraceLogger::getActiveAncestor() -{ - uint32_t parentId = stack.lastEntryId(); - while (!stack[parentId].active()) - parentId--; - return stack[parentId]; -} - -bool -TraceLogger::startEvent(uint32_t id, uint64_t timestamp) -{ - if (!stack.ensureSpaceBeforeAdd()) - return false; - - // When a textId is disabled, a stack entry still needs to be pushed, - // together with an annotation that nothing needs to get done when receiving - // the stop event. - if (!traceLoggers.isTextIdEnabled(id)) { - StackEntry &stackEntry = stack.pushUninitialized(); - stackEntry.setActive(false); - return true; - } - - // Patch up the tree to be correct. There are two scenarios: - // 1) Parent has no children yet. So update parent to include children. - // 2) Parent has already children. Update last child to link to the new - // child. - StackEntry &parent = getActiveAncestor(); -#ifdef DEBUG - TreeEntry entry; - if (!getTreeEntry(parent.treeId(), &entry)) - return false; -#endif - - if (parent.lastChildId() == 0) { - MOZ_ASSERT(!entry.hasChildren()); - MOZ_ASSERT(parent.treeId() == tree.lastEntryId() + treeOffset); - - if (!updateHasChildren(parent.treeId())) - return false; - } else { - MOZ_ASSERT(entry.hasChildren()); - - if (!updateNextId(parent.lastChildId(), tree.size() + treeOffset)) - return false; - } - - // Add a new tree entry. - TreeEntry &treeEntry = tree.pushUninitialized(); - treeEntry.setStart(timestamp); - treeEntry.setStop(0); - treeEntry.setTextId(id); - treeEntry.setHasChildren(false); - treeEntry.setNextId(0); - - // Add a new stack entry. - StackEntry &stackEntry = stack.pushUninitialized(); - stackEntry.setTreeId(tree.lastEntryId() + treeOffset); - stackEntry.setLastChildId(0); - stackEntry.setActive(true); - - // Set the last child of the parent to this newly added entry. - parent.setLastChildId(tree.lastEntryId() + treeOffset); - - return true; -} - -void -TraceLogger::stopEvent(uint32_t id) -{ -#ifdef DEBUG - if (stack.size() > 1) { - TreeEntry entry; - MOZ_ASSERT_IF(stack.lastEntry().active(), getTreeEntry(stack.lastEntry().treeId(), &entry)); - MOZ_ASSERT_IF(stack.lastEntry().active(), entry.textId() == id); - } -#endif - stopEvent(); -} - -void -TraceLogger::stopEvent() -{ - if (enabled > 0 && stack.lastEntry().active()) { - uint64_t stop = rdtsc() - traceLoggers.startupTime; - if (!updateStop(stack.lastEntry().treeId(), stop)) { - fprintf(stderr, "TraceLogging: Failed to stop an event.\n"); - enabled = 0; - failed = true; - return; - } - } - if (stack.size() == 1) { - if (enabled == 0) - return; - - // Forcefully disable logging. We have no stack information anymore. - enabled = 1; - disable(); - return; - } - stack.pop(); -} - -TraceLogging::TraceLogging() +TraceLoggerThreadState::TraceLoggerThreadState() { initialized = false; enabled = 0; - mainThreadEnabled = true; - offThreadEnabled = true; - loggerId = 0; + mainThreadEnabled = false; + offThreadEnabled = false; + graphSpewingEnabled = false; lock = PR_NewLock(); if (!lock) MOZ_CRASH(); } -TraceLogging::~TraceLogging() +TraceLoggerThreadState::~TraceLoggerThreadState() { - if (out) { - fprintf(out, "]"); - fclose(out); - out = nullptr; - } - for (size_t i = 0; i < mainThreadLoggers.length(); i++) - delete mainThreadLoggers[i]; + js_delete(mainThreadLoggers[i]); mainThreadLoggers.clear(); if (threadLoggers.initialized()) { for (ThreadLoggerHashMap::Range r = threadLoggers.all(); !r.empty(); r.popFront()) - delete r.front().value(); + js_delete(r.front().value()); threadLoggers.finish(); } @@ -759,18 +589,13 @@ ContainsFlag(const char *str, const char *flag) } bool -TraceLogging::lazyInit() +TraceLoggerThreadState::lazyInit() { if (initialized) return enabled > 0; initialized = true; - out = fopen(TRACE_LOG_DIR "tl-data.json", "w"); - if (!out) - return false; - fprintf(out, "["); - if (!threadLoggers.init()) return false; @@ -790,63 +615,76 @@ TraceLogging::lazyInit() "\n" "Specific log items:\n" ); - for (uint32_t i = 1; i < TraceLogger::LAST; i++) { - printf(" %s\n", text[i]); + for (uint32_t i = 1; i < TraceLogger_Last; i++) { + TraceLoggerTextId id = TraceLoggerTextId(i); + if (!TLTextIdIsToggable(id)) + continue; + printf(" %s\n", TLTextIdString(id)); } printf("\n"); exit(0); /*NOTREACHED*/ } - for (uint32_t i = 1; i < TraceLogger::LAST; i++) - enabledTextIds[i] = ContainsFlag(env, text[i]); - - enabledTextIds[TraceLogger::TL_Error] = true; - enabledTextIds[TraceLogger::TL] = true; - - if (ContainsFlag(env, "Default") || strlen(env) == 0) { - enabledTextIds[TraceLogger::Bailout] = true; - enabledTextIds[TraceLogger::Baseline] = true; - enabledTextIds[TraceLogger::BaselineCompilation] = true; - enabledTextIds[TraceLogger::GC] = true; - enabledTextIds[TraceLogger::GCAllocation] = true; - enabledTextIds[TraceLogger::GCSweeping] = true; - enabledTextIds[TraceLogger::Interpreter] = true; - enabledTextIds[TraceLogger::IonCompilation] = true; - enabledTextIds[TraceLogger::IonLinking] = true; - enabledTextIds[TraceLogger::IonMonkey] = true; - enabledTextIds[TraceLogger::MinorGC] = true; - enabledTextIds[TraceLogger::ParserCompileFunction] = true; - enabledTextIds[TraceLogger::ParserCompileLazy] = true; - enabledTextIds[TraceLogger::ParserCompileScript] = true; - enabledTextIds[TraceLogger::IrregexpCompile] = true; - enabledTextIds[TraceLogger::IrregexpExecute] = true; + for (uint32_t i = 1; i < TraceLogger_Last; i++) { + TraceLoggerTextId id = TraceLoggerTextId(i); + if (TLTextIdIsToggable(id)) + enabledTextIds[i] = ContainsFlag(env, TLTextIdString(id)); + else + enabledTextIds[i] = true; } - if (ContainsFlag(env, "IonCompiler") || strlen(env) == 0) { - enabledTextIds[TraceLogger::IonCompilation] = true; - enabledTextIds[TraceLogger::IonLinking] = true; - enabledTextIds[TraceLogger::FoldTests] = true; - enabledTextIds[TraceLogger::SplitCriticalEdges] = true; - enabledTextIds[TraceLogger::RenumberBlocks] = true; - enabledTextIds[TraceLogger::DominatorTree] = true; - enabledTextIds[TraceLogger::PhiAnalysis] = true; - enabledTextIds[TraceLogger::ApplyTypes] = true; - enabledTextIds[TraceLogger::ParallelSafetyAnalysis] = true; - enabledTextIds[TraceLogger::AliasAnalysis] = true; - enabledTextIds[TraceLogger::GVN] = true; - enabledTextIds[TraceLogger::LICM] = true; - enabledTextIds[TraceLogger::RangeAnalysis] = true; - enabledTextIds[TraceLogger::LoopUnrolling] = true; - enabledTextIds[TraceLogger::EffectiveAddressAnalysis] = true; - enabledTextIds[TraceLogger::EliminateDeadCode] = true; - enabledTextIds[TraceLogger::EdgeCaseAnalysis] = true; - enabledTextIds[TraceLogger::EliminateRedundantChecks] = true; - enabledTextIds[TraceLogger::GenerateLIR] = true; - enabledTextIds[TraceLogger::RegisterAllocation] = true; - enabledTextIds[TraceLogger::GenerateCode] = true; + if (ContainsFlag(env, "Default")) { + enabledTextIds[TraceLogger_AnnotateScripts] = true; + enabledTextIds[TraceLogger_Bailout] = true; + enabledTextIds[TraceLogger_Baseline] = true; + enabledTextIds[TraceLogger_BaselineCompilation] = true; + enabledTextIds[TraceLogger_GC] = true; + enabledTextIds[TraceLogger_GCAllocation] = true; + enabledTextIds[TraceLogger_GCSweeping] = true; + enabledTextIds[TraceLogger_Interpreter] = true; + enabledTextIds[TraceLogger_IonCompilation] = true; + enabledTextIds[TraceLogger_IonLinking] = true; + enabledTextIds[TraceLogger_IonMonkey] = true; + enabledTextIds[TraceLogger_MinorGC] = true; + enabledTextIds[TraceLogger_ParserCompileFunction] = true; + enabledTextIds[TraceLogger_ParserCompileLazy] = true; + enabledTextIds[TraceLogger_ParserCompileScript] = true; + enabledTextIds[TraceLogger_IrregexpCompile] = true; + enabledTextIds[TraceLogger_IrregexpExecute] = true; + enabledTextIds[TraceLogger_Scripts] = true; + enabledTextIds[TraceLogger_Engine] = true; } + if (ContainsFlag(env, "IonCompiler")) { + enabledTextIds[TraceLogger_IonCompilation] = true; + enabledTextIds[TraceLogger_IonLinking] = true; + enabledTextIds[TraceLogger_FoldTests] = true; + enabledTextIds[TraceLogger_SplitCriticalEdges] = true; + enabledTextIds[TraceLogger_RenumberBlocks] = true; + enabledTextIds[TraceLogger_DominatorTree] = true; + enabledTextIds[TraceLogger_PhiAnalysis] = true; + enabledTextIds[TraceLogger_ApplyTypes] = true; + enabledTextIds[TraceLogger_ParallelSafetyAnalysis] = true; + enabledTextIds[TraceLogger_AliasAnalysis] = true; + enabledTextIds[TraceLogger_GVN] = true; + enabledTextIds[TraceLogger_LICM] = true; + enabledTextIds[TraceLogger_RangeAnalysis] = true; + enabledTextIds[TraceLogger_LoopUnrolling] = true; + enabledTextIds[TraceLogger_EffectiveAddressAnalysis] = true; + enabledTextIds[TraceLogger_EliminateDeadCode] = true; + enabledTextIds[TraceLogger_EdgeCaseAnalysis] = true; + enabledTextIds[TraceLogger_EliminateRedundantChecks] = true; + enabledTextIds[TraceLogger_GenerateLIR] = true; + enabledTextIds[TraceLogger_RegisterAllocation] = true; + enabledTextIds[TraceLogger_GenerateCode] = true; + enabledTextIds[TraceLogger_Scripts] = true; + } + + enabledTextIds[TraceLogger_Interpreter] = enabledTextIds[TraceLogger_Engine]; + enabledTextIds[TraceLogger_Baseline] = enabledTextIds[TraceLogger_Engine]; + enabledTextIds[TraceLogger_IonMonkey] = enabledTextIds[TraceLogger_Engine]; + const char *options = getenv("TLOPTIONS"); if (options) { if (strstr(options, "help")) { @@ -855,18 +693,21 @@ TraceLogging::lazyInit() "\n" "usage: TLOPTIONS=option,option,option,... where options can be:\n" "\n" - " DisableMainThread Don't start logging the mainThread automatically.\n" - " DisableOffThread Don't start logging the off mainThread automatically.\n" + " EnableMainThread Start logging the main thread immediately.\n" + " EnableOffThread Start logging helper threads immediately.\n" + " EnableGraph Enable spewing the tracelogging graph to a file.\n" ); printf("\n"); exit(0); /*NOTREACHED*/ } - if (strstr(options, "DisableMainThread")) - mainThreadEnabled = false; - if (strstr(options, "DisableOffThread")) - offThreadEnabled = false; + if (strstr(options, "EnableMainThread")) + mainThreadEnabled = true; + if (strstr(options, "EnableOffThread")) + offThreadEnabled = true; + if (strstr(options, "EnableGraph")) + graphSpewingEnabled = true; } startupTime = rdtsc(); @@ -874,44 +715,99 @@ TraceLogging::lazyInit() return true; } -TraceLogger * +void +TraceLoggerThreadState::enableTextId(JSContext *cx, uint32_t textId) +{ + MOZ_ASSERT(TLTextIdIsToggable(textId)); + + if (enabledTextIds[textId]) + return; + + enabledTextIds[textId] = true; + if (textId == TraceLogger_Engine) { + enabledTextIds[TraceLogger_IonMonkey] = true; + enabledTextIds[TraceLogger_Baseline] = true; + enabledTextIds[TraceLogger_Interpreter] = true; + } + + ReleaseAllJITCode(cx->runtime()->defaultFreeOp()); + + if (textId == TraceLogger_Scripts) + jit::ToggleBaselineTraceLoggerScripts(cx->runtime(), true); + if (textId == TraceLogger_Engine) + jit::ToggleBaselineTraceLoggerEngine(cx->runtime(), true); + +} +void +TraceLoggerThreadState::disableTextId(JSContext *cx, uint32_t textId) +{ + MOZ_ASSERT(TLTextIdIsToggable(textId)); + + if (!enabledTextIds[textId]) + return; + + enabledTextIds[textId] = false; + if (textId == TraceLogger_Engine) { + enabledTextIds[TraceLogger_IonMonkey] = false; + enabledTextIds[TraceLogger_Baseline] = false; + enabledTextIds[TraceLogger_Interpreter] = false; + } + + ReleaseAllJITCode(cx->runtime()->defaultFreeOp()); + + if (textId == TraceLogger_Scripts) + jit::ToggleBaselineTraceLoggerScripts(cx->runtime(), false); + if (textId == TraceLogger_Engine) + jit::ToggleBaselineTraceLoggerEngine(cx->runtime(), false); +} + + +TraceLoggerThread * js::TraceLoggerForMainThread(CompileRuntime *runtime) { return traceLoggers.forMainThread(runtime); } -TraceLogger * -TraceLogging::forMainThread(CompileRuntime *runtime) +TraceLoggerThread * +TraceLoggerThreadState::forMainThread(CompileRuntime *runtime) { return forMainThread(runtime->mainThread()); } -TraceLogger * +TraceLoggerThread * js::TraceLoggerForMainThread(JSRuntime *runtime) { return traceLoggers.forMainThread(runtime); } -TraceLogger * -TraceLogging::forMainThread(JSRuntime *runtime) +TraceLoggerThread * +TraceLoggerThreadState::forMainThread(JSRuntime *runtime) { return forMainThread(&runtime->mainThread); } -TraceLogger * -TraceLogging::forMainThread(PerThreadData *mainThread) +TraceLoggerThread * +TraceLoggerThreadState::forMainThread(PerThreadData *mainThread) { if (!mainThread->traceLogger) { - AutoTraceLoggingLock lock(this); + AutoTraceLoggerThreadStateLock lock(this); if (!lazyInit()) return nullptr; - TraceLogger *logger = create(); + TraceLoggerThread *logger = create(); + if (!logger) + return nullptr; + + if (!mainThreadLoggers.append(logger)) { + js_delete(logger); + return nullptr; + } + mainThread->traceLogger = logger; - if (!mainThreadLoggers.append(logger)) - return nullptr; + if (graphSpewingEnabled) + logger->initGraph(); if (!mainThreadEnabled) logger->disable(); @@ -920,17 +816,17 @@ TraceLogging::forMainThread(PerThreadData *mainThread) return mainThread->traceLogger; } -TraceLogger * +TraceLoggerThread * js::TraceLoggerForCurrentThread() { PRThread *thread = PR_GetCurrentThread(); return traceLoggers.forThread(thread); } -TraceLogger * -TraceLogging::forThread(PRThread *thread) +TraceLoggerThread * +TraceLoggerThreadState::forThread(PRThread *thread) { - AutoTraceLoggingLock lock(this); + AutoTraceLoggerThreadStateLock lock(this); if (!lazyInit()) return nullptr; @@ -939,49 +835,33 @@ TraceLogging::forThread(PRThread *thread) if (p) return p->value(); - TraceLogger *logger = create(); + TraceLoggerThread *logger = create(); if (!logger) return nullptr; if (!threadLoggers.add(p, thread, logger)) { - delete logger; + js_delete(logger); return nullptr; } + if (graphSpewingEnabled) + logger->initGraph(); + if (!offThreadEnabled) logger->disable(); return logger; } -TraceLogger * -TraceLogging::create() +TraceLoggerThread * +TraceLoggerThreadState::create() { - if (loggerId > 999) { - fprintf(stderr, "TraceLogging: Can't create more than 999 different loggers."); - return nullptr; - } - - if (loggerId > 0) { - int written = fprintf(out, ",\n"); - if (written < 0) - fprintf(stderr, "TraceLogging: Error while writing.\n"); - } - - loggerId++; - - int written = fprintf(out, "{\"tree\":\"tl-tree.%d.tl\", \"events\":\"tl-event.%d.tl\", \"dict\":\"tl-dict.%d.json\", \"treeFormat\":\"64,64,31,1,32\"}", - loggerId, loggerId, loggerId); - if (written < 0) - fprintf(stderr, "TraceLogging: Error while writing.\n"); - - - TraceLogger *logger = new TraceLogger(); + TraceLoggerThread *logger = js_new(); if (!logger) return nullptr; - if (!logger->init(loggerId)) { - delete logger; + if (!logger->init()) { + js_delete(logger); return nullptr; } @@ -993,3 +873,50 @@ js::TraceLogTextIdEnabled(uint32_t textId) { return traceLoggers.isTextIdEnabled(textId); } + +void +js::TraceLogEnableTextId(JSContext *cx, uint32_t textId) +{ + traceLoggers.enableTextId(cx, textId); +} +void +js::TraceLogDisableTextId(JSContext *cx, uint32_t textId) +{ + traceLoggers.disableTextId(cx, textId); +} + +TraceLoggerEvent::TraceLoggerEvent(TraceLoggerThread *logger, TraceLoggerTextId textId) +{ + payload_ = logger->getOrCreateEventPayload(textId); + if (payload_) + payload_->use(); +} + +TraceLoggerEvent::TraceLoggerEvent(TraceLoggerThread *logger, TraceLoggerTextId type, + JSScript *script) +{ + payload_ = logger->getOrCreateEventPayload(type, script); + if (payload_) + payload_->use(); +} + +TraceLoggerEvent::TraceLoggerEvent(TraceLoggerThread *logger, TraceLoggerTextId type, + const JS::ReadOnlyCompileOptions &compileOptions) +{ + payload_ = logger->getOrCreateEventPayload(type, compileOptions); + if (payload_) + payload_->use(); +} + +TraceLoggerEvent::TraceLoggerEvent(TraceLoggerThread *logger, const char *text) +{ + payload_ = logger->getOrCreateEventPayload(text); + if (payload_) + payload_->use(); +} + +TraceLoggerEvent::~TraceLoggerEvent() +{ + if (payload_) + payload_->release(); +} diff --git a/js/src/vm/TraceLogging.h b/js/src/vm/TraceLogging.h index 5b698053dcd8..2affbe4737c4 100644 --- a/js/src/vm/TraceLogging.h +++ b/js/src/vm/TraceLogging.h @@ -8,6 +8,7 @@ #define TraceLogging_h #include "mozilla/GuardObjects.h" +#include "mozilla/UniquePtr.h" #include "jsalloc.h" #include "jslock.h" @@ -15,6 +16,8 @@ #include "js/HashTable.h" #include "js/TypeDecls.h" #include "js/Vector.h" +#include "vm/TraceLoggingGraph.h" +#include "vm/TraceLoggingTypes.h" struct JSRuntime; @@ -32,513 +35,315 @@ namespace jit { /* * Tracelogging overview. * - * Tracelogging makes it possible to trace the timestamp of a single event and/or - * the duration of an event. This is implemented to give an as low overhead as + * Tracelogging makes it possible to trace the occurence of a single event and/or + * the start and stop of an event. This is implemented to give an as low overhead as * possible so it doesn't interfere with running. * - * The output of a tracelogging session is saved in /tmp/tl-data.json. - * The format of that file is a JS array per tracelogger (=thread), with a map - * containing: - * - dict: Name of the file containing a json table with the log text. - * All other files only contain a index to this table when logging. - * - events: Name of the file containing a flat list of log events saved - * in binary format. - * (64bit: Time Stamp Counter, 32bit index to dict) - * - tree: Name of the file containing the events with duration. The content - * is already in a tree data structure. This is also saved in a - * binary file. - * - treeFormat: The format used to encode the tree. By default "64,64,31,1,32". - * There are currently no other formats to save the tree. - * - 64,64,31,1,31 signifies how many bytes are used for the different - * parts of the tree. - * => 64 bits: Time Stamp Counter of start of event. - * => 64 bits: Time Stamp Counter of end of event. - * => 31 bits: Index to dict file containing the log text. - * => 1 bit: Boolean signifying if this entry has children. - * When true, the child can be found just behind this entry. - * => 32 bits: Containing the ID of the next event on the same depth - * or 0 if there isn't an event on the same depth anymore. - * - * /-> The position in the file. Id is this divided by size of entry. - * | So in this case this would be 1 (192bits per entry). - * | /-> Indicates there are children. The - * | | first child is located at current - * | | ID + 1. So 1 + 1 in this case: 2. - * | | Or 0x00180 in the tree file. - * | | /-> Next event on the same depth is - * | | | located at 4. So 0x00300 in the - * | | | tree file. - * 0x0000C0: [start, end, dictId, 1, 4] - * - * - * Example: - * 0x0: [start, end, dictId, 1, 0] - * | - * /----------------------------------\ - * | | - * 0xC0: [start, end, dictId, 0, 2] 0x180 [start, end, dictId, 1, 0] - * | - * /----------------------------------\ - * | | - * 0x240: [start, end, dictId, 0, 4] 0x300 [start, end, dictId, 0, 0] - * * * Logging something is done in 3 stages. * 1) Get the tracelogger of the current thread. * - TraceLoggerForMainThread(JSRuntime *) * - TraceLoggerForCurrentThread(); // Should NOT be used for the mainthread. - * 2) Optionally create a textId for the text that needs to get logged. This + * + * 2) Optionally create a TraceLoggerEvent for the text that needs to get logged. This * step takes some time, so try to do this beforehand, outside the hot * path and don't do unnecessary repetitions, since it will criple * performance. - * - TraceLogCreateTextId(logger, ...); + * - TraceLoggerEvent event(logger, "foo"); * - * There are also some text IDs created beforehand. They are located in - * Tracelogger::TextId. - * 3) Log the timestamp of an event: - * - TraceLogTimestamp(logger, textId); + * There are also some predefined events. They are located in + * TraceLoggerTextId. They don't require to create an TraceLoggerEvent and + * can also be used as an argument to these functions. + * 3) Log the occurence of a single event: + * - TraceLogTimestamp(logger, TraceLoggerTextId); + * Note: it is temporarily not supported to provide an TraceLoggerEvent as + * argument to log the occurence of a single event. * - * or the duration: - * - TraceLogStartEvent(logger, textId); - * - TraceLogStopEvent(logger, textId); + * or log the start and stop of an event: + * - TraceLogStartEvent(logger, TraceLoggerTextId); + * - TraceLogStartEvent(logger, TraceLoggerEvent); + * - TraceLogStopEvent(logger, TraceLoggerTextId); + * - TraceLogStopEvent(logger, TraceLoggerEvent); * - * or the duration with a RAII class: - * - AutoTraceLog logger(logger, textId); + * or the start/stop of an event with a RAII class: + * - AutoTraceLog logger(logger, TraceLoggerTextId); + * - AutoTraceLog logger(logger, TraceLoggerEvent); */ -#define TRACELOGGER_TEXT_ID_LIST(_) \ - _(Bailout) \ - _(Baseline) \ - _(BaselineCompilation) \ - _(GC) \ - _(GCAllocation) \ - _(GCSweeping) \ - _(Interpreter) \ - _(Invalidation) \ - _(IonCompilation) \ - _(IonCompilationPaused) \ - _(IonLinking) \ - _(IonMonkey) \ - _(MinorGC) \ - _(ParserCompileFunction) \ - _(ParserCompileLazy) \ - _(ParserCompileScript) \ - _(TL) \ - _(IrregexpCompile) \ - _(IrregexpExecute) \ - _(VM) \ - \ - /* Specific passes during ion compilation */ \ - _(FoldTests) \ - _(SplitCriticalEdges) \ - _(RenumberBlocks) \ - _(ScalarReplacement) \ - _(DominatorTree) \ - _(PhiAnalysis) \ - _(MakeLoopsContiguous) \ - _(ApplyTypes) \ - _(ParallelSafetyAnalysis) \ - _(AliasAnalysis) \ - _(GVN) \ - _(LICM) \ - _(RangeAnalysis) \ - _(LoopUnrolling) \ - _(EffectiveAddressAnalysis) \ - _(EliminateDeadCode) \ - _(EdgeCaseAnalysis) \ - _(EliminateRedundantChecks) \ - _(GenerateLIR) \ - _(RegisterAllocation) \ - _(GenerateCode) \ - class AutoTraceLog; +class TraceLoggerEventPayload; +class TraceLoggerThread; -template -class ContinuousSpace { - T *data_; - uint32_t size_; - uint32_t capacity_; +/** + * An event that can be used to report start/stop events to TraceLogger. + * It prepares the given info, by requesting a TraceLoggerEventPayload for the + * given info. (Which contains the string that needs to get reported and an + * unique id). It also increases the useCount of this payload, so it cannot + * get removed. + */ +class TraceLoggerEvent { + private: + TraceLoggerEventPayload *payload_; public: - ContinuousSpace () - : data_(nullptr) - { } + TraceLoggerEvent() { payload_ = nullptr; }; +#ifdef JS_TRACE_LOGGING + TraceLoggerEvent(TraceLoggerThread *logger, TraceLoggerTextId textId); + TraceLoggerEvent(TraceLoggerThread *logger, TraceLoggerTextId type, JSScript *script); + TraceLoggerEvent(TraceLoggerThread *logger, TraceLoggerTextId type, + const JS::ReadOnlyCompileOptions &compileOptions); + TraceLoggerEvent(TraceLoggerThread *logger, const char *text); + ~TraceLoggerEvent(); +#else + TraceLoggerEvent (TraceLoggerThread *logger, TraceLoggerTextId textId) {} + TraceLoggerEvent (TraceLoggerThread *logger, TraceLoggerTextId type, JSScript *script) {} + TraceLoggerEvent (TraceLoggerThread *logger, TraceLoggerTextId type, + const JS::ReadOnlyCompileOptions &compileOptions) {} + TraceLoggerEvent (TraceLoggerThread *logger, const char *text) {} + ~TraceLoggerEvent() {} +#endif - bool init() { - capacity_ = 64; - size_ = 0; - data_ = (T *) js_malloc(capacity_ * sizeof(T)); - if (!data_) - return false; - - return true; + TraceLoggerEventPayload *payload() const { + MOZ_ASSERT(hasPayload()); + return payload_; } - - T *data() { - return data_; - } - - uint32_t capacity() { - return capacity_; - } - - uint32_t size() { - return size_; - } - - bool empty() { - return size_ == 0; - } - - uint32_t lastEntryId() { - MOZ_ASSERT(!empty()); - return size_ - 1; - } - - T &lastEntry() { - return data()[lastEntryId()]; - } - - bool hasSpaceForAdd(uint32_t count = 1) { - if (size_ + count <= capacity_) - return true; - return false; - } - - bool ensureSpaceBeforeAdd(uint32_t count = 1) { - if (hasSpaceForAdd(count)) - return true; - - uint32_t nCapacity = capacity_ * 2; - if (size_ + count > nCapacity) - nCapacity = size_ + count; - T *entries = (T *) js_realloc(data_, nCapacity * sizeof(T)); - - if (!entries) - return false; - - data_ = entries; - capacity_ = nCapacity; - - return true; - } - - T &operator[](size_t i) { - MOZ_ASSERT(i < size_); - return data()[i]; - } - - void push(T &data) { - MOZ_ASSERT(size_ < capacity_); - data()[size_++] = data; - } - - T &pushUninitialized() { - MOZ_ASSERT(size_ < capacity_); - return data()[size_++]; - } - - void pop() { - MOZ_ASSERT(!empty()); - size_--; - } - - void clear() { - size_ = 0; + bool hasPayload() const { + return !!payload_; } }; -class TraceLogger -{ - public: - // Predefined IDs for common operations. These IDs can be used - // without using TraceLogCreateTextId, because there are already created. - enum TextId { - TL_Error = 0, -# define DEFINE_TEXT_ID(textId) textId, - TRACELOGGER_TEXT_ID_LIST(DEFINE_TEXT_ID) -# undef DEFINE_TEXT_ID - LAST - }; +/** + * An internal class holding the to-report string information, together with an + * unique id and a useCount. Whenever this useCount reaches 0, this event + * cannot get started/stopped anymore. Though consumers might still request the + * to-report string information. + */ +class TraceLoggerEventPayload { + uint32_t textId_; + mozilla::UniquePtr string_; + uint32_t uses_; + public: + TraceLoggerEventPayload(uint32_t textId, char *string) + : textId_(textId), + string_(string), + uses_(0) + { } + + uint32_t textId() { + return textId_; + } + const char *string() { + return string_.get(); + } + uint32_t uses() { + return uses_; + } + void use() { + uses_++; + } + void release() { + uses_--; + } +}; + +class TraceLoggerThread +{ #ifdef JS_TRACE_LOGGING private: typedef HashMap, SystemAllocPolicy> PointerHashMap; - - // The layout of the tree in memory and in the log file. Readable by JS - // using TypedArrays. - struct TreeEntry { - uint64_t start_; - uint64_t stop_; - union { - struct { - uint32_t textId_: 31; - uint32_t hasChildren_: 1; - } s; - uint32_t value_; - } u; - uint32_t nextId_; - - TreeEntry(uint64_t start, uint64_t stop, uint32_t textId, bool hasChildren, - uint32_t nextId) - { - start_ = start; - stop_ = stop; - u.s.textId_ = textId; - u.s.hasChildren_ = hasChildren; - nextId_ = nextId; - } - TreeEntry() - { } - uint64_t start() { - return start_; - } - uint64_t stop() { - return stop_; - } - uint32_t textId() { - return u.s.textId_; - } - bool hasChildren() { - return u.s.hasChildren_; - } - uint32_t nextId() { - return nextId_; - } - void setStart(uint64_t start) { - start_ = start; - } - void setStop(uint64_t stop) { - stop_ = stop; - } - void setTextId(uint32_t textId) { - MOZ_ASSERT(textId < uint32_t(1<<31) ); - u.s.textId_ = textId; - } - void setHasChildren(bool hasChildren) { - u.s.hasChildren_ = hasChildren; - } - void setNextId(uint32_t nextId) { - nextId_ = nextId; - } - }; - - // Helper structure for keeping track of the current entries in - // the tree. Pushed by `start(id)`, popped by `stop(id)`. The active flag - // is used to know if a subtree doesn't need to get logged. - struct StackEntry { - uint32_t treeId_; - uint32_t lastChildId_; - struct { - uint32_t textId_: 31; - uint32_t active_: 1; - } s; - StackEntry(uint32_t treeId, uint32_t lastChildId, bool active = true) - : treeId_(treeId), lastChildId_(lastChildId) - { - s.textId_ = 0; - s.active_ = active; - } - uint32_t treeId() { - return treeId_; - } - uint32_t lastChildId() { - return lastChildId_; - } - uint32_t textId() { - return s.textId_; - } - bool active() { - return s.active_; - } - void setTreeId(uint32_t treeId) { - treeId_ = treeId; - } - void setLastChildId(uint32_t lastChildId) { - lastChildId_ = lastChildId; - } - void setTextId(uint32_t textId) { - MOZ_ASSERT(textId < uint32_t(1<<31) ); - s.textId_ = textId; - } - void setActive(bool active) { - s.active_ = active; - } - }; - - // The layout of the event log in memory and in the log file. - // Readable by JS using TypedArrays. - struct EventEntry { - uint64_t time; - uint32_t textId; - EventEntry(uint64_t time, uint32_t textId) - : time(time), textId(textId) - { } - }; - - FILE *dictFile; - FILE *treeFile; - FILE *eventFile; + typedef HashMap, + SystemAllocPolicy> TextIdHashMap; uint32_t enabled; bool failed; - uint32_t nextTextId; + + mozilla::UniquePtr graph; PointerHashMap pointerMap; + TextIdHashMap extraTextId; - ContinuousSpace tree; - ContinuousSpace stack; ContinuousSpace events; - uint32_t treeOffset; + // Every time the events get flushed, this count is increased by one. + // This together with events.lastEntryId(), gives an unique position. + uint32_t iteration_; public: AutoTraceLog *top; - private: - // Helper functions that convert a TreeEntry in different endianness - // in place. - void entryToBigEndian(TreeEntry *entry); - void entryToSystemEndian(TreeEntry *entry); - - // Helper functions to get/save a tree from file. - bool getTreeEntry(uint32_t treeId, TreeEntry *entry); - bool saveTreeEntry(uint32_t treeId, TreeEntry *entry); - - // Return the first StackEntry that is active. - StackEntry &getActiveAncestor(); - - // This contains the meat of startEvent, except the test for enough space, - // the test if tracelogger is enabled and the timestamp computation. - bool startEvent(uint32_t id, uint64_t timestamp); - - // Update functions that can adjust the items in the tree, - // both in memory or already written to disk. - bool updateHasChildren(uint32_t treeId, bool hasChildren = true); - bool updateNextId(uint32_t treeId, uint32_t nextId); - bool updateStop(uint32_t treeId, uint64_t timestamp); - - // Flush the tree and events. - bool flush(); - - public: - TraceLogger(); - ~TraceLogger(); + TraceLoggerThread(); + bool init(); + ~TraceLoggerThread(); bool init(uint32_t loggerId); + void initGraph(); bool enable(); bool enable(JSContext *cx); bool disable(); - // The createTextId functions map a unique input to a logger ID. - // This ID can be used to log something. Calls to these functions should be - // limited if possible, because of the overhead. - uint32_t createTextId(const char *text); - uint32_t createTextId(JSScript *script); - uint32_t createTextId(const JS::ReadOnlyCompileOptions &script); + // Given the previous iteration and lastEntryId, return an array of events + // (there could be lost events). At the same time update the iteration and + // lastEntry and gives back how many events there are. + EventEntry *getEventsStartingAt(uint32_t *lastIteration, uint32_t *lastEntryId, size_t *num) { + EventEntry *start; + if (iteration_ == *lastIteration) { + MOZ_ASSERT(events.lastEntryId() >= *lastEntryId); + *num = events.lastEntryId() - *lastEntryId; + start = events.data() + *lastEntryId + 1; + } else { + *num = events.lastEntryId() + 1; + start = events.data(); + } - // Log an event (no start/stop, only the timestamp is recorded). - void logTimestamp(uint32_t id); - - // Record timestamps for start and stop of an event. - // In the stop method, the ID is only used in debug builds to test - // correctness. - void startEvent(uint32_t id); - void stopEvent(uint32_t id); - void stopEvent(); - - static unsigned offsetOfEnabled() { - return offsetof(TraceLogger, enabled); + *lastIteration = iteration_; + *lastEntryId = events.lastEntryId(); + return start; } + // Extract the details filename, lineNumber and columnNumber out of a event + // containing script information. + void extractScriptDetails(uint32_t textId, const char **filename, size_t *filename_len, + const char **lineno, size_t *lineno_len, const char **colno, + size_t *colno_len); + + bool lostEvents(uint32_t lastIteration, uint32_t lastEntryId) { + // If still logging in the same iteration, there are no lost events. + if (lastIteration == iteration_) { + MOZ_ASSERT(lastEntryId <= events.lastEntryId()); + return false; + } + + // When proceeded to the next iteration and lastEntryId points to + // the maximum capacity there are no logs that are lost. + if (lastIteration + 1 == iteration_ && lastEntryId == events.capacity()) + return false; + + return true; + } + + const char *eventText(uint32_t id); + bool textIdIsScriptEvent(uint32_t id); + + // The createTextId functions map a unique input to a logger ID. + // This can be used to give start and stop events. Calls to these functions should be + // limited if possible, because of the overhead. + // Note: it is not allowed to use them in logTimestamp. + TraceLoggerEventPayload *getOrCreateEventPayload(TraceLoggerTextId textId); + TraceLoggerEventPayload *getOrCreateEventPayload(const char *text); + TraceLoggerEventPayload *getOrCreateEventPayload(TraceLoggerTextId type, JSScript *script); + TraceLoggerEventPayload *getOrCreateEventPayload(TraceLoggerTextId type, + const JS::ReadOnlyCompileOptions &script); private: - void assertNoQuotes(const char *text) { -#ifdef DEBUG - const char *quote = strchr(text, '"'); - MOZ_ASSERT(!quote); -#endif + TraceLoggerEventPayload *getOrCreateEventPayload(TraceLoggerTextId type, const char *filename, + size_t lineno, size_t colno, const void *p); + + public: + // Log an event (no start/stop, only the timestamp is recorded). + void logTimestamp(TraceLoggerTextId id); + + // Record timestamps for start and stop of an event. + void startEvent(TraceLoggerTextId id); + void startEvent(const TraceLoggerEvent &event); + void stopEvent(TraceLoggerTextId id); + void stopEvent(const TraceLoggerEvent &event); + + // These functions are actually private and shouldn't be used in normal + // code. They are made public so they can get used in assembly. + void logTimestamp(uint32_t id); + void startEvent(uint32_t id); + void stopEvent(uint32_t id); + private: + void stopEvent(); + + public: + static unsigned offsetOfEnabled() { + return offsetof(TraceLoggerThread, enabled); } #endif }; -class TraceLogging +class TraceLoggerThreadState { #ifdef JS_TRACE_LOGGING typedef HashMap, SystemAllocPolicy> ThreadLoggerHashMap; - typedef Vector MainThreadLoggers; + typedef Vector MainThreadLoggers; bool initialized; bool enabled; - bool enabledTextIds[TraceLogger::LAST]; + bool enabledTextIds[TraceLogger_Last]; bool mainThreadEnabled; bool offThreadEnabled; + bool graphSpewingEnabled; ThreadLoggerHashMap threadLoggers; MainThreadLoggers mainThreadLoggers; - uint32_t loggerId; - FILE *out; public: uint64_t startupTime; PRLock *lock; - TraceLogging(); - ~TraceLogging(); + TraceLoggerThreadState(); + ~TraceLoggerThreadState(); - TraceLogger *forMainThread(JSRuntime *runtime); - TraceLogger *forMainThread(jit::CompileRuntime *runtime); - TraceLogger *forThread(PRThread *thread); + TraceLoggerThread *forMainThread(JSRuntime *runtime); + TraceLoggerThread *forMainThread(jit::CompileRuntime *runtime); + TraceLoggerThread *forThread(PRThread *thread); bool isTextIdEnabled(uint32_t textId) { - if (textId < TraceLogger::LAST) + if (textId < TraceLogger_Last) return enabledTextIds[textId]; return true; } + void enableTextId(JSContext *cx, uint32_t textId); + void disableTextId(JSContext *cx, uint32_t textId); private: - TraceLogger *forMainThread(PerThreadData *mainThread); - TraceLogger *create(); + TraceLoggerThread *forMainThread(PerThreadData *mainThread); + TraceLoggerThread *create(); bool lazyInit(); #endif }; #ifdef JS_TRACE_LOGGING -TraceLogger *TraceLoggerForMainThread(JSRuntime *runtime); -TraceLogger *TraceLoggerForMainThread(jit::CompileRuntime *runtime); -TraceLogger *TraceLoggerForCurrentThread(); +TraceLoggerThread *TraceLoggerForMainThread(JSRuntime *runtime); +TraceLoggerThread *TraceLoggerForMainThread(jit::CompileRuntime *runtime); +TraceLoggerThread *TraceLoggerForCurrentThread(); #else -inline TraceLogger *TraceLoggerForMainThread(JSRuntime *runtime) { +inline TraceLoggerThread *TraceLoggerForMainThread(JSRuntime *runtime) { return nullptr; }; -inline TraceLogger *TraceLoggerForMainThread(jit::CompileRuntime *runtime) { +inline TraceLoggerThread *TraceLoggerForMainThread(jit::CompileRuntime *runtime) { return nullptr; }; -inline TraceLogger *TraceLoggerForCurrentThread() { +inline TraceLoggerThread *TraceLoggerForCurrentThread() { return nullptr; }; #endif -inline bool TraceLoggerEnable(TraceLogger *logger) { +inline bool TraceLoggerEnable(TraceLoggerThread *logger) { #ifdef JS_TRACE_LOGGING if (logger) return logger->enable(); #endif return false; } -inline bool TraceLoggerEnable(TraceLogger *logger, JSContext *cx) { +inline bool TraceLoggerEnable(TraceLoggerThread *logger, JSContext *cx) { #ifdef JS_TRACE_LOGGING if (logger) return logger->enable(cx); #endif return false; } -inline bool TraceLoggerDisable(TraceLogger *logger) { +inline bool TraceLoggerDisable(TraceLoggerThread *logger) { #ifdef JS_TRACE_LOGGING if (logger) return logger->disable(); @@ -546,78 +351,107 @@ inline bool TraceLoggerDisable(TraceLogger *logger) { return false; } -inline uint32_t TraceLogCreateTextId(TraceLogger *logger, JSScript *script) { -#ifdef JS_TRACE_LOGGING - if (logger) - return logger->createTextId(script); -#endif - return TraceLogger::TL_Error; -} -inline uint32_t TraceLogCreateTextId(TraceLogger *logger, - const JS::ReadOnlyCompileOptions &compileOptions) -{ -#ifdef JS_TRACE_LOGGING - if (logger) - return logger->createTextId(compileOptions); -#endif - return TraceLogger::TL_Error; -} -inline uint32_t TraceLogCreateTextId(TraceLogger *logger, const char *text) { -#ifdef JS_TRACE_LOGGING - if (logger) - return logger->createTextId(text); -#endif - return TraceLogger::TL_Error; -} #ifdef JS_TRACE_LOGGING bool TraceLogTextIdEnabled(uint32_t textId); +void TraceLogEnableTextId(JSContext *cx, uint32_t textId); +void TraceLogDisableTextId(JSContext *cx, uint32_t textId); #else inline bool TraceLogTextIdEnabled(uint32_t textId) { return false; } +inline void TraceLogEnableTextId(JSContext *cx, uint32_t textId) {} +inline void TraceLogDisableTextId(JSContext *cx, uint32_t textId) {} #endif -inline void TraceLogTimestamp(TraceLogger *logger, uint32_t textId) { +inline void TraceLogTimestamp(TraceLoggerThread *logger, TraceLoggerTextId textId) { #ifdef JS_TRACE_LOGGING if (logger) logger->logTimestamp(textId); #endif } -inline void TraceLogStartEvent(TraceLogger *logger, uint32_t textId) { +inline void TraceLogStartEvent(TraceLoggerThread *logger, TraceLoggerTextId textId) { #ifdef JS_TRACE_LOGGING if (logger) logger->startEvent(textId); #endif } -inline void TraceLogStopEvent(TraceLogger *logger, uint32_t textId) { +inline void TraceLogStartEvent(TraceLoggerThread *logger, const TraceLoggerEvent &event) { +#ifdef JS_TRACE_LOGGING + if (logger) + logger->startEvent(event); +#endif +} +inline void TraceLogStopEvent(TraceLoggerThread *logger, TraceLoggerTextId textId) { #ifdef JS_TRACE_LOGGING if (logger) logger->stopEvent(textId); #endif } -inline void TraceLogStopEvent(TraceLogger *logger) { +inline void TraceLogStopEvent(TraceLoggerThread *logger, const TraceLoggerEvent &event) { #ifdef JS_TRACE_LOGGING if (logger) - logger->stopEvent(); + logger->stopEvent(event); +#endif +} + +// Helper functions for assembly. May not be used otherwise. +inline void TraceLogTimestampPrivate(TraceLoggerThread *logger, uint32_t id) { +#ifdef JS_TRACE_LOGGING + if (logger) + logger->logTimestamp(id); +#endif +} +inline void TraceLogStartEventPrivate(TraceLoggerThread *logger, uint32_t id) { +#ifdef JS_TRACE_LOGGING + if (logger) + logger->startEvent(id); +#endif +} +inline void TraceLogStopEventPrivate(TraceLoggerThread *logger, uint32_t id) { +#ifdef JS_TRACE_LOGGING + if (logger) + logger->stopEvent(id); #endif } // Automatic logging at the start and end of function call. -class AutoTraceLog { +class AutoTraceLog +{ #ifdef JS_TRACE_LOGGING - TraceLogger *logger; - uint32_t textId; + TraceLoggerThread *logger; + union { + const TraceLoggerEvent *event; + TraceLoggerTextId id; + } payload; + bool isEvent; bool executed; AutoTraceLog *prev; public: - AutoTraceLog(TraceLogger *logger, uint32_t textId MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + AutoTraceLog(TraceLoggerThread *logger, + const TraceLoggerEvent &event MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : logger(logger), - textId(textId), + isEvent(true), executed(false) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; + payload.event = &event; if (logger) { - TraceLogStartEvent(logger, textId); + logger->startEvent(event); + + prev = logger->top; + logger->top = this; + } + } + + AutoTraceLog(TraceLoggerThread *logger, TraceLoggerTextId id MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : logger(logger), + isEvent(false), + executed(false) + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + payload.id = id; + if (logger) { + logger->startEvent(id); prev = logger->top; logger->top = this; @@ -636,7 +470,10 @@ class AutoTraceLog { void stop() { if (!executed) { executed = true; - TraceLogStopEvent(logger, textId); + if (isEvent) + logger->stopEvent(*payload.event); + else + logger->stopEvent(payload.id); } if (logger->top == this) @@ -644,7 +481,12 @@ class AutoTraceLog { } #else public: - AutoTraceLog(TraceLogger *logger, uint32_t textId MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + AutoTraceLog(TraceLoggerThread *logger, uint32_t textId MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + } + AutoTraceLog(TraceLoggerThread *logger, + const TraceLoggerEvent &event MOZ_GUARD_OBJECT_NOTIFIER_PARAM) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; } @@ -654,26 +496,6 @@ class AutoTraceLog { MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; -#ifdef JS_TRACE_LOGGING -class AutoTraceLoggingLock -{ - TraceLogging *logging; - - public: - AutoTraceLoggingLock(TraceLogging *logging MOZ_GUARD_OBJECT_NOTIFIER_PARAM) - : logging(logging) - { - MOZ_GUARD_OBJECT_NOTIFIER_INIT; - PR_Lock(logging->lock); - } - ~AutoTraceLoggingLock() { - PR_Unlock(logging->lock); - } - private: - MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER -}; -#endif - } /* namedata js */ #endif /* TraceLogging_h */ diff --git a/js/src/vm/TraceLoggingGraph.cpp b/js/src/vm/TraceLoggingGraph.cpp new file mode 100644 index 000000000000..a7c339acc7fb --- /dev/null +++ b/js/src/vm/TraceLoggingGraph.cpp @@ -0,0 +1,567 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "vm/TraceLoggingGraph.h" + +#include "mozilla/Endian.h" + +#include "jsstr.h" + +#include "vm/TraceLogging.h" + +#ifndef TRACE_LOG_DIR +# if defined(_WIN32) +# define TRACE_LOG_DIR "" +# else +# define TRACE_LOG_DIR "/tmp/" +# endif +#endif + +using mozilla::NativeEndian; + +TraceLoggerGraphState traceLoggersGraph; + +class AutoTraceLoggerGraphStateLock +{ + TraceLoggerGraphState *graph; + + public: + explicit AutoTraceLoggerGraphStateLock(TraceLoggerGraphState *graph MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : graph(graph) + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + PR_Lock(graph->lock); + } + ~AutoTraceLoggerGraphStateLock() { + PR_Unlock(graph->lock); + } + private: + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + +TraceLoggerGraphState::TraceLoggerGraphState() + : numLoggers(0), + out(nullptr) +{ + lock = PR_NewLock(); + if (!lock) + MOZ_CRASH(); +} + +bool +TraceLoggerGraphState::ensureInitialized() +{ + if (out) + return true; + + out = fopen(TRACE_LOG_DIR "tl-data.json", "w"); + if (!out) + return false; + + fprintf(out, "["); + return true; +} + +TraceLoggerGraphState::~TraceLoggerGraphState() +{ + if (out) { + fprintf(out, "]"); + fclose(out); + out = nullptr; + } + + if (lock) { + PR_DestroyLock(lock); + lock = nullptr; + } +} + +uint32_t +TraceLoggerGraphState::nextLoggerId() +{ + AutoTraceLoggerGraphStateLock lock(this); + + if (!ensureInitialized()) { + fprintf(stderr, "TraceLogging: Couldn't create the main log file."); + return uint32_t(-1); + } + + if (numLoggers > 999) { + fprintf(stderr, "TraceLogging: Can't create more than 999 different loggers."); + return uint32_t(-1); + } + + if (numLoggers > 0) { + int written = fprintf(out, ",\n"); + if (written < 0) { + fprintf(stderr, "TraceLogging: Error while writing.\n"); + return uint32_t(-1); + } + } + + int written = fprintf(out, "{\"tree\":\"tl-tree.%d.tl\", \"events\":\"tl-event.%d.tl\", " + "\"dict\":\"tl-dict.%d.json\", \"treeFormat\":\"64,64,31,1,32\"}", + numLoggers, numLoggers, numLoggers); + if (written < 0) { + fprintf(stderr, "TraceLogging: Error while writing.\n"); + return uint32_t(-1); + } + + return numLoggers++; +} + +bool +TraceLoggerGraph::init(uint64_t startTimestamp) +{ + if (!tree.init()) { + failed = true; + return false; + } + if (!stack.init()) { + failed = true; + return false; + } + + uint32_t loggerId = traceLoggersGraph.nextLoggerId(); + if (loggerId == uint32_t(-1)) { + failed = true; + return false; + } + + char dictFilename[sizeof TRACE_LOG_DIR "tl-dict.100.json"]; + sprintf(dictFilename, TRACE_LOG_DIR "tl-dict.%d.json", loggerId); + dictFile = fopen(dictFilename, "w"); + if (!dictFile) { + failed = true; + return false; + } + + char treeFilename[sizeof TRACE_LOG_DIR "tl-tree.100.tl"]; + sprintf(treeFilename, TRACE_LOG_DIR "tl-tree.%d.tl", loggerId); + treeFile = fopen(treeFilename, "w+b"); + if (!treeFile) { + fclose(dictFile); + dictFile = nullptr; + failed = true; + return false; + } + + char eventFilename[sizeof TRACE_LOG_DIR "tl-event.100.tl"]; + sprintf(eventFilename, TRACE_LOG_DIR "tl-event.%d.tl", loggerId); + eventFile = fopen(eventFilename, "wb"); + if (!eventFile) { + fclose(dictFile); + fclose(treeFile); + dictFile = nullptr; + treeFile = nullptr; + failed = true; + return false; + } + + // Create the top tree node and corresponding first stack item. + TreeEntry &treeEntry = tree.pushUninitialized(); + treeEntry.setStart(startTimestamp); + treeEntry.setStop(0); + treeEntry.setTextId(0); + treeEntry.setHasChildren(false); + treeEntry.setNextId(0); + + StackEntry &stackEntry = stack.pushUninitialized(); + stackEntry.setTreeId(0); + stackEntry.setLastChildId(0); + stackEntry.setActive(true); + + int written = fprintf(dictFile, "["); + if (written < 0) { + fprintf(stderr, "TraceLogging: Error while writing.\n"); + fclose(dictFile); + fclose(treeFile); + fclose(eventFile); + dictFile = nullptr; + treeFile = nullptr; + eventFile = nullptr; + failed = true; + return false; + } + + return true; +} + +TraceLoggerGraph::~TraceLoggerGraph() +{ + // Write dictionary to disk + if (dictFile) { + int written = fprintf(dictFile, "]"); + if (written < 0) + fprintf(stderr, "TraceLogging: Error while writing.\n"); + fclose(dictFile); + + dictFile = nullptr; + } + + if (!failed && treeFile) { + // Make sure every start entry has a corresponding stop value. + // We temporary enable logging for this. Stop doesn't need any extra data, + // so is safe to do, even when we encountered OOM. + enabled = 1; + while (stack.size() > 1) + stopEvent(0); + enabled = 0; + } + + if (!failed && !flush()) { + fprintf(stderr, "TraceLogging: Couldn't write the data to disk.\n"); + enabled = 0; + failed = true; + } + + if (treeFile) { + fclose(treeFile); + treeFile = nullptr; + } + + if (eventFile) { + fclose(eventFile); + eventFile = nullptr; + } +} + +bool +TraceLoggerGraph::flush() +{ + MOZ_ASSERT(!failed); + + if (treeFile) { + // Format data in big endian. + for (size_t i = 0; i < tree.size(); i++) + entryToBigEndian(&tree[i]); + + int success = fseek(treeFile, 0, SEEK_END); + if (success != 0) + return false; + + size_t bytesWritten = fwrite(tree.data(), sizeof(TreeEntry), tree.size(), treeFile); + if (bytesWritten < tree.size()) + return false; + + treeOffset += tree.lastEntryId(); + tree.clear(); + } + + return true; +} + +void +TraceLoggerGraph::entryToBigEndian(TreeEntry *entry) +{ + entry->start_ = NativeEndian::swapToBigEndian(entry->start_); + entry->stop_ = NativeEndian::swapToBigEndian(entry->stop_); + uint32_t data = (entry->u.s.textId_ << 1) + entry->u.s.hasChildren_; + entry->u.value_ = NativeEndian::swapToBigEndian(data); + entry->nextId_ = NativeEndian::swapToBigEndian(entry->nextId_); +} + +void +TraceLoggerGraph::entryToSystemEndian(TreeEntry *entry) +{ + entry->start_ = NativeEndian::swapFromBigEndian(entry->start_); + entry->stop_ = NativeEndian::swapFromBigEndian(entry->stop_); + + uint32_t data = NativeEndian::swapFromBigEndian(entry->u.value_); + entry->u.s.textId_ = data >> 1; + entry->u.s.hasChildren_ = data & 0x1; + + entry->nextId_ = NativeEndian::swapFromBigEndian(entry->nextId_); +} + +void +TraceLoggerGraph::startEvent(uint32_t id, uint64_t timestamp) +{ + if (failed || enabled == 0) + return; + + if (!tree.hasSpaceForAdd()) { + if (!tree.ensureSpaceBeforeAdd()) { + if (!flush()) { + fprintf(stderr, "TraceLogging: Couldn't write the data to disk.\n"); + enabled = 0; + failed = true; + return; + } + } + } + + if (!startEventInternal(id, timestamp)) { + fprintf(stderr, "TraceLogging: Failed to start an event.\n"); + enabled = 0; + failed = true; + return; + } +} + +TraceLoggerGraph::StackEntry & +TraceLoggerGraph::getActiveAncestor() +{ + uint32_t parentId = stack.lastEntryId(); + while (!stack[parentId].active()) + parentId--; + return stack[parentId]; +} + +bool +TraceLoggerGraph::startEventInternal(uint32_t id, uint64_t timestamp) +{ + if (!stack.ensureSpaceBeforeAdd()) + return false; + + // Patch up the tree to be correct. There are two scenarios: + // 1) Parent has no children yet. So update parent to include children. + // 2) Parent has already children. Update last child to link to the new + // child. + StackEntry &parent = getActiveAncestor(); +#ifdef DEBUG + TreeEntry entry; + if (!getTreeEntry(parent.treeId(), &entry)) + return false; +#endif + + if (parent.lastChildId() == 0) { + MOZ_ASSERT(!entry.hasChildren()); + MOZ_ASSERT(parent.treeId() == tree.lastEntryId() + treeOffset); + + if (!updateHasChildren(parent.treeId())) + return false; + } else { + MOZ_ASSERT(entry.hasChildren()); + + if (!updateNextId(parent.lastChildId(), tree.size() + treeOffset)) + return false; + } + + // Add a new tree entry. + TreeEntry &treeEntry = tree.pushUninitialized(); + treeEntry.setStart(timestamp); + treeEntry.setStop(0); + treeEntry.setTextId(id); + treeEntry.setHasChildren(false); + treeEntry.setNextId(0); + + // Add a new stack entry. + StackEntry &stackEntry = stack.pushUninitialized(); + stackEntry.setTreeId(tree.lastEntryId() + treeOffset); + stackEntry.setLastChildId(0); + stackEntry.setActive(true); + + // Set the last child of the parent to this newly added entry. + parent.setLastChildId(tree.lastEntryId() + treeOffset); + + return true; +} + +void +TraceLoggerGraph::stopEvent(uint32_t id, uint64_t timestamp) +{ +#ifdef DEBUG + if (id != TraceLogger_Scripts && + id != TraceLogger_Engine && + stack.size() > 1 && + stack.lastEntry().active()) + { + TreeEntry entry; + MOZ_ASSERT(getTreeEntry(stack.lastEntry().treeId(), &entry)); + MOZ_ASSERT(entry.textId() == id); + } +#endif + + stopEvent(timestamp); +} + +void +TraceLoggerGraph::stopEvent(uint64_t timestamp) +{ + if (enabled && stack.lastEntry().active()) { + if (!updateStop(stack.lastEntry().treeId(), timestamp)) { + fprintf(stderr, "TraceLogging: Failed to stop an event.\n"); + enabled = 0; + failed = true; + return; + } + } + if (stack.size() == 1) { + if (!enabled) + return; + + // Forcefully disable logging. We have no stack information anymore. + logTimestamp(TraceLogger_Disable, timestamp); + return; + } + stack.pop(); +} + +void +TraceLoggerGraph::logTimestamp(uint32_t id, uint64_t timestamp) +{ + if (failed) + return; + + if (id == TraceLogger_Enable) + enabled = true; + + if (!enabled) + return; + + if (id == TraceLogger_Disable) + disable(timestamp); + + MOZ_ASSERT(eventFile); + + // Format data in big endian + timestamp = NativeEndian::swapToBigEndian(timestamp); + id = NativeEndian::swapToBigEndian(id); + + // The layout of the event log in the log file is: + // [timestamp, textId] + size_t itemsWritten = 0; + itemsWritten += fwrite(×tamp, sizeof(uint64_t), 1, eventFile); + itemsWritten += fwrite(&id, sizeof(uint32_t), 1, eventFile); + if (itemsWritten < 2) { + failed = true; + enabled = false; + } +} + +bool +TraceLoggerGraph::getTreeEntry(uint32_t treeId, TreeEntry *entry) +{ + // Entry is still in memory + if (treeId >= treeOffset) { + *entry = tree[treeId - treeOffset]; + return true; + } + + int success = fseek(treeFile, treeId * sizeof(TreeEntry), SEEK_SET); + if (success != 0) + return false; + + size_t itemsRead = fread((void *)entry, sizeof(TreeEntry), 1, treeFile); + if (itemsRead < 1) + return false; + + entryToSystemEndian(entry); + return true; +} + +bool +TraceLoggerGraph::saveTreeEntry(uint32_t treeId, TreeEntry *entry) +{ + int success = fseek(treeFile, treeId * sizeof(TreeEntry), SEEK_SET); + if (success != 0) + return false; + + entryToBigEndian(entry); + + size_t itemsWritten = fwrite(entry, sizeof(TreeEntry), 1, treeFile); + if (itemsWritten < 1) + return false; + + return true; +} + +bool +TraceLoggerGraph::updateHasChildren(uint32_t treeId, bool hasChildren) +{ + if (treeId < treeOffset) { + TreeEntry entry; + if (!getTreeEntry(treeId, &entry)) + return false; + entry.setHasChildren(hasChildren); + if (!saveTreeEntry(treeId, &entry)) + return false; + return true; + } + + tree[treeId - treeOffset].setHasChildren(hasChildren); + return true; +} + +bool +TraceLoggerGraph::updateNextId(uint32_t treeId, uint32_t nextId) +{ + if (treeId < treeOffset) { + TreeEntry entry; + if (!getTreeEntry(treeId, &entry)) + return false; + entry.setNextId(nextId); + if (!saveTreeEntry(treeId, &entry)) + return false; + return true; + } + + tree[treeId - treeOffset].setNextId(nextId); + return true; +} + +bool +TraceLoggerGraph::updateStop(uint32_t treeId, uint64_t timestamp) +{ + if (treeId < treeOffset) { + TreeEntry entry; + if (!getTreeEntry(treeId, &entry)) + return false; + entry.setStop(timestamp); + if (!saveTreeEntry(treeId, &entry)) + return false; + return true; + } + + tree[treeId - treeOffset].setStop(timestamp); + return true; +} + +void +TraceLoggerGraph::disable(uint64_t timestamp) +{ + MOZ_ASSERT(enabled); + while (stack.size() > 1) + stopEvent(timestamp); + + enabled = false; +} + +void +TraceLoggerGraph::log(ContinuousSpace &events) +{ + for (uint32_t i = 0; i < events.size(); i++) { + if (events[i].textId == TraceLogger_Stop) + stopEvent(events[i].time); + else if (TLTextIdIsTreeEvent(events[i].textId)) + startEvent(events[i].textId, events[i].time); + else + logTimestamp(events[i].textId, events[i].time); + } +} + +void +TraceLoggerGraph::addTextId(uint32_t id, const char *text) +{ + if (failed) + return; + + // Assume ids are given in order. Which is currently true. + MOZ_ASSERT(id == nextTextId); + nextTextId++; + + if (id > 0) { + int written = fprintf(dictFile, ",\n"); + if (written < 0) { + failed = true; + return; + } + } + + if (!js::FileEscapedString(dictFile, text, strlen(text), '"')) + failed = true; +} diff --git a/js/src/vm/TraceLoggingGraph.h b/js/src/vm/TraceLoggingGraph.h new file mode 100644 index 000000000000..50a5edbe0e01 --- /dev/null +++ b/js/src/vm/TraceLoggingGraph.h @@ -0,0 +1,256 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TraceLoggingGraph_h +#define TraceLoggingGraph_h + +#include "mozilla/DebugOnly.h" + +#include "jslock.h" + +#include "js/TypeDecls.h" +#include "vm/TraceLoggingTypes.h" + +/* + * The output of a tracelogging session is saved in /tmp/tl-data.json. + * The format of that file is a JS array per tracelogger (=thread), with a map + * containing: + * - dict: Name of the file containing a json table with the log text. + * All other files only contain a index to this table when logging. + * - events: Name of the file containing a flat list of log events saved + * in binary format. + * (64bit: Time Stamp Counter, 32bit index to dict) + * - tree: Name of the file containing the events with duration. The content + * is already in a tree data structure. This is also saved in a + * binary file. + * - treeFormat: The format used to encode the tree. By default "64,64,31,1,32". + * There are currently no other formats to save the tree. + * - 64,64,31,1,31 signifies how many bytes are used for the different + * parts of the tree. + * => 64 bits: Time Stamp Counter of start of event. + * => 64 bits: Time Stamp Counter of end of event. + * => 31 bits: Index to dict file containing the log text. + * => 1 bit: Boolean signifying if this entry has children. + * When true, the child can be found just right after this entry. + * => 32 bits: Containing the ID of the next event on the same depth + * or 0 if there isn't an event on the same depth anymore. + * + * /-> The position in the file. Id is this divided by size of entry. + * | So in this case this would be 1 (192bits per entry). + * | /-> Indicates there are children. The + * | | first child is located at current + * | | ID + 1. So 1 + 1 in this case: 2. + * | | Or 0x00180 in the tree file. + * | | /-> Next event on the same depth is + * | | | located at 4. So 0x00300 in the + * | | | tree file. + * 0x0000C0: [start, end, dictId, 1, 4] + * + * + * Example: + * 0x0: [start, end, dictId, 1, 0] + * | + * /----------------------------------\ + * | | + * 0xC0: [start, end, dictId, 0, 2] 0x180 [start, end, dictId, 1, 0] + * | + * /----------------------------------\ + * | | + * 0x240: [start, end, dictId, 0, 4] 0x300 [start, end, dictId, 0, 0] + */ + +class TraceLoggerGraphState +{ + uint32_t numLoggers; + + // File pointer to the "tl-data.json" file. (Explained above). + FILE *out; + + public: + PRLock *lock; + + public: + TraceLoggerGraphState(); + ~TraceLoggerGraphState(); + + uint32_t nextLoggerId(); + + private: + bool ensureInitialized(); +}; + +class TraceLoggerGraph +{ + // The layout of the tree in memory and in the log file. Readable by JS + // using TypedArrays. + struct TreeEntry { + uint64_t start_; + uint64_t stop_; + union { + struct { + uint32_t textId_: 31; + uint32_t hasChildren_: 1; + } s; + uint32_t value_; + } u; + uint32_t nextId_; + + TreeEntry(uint64_t start, uint64_t stop, uint32_t textId, bool hasChildren, + uint32_t nextId) + { + start_ = start; + stop_ = stop; + u.s.textId_ = textId; + u.s.hasChildren_ = hasChildren; + nextId_ = nextId; + } + TreeEntry() + { } + uint64_t start() { + return start_; + } + uint64_t stop() { + return stop_; + } + uint32_t textId() { + return u.s.textId_; + } + bool hasChildren() { + return u.s.hasChildren_; + } + uint32_t nextId() { + return nextId_; + } + void setStart(uint64_t start) { + start_ = start; + } + void setStop(uint64_t stop) { + stop_ = stop; + } + void setTextId(uint32_t textId) { + MOZ_ASSERT(textId < uint32_t(1 << 31)); + u.s.textId_ = textId; + } + void setHasChildren(bool hasChildren) { + u.s.hasChildren_ = hasChildren; + } + void setNextId(uint32_t nextId) { + nextId_ = nextId; + } + }; + + // Helper structure for keeping track of the current entries in + // the tree. Pushed by `start(id)`, popped by `stop(id)`. The active flag + // is used to know if a subtree doesn't need to get logged. + struct StackEntry { + uint32_t treeId_; + uint32_t lastChildId_; + struct { + uint32_t textId_: 31; + uint32_t active_: 1; + } s; + StackEntry(uint32_t treeId, uint32_t lastChildId, bool active = true) + : treeId_(treeId), lastChildId_(lastChildId) + { + s.textId_ = 0; + s.active_ = active; + } + uint32_t treeId() { + return treeId_; + } + uint32_t lastChildId() { + return lastChildId_; + } + uint32_t textId() { + return s.textId_; + } + bool active() { + return s.active_; + } + void setTreeId(uint32_t treeId) { + treeId_ = treeId; + } + void setLastChildId(uint32_t lastChildId) { + lastChildId_ = lastChildId; + } + void setTextId(uint32_t textId) { + MOZ_ASSERT(textId < uint32_t(1<<31)); + s.textId_ = textId; + } + void setActive(bool active) { + s.active_ = active; + } + }; + + public: + TraceLoggerGraph() + : failed(false), + enabled(false), + nextTextId(0), + treeOffset(0) + { } + ~TraceLoggerGraph(); + + bool init(uint64_t timestamp); + + // Link a textId with a particular text. + void addTextId(uint32_t id, const char *text); + + // Create a tree out of all the given events. + void log(ContinuousSpace &events); + + private: + bool failed; + bool enabled; + mozilla::DebugOnly nextTextId; + + FILE *dictFile; + FILE *treeFile; + FILE *eventFile; + + ContinuousSpace tree; + ContinuousSpace stack; + uint32_t treeOffset; + + // Helper functions that convert a TreeEntry in different endianness + // in place. + void entryToBigEndian(TreeEntry *entry); + void entryToSystemEndian(TreeEntry *entry); + + // Helper functions to get/save a tree from file. + bool getTreeEntry(uint32_t treeId, TreeEntry *entry); + bool saveTreeEntry(uint32_t treeId, TreeEntry *entry); + + // Return the first StackEntry that is active. + StackEntry &getActiveAncestor(); + + // This contains the meat of startEvent, except the test for enough space, + // the test if tracelogger is enabled and the timestamp computation. + void startEvent(uint32_t id, uint64_t timestamp); + bool startEventInternal(uint32_t id, uint64_t timestamp); + + // Update functions that can adjust the items in the tree, + // both in memory or already written to disk. + bool updateHasChildren(uint32_t treeId, bool hasChildren = true); + bool updateNextId(uint32_t treeId, uint32_t nextId); + bool updateStop(uint32_t treeId, uint64_t timestamp); + + // Flush the tree. + bool flush(); + + // Stop a tree event. + void stopEvent(uint32_t id, uint64_t timestamp); + void stopEvent(uint64_t timestamp); + + // Log an (non-tree) event. + void logTimestamp(uint32_t id, uint64_t timestamp); + + // Disable logging and forcefully report all not yet stopped tree events + // as stopped. + void disable(uint64_t timestamp); +}; + +#endif /* TraceLoggingGraph_h */ diff --git a/js/src/vm/TraceLoggingTypes.cpp b/js/src/vm/TraceLoggingTypes.cpp new file mode 100644 index 000000000000..8fd42e737ecd --- /dev/null +++ b/js/src/vm/TraceLoggingTypes.cpp @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "vm/TraceLoggingTypes.h" + +class JSLinearString; + +uint32_t +TLStringToTextId(JSLinearString *str) +{ +#define NAME(textId) if (js::StringEqualsAscii(str, #textId)) return TraceLogger_ ## textId; + TRACELOGGER_TREE_ITEMS(NAME) + TRACELOGGER_LOG_ITEMS(NAME) +#undef NAME + return TraceLogger_Error; +} + diff --git a/js/src/vm/TraceLoggingTypes.h b/js/src/vm/TraceLoggingTypes.h new file mode 100644 index 000000000000..0ec9b7594f76 --- /dev/null +++ b/js/src/vm/TraceLoggingTypes.h @@ -0,0 +1,239 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TraceLoggingTypes_h +#define TraceLoggingTypes_h + +#include "jsalloc.h" +#include "jsstr.h" + +#define TRACELOGGER_TREE_ITEMS(_) \ + _(AnnotateScripts) \ + _(Baseline) \ + _(BaselineCompilation) \ + _(Engine) \ + _(GC) \ + _(GCAllocation) \ + _(GCSweeping) \ + _(Internal) \ + _(Interpreter) \ + _(InlinedScripts) \ + _(Invalidation) \ + _(IonCompilation) \ + _(IonCompilationPaused) \ + _(IonLinking) \ + _(IonMonkey) \ + _(IrregexpCompile) \ + _(IrregexpExecute) \ + _(MinorGC) \ + _(ParserCompileFunction) \ + _(ParserCompileLazy) \ + _(ParserCompileScript) \ + _(Scripts) \ + _(VM) \ + \ + /* Specific passes during ion compilation */ \ + _(FoldTests) \ + _(SplitCriticalEdges) \ + _(RenumberBlocks) \ + _(ScalarReplacement) \ + _(DominatorTree) \ + _(PhiAnalysis) \ + _(MakeLoopsContiguous) \ + _(ApplyTypes) \ + _(ParallelSafetyAnalysis) \ + _(AliasAnalysis) \ + _(GVN) \ + _(LICM) \ + _(RangeAnalysis) \ + _(LoopUnrolling) \ + _(EffectiveAddressAnalysis) \ + _(EliminateDeadCode) \ + _(EdgeCaseAnalysis) \ + _(EliminateRedundantChecks) \ + _(GenerateLIR) \ + _(RegisterAllocation) \ + _(GenerateCode) + +#define TRACELOGGER_LOG_ITEMS(_) \ + _(Bailout) \ + _(Disable) \ + _(Enable) \ + _(Stop) + +// Predefined IDs for common operations. These IDs can be used +// without using TraceLogCreateTextId, because there are already created. +enum TraceLoggerTextId { + TraceLogger_Error = 0, +#define DEFINE_TEXT_ID(textId) TraceLogger_ ## textId, + TRACELOGGER_TREE_ITEMS(DEFINE_TEXT_ID) + TraceLogger_LastTreeItem, + TRACELOGGER_LOG_ITEMS(DEFINE_TEXT_ID) +#undef DEFINE_TEXT_ID + TraceLogger_Last +}; + +inline const char * +TLTextIdString(TraceLoggerTextId id) +{ + switch (id) { + case TraceLogger_Error: + return "TraceLogger failed to process text"; +#define NAME(textId) case TraceLogger_ ## textId: return #textId; + TRACELOGGER_TREE_ITEMS(NAME) + TRACELOGGER_LOG_ITEMS(NAME) +#undef NAME + default: + MOZ_CRASH(); + } +} + +uint32_t +TLStringToTextId(JSLinearString *str); + +inline bool +TLTextIdIsToggable(uint32_t id) +{ + if (id == TraceLogger_Error) + return false; + if (id == TraceLogger_Internal) + return false; + if (id == TraceLogger_Stop) + return false; + // Actually never used. But added here so it doesn't show as toggle + if (id == TraceLogger_LastTreeItem) + return false; + if (id == TraceLogger_Last) + return false; + // Cannot toggle the logging of one engine on/off, because at the stop + // event it is sometimes unknown which engine was running. + if (id == TraceLogger_IonMonkey || id == TraceLogger_Baseline || id == TraceLogger_Interpreter) + return false; + return true; +} + +inline bool +TLTextIdIsTreeEvent(uint32_t id) +{ + // Everything between TraceLogger_Error and TraceLogger_LastTreeItem are tree events and + // atm also every custom event. + return (id > TraceLogger_Error && id < TraceLogger_LastTreeItem) || + id >= TraceLogger_Last; +} + +template +class ContinuousSpace { + T *data_; + uint32_t size_; + uint32_t capacity_; + + public: + ContinuousSpace () + : data_(nullptr) + { } + + bool init() { + capacity_ = 64; + size_ = 0; + data_ = (T *) js_malloc(capacity_ * sizeof(T)); + if (!data_) + return false; + + return true; + } + + ~ContinuousSpace() + { + js_free(data_); + data_ = nullptr; + } + + T *data() { + return data_; + } + + uint32_t capacity() { + return capacity_; + } + + uint32_t size() { + return size_; + } + + bool empty() { + return size_ == 0; + } + + uint32_t lastEntryId() { + MOZ_ASSERT(!empty()); + return size_ - 1; + } + + T &lastEntry() { + return data()[lastEntryId()]; + } + + bool hasSpaceForAdd(uint32_t count = 1) { + if (size_ + count <= capacity_) + return true; + return false; + } + + bool ensureSpaceBeforeAdd(uint32_t count = 1) { + MOZ_ASSERT(data_); + if (hasSpaceForAdd(count)) + return true; + + uint32_t nCapacity = capacity_ * 2; + if (size_ + count > nCapacity) + nCapacity = size_ + count; + T *entries = (T *) js_realloc(data_, nCapacity * sizeof(T)); + + if (!entries) + return false; + + data_ = entries; + capacity_ = nCapacity; + + return true; + } + + T &operator[](size_t i) { + MOZ_ASSERT(i < size_); + return data()[i]; + } + + void push(T &data) { + MOZ_ASSERT(size_ < capacity_); + data()[size_++] = data; + } + + T &pushUninitialized() { + MOZ_ASSERT(size_ < capacity_); + return data()[size_++]; + } + + void pop() { + MOZ_ASSERT(!empty()); + size_--; + } + + void clear() { + size_ = 0; + } +}; + +// The layout of the event log in memory and in the log file. +// Readable by JS using TypedArrays. +struct EventEntry { + uint64_t time; + uint32_t textId; + EventEntry(uint64_t time, uint32_t textId) + : time(time), textId(textId) + { } +}; + +#endif /* TraceLoggingTypes_h */ diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index 010b3f80f22c..da5e20639f49 100644 --- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -785,6 +785,7 @@ TypedArrayObject::protoFunctions[] = { JS_SELF_HOSTED_FN("find", "TypedArrayFind", 2, 0), JS_SELF_HOSTED_FN("findIndex", "TypedArrayFindIndex", 2, 0), JS_SELF_HOSTED_FN("indexOf", "TypedArrayIndexOf", 2, 0), + JS_SELF_HOSTED_FN("join", "TypedArrayJoin", 1, 0), JS_SELF_HOSTED_FN("lastIndexOf", "TypedArrayLastIndexOf", 2, 0), JS_SELF_HOSTED_FN("reverse", "TypedArrayReverse", 0, 0), #ifdef NIGHTLY_BUILD diff --git a/js/xpconnect/tests/chrome/test_xrayToJS.xul b/js/xpconnect/tests/chrome/test_xrayToJS.xul index ba79ca07b6b4..572187c2cb0a 100644 --- a/js/xpconnect/tests/chrome/test_xrayToJS.xul +++ b/js/xpconnect/tests/chrome/test_xrayToJS.xul @@ -180,7 +180,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=933681 } gPrototypeProperties['TypedArray'] = ["length", "buffer", "byteLength", "byteOffset", kIteratorSymbol, "subarray", - "set", "copyWithin", "find", "findIndex", "indexOf", "lastIndexOf", "reverse"]; + "set", "copyWithin", "find", "findIndex", "indexOf", "lastIndexOf", "reverse", + "join"]; if (isNightlyBuild) { gPrototypeProperties['TypedArray'].push('includes'); } diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp index e7de284b8130..66f8a4bf8d7c 100644 --- a/layout/base/nsCSSFrameConstructor.cpp +++ b/layout/base/nsCSSFrameConstructor.cpp @@ -4550,7 +4550,7 @@ nsCSSFrameConstructor::FindDisplayData(const nsStyleDisplay* aDisplay, } // Handle various non-scrollable blocks. - static const FrameConstructionData sNonScrollableBlockData[2][2] { + static const FrameConstructionData sNonScrollableBlockData[2][2] = { { FULL_CTOR_FCDATA(0, &nsCSSFrameConstructor::ConstructNonScrollableBlock), FULL_CTOR_FCDATA(kCaptionCtorFlags, diff --git a/media/libstagefright/binding/mp4_demuxer.cpp b/media/libstagefright/binding/mp4_demuxer.cpp index 071ba4a4ea2c..83b68a1088b3 100644 --- a/media/libstagefright/binding/mp4_demuxer.cpp +++ b/media/libstagefright/binding/mp4_demuxer.cpp @@ -92,6 +92,7 @@ MP4Demuxer::~MP4Demuxer() bool MP4Demuxer::Init() { + mMonitor->AssertCurrentThreadOwns(); sp e = mPrivate->mExtractor; for (size_t i = 0; i < e->countTracks(); i++) { sp metaData = e->getTrackMetaData(i); @@ -138,30 +139,35 @@ MP4Demuxer::Init() bool MP4Demuxer::HasValidAudio() { + mMonitor->AssertCurrentThreadOwns(); return mPrivate->mAudio.get() && mAudioConfig.IsValid(); } bool MP4Demuxer::HasValidVideo() { + mMonitor->AssertCurrentThreadOwns(); return mPrivate->mVideo.get() && mVideoConfig.IsValid(); } Microseconds MP4Demuxer::Duration() { + mMonitor->AssertCurrentThreadOwns(); return std::max(mVideoConfig.duration, mAudioConfig.duration); } bool MP4Demuxer::CanSeek() { + mMonitor->AssertCurrentThreadOwns(); return mPrivate->mExtractor->flags() & MediaExtractor::CAN_SEEK; } void MP4Demuxer::SeekAudio(Microseconds aTime) { + mMonitor->AssertCurrentThreadOwns(); if (mPrivate->mAudioIterator) { mPrivate->mAudioIterator->Seek(aTime); } else { @@ -173,6 +179,7 @@ MP4Demuxer::SeekAudio(Microseconds aTime) void MP4Demuxer::SeekVideo(Microseconds aTime) { + mMonitor->AssertCurrentThreadOwns(); if (mPrivate->mVideoIterator) { mPrivate->mVideoIterator->Seek(aTime); } else { @@ -184,6 +191,7 @@ MP4Demuxer::SeekVideo(Microseconds aTime) MP4Sample* MP4Demuxer::DemuxAudioSample() { + mMonitor->AssertCurrentThreadOwns(); if (mPrivate->mAudioIterator) { nsAutoPtr sample(mPrivate->mAudioIterator->GetNext()); if (sample) { @@ -213,6 +221,7 @@ MP4Demuxer::DemuxAudioSample() MP4Sample* MP4Demuxer::DemuxVideoSample() { + mMonitor->AssertCurrentThreadOwns(); if (mPrivate->mVideoIterator) { nsAutoPtr sample(mPrivate->mVideoIterator->GetNext()); if (sample) { @@ -243,6 +252,7 @@ MP4Demuxer::DemuxVideoSample() void MP4Demuxer::UpdateIndex(const nsTArray& aByteRanges) { + mMonitor->AssertCurrentThreadOwns(); for (int i = 0; i < mPrivate->mIndexes.Length(); i++) { mPrivate->mIndexes[i]->UpdateMoofIndex(aByteRanges); } @@ -253,6 +263,7 @@ MP4Demuxer::ConvertByteRangesToTime( const nsTArray& aByteRanges, nsTArray>* aIntervals) { + mMonitor->AssertCurrentThreadOwns(); if (mPrivate->mIndexes.IsEmpty()) { return; } @@ -292,6 +303,7 @@ MP4Demuxer::ConvertByteRangesToTime( int64_t MP4Demuxer::GetEvictionOffset(Microseconds aTime) { + mMonitor->AssertCurrentThreadOwns(); if (mPrivate->mIndexes.IsEmpty()) { return 0; } diff --git a/mobile/android/base/BrowserApp.java b/mobile/android/base/BrowserApp.java index 1521171255a8..f8d172d31c24 100644 --- a/mobile/android/base/BrowserApp.java +++ b/mobile/android/base/BrowserApp.java @@ -8,6 +8,7 @@ package org.mozilla.gecko; import java.io.File; import java.io.FileNotFoundException; import java.lang.Override; +import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URLEncoder; import java.util.EnumSet; @@ -16,6 +17,7 @@ import java.util.List; import java.util.Locale; import java.util.Vector; +import android.support.v4.app.Fragment; import org.json.JSONException; import org.json.JSONObject; @@ -651,16 +653,6 @@ public class BrowserApp extends GeckoApp // Set the maximum bits-per-pixel the favicon system cares about. IconDirectoryEntry.setMaxBPP(GeckoAppShell.getScreenDepth()); - Class mediaManagerClass = getMediaPlayerManager(); - if (mediaManagerClass != null) { - try { - Method init = mediaManagerClass.getMethod("init", Context.class); - init.invoke(null, this); - } catch(Exception ex) { - Log.e(LOGTAG, "Error initializing media manager", ex); - } - } - mTilesRecorder = new TilesRecorder(); } @@ -1163,16 +1155,6 @@ public class BrowserApp extends GeckoApp } } - Class mediaManagerClass = getMediaPlayerManager(); - if (mediaManagerClass != null) { - try { - Method destroy = mediaManagerClass.getMethod("onDestroy", (Class[]) null); - destroy.invoke(null); - } catch(Exception ex) { - Log.e(LOGTAG, "Error destroying media manager", ex); - } - } - super.onDestroy(); } @@ -1599,6 +1581,29 @@ public class BrowserApp extends GeckoApp } }); + if (AppConstants.MOZ_MEDIA_PLAYER) { + // Check if the fragment is already added. This should never be true here, but this is + // a nice safety check. + // If casting is disabled, these classes aren't built. We use reflection to initialize them. + final Class mediaManagerClass = getMediaPlayerManager(); + + if (mediaManagerClass != null) { + try { + final String tag = ""; + mediaManagerClass.getDeclaredField("MEDIA_PLAYER_TAG").get(tag); + Log.i(LOGTAG, "Found tag " + tag); + final Fragment frag = getSupportFragmentManager().findFragmentByTag(tag); + if (frag == null) { + final Method getInstance = mediaManagerClass.getMethod("newInstance", (Class[]) null); + final Fragment mpm = (Fragment) getInstance.invoke(null); + getSupportFragmentManager().beginTransaction().disallowAddToBackStack().add(mpm, tag).commit(); + } + } catch (Exception ex) { + Log.e(LOGTAG, "Error initializing media manager", ex); + } + } + } + if (AppConstants.MOZ_STUMBLER_BUILD_TIME_ENABLED) { // Start (this acts as ping if started already) the stumbler lib; if the stumbler has queued data it will upload it. // Stumbler operates on its own thread, and startup impact is further minimized by delaying work (such as upload) a few seconds. @@ -1611,6 +1616,7 @@ public class BrowserApp extends GeckoApp } }, oneSecondInMillis); } + super.handleMessage(event, message); } else if (event.equals("Gecko:Ready")) { // Handle this message in GeckoApp, but also enable the Settings diff --git a/mobile/android/base/ChromeCast.java b/mobile/android/base/ChromeCast.java index feb73f2f8d81..5c1370ade71d 100644 --- a/mobile/android/base/ChromeCast.java +++ b/mobile/android/base/ChromeCast.java @@ -161,7 +161,7 @@ class ChromeCast implements GeckoMediaPlayer { public ChromeCast(Context context, RouteInfo route) { int status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(context); if (status != ConnectionResult.SUCCESS) { - throw new IllegalStateException("Play services are required for Chromecast support (go status code " + status + ")"); + throw new IllegalStateException("Play services are required for Chromecast support (got status code " + status + ")"); } this.context = context; diff --git a/mobile/android/base/GeckoApp.java b/mobile/android/base/GeckoApp.java index 02a2cbe3ea55..339d90871c63 100644 --- a/mobile/android/base/GeckoApp.java +++ b/mobile/android/base/GeckoApp.java @@ -1914,7 +1914,7 @@ public abstract class GeckoApp } if (mAppStateListeners != null) { - for (GeckoAppShell.AppStateListener listener: mAppStateListeners) { + for (GeckoAppShell.AppStateListener listener : mAppStateListeners) { listener.onResume(); } } @@ -1946,7 +1946,7 @@ public abstract class GeckoApp Log.w(LOGTAG, "Can't record session: rec is null."); } } - }); + }); } @Override @@ -1996,7 +1996,7 @@ public abstract class GeckoApp }); if (mAppStateListeners != null) { - for(GeckoAppShell.AppStateListener listener: mAppStateListeners) { + for (GeckoAppShell.AppStateListener listener : mAppStateListeners) { listener.onPause(); } } diff --git a/mobile/android/base/GeckoAppShell.java b/mobile/android/base/GeckoAppShell.java index 3e0206dafb6d..449bdaa2b038 100644 --- a/mobile/android/base/GeckoAppShell.java +++ b/mobile/android/base/GeckoAppShell.java @@ -1175,7 +1175,7 @@ public class GeckoAppShell } final Uri uri = normalizeUriScheme(targetURI.indexOf(':') >= 0 ? Uri.parse(targetURI) : new Uri.Builder().scheme(targetURI).build()); - if (mimeType.length() > 0) { + if (!TextUtils.isEmpty(mimeType)) { Intent intent = getIntentForActionString(action); intent.setDataAndType(uri, mimeType); return intent; @@ -1191,8 +1191,10 @@ public class GeckoAppShell // custom handlers that would apply. // Start with the original URI. If we end up modifying it, we'll // overwrite it. + final String extension = MimeTypeMap.getFileExtensionFromUrl(targetURI); + final String mimeType2 = getMimeTypeFromExtension(extension); final Intent intent = getIntentForActionString(action); - intent.setData(uri); + intent.setDataAndType(uri, mimeType2); if ("vnd.youtube".equals(scheme) && !hasHandlersForIntent(intent) && diff --git a/mobile/android/base/MediaPlayerManager.java b/mobile/android/base/MediaPlayerManager.java index 223f81833184..5ebf2ac91a50 100644 --- a/mobile/android/base/MediaPlayerManager.java +++ b/mobile/android/base/MediaPlayerManager.java @@ -5,35 +5,44 @@ package org.mozilla.gecko; -import org.mozilla.gecko.util.EventCallback; -import org.mozilla.gecko.mozglue.JNITarget; -import org.mozilla.gecko.util.NativeEventListener; -import org.mozilla.gecko.util.NativeJSObject; - -import org.json.JSONArray; -import org.json.JSONObject; -import org.json.JSONException; - -import android.content.Context; +import android.os.Bundle; +import android.support.v4.app.Fragment; import android.support.v7.media.MediaControlIntent; import android.support.v7.media.MediaRouteSelector; import android.support.v7.media.MediaRouter; import android.support.v7.media.MediaRouter.RouteInfo; import android.util.Log; - import com.google.android.gms.cast.CastMediaControlIntent; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.mozilla.gecko.mozglue.JNITarget; +import org.mozilla.gecko.util.EventCallback; +import org.mozilla.gecko.util.NativeEventListener; +import org.mozilla.gecko.util.NativeJSObject; import java.util.HashMap; -import java.util.Map; import java.util.Iterator; +import java.util.Map; /* Manages a list of GeckoMediaPlayers methods (i.e. Chromecast/Miracast). Routes messages * from Gecko to the correct caster based on the id of the display */ -class MediaPlayerManager implements NativeEventListener, - GeckoAppShell.AppStateListener { +public class MediaPlayerManager extends Fragment implements NativeEventListener { + /** + * Create a new instance of DetailsFragment, initialized to + * show the text at 'index'. + */ + @JNITarget + public static MediaPlayerManager newInstance() { + return new MediaPlayerManager(); + } + private static final String LOGTAG = "GeckoMediaPlayerManager"; + @JNITarget + public static final String MEDIA_PLAYER_TAG = "MPManagerFragment"; + private static final boolean SHOW_DEBUG = false; // Simplified debugging interfaces private static void debug(String msg, Exception e) { @@ -48,62 +57,36 @@ class MediaPlayerManager implements NativeEventListener, } } - private final Context context; - private final MediaRouter mediaRouter; + private MediaRouter mediaRouter = null; private final Map displays = new HashMap(); - private static MediaPlayerManager instance; - @JNITarget - public static void init(Context context) { - if (instance != null) { - debug("MediaPlayerManager initialized twice"); - return; - } - - instance = new MediaPlayerManager(context); - } - - private MediaPlayerManager(Context context) { - this.context = context; - - if (context instanceof GeckoApp) { - GeckoApp app = (GeckoApp) context; - app.addAppStateListener(this); - } - - mediaRouter = MediaRouter.getInstance(context); + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); EventDispatcher.getInstance().registerGeckoThreadListener(this, - "MediaPlayer:Load", - "MediaPlayer:Start", - "MediaPlayer:Stop", - "MediaPlayer:Play", - "MediaPlayer:Pause", - "MediaPlayer:Get", - "MediaPlayer:End", - "MediaPlayer:Mirror", - "MediaPlayer:Message"); + "MediaPlayer:Load", + "MediaPlayer:Start", + "MediaPlayer:Stop", + "MediaPlayer:Play", + "MediaPlayer:Pause", + "MediaPlayer:End", + "MediaPlayer:Mirror", + "MediaPlayer:Message"); } + @Override @JNITarget - public static void onDestroy() { - if (instance == null) { - return; - } - - EventDispatcher.getInstance().unregisterGeckoThreadListener(instance, + public void onDestroy() { + super.onDestroy(); + EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "MediaPlayer:Load", "MediaPlayer:Start", "MediaPlayer:Stop", "MediaPlayer:Play", "MediaPlayer:Pause", - "MediaPlayer:Get", "MediaPlayer:End", "MediaPlayer:Mirror", "MediaPlayer:Message"); - if (instance.context instanceof GeckoApp) { - GeckoApp app = (GeckoApp) instance.context; - app.removeAppStateListener(instance); - } } // GeckoEventListener implementation @@ -111,37 +94,6 @@ class MediaPlayerManager implements NativeEventListener, public void handleMessage(String event, final NativeJSObject message, final EventCallback callback) { debug(event); - if ("MediaPlayer:Get".equals(event)) { - final JSONObject result = new JSONObject(); - final JSONArray disps = new JSONArray(); - - final Iterator items = displays.values().iterator(); - while (items.hasNext()) { - GeckoMediaPlayer disp = items.next(); - try { - JSONObject json = disp.toJSON(); - if (json == null) { - items.remove(); - } else { - disps.put(json); - } - } catch(Exception ex) { - // This may happen if the device isn't a real Chromecast, - // for example Matchstick casting devices. - Log.e(LOGTAG, "Couldn't create JSON for display", ex); - } - } - - try { - result.put("displays", disps); - } catch(JSONException ex) { - Log.i(LOGTAG, "Error sending displays", ex); - } - - callback.sendSuccess(result); - return; - } - final GeckoMediaPlayer display = displays.get(message.getString("id")); if (display == null) { Log.e(LOGTAG, "Couldn't find a display for this id: " + message.getString("id") + " for message: " + event); @@ -179,6 +131,8 @@ class MediaPlayerManager implements NativeEventListener, public void onRouteRemoved(MediaRouter router, RouteInfo route) { debug("onRouteRemoved: route=" + route); displays.remove(route.getId()); + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent( + "MediaPlayer:Removed", route.getId())); } @SuppressWarnings("unused") @@ -201,18 +155,22 @@ class MediaPlayerManager implements NativeEventListener, @Override public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo route) { debug("onRouteAdded: route=" + route); - GeckoMediaPlayer display = getMediaPlayerForRoute(route); + final GeckoMediaPlayer display = getMediaPlayerForRoute(route); if (display != null) { displays.put(route.getId(), display); + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent( + "MediaPlayer:Added", display.toJSON().toString())); } } @Override public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) { debug("onRouteChanged: route=" + route); - GeckoMediaPlayer display = displays.get(route.getId()); + final GeckoMediaPlayer display = displays.get(route.getId()); if (display != null) { displays.put(route.getId(), display); + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent( + "MediaPlayer:Changed", display.toJSON().toString())); } } }; @@ -220,7 +178,7 @@ class MediaPlayerManager implements NativeEventListener, private GeckoMediaPlayer getMediaPlayerForRoute(MediaRouter.RouteInfo route) { try { if (route.supportsControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) { - return new ChromeCast(context, route); + return new ChromeCast(getActivity(), route); } } catch(Exception ex) { debug("Error handling presentation", ex); @@ -229,23 +187,28 @@ class MediaPlayerManager implements NativeEventListener, return null; } - /* Implementing GeckoAppShell.AppStateListener */ @Override public void onPause() { + super.onPause(); mediaRouter.removeCallback(callback); + mediaRouter = null; } @Override public void onResume() { - MediaRouteSelector selectorBuilder = new MediaRouteSelector.Builder() + super.onResume(); + + // The mediaRouter shouldn't exist here, but this is a nice safety check. + if (mediaRouter != null) { + return; + } + + mediaRouter = MediaRouter.getInstance(getActivity()); + final MediaRouteSelector selectorBuilder = new MediaRouteSelector.Builder() .addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO) .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK) .addControlCategory(CastMediaControlIntent.categoryForCast(ChromeCast.MIRROR_RECEIVER_APP_ID)) .build(); mediaRouter.addCallback(selectorBuilder, callback, MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY); } - - @Override - public void onOrientationChanged() { } - } diff --git a/mobile/android/chrome/content/CastingApps.js b/mobile/android/chrome/content/CastingApps.js index 8f0336e6ff80..2f649ec92567 100644 --- a/mobile/android/chrome/content/CastingApps.js +++ b/mobile/android/chrome/content/CastingApps.js @@ -43,7 +43,35 @@ var mediaPlayerDevice = { return new MediaPlayerApp(aService); }, types: ["video/mp4", "video/webm", "application/x-mpegurl"], - extensions: ["mp4", "webm", "m3u", "m3u8"] + extensions: ["mp4", "webm", "m3u", "m3u8"], + init: function() { + Services.obs.addObserver(this, "MediaPlayer:Added", false); + Services.obs.addObserver(this, "MediaPlayer:Changed", false); + Services.obs.addObserver(this, "MediaPlayer:Removed", false); + }, + observe: function(subject, topic, data) { + if (topic === "MediaPlayer:Added") { + let service = this.toService(JSON.parse(data)); + SimpleServiceDiscovery.addService(service); + } else if (topic === "MediaPlayer:Changed") { + let service = this.toService(JSON.parse(data)); + SimpleServiceDiscovery.updateService(service); + } else if (topic === "MediaPlayer:Removed") { + SimpleServiceDiscovery.removeService(data); + } + }, + toService: function(display) { + // Convert the native data into something matching what is created in _processService() + return { + location: display.location, + target: "media:router", + friendlyName: display.friendlyName, + uuid: display.uuid, + manufacturer: display.manufacturer, + modelName: display.modelName, + mirror: display.mirror + }; + } }; var CastingApps = { @@ -59,6 +87,9 @@ var CastingApps = { // Register targets SimpleServiceDiscovery.registerDevice(rokuDevice); SimpleServiceDiscovery.registerDevice(matchstickDevice); + + // MediaPlayerDevice will notify us any time the native device list changes. + mediaPlayerDevice.init(); SimpleServiceDiscovery.registerDevice(mediaPlayerDevice); // Search for devices continuously every 120 seconds diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index 31f255c2d73e..2a417d4aacab 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -339,6 +339,7 @@ var BrowserApp = { Services.tm.mainThread.dispatch(function() { // Init LoginManager Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager); + CastingApps.init(); }, Ci.nsIThread.DISPATCH_NORMAL); #ifdef MOZ_SAFE_BROWSING @@ -440,7 +441,6 @@ var BrowserApp = { RemoteDebugger.init(); UserAgentOverrides.init(); DesktopUserAgent.init(); - CastingApps.init(); Distribution.init(); Tabs.init(); #ifdef ACCESSIBILITY diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 730e94889ba4..19a7e96aab92 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -2367,6 +2367,8 @@ pref("dom.ipc.plugins.reportCrashURL", true); // Defaults to 30 seconds. pref("dom.ipc.plugins.unloadTimeoutSecs", 30); +pref("dom.ipc.plugins.asyncInit", false); + pref("dom.ipc.processCount", 1); // Enable caching of Moz2D Path objects for SVG geometry elements diff --git a/python/moz.build b/python/moz.build index 33e4679f56d1..4b6b7168588d 100644 --- a/python/moz.build +++ b/python/moz.build @@ -5,7 +5,6 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. SPHINX_PYTHON_PACKAGE_DIRS += [ - 'codegen', 'mach', 'mozbuild/mozbuild', 'mozbuild/mozpack', diff --git a/python/mozbuild/mozbuild/backend/templates/android_eclipse/project.properties b/python/mozbuild/mozbuild/backend/templates/android_eclipse/project.properties index f34d1d0180bc..2106d9646aac 100644 --- a/python/mozbuild/mozbuild/backend/templates/android_eclipse/project.properties +++ b/python/mozbuild/mozbuild/backend/templates/android_eclipse/project.properties @@ -9,6 +9,6 @@ # project structure. # Project target. -target=android-@ANDROID_TARGET_SDK@ +target=android-L @IDE_PROJECT_LIBRARY_SETTING@ @IDE_PROJECT_LIBRARY_REFERENCES@ diff --git a/testing/tools/screenshot/gdk-screenshot.cpp b/testing/tools/screenshot/gdk-screenshot.cpp index a6ddb8a81e26..9e944bf22696 100644 --- a/testing/tools/screenshot/gdk-screenshot.cpp +++ b/testing/tools/screenshot/gdk-screenshot.cpp @@ -127,10 +127,10 @@ int main(int argc, char** argv) } #endif - GdkWindow* window = gdk_get_default_root_window(); GdkPixbuf* screenshot = nullptr; // TODO GTK3 #if (MOZ_WIDGET_GTK == 2) + GdkWindow* window = gdk_get_default_root_window(); screenshot = gdk_pixbuf_get_from_drawable(nullptr, window, nullptr, 0, 0, 0, 0, gdk_screen_width(), diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index 3d38b7075ba8..fde9f213b3f0 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -7029,6 +7029,15 @@ "keyed": true, "description": "Time (ms) that the main thread has been blocked on NPP_NewStream in an IPC plugin" }, + "BLOCKED_ON_PLUGINASYNCSURROGATE_WAITFORINIT_MS": { + "expires_in_version": "40", + "kind": "exponential", + "high": "10000", + "n_buckets": 20, + "extended_statistics_ok": true, + "keyed": true, + "description": "Time (ms) that the main thread has been blocked on PluginAsyncSurrogate::WaitForInit in an IPC plugin" + }, "BLOCKED_ON_PLUGIN_INSTANCE_DESTROY_MS": { "expires_in_version": "40", "kind": "exponential", diff --git a/toolkit/content/tests/chrome/test_bug437844.xul b/toolkit/content/tests/chrome/test_bug437844.xul index bbaf84c873f6..b194b304196a 100644 --- a/toolkit/content/tests/chrome/test_bug437844.xul +++ b/toolkit/content/tests/chrome/test_bug437844.xul @@ -37,7 +37,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=348233