diff --git a/CLOBBER b/CLOBBER index d71a89884b10..fe1c29c2ff52 100644 --- a/CLOBBER +++ b/CLOBBER @@ -19,3 +19,8 @@ # Bug 942231 needs a clobber -- JNI wrappers need to be re-generated. +and +Bug 934646 needs a clobber -- the icon resources previously copied +into $OBJDIR/mobile/android/base/res will conflict with those in +$BRANDING_DIRECTORY/res. + diff --git a/accessible/src/html/HTMLFormControlAccessible.cpp b/accessible/src/html/HTMLFormControlAccessible.cpp index 83e5b8d14660..24359077b637 100644 --- a/accessible/src/html/HTMLFormControlAccessible.cpp +++ b/accessible/src/html/HTMLFormControlAccessible.cpp @@ -14,7 +14,6 @@ #include "States.h" #include "nsContentList.h" -#include "nsCxPusher.h" #include "mozilla/dom/HTMLInputElement.h" #include "nsIAccessibleRelation.h" #include "nsIDOMNSEditableElement.h" @@ -26,6 +25,7 @@ #include "nsISelectionController.h" #include "nsIServiceManager.h" #include "nsITextControlFrame.h" +#include "mozilla/dom/ScriptSettings.h" #include "mozilla/FloatingPoint.h" #include "mozilla/Preferences.h" @@ -468,8 +468,7 @@ HTMLTextFieldAccessible::GetEditor() const // nsGenericHTMLElement::GetEditor has a security check. // Make sure we're not restricted by the permissions of // whatever script is currently running. - nsCxPusher pusher; - pusher.PushNull(); + mozilla::dom::AutoSystemCaller asc; nsCOMPtr editor; editableElt->GetEditor(getter_AddRefs(editor)); diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 6eaefe5f55b7..073e1333333b 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,4 +1,4 @@ { - "revision": "8a192fcf2927a866574996b4895426213e01a325", + "revision": "5bfef5faac50d14e055f642a44ed2df8483fb2fe", "repo_path": "/integration/gaia-central" } diff --git a/b2g/confvars.sh b/b2g/confvars.sh index 8c1cd65a385d..c00edb5f67ad 100644 --- a/b2g/confvars.sh +++ b/b2g/confvars.sh @@ -57,7 +57,7 @@ MOZ_PLACES= MOZ_B2G=1 if test "$OS_TARGET" = "Android"; then -MOZ_NUWA_PROCESS=0 +MOZ_NUWA_PROCESS= fi MOZ_FOLD_LIBS=1 diff --git a/browser/devtools/commandline/test/browser_cmd_screenshot.js b/browser/devtools/commandline/test/browser_cmd_screenshot.js index f3f8463b1b88..3265fc3b15b3 100644 --- a/browser/devtools/commandline/test/browser_cmd_screenshot.js +++ b/browser/devtools/commandline/test/browser_cmd_screenshot.js @@ -170,9 +170,7 @@ function addWindow(windowOptions, callback) { let win = OpenBrowserWindow(windowOptions); - let onLoad = function() { - win.removeEventListener("load", onLoad, false); - + whenDelayedStartupFinished(win, function() { // Would like to get rid of this executeSoon, but without it the url // (TEST_URI) provided in addTabWithToolbarRunTests hasn't loaded executeSoon(function() { @@ -187,9 +185,7 @@ function addWindow(windowOptions, callback) { deferred.reject(ex); } }); - }; - - win.addEventListener("load", onLoad, false); + }); return deferred.promise; } diff --git a/browser/devtools/commandline/test/head.js b/browser/devtools/commandline/test/head.js index c7e71fea3bd6..40762de18dd1 100644 --- a/browser/devtools/commandline/test/head.js +++ b/browser/devtools/commandline/test/head.js @@ -10,6 +10,15 @@ let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/")); Services.scriptloader.loadSubScript(testDir + "/helpers.js", this); Services.scriptloader.loadSubScript(testDir + "/mockCommands.js", this); +function whenDelayedStartupFinished(aWindow, aCallback) { + Services.obs.addObserver(function observer(aSubject, aTopic) { + if (aWindow == aSubject) { + Services.obs.removeObserver(observer, aTopic); + executeSoon(aCallback); + } + }, "browser-delayed-startup-finished", false); +} + /** * Force GC on shutdown, because it seems that GCLI can outrun the garbage * collector in some situations, which causes test failures in later tests diff --git a/browser/metro/shell/commandexecutehandler/Makefile.in b/browser/metro/shell/commandexecutehandler/Makefile.in index 2f8e55fbe4a2..4436b3fdc767 100644 --- a/browser/metro/shell/commandexecutehandler/Makefile.in +++ b/browser/metro/shell/commandexecutehandler/Makefile.in @@ -2,8 +2,6 @@ # 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/. -NO_PROFILE_GUIDED_OPTIMIZE = 1 - include $(topsrcdir)/config/config.mk DIST_PROGRAM = CommandExecuteHandler$(BIN_SUFFIX) diff --git a/browser/metro/shell/commandexecutehandler/moz.build b/browser/metro/shell/commandexecutehandler/moz.build index 89a07cf1bcba..cd2ac7912ba7 100644 --- a/browser/metro/shell/commandexecutehandler/moz.build +++ b/browser/metro/shell/commandexecutehandler/moz.build @@ -16,3 +16,5 @@ DIST_SUBDIR = '' for var in ('UNICODE', '_UNICODE', 'NS_NO_XPCOM'): DEFINES[var] = True + +NO_PGO = True diff --git a/browser/metro/shell/linktool/Makefile.in b/browser/metro/shell/linktool/Makefile.in index 786ce3c83c72..7795c9f4c3af 100644 --- a/browser/metro/shell/linktool/Makefile.in +++ b/browser/metro/shell/linktool/Makefile.in @@ -2,8 +2,6 @@ # 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/. -NO_PROFILE_GUIDED_OPTIMIZE = 1 - include $(topsrcdir)/config/config.mk OS_LIBS = \ diff --git a/browser/metro/shell/linktool/moz.build b/browser/metro/shell/linktool/moz.build index 7d93db9dc856..5f8d38ec6bcc 100644 --- a/browser/metro/shell/linktool/moz.build +++ b/browser/metro/shell/linktool/moz.build @@ -14,3 +14,5 @@ DIST_SUBDIR = 'metro/install' for var in ('UNICODE', '_UNICODE'): DEFINES[var] = True + +NO_PGO = True diff --git a/browser/metro/shell/testing/Makefile.in b/browser/metro/shell/testing/Makefile.in index 230a7912fa81..935e997cff70 100644 --- a/browser/metro/shell/testing/Makefile.in +++ b/browser/metro/shell/testing/Makefile.in @@ -9,8 +9,6 @@ USE_STATIC_LIBS = 1 MOZ_GLUE_LDFLAGS = MOZ_GLUE_PROGRAM_LDFLAGS = -NO_PROFILE_GUIDED_OPTIMIZE = 1 - include $(topsrcdir)/config/config.mk OS_LIBS = \ diff --git a/browser/metro/shell/testing/moz.build b/browser/metro/shell/testing/moz.build index fb7c08870f11..ffd44a76d4be 100644 --- a/browser/metro/shell/testing/moz.build +++ b/browser/metro/shell/testing/moz.build @@ -15,3 +15,5 @@ DIST_SUBDIR = '' for var in ('UNICODE', '_UNICODE'): DEFINES[var] = True + +NO_PGO = True diff --git a/build/ConfigStatus.py b/build/ConfigStatus.py index 650e24aeb9cb..b73859798619 100644 --- a/build/ConfigStatus.py +++ b/build/ConfigStatus.py @@ -64,7 +64,9 @@ def config_status(topobjdir='.', topsrcdir='.', help='display verbose output') parser.add_option('-n', dest='not_topobjdir', action='store_true', help='do not consider current directory as top object directory') - (options, args) = parser.parse_args() + parser.add_option('-d', '--diff', action='store_true', + help='print diffs of changed files.') + options, args = parser.parse_args() # Without -n, the current directory is meant to be the top object directory if not options.not_topobjdir: @@ -98,3 +100,7 @@ def config_status(topobjdir='.', topsrcdir='.', for line in summary.summaries(): print(line, file=sys.stderr) + + if options.diff: + for path, diff in sorted(summary.file_diffs.items()): + print(diff) diff --git a/build/docs/index.rst b/build/docs/index.rst index 8a7cb973b782..ee4c1338de52 100644 --- a/build/docs/index.rst +++ b/build/docs/index.rst @@ -1,20 +1,13 @@ -================================== -Mozilla Build System Documentation -================================== - -Overview -======== - -.. toctree:: - :maxdepth: 1 - - glossary +============ +Build System +============ Important Concepts ================== .. toctree:: :maxdepth: 1 + glossary build-overview supported-configurations Mozconfig Files @@ -41,24 +34,3 @@ Mozilla build system. mozbuild/index mozbuild/dumbmake - -Python Packages -=============== - -.. toctree:: - :maxdepth: 2 - - python/codegen - python/makeutils - python/mozbuild - python/mozpack - python/mozversioncontrol - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/build/docs/python/makeutils.rst b/build/docs/python/makeutils.rst deleted file mode 100644 index a99a773e7dd7..000000000000 --- a/build/docs/python/makeutils.rst +++ /dev/null @@ -1,7 +0,0 @@ -makeutils Module -================ - -.. automodule:: makeutils - :members: - :undoc-members: - :show-inheritance: diff --git a/build/docs/python/mozbuild.action.rst b/build/docs/python/mozbuild.action.rst deleted file mode 100644 index 6fcbeb7a944c..000000000000 --- a/build/docs/python/mozbuild.action.rst +++ /dev/null @@ -1,35 +0,0 @@ -action Package -============== - -:mod:`link_deps` Module ------------------------ - -.. automodule:: mozbuild.action.link_deps - :members: - :undoc-members: - :show-inheritance: - -:mod:`process_install_manifest` Module --------------------------------------- - -.. automodule:: mozbuild.action.process_install_manifest - :members: - :undoc-members: - :show-inheritance: - -:mod:`xpccheck` Module ----------------------- - -.. automodule:: mozbuild.action.xpccheck - :members: - :undoc-members: - :show-inheritance: - -:mod:`xpidl-process` Module ---------------------------- - -.. automodule:: mozbuild.action.xpidl-process - :members: - :undoc-members: - :show-inheritance: - diff --git a/build/docs/python/mozbuild.backend.rst b/build/docs/python/mozbuild.backend.rst deleted file mode 100644 index 8572675be545..000000000000 --- a/build/docs/python/mozbuild.backend.rst +++ /dev/null @@ -1,35 +0,0 @@ -backend Package -=============== - -:mod:`base` Module ------------------- - -.. automodule:: mozbuild.backend.base - :members: - :undoc-members: - :show-inheritance: - -:mod:`common` Module --------------------- - -.. automodule:: mozbuild.backend.common - :members: - :undoc-members: - :show-inheritance: - -:mod:`configenvironment` Module -------------------------------- - -.. automodule:: mozbuild.backend.configenvironment - :members: - :undoc-members: - :show-inheritance: - -:mod:`recursivemake` Module ---------------------------- - -.. automodule:: mozbuild.backend.recursivemake - :members: - :undoc-members: - :show-inheritance: - diff --git a/build/docs/python/mozbuild.compilation.rst b/build/docs/python/mozbuild.compilation.rst deleted file mode 100644 index 6870d98715c1..000000000000 --- a/build/docs/python/mozbuild.compilation.rst +++ /dev/null @@ -1,11 +0,0 @@ -compilation Package -=================== - -:mod:`warnings` Module ----------------------- - -.. automodule:: mozbuild.compilation.warnings - :members: - :undoc-members: - :show-inheritance: - diff --git a/build/docs/python/mozbuild.controller.rst b/build/docs/python/mozbuild.controller.rst deleted file mode 100644 index a54af5449b77..000000000000 --- a/build/docs/python/mozbuild.controller.rst +++ /dev/null @@ -1,19 +0,0 @@ -controller Package -================== - -:mod:`building` Module ----------------------- - -.. automodule:: mozbuild.controller.building - :members: - :undoc-members: - :show-inheritance: - -:mod:`clobber` Module ---------------------- - -.. automodule:: mozbuild.controller.clobber - :members: - :undoc-members: - :show-inheritance: - diff --git a/build/docs/python/mozbuild.frontend.rst b/build/docs/python/mozbuild.frontend.rst deleted file mode 100644 index f10e2ccf4def..000000000000 --- a/build/docs/python/mozbuild.frontend.rst +++ /dev/null @@ -1,51 +0,0 @@ -frontend Package -================ - -:mod:`data` Module ------------------- - -.. automodule:: mozbuild.frontend.data - :members: - :undoc-members: - :show-inheritance: - -:mod:`emitter` Module ---------------------- - -.. automodule:: mozbuild.frontend.emitter - :members: - :undoc-members: - :show-inheritance: - -:mod:`mach_commands` Module ---------------------------- - -.. automodule:: mozbuild.frontend.mach_commands - :members: - :undoc-members: - :show-inheritance: - -:mod:`reader` Module --------------------- - -.. automodule:: mozbuild.frontend.reader - :members: - :undoc-members: - :show-inheritance: - -:mod:`sandbox` Module ---------------------- - -.. automodule:: mozbuild.frontend.sandbox - :members: - :undoc-members: - :show-inheritance: - -:mod:`sandbox_symbols` Module ------------------------------ - -.. automodule:: mozbuild.frontend.sandbox_symbols - :members: - :undoc-members: - :show-inheritance: - diff --git a/build/docs/python/mozbuild.rst b/build/docs/python/mozbuild.rst deleted file mode 100644 index 5bb0a4c947a7..000000000000 --- a/build/docs/python/mozbuild.rst +++ /dev/null @@ -1,103 +0,0 @@ -mozbuild Package -================ - -:mod:`base` Module ------------------- - -.. automodule:: mozbuild.base - :members: - :undoc-members: - :show-inheritance: - -:mod:`config` Module --------------------- - -.. automodule:: mozbuild.config - :members: - :undoc-members: - :show-inheritance: - -:mod:`html_build_viewer` Module -------------------------------- - -.. automodule:: mozbuild.html_build_viewer - :members: - :undoc-members: - :show-inheritance: - -:mod:`mach_commands` Module ---------------------------- - -.. automodule:: mozbuild.mach_commands - :members: - :undoc-members: - :show-inheritance: - -:mod:`makeutil` Module ----------------------- - -.. automodule:: mozbuild.makeutil - :members: - :undoc-members: - :show-inheritance: - -:mod:`mozconfig` Module ------------------------ - -.. automodule:: mozbuild.mozconfig - :members: - :undoc-members: - :show-inheritance: - -:mod:`mozinfo` Module ---------------------- - -.. automodule:: mozbuild.mozinfo - :members: - :undoc-members: - :show-inheritance: - -:mod:`pythonutil` Module ------------------------- - -.. automodule:: mozbuild.pythonutil - :members: - :undoc-members: - :show-inheritance: - -:mod:`sphinx` Module --------------------- - -.. automodule:: mozbuild.sphinx - :members: - :undoc-members: - :show-inheritance: - -:mod:`util` Module ------------------- - -.. automodule:: mozbuild.util - :members: - :undoc-members: - :show-inheritance: - -:mod:`virtualenv` Module ------------------------- - -.. automodule:: mozbuild.virtualenv - :members: - :undoc-members: - :show-inheritance: - -Subpackages ------------ - -.. toctree:: - - mozbuild.action - mozbuild.backend - mozbuild.compilation - mozbuild.controller - mozbuild.frontend - mozbuild.test - diff --git a/build/docs/python/mozpack.chrome.rst b/build/docs/python/mozpack.chrome.rst deleted file mode 100644 index a2b0e61edc59..000000000000 --- a/build/docs/python/mozpack.chrome.rst +++ /dev/null @@ -1,19 +0,0 @@ -chrome Package -============== - -:mod:`flags` Module -------------------- - -.. automodule:: mozpack.chrome.flags - :members: - :undoc-members: - :show-inheritance: - -:mod:`manifest` Module ----------------------- - -.. automodule:: mozpack.chrome.manifest - :members: - :undoc-members: - :show-inheritance: - diff --git a/build/docs/python/mozpack.packager.rst b/build/docs/python/mozpack.packager.rst deleted file mode 100644 index e7f5cecde941..000000000000 --- a/build/docs/python/mozpack.packager.rst +++ /dev/null @@ -1,35 +0,0 @@ -packager Package -================ - -:mod:`packager` Package ------------------------ - -.. automodule:: mozpack.packager - :members: - :undoc-members: - :show-inheritance: - -:mod:`formats` Module ---------------------- - -.. automodule:: mozpack.packager.formats - :members: - :undoc-members: - :show-inheritance: - -:mod:`l10n` Module ------------------- - -.. automodule:: mozpack.packager.l10n - :members: - :undoc-members: - :show-inheritance: - -:mod:`unpack` Module --------------------- - -.. automodule:: mozpack.packager.unpack - :members: - :undoc-members: - :show-inheritance: - diff --git a/build/docs/python/mozpack.rst b/build/docs/python/mozpack.rst deleted file mode 100644 index a40d815e86f0..000000000000 --- a/build/docs/python/mozpack.rst +++ /dev/null @@ -1,76 +0,0 @@ -mozpack Package -=============== - -:mod:`copier` Module --------------------- - -.. automodule:: mozpack.copier - :members: - :undoc-members: - :show-inheritance: - -:mod:`errors` Module --------------------- - -.. automodule:: mozpack.errors - :members: - :undoc-members: - :show-inheritance: - -:mod:`executables` Module -------------------------- - -.. automodule:: mozpack.executables - :members: - :undoc-members: - :show-inheritance: - -:mod:`files` Module -------------------- - -.. automodule:: mozpack.files - :members: - :undoc-members: - :show-inheritance: - -:mod:`manifests` Module ------------------------ - -.. automodule:: mozpack.manifests - :members: - :undoc-members: - :show-inheritance: - -:mod:`mozjar` Module --------------------- - -.. automodule:: mozpack.mozjar - :members: - :undoc-members: - :show-inheritance: - -:mod:`path` Module ------------------- - -.. automodule:: mozpack.path - :members: - :undoc-members: - :show-inheritance: - -:mod:`unify` Module -------------------- - -.. automodule:: mozpack.unify - :members: - :undoc-members: - :show-inheritance: - -Subpackages ------------ - -.. toctree:: - - mozpack.chrome - mozpack.packager - mozpack.test - diff --git a/build/docs/python/mozversioncontrol.rst b/build/docs/python/mozversioncontrol.rst deleted file mode 100644 index ab9f2248e51c..000000000000 --- a/build/docs/python/mozversioncontrol.rst +++ /dev/null @@ -1,11 +0,0 @@ -mozversioncontrol Package -========================= - -:mod:`repoupdate` Module ------------------------- - -.. automodule:: mozversioncontrol.repoupdate - :members: - :undoc-members: - :show-inheritance: - diff --git a/build/mach_bootstrap.py b/build/mach_bootstrap.py index 0b84d46d24bd..500646dd1ba2 100644 --- a/build/mach_bootstrap.py +++ b/build/mach_bootstrap.py @@ -75,6 +75,7 @@ MACH_MODULES = [ 'testing/xpcshell/mach_commands.py', 'testing/talos/mach_commands.py', 'testing/xpcshell/mach_commands.py', + 'tools/docs/mach_commands.py', 'tools/mercurial/mach_commands.py', 'tools/mach_commands.py', ] diff --git a/build/mobile/robocop/moz.build b/build/mobile/robocop/moz.build index df3ab1e557d2..7c2a3b798c8f 100644 --- a/build/mobile/robocop/moz.build +++ b/build/mobile/robocop/moz.build @@ -4,8 +4,4 @@ # 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/. -ANDROID_RESFILES = [ - 'res/values/strings.xml', -] - DEFINES['ANDROID_PACKAGE_NAME'] = CONFIG['ANDROID_PACKAGE_NAME'] diff --git a/build/mobile/sutagent/android/fencp/moz.build b/build/mobile/sutagent/android/fencp/moz.build index 835e25a84c14..c271ec3908ce 100644 --- a/build/mobile/sutagent/android/fencp/moz.build +++ b/build/mobile/sutagent/android/fencp/moz.build @@ -3,11 +3,3 @@ # 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/. - -ANDROID_RESFILES = [ - 'res/drawable-hdpi/icon.png', - 'res/drawable-ldpi/icon.png', - 'res/drawable-mdpi/icon.png', - 'res/layout/main.xml', - 'res/values/strings.xml', -] diff --git a/build/mobile/sutagent/android/ffxcp/moz.build b/build/mobile/sutagent/android/ffxcp/moz.build index 835e25a84c14..c271ec3908ce 100644 --- a/build/mobile/sutagent/android/ffxcp/moz.build +++ b/build/mobile/sutagent/android/ffxcp/moz.build @@ -3,11 +3,3 @@ # 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/. - -ANDROID_RESFILES = [ - 'res/drawable-hdpi/icon.png', - 'res/drawable-ldpi/icon.png', - 'res/drawable-mdpi/icon.png', - 'res/layout/main.xml', - 'res/values/strings.xml', -] diff --git a/build/mobile/sutagent/android/moz.build b/build/mobile/sutagent/android/moz.build index 27426490f46d..c271ec3908ce 100644 --- a/build/mobile/sutagent/android/moz.build +++ b/build/mobile/sutagent/android/moz.build @@ -3,13 +3,3 @@ # 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/. - -ANDROID_RESFILES = [ - 'res/drawable/ateamlogo.png', - 'res/drawable/ic_stat_first.png', - 'res/drawable/ic_stat_neterror.png', - 'res/drawable/ic_stat_warning.png', - 'res/drawable/icon.png', - 'res/layout/main.xml', - 'res/values/strings.xml', -] diff --git a/build/mobile/sutagent/android/watcher/moz.build b/build/mobile/sutagent/android/watcher/moz.build index 1794b48751c7..c271ec3908ce 100644 --- a/build/mobile/sutagent/android/watcher/moz.build +++ b/build/mobile/sutagent/android/watcher/moz.build @@ -3,14 +3,3 @@ # 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/. - -ANDROID_RESFILES = [ - 'res/drawable-hdpi/ateamlogo.png', - 'res/drawable-hdpi/icon.png', - 'res/drawable-ldpi/ateamlogo.png', - 'res/drawable-ldpi/icon.png', - 'res/drawable-mdpi/ateamlogo.png', - 'res/drawable-mdpi/icon.png', - 'res/layout/main.xml', - 'res/values/strings.xml', -] diff --git a/build/moz.build b/build/moz.build index d76748b881d7..344a356878e9 100644 --- a/build/moz.build +++ b/build/moz.build @@ -4,6 +4,8 @@ # 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/. +SPHINX_TREES['build'] = 'docs' + if CONFIG['OS_ARCH'] not in ('WINNT', 'OS2'): DIRS += ['unix'] elif CONFIG['OS_ARCH'] == 'WINNT': diff --git a/build/unix/elfhack/Makefile.in b/build/unix/elfhack/Makefile.in index 71db949b934f..cfbbb607bb3f 100644 --- a/build/unix/elfhack/Makefile.in +++ b/build/unix/elfhack/Makefile.in @@ -5,8 +5,6 @@ INTERNAL_TOOLS = 1 -NO_PROFILE_GUIDED_OPTIMIZE = 1 - VPATH += $(topsrcdir)/build OS_CXXFLAGS := $(filter-out -fno-exceptions,$(OS_CXXFLAGS)) -fexceptions diff --git a/build/unix/elfhack/inject/Makefile.in b/build/unix/elfhack/inject/Makefile.in index 79831fcc411b..07357a2b9476 100644 --- a/build/unix/elfhack/inject/Makefile.in +++ b/build/unix/elfhack/inject/Makefile.in @@ -4,7 +4,6 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. INTERNAL_TOOLS = 1 -NO_PROFILE_GUIDED_OPTIMIZE = 1 include $(topsrcdir)/config/rules.mk diff --git a/build/unix/elfhack/inject/moz.build b/build/unix/elfhack/inject/moz.build index b560c8f69b9b..3b069ef337a6 100644 --- a/build/unix/elfhack/inject/moz.build +++ b/build/unix/elfhack/inject/moz.build @@ -18,3 +18,5 @@ GENERATED_SOURCES += [ ] DEFINES['ELFHACK_BUILD'] = True + +NO_PGO = True diff --git a/build/unix/elfhack/moz.build b/build/unix/elfhack/moz.build index 4a96904f2287..b461987a6ae8 100644 --- a/build/unix/elfhack/moz.build +++ b/build/unix/elfhack/moz.build @@ -25,3 +25,5 @@ HOST_SOURCES += [ HOST_PROGRAM = 'elfhack' DEFINES['ELFHACK_BUILD'] = True + +NO_PGO = True diff --git a/build/unix/stdc++compat/Makefile.in b/build/unix/stdc++compat/Makefile.in index dabfae73fc9b..b9ef76d1b6e9 100644 --- a/build/unix/stdc++compat/Makefile.in +++ b/build/unix/stdc++compat/Makefile.in @@ -4,7 +4,6 @@ STL_FLAGS = NO_EXPAND_LIBS = 1 -NO_PROFILE_GUIDED_OPTIMIZE = 1 include $(topsrcdir)/config/rules.mk diff --git a/build/unix/stdc++compat/moz.build b/build/unix/stdc++compat/moz.build index 2f0deb490e89..739879e83b18 100644 --- a/build/unix/stdc++compat/moz.build +++ b/build/unix/stdc++compat/moz.build @@ -15,3 +15,5 @@ if CONFIG['MOZ_LIBSTDCXX_HOST_VERSION']: ] FORCE_STATIC_LIB = True + +NO_PGO = True diff --git a/build/virtualenv_packages.txt b/build/virtualenv_packages.txt index 98003ce2bff1..056423aa3f6e 100644 --- a/build/virtualenv_packages.txt +++ b/build/virtualenv_packages.txt @@ -13,6 +13,7 @@ mock.pth:python/mock-1.0.0 mozilla.pth:build mozilla.pth:config mozilla.pth:xpcom/typelib/xpt/tools +moztreedocs.pth:tools/docs copy:build/buildconfig.py packages.txt:testing/mozbase/packages.txt objdir:build diff --git a/build/win32/Makefile.in b/build/win32/Makefile.in index 00bdae24ef9c..e57bf7454d20 100644 --- a/build/win32/Makefile.in +++ b/build/win32/Makefile.in @@ -2,8 +2,6 @@ # 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/. -NO_PROFILE_GUIDED_OPTIMIZE = 1 - ifdef ENABLE_TESTS USE_STATIC_LIBS = 1 diff --git a/build/win32/moz.build b/build/win32/moz.build index 633dfffec58b..2c94a64b7686 100644 --- a/build/win32/moz.build +++ b/build/win32/moz.build @@ -14,3 +14,5 @@ if CONFIG['ENABLE_TESTS']: SOURCES += [ 'crashinject.cpp', ] + +NO_PGO = True diff --git a/config/config.mk b/config/config.mk index 8534697ec65f..2c1903172a22 100644 --- a/config/config.mk +++ b/config/config.mk @@ -35,7 +35,7 @@ endif # responsibility between Makefile.in and mozbuild files. _MOZBUILD_EXTERNAL_VARIABLES := \ ANDROID_GENERATED_RESFILES \ - ANDROID_RESFILES \ + ANDROID_RES_DIRS \ CMSRCS \ CMMSRCS \ CPP_UNIT_TESTS \ @@ -70,6 +70,7 @@ _MOZBUILD_EXTERNAL_VARIABLES := \ $(NULL) _DEPRECATED_VARIABLES := \ + ANDROID_RESFILES \ MOCHITEST_FILES_PARTS \ MOCHITEST_BROWSER_FILES_PARTS \ SHORT_LIBNAME \ diff --git a/config/makefiles/java-build.mk b/config/makefiles/java-build.mk index 179063b8fd5d..b3f34ab1d033 100644 --- a/config/makefiles/java-build.mk +++ b/config/makefiles/java-build.mk @@ -7,29 +7,6 @@ ifndef INCLUDED_JAVA_BUILD_MK #{ -ifdef ANDROID_RESFILES #{ -ifndef IGNORE_ANDROID_RESFILES #{ -res-dep := .deps-copy-java-res - -GENERATED_DIRS += res -GARBAGE += $(res-dep) - -export:: $(res-dep) - -res-dep-preqs := \ - $(addprefix $(srcdir)/,$(ANDROID_RESFILES)) \ - $(call mkdir_deps,res) \ - $(if $(IS_LANGUAGE_REPACK),FORCE) \ - $(NULL) - -# nop-build: only copy res/ files when needed -$(res-dep): $(res-dep-preqs) - $(call copy_dir,$(srcdir)/res,$(CURDIR)/res) - @$(TOUCH) $@ -endif #} IGNORE_ANDROID_RESFILES -endif #} ANDROID_RESFILES - - ifdef JAVAFILES #{ GENERATED_DIRS += classes @@ -39,7 +16,8 @@ endif #} JAVAFILES ifdef ANDROID_APK_NAME #{ -_ANDROID_RES_FLAG := -S $(or $(ANDROID_RES_DIR),res) +android_res_dirs := $(addprefix $(srcdir)/,$(or $(ANDROID_RES_DIRS),res)) +_ANDROID_RES_FLAG := $(addprefix -S ,$(android_res_dirs)) _ANDROID_ASSETS_FLAG := $(addprefix -A ,$(ANDROID_ASSETS_DIR)) GENERATED_DIRS += classes @@ -57,7 +35,11 @@ classes.dex: $(JAVAFILES) R.java: .aapt.deps $(ANDROID_APK_NAME).ap_: .aapt.deps -.aapt.deps: AndroidManifest.xml $(wildcard $(ANDROID_RES_DIR)) $(wildcard $(ANDROID_ASSETS_DIR)) +# This uses the fact that Android resource directories list all +# resource files one subdirectory below the parent resource directory. +android_res_files := $(wildcard $(addsuffix /*,$(wildcard $(addsuffix /*,$(android_res_dirs))))) + +.aapt.deps: AndroidManifest.xml $(android_res_files) $(wildcard $(ANDROID_ASSETS_DIR)) $(AAPT) package -f -M $< -I $(ANDROID_SDK)/android.jar $(_ANDROID_RES_FLAG) $(_ANDROID_ASSETS_FLAG) \ -J ${@D} \ -F $(ANDROID_APK_NAME).ap_ diff --git a/config/rules.mk b/config/rules.mk index 9f8d932c0888..4db9adab9d7e 100644 --- a/config/rules.mk +++ b/config/rules.mk @@ -642,7 +642,7 @@ tools:: endif ############################################## -ifndef NO_PROFILE_GUIDED_OPTIMIZE +ifneq (1,$(NO_PROFILE_GUIDED_OPTIMIZE)) ifdef MOZ_PROFILE_USE ifeq ($(OS_ARCH)_$(GNU_CC), WINNT_) # When building with PGO, we have to make sure to re-link diff --git a/content/base/public/nsContentUtils.h b/content/base/public/nsContentUtils.h index 1faa290e00db..ba65fea3dd6a 100644 --- a/content/base/public/nsContentUtils.h +++ b/content/base/public/nsContentUtils.h @@ -1545,6 +1545,7 @@ public: static JSContext *GetCurrentJSContext(); static JSContext *GetSafeJSContext(); + static JSContext *GetCurrentJSContextForThread(); static JSContext *GetDefaultJSContextForThread(); /** diff --git a/content/base/src/nsContentUtils.cpp b/content/base/src/nsContentUtils.cpp index 99c402f7b08c..b9690cac6bba 100644 --- a/content/base/src/nsContentUtils.cpp +++ b/content/base/src/nsContentUtils.cpp @@ -5262,6 +5262,17 @@ nsContentUtils::GetDefaultJSContextForThread() } } +/* static */ +JSContext * +nsContentUtils::GetCurrentJSContextForThread() +{ + if (MOZ_LIKELY(NS_IsMainThread())) { + return GetCurrentJSContext(); + } else { + return workers::GetCurrentThreadJSContext(); + } +} + /* static */ nsresult nsContentUtils::ASCIIToLower(nsAString& aStr) diff --git a/content/base/src/nsImageLoadingContent.cpp b/content/base/src/nsImageLoadingContent.cpp index 8cf824e9912f..bf21f5ad3b4f 100644 --- a/content/base/src/nsImageLoadingContent.cpp +++ b/content/base/src/nsImageLoadingContent.cpp @@ -39,7 +39,6 @@ #include "nsIDOMNode.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsLayoutUtils.h" #include "nsIContentPolicy.h" #include "nsEventDispatcher.h" @@ -47,6 +46,7 @@ #include "mozAutoDocUpdate.h" #include "mozilla/dom/Element.h" +#include "mozilla/dom/ScriptSettings.h" #if defined(XP_WIN) // Undefine LoadImage to prevent naming conflict with Windows. @@ -1194,12 +1194,6 @@ nsImageLoadingContent::ClearPendingRequest(nsresult aReason, if (!mPendingRequest) return; - // Push a null JSContext on the stack so that code that runs within - // the below code doesn't think it's being called by JS. See bug - // 604262. - nsCxPusher pusher; - pusher.PushNull(); - // Deregister this image from the refresh driver so it no longer receives // notifications. nsLayoutUtils::DeregisterImageRequest(GetFramePresContext(), mPendingRequest, @@ -1259,11 +1253,6 @@ nsImageLoadingContent::BindToTree(nsIDocument* aDocument, nsIContent* aParent, if (!aDocument) return; - // Push a null JSContext on the stack so that callbacks triggered by the - // below code won't think they're being called from JS. - nsCxPusher pusher; - pusher.PushNull(); - TrackImage(mCurrentRequest); TrackImage(mPendingRequest); @@ -1279,11 +1268,6 @@ nsImageLoadingContent::UnbindFromTree(bool aDeep, bool aNullParent) if (!doc) return; - // Push a null JSContext on the stack so that callbacks triggered by the - // below code won't think they're being called from JS. - nsCxPusher pusher; - pusher.PushNull(); - UntrackImage(mCurrentRequest); UntrackImage(mPendingRequest); diff --git a/content/canvas/src/CanvasImageCache.cpp b/content/canvas/src/CanvasImageCache.cpp index d29e74e55a1c..a072abac9ce9 100644 --- a/content/canvas/src/CanvasImageCache.cpp +++ b/content/canvas/src/CanvasImageCache.cpp @@ -14,10 +14,12 @@ #include "mozilla/dom/HTMLCanvasElement.h" #include "nsContentUtils.h" #include "mozilla/Preferences.h" +#include "mozilla/gfx/2D.h" namespace mozilla { using namespace dom; +using namespace gfx; struct ImageCacheKey { ImageCacheKey(Element* aImage, HTMLCanvasElement* aCanvas) @@ -32,7 +34,7 @@ struct ImageCacheEntryData { , mILC(aOther.mILC) , mCanvas(aOther.mCanvas) , mRequest(aOther.mRequest) - , mSurface(aOther.mSurface) + , mSourceSurface(aOther.mSourceSurface) , mSize(aOther.mSize) {} ImageCacheEntryData(const ImageCacheKey& aKey) @@ -51,7 +53,7 @@ struct ImageCacheEntryData { nsRefPtr mCanvas; // Value nsCOMPtr mRequest; - nsRefPtr mSurface; + RefPtr mSourceSurface; gfxIntSize mSize; nsExpirationState mState; }; @@ -127,7 +129,7 @@ void CanvasImageCache::NotifyDrawImage(Element* aImage, HTMLCanvasElement* aCanvas, imgIRequest* aRequest, - gfxASurface* aSurface, + SourceSurface* aSource, const gfxIntSize& aSize) { if (!gImageCache) { @@ -137,7 +139,7 @@ CanvasImageCache::NotifyDrawImage(Element* aImage, ImageCacheEntry* entry = gImageCache->mCache.PutEntry(ImageCacheKey(aImage, aCanvas)); if (entry) { - if (entry->mData->mSurface) { + if (entry->mData->mSourceSurface) { // We are overwriting an existing entry. gImageCache->mTotal -= entry->mData->SizeInBytes(); gImageCache->RemoveObject(entry->mData); @@ -150,7 +152,7 @@ CanvasImageCache::NotifyDrawImage(Element* aImage, getter_AddRefs(entry->mData->mRequest)); } entry->mData->mILC = ilc; - entry->mData->mSurface = aSurface; + entry->mData->mSourceSurface = aSource; entry->mData->mSize = aSize; gImageCache->mTotal += entry->mData->SizeInBytes(); @@ -164,7 +166,7 @@ CanvasImageCache::NotifyDrawImage(Element* aImage, gImageCache->AgeOneGeneration(); } -gfxASurface* +SourceSurface* CanvasImageCache::Lookup(Element* aImage, HTMLCanvasElement* aCanvas, gfxIntSize* aSize) @@ -184,7 +186,7 @@ CanvasImageCache::Lookup(Element* aImage, gImageCache->MarkUsed(entry->mData); *aSize = entry->mData->mSize; - return entry->mData->mSurface; + return entry->mData->mSourceSurface; } NS_IMPL_ISUPPORTS1(CanvasImageCacheShutdownObserver, nsIObserver) diff --git a/content/canvas/src/CanvasImageCache.h b/content/canvas/src/CanvasImageCache.h index 91ae57ce490d..88b17eaff051 100644 --- a/content/canvas/src/CanvasImageCache.h +++ b/content/canvas/src/CanvasImageCache.h @@ -11,6 +11,9 @@ namespace dom { class Element; class HTMLCanvasElement; } // namespace dom +namespace gfx { +class SourceSurface; +} // namespace gfx } // namespace mozilla class imgIRequest; class gfxASurface; @@ -20,6 +23,7 @@ class gfxASurface; namespace mozilla { class CanvasImageCache { + typedef mozilla::gfx::SourceSurface SourceSurface; public: /** * Notify that image element aImage was (or is about to be) drawn to aCanvas @@ -29,7 +33,7 @@ public: static void NotifyDrawImage(dom::Element* aImage, dom::HTMLCanvasElement* aCanvas, imgIRequest* aRequest, - gfxASurface* aSurface, + SourceSurface* aSource, const gfxIntSize& aSize); /** @@ -38,9 +42,9 @@ public: * (with the same image request) and the returned surface contains the image * data, and the image size will be returned in aSize. */ - static gfxASurface* Lookup(dom::Element* aImage, - dom::HTMLCanvasElement* aCanvas, - gfxIntSize* aSize); + static SourceSurface* Lookup(dom::Element* aImage, + dom::HTMLCanvasElement* aCanvas, + gfxIntSize* aSize); }; } diff --git a/content/canvas/src/CanvasRenderingContext2D.cpp b/content/canvas/src/CanvasRenderingContext2D.cpp index fdf52872a83b..02c6f453031d 100644 --- a/content/canvas/src/CanvasRenderingContext2D.cpp +++ b/content/canvas/src/CanvasRenderingContext2D.cpp @@ -1450,29 +1450,21 @@ CanvasRenderingContext2D::CreatePattern(const HTMLImageOrCanvasOrVideoElement& e htmlElement = &element.GetAsHTMLVideoElement(); } + EnsureTarget(); + // The canvas spec says that createPattern should use the first frame // of animated images nsLayoutUtils::SurfaceFromElementResult res = nsLayoutUtils::SurfaceFromElement(htmlElement, - nsLayoutUtils::SFE_WANT_FIRST_FRAME | nsLayoutUtils::SFE_WANT_NEW_SURFACE); + nsLayoutUtils::SFE_WANT_FIRST_FRAME, mTarget); - if (!res.mSurface) { + if (!res.mSourceSurface) { error.Throw(NS_ERROR_NOT_AVAILABLE); return nullptr; } - // Ignore nullptr cairo surfaces! See bug 666312. - if (!res.mSurface->CairoSurface() || res.mSurface->CairoStatus()) { - error.Throw(NS_ERROR_NOT_AVAILABLE); - return nullptr; - } - - EnsureTarget(); - RefPtr srcSurf = - gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mTarget, res.mSurface); - nsRefPtr pat = - new CanvasPattern(this, srcSurf, repeatMode, res.mPrincipal, + new CanvasPattern(this, res.mSourceSurface, repeatMode, res.mPrincipal, res.mIsWriteOnly, res.mCORSUsed); return pat.forget(); @@ -3084,11 +3076,8 @@ CanvasRenderingContext2D::DrawImage(const HTMLImageOrCanvasOrVideoElement& image element = video; } - gfxASurface* imgsurf = + srcSurf = CanvasImageCache::Lookup(element, mCanvasElement, &imgSize); - if (imgsurf) { - srcSurf = gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mTarget, imgsurf); - } } if (!srcSurf) { @@ -3096,9 +3085,9 @@ CanvasRenderingContext2D::DrawImage(const HTMLImageOrCanvasOrVideoElement& image // of animated images uint32_t sfeFlags = nsLayoutUtils::SFE_WANT_FIRST_FRAME; nsLayoutUtils::SurfaceFromElementResult res = - nsLayoutUtils::SurfaceFromElement(element, sfeFlags); + nsLayoutUtils::SurfaceFromElement(element, sfeFlags, mTarget); - if (!res.mSurface) { + if (!res.mSourceSurface) { // Spec says to silently do nothing if the element is still loading. if (!res.mIsStillLoading) { error.Throw(NS_ERROR_NOT_AVAILABLE); @@ -3106,11 +3095,6 @@ CanvasRenderingContext2D::DrawImage(const HTMLImageOrCanvasOrVideoElement& image return; } - // Ignore cairo surfaces that are bad! See bug 666312. - if (res.mSurface->CairoStatus()) { - return; - } - imgSize = res.mSize; // Scale sw/sh based on aspect ratio @@ -3129,11 +3113,11 @@ CanvasRenderingContext2D::DrawImage(const HTMLImageOrCanvasOrVideoElement& image } if (res.mImageRequest) { - CanvasImageCache::NotifyDrawImage(element, mCanvasElement, - res.mImageRequest, res.mSurface, imgSize); + CanvasImageCache::NotifyDrawImage(element, mCanvasElement, res.mImageRequest, + res.mSourceSurface, imgSize); } - srcSurf = gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(mTarget, res.mSurface); + srcSurf = res.mSourceSurface; } if (optional_argc == 0) { diff --git a/content/canvas/src/WebGLContext.h b/content/canvas/src/WebGLContext.h index 91217f141cdb..f1e9a3476bd2 100644 --- a/content/canvas/src/WebGLContext.h +++ b/content/canvas/src/WebGLContext.h @@ -28,6 +28,7 @@ #include "mozilla/LinkedList.h" #include "mozilla/CheckedInt.h" #include "mozilla/Scoped.h" +#include "mozilla/gfx/2D.h" #ifdef XP_MACOSX #include "ForceDiscreteGPUHelperCGL.h" @@ -420,18 +421,19 @@ public: { if (IsContextLost()) return; - nsRefPtr isurf; + RefPtr data; WebGLTexelFormat srcFormat; nsLayoutUtils::SurfaceFromElementResult res = SurfaceFromElement(elt); - rv = SurfaceFromElementResultToImageSurface(res, getter_AddRefs(isurf), + rv = SurfaceFromElementResultToImageSurface(res, data, &srcFormat); - if (rv.Failed() || !isurf) + if (rv.Failed() || !data) return; - uint32_t byteLength = isurf->Stride() * isurf->Height(); + gfx::IntSize size = data->GetSize(); + uint32_t byteLength = data->Stride() * size.height; return TexImage2D_base(target, level, internalformat, - isurf->Width(), isurf->Height(), isurf->Stride(), - 0, format, type, isurf->Data(), byteLength, + size.width, size.height, data->Stride(), + 0, format, type, data->GetData(), byteLength, -1, srcFormat, mPixelStorePremultiplyAlpha); } void TexParameterf(GLenum target, GLenum pname, GLfloat param) { @@ -459,19 +461,20 @@ public: { if (IsContextLost()) return; - nsRefPtr isurf; + RefPtr data; WebGLTexelFormat srcFormat; nsLayoutUtils::SurfaceFromElementResult res = SurfaceFromElement(elt); - rv = SurfaceFromElementResultToImageSurface(res, getter_AddRefs(isurf), + rv = SurfaceFromElementResultToImageSurface(res, data, &srcFormat); - if (rv.Failed() || !isurf) + if (rv.Failed() || !data) return; - uint32_t byteLength = isurf->Stride() * isurf->Height(); + gfx::IntSize size = data->GetSize(); + uint32_t byteLength = data->Stride() * size.height; return TexSubImage2D_base(target, level, xoffset, yoffset, - isurf->Width(), isurf->Height(), - isurf->Stride(), format, type, - isurf->Data(), byteLength, + size.width, size.height, + data->Stride(), format, type, + data->GetData(), byteLength, -1, srcFormat, mPixelStorePremultiplyAlpha); } @@ -992,7 +995,7 @@ protected: nsLayoutUtils::SurfaceFromElementResult SurfaceFromElement(ElementType* aElement) { MOZ_ASSERT(aElement); uint32_t flags = - nsLayoutUtils::SFE_WANT_IMAGE_SURFACE; + nsLayoutUtils::SFE_WANT_IMAGE_SURFACE; if (mPixelStoreColorspaceConversion == LOCAL_GL_NONE) flags |= nsLayoutUtils::SFE_NO_COLORSPACE_CONVERSION; @@ -1007,7 +1010,7 @@ protected: } nsresult SurfaceFromElementResultToImageSurface(nsLayoutUtils::SurfaceFromElementResult& res, - gfxImageSurface **imageOut, + RefPtr& imageOut, WebGLTexelFormat *format); void CopyTexSubImage2D_base(GLenum target, diff --git a/content/canvas/src/WebGLContextGL.cpp b/content/canvas/src/WebGLContextGL.cpp index e8bdbeec0376..c8eacad7de1f 100644 --- a/content/canvas/src/WebGLContextGL.cpp +++ b/content/canvas/src/WebGLContextGL.cpp @@ -48,6 +48,7 @@ using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::gl; +using namespace mozilla::gfx; static bool BaseTypeAndSizeFromUniformType(GLenum uType, GLenum *baseType, GLint *unitSize); static GLenum InternalFormatForFormatAndType(GLenum format, GLenum type, bool isGLES2); @@ -2640,14 +2641,14 @@ WebGLContext::StencilOpSeparate(GLenum face, GLenum sfail, GLenum dpfail, GLenum nsresult WebGLContext::SurfaceFromElementResultToImageSurface(nsLayoutUtils::SurfaceFromElementResult& res, - gfxImageSurface **imageOut, WebGLTexelFormat *format) + RefPtr& imageOut, WebGLTexelFormat *format) { - *imageOut = nullptr; *format = WebGLTexelFormat::None; - if (!res.mSurface) + if (!res.mSourceSurface) return NS_OK; - if (res.mSurface->GetType() != gfxSurfaceTypeImage) { + RefPtr data = res.mSourceSurface->GetDataSurface(); + if (!data) { // SurfaceFromElement lied! return NS_OK; } @@ -2687,22 +2688,17 @@ WebGLContext::SurfaceFromElementResultToImageSurface(nsLayoutUtils::SurfaceFromE // Notice that there is never a need to mark the WebGL canvas as write-only, since we reject write-only/cross-domain // texture sources in the first place. - gfxImageSurface* surf = static_cast(res.mSurface.get()); - - res.mSurface.forget(); - *imageOut = surf; - - switch (surf->Format()) { - case gfxImageFormatARGB32: + switch (data->GetFormat()) { + case FORMAT_B8G8R8A8: *format = WebGLTexelFormat::BGRA8; // careful, our ARGB means BGRA break; - case gfxImageFormatRGB24: + case FORMAT_B8G8R8X8: *format = WebGLTexelFormat::BGRX8; // careful, our RGB24 is not tightly packed. Whence BGRX8. break; - case gfxImageFormatA8: + case FORMAT_A8: *format = WebGLTexelFormat::A8; break; - case gfxImageFormatRGB16_565: + case FORMAT_R5G6B5: *format = WebGLTexelFormat::RGB565; break; default: @@ -2710,6 +2706,8 @@ WebGLContext::SurfaceFromElementResultToImageSurface(nsLayoutUtils::SurfaceFromE return NS_ERROR_NOT_IMPLEMENTED; } + imageOut = data; + return NS_OK; } diff --git a/content/events/src/nsDOMEventTargetHelper.cpp b/content/events/src/nsDOMEventTargetHelper.cpp index 800c82ab0173..fdebfff23502 100644 --- a/content/events/src/nsDOMEventTargetHelper.cpp +++ b/content/events/src/nsDOMEventTargetHelper.cpp @@ -9,6 +9,7 @@ #include "nsIDocument.h" #include "prprf.h" #include "nsGlobalWindow.h" +#include "ScriptSettings.h" #include "mozilla/Likely.h" using namespace mozilla; @@ -271,10 +272,10 @@ nsDOMEventTargetHelper::SetEventHandler(nsIAtom* aType, const JS::Value& aValue) { nsRefPtr handler; - JSObject* callable; + JS::Rooted callable(aCx); if (aValue.isObject() && JS_ObjectIsCallable(aCx, callable = &aValue.toObject())) { - handler = new EventHandlerNonNull(callable); + handler = new EventHandlerNonNull(callable, mozilla::dom::GetIncumbentGlobal()); } SetEventHandler(aType, EmptyString(), handler); return NS_OK; diff --git a/content/events/src/nsEventListenerManager.cpp b/content/events/src/nsEventListenerManager.cpp index 928b3760a2e0..bcdda331095b 100644 --- a/content/events/src/nsEventListenerManager.cpp +++ b/content/events/src/nsEventListenerManager.cpp @@ -883,19 +883,23 @@ nsEventListenerManager::CompileEventHandlerInternal(nsListenerStruct *aListenerS JS::Rooted scope(cx, listener->GetEventScope()); context->BindCompiledEventHandler(mTarget, scope, handler, &boundHandler); aListenerStruct = nullptr; + // Note - We pass null for aIncumbentGlobal below. We could also pass the + // compilation global, but since the handler is guaranteed to be scripted, + // there's no need to use an override, since the JS engine will always give + // us the right answer. if (!boundHandler) { listener->ForgetHandler(); } else if (listener->EventName() == nsGkAtoms::onerror && win) { nsRefPtr handlerCallback = - new OnErrorEventHandlerNonNull(boundHandler); + new OnErrorEventHandlerNonNull(boundHandler, /* aIncumbentGlobal = */ nullptr); listener->SetHandler(handlerCallback); } else if (listener->EventName() == nsGkAtoms::onbeforeunload && win) { nsRefPtr handlerCallback = - new OnBeforeUnloadEventHandlerNonNull(boundHandler); + new OnBeforeUnloadEventHandlerNonNull(boundHandler, /* aIncumbentGlobal = */ nullptr); listener->SetHandler(handlerCallback); } else { nsRefPtr handlerCallback = - new EventHandlerNonNull(boundHandler); + new EventHandlerNonNull(boundHandler, /* aIncumbentGlobal = */ nullptr); listener->SetHandler(handlerCallback); } } diff --git a/content/events/src/nsEventStateManager.cpp b/content/events/src/nsEventStateManager.cpp index 5901d7ae7c5f..d6b33c7e695b 100644 --- a/content/events/src/nsEventStateManager.cpp +++ b/content/events/src/nsEventStateManager.cpp @@ -2504,11 +2504,11 @@ void nsEventStateManager::DoScrollZoom(nsIFrame *aTargetFrame, int32_t adjustment) { - // Exclude form controls and XUL content. + // Exclude form controls and content in chrome docshells. nsIContent *content = aTargetFrame->GetContent(); if (content && !content->IsNodeOfType(nsINode::eHTML_FORM_CONTROL) && - !content->OwnerDoc()->IsXUL()) + !nsContentUtils::IsInChromeDocshell(content->OwnerDoc())) { // positive adjustment to decrease zoom, negative to increase int32_t change = (adjustment > 0) ? -1 : 1; diff --git a/content/html/content/public/HTMLMediaElement.h b/content/html/content/public/HTMLMediaElement.h index cb7ce243e13f..bca96f99b595 100644 --- a/content/html/content/public/HTMLMediaElement.h +++ b/content/html/content/public/HTMLMediaElement.h @@ -207,10 +207,6 @@ public: virtual VideoFrameContainer* GetVideoFrameContainer() MOZ_FINAL MOZ_OVERRIDE; layers::ImageContainer* GetImageContainer(); - // Called by the video frame to get the print surface, if this is - // a static document and we're not actually playing video - gfxASurface* GetPrintSurface() { return mPrintSurface; } - // Dispatch events using nsGenericHTMLElement::DispatchEvent; virtual nsresult DispatchEvent(const nsAString& aName) MOZ_FINAL MOZ_OVERRIDE; @@ -1013,8 +1009,6 @@ protected: // non-intrinsic value. bool mPreservesPitch; - nsRefPtr mPrintSurface; - // Reference to the source element last returned by GetNextSource(). // This is the child source element which we're trying to load from. nsCOMPtr mSourceLoadCandidate; diff --git a/content/html/content/src/HTMLMediaElement.cpp b/content/html/content/src/HTMLMediaElement.cpp index 18da1c9cd68c..746454824ee6 100644 --- a/content/html/content/src/HTMLMediaElement.cpp +++ b/content/html/content/src/HTMLMediaElement.cpp @@ -3223,11 +3223,6 @@ VideoFrameContainer* HTMLMediaElement::GetVideoFrameContainer() if (mVideoFrameContainer) return mVideoFrameContainer; - // If we have a print surface, this is just a static image so - // no image container is required - if (mPrintSurface) - return nullptr; - // Only video frames need an image container. nsCOMPtr video = do_QueryObject(this); if (!video) @@ -3604,26 +3599,7 @@ HTMLMediaElement::CopyInnerTo(Element* aDest) NS_ENSURE_SUCCESS(rv, rv); if (aDest->OwnerDoc()->IsStaticDocument()) { HTMLMediaElement* dest = static_cast(aDest); - if (mPrintSurface) { - dest->mPrintSurface = mPrintSurface; - dest->mMediaSize = mMediaSize; - } else { - nsIFrame* frame = GetPrimaryFrame(); - Element* element; - if (frame && frame->GetType() == nsGkAtoms::HTMLVideoFrame && - static_cast(frame)->ShouldDisplayPoster()) { - nsIContent* content = static_cast(frame)->GetPosterImage(); - element = content ? content->AsElement() : nullptr; - } else { - element = const_cast(this); - } - - nsLayoutUtils::SurfaceFromElementResult res = - nsLayoutUtils::SurfaceFromElement(element, - nsLayoutUtils::SFE_WANT_NEW_SURFACE); - dest->mPrintSurface = res.mSurface; - dest->mMediaSize = nsIntSize(res.mSize.width, res.mSize.height); - } + dest->mMediaSize = mMediaSize; } return rv; } diff --git a/content/html/content/src/nsTextEditorState.cpp b/content/html/content/src/nsTextEditorState.cpp index 8f23090897d0..5bff5b9bba43 100644 --- a/content/html/content/src/nsTextEditorState.cpp +++ b/content/html/content/src/nsTextEditorState.cpp @@ -38,11 +38,11 @@ #include "mozilla/Selection.h" #include "nsEventListenerManager.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "mozilla/Preferences.h" #include "nsTextNode.h" #include "nsIController.h" #include "mozilla/TextEvents.h" +#include "mozilla/dom/ScriptSettings.h" using namespace mozilla; using namespace mozilla::dom; @@ -1283,13 +1283,12 @@ nsTextEditorState::PrepareEditor(const nsAString *aValue) // What follows is a bit of a hack. The editor uses the public DOM APIs // for its content manipulations, and it causes it to fail some security - // checks deep inside when initializing. So we push a null JSContext - // on the JS stack here to make it clear that we're native code. + // checks deep inside when initializing. So we explictly make it clear that + // we're native code. // Note that any script that's directly trying to access our value // has to be going through some scriptable object to do that and that // already does the relevant security checks. - nsCxPusher pusher; - pusher.PushNull(); + AutoSystemCaller asc; rv = newEditor->Init(domdoc, GetRootNode(), mSelCon, editorFlags); NS_ENSURE_SUCCESS(rv, rv); @@ -1777,9 +1776,8 @@ nsTextEditorState::GetValue(nsAString& aValue, bool aIgnoreWrap) const // XXXbz if we could just get the textContent of our anonymous content (eg // if plaintext editor didn't create
nodes all over), we wouldn't need // this. - { /* Scope for context pusher */ - nsCxPusher pusher; - pusher.PushNull(); + { /* Scope for AutoSystemCaller. */ + AutoSystemCaller asc; mEditor->OutputToString(NS_LITERAL_STRING("text/plain"), flags, aValue); @@ -1857,9 +1855,8 @@ nsTextEditorState::SetValue(const nsAString& aValue, bool aUserInput, // Time to mess with our security context... See comments in GetValue() // for why this is needed. Note that we have to do this up here, because // otherwise SelectAll() will fail. - { /* Scope for context pusher */ - nsCxPusher pusher; - pusher.PushNull(); + { + AutoSystemCaller asc; nsCOMPtr domSel; nsCOMPtr selPriv; diff --git a/content/xbl/src/nsXBLPrototypeHandler.cpp b/content/xbl/src/nsXBLPrototypeHandler.cpp index a54a18bad5f2..7621c60491ac 100644 --- a/content/xbl/src/nsXBLPrototypeHandler.cpp +++ b/content/xbl/src/nsXBLPrototypeHandler.cpp @@ -322,7 +322,7 @@ nsXBLPrototypeHandler::ExecuteHandler(EventTarget* aTarget, } nsRefPtr handlerCallback = - new EventHandlerNonNull(bound); + new EventHandlerNonNull(bound, /* aIncumbentGlobal = */ nullptr); nsEventHandler eventHandler(handlerCallback); diff --git a/db/sqlite3/src/Makefile.in b/db/sqlite3/src/Makefile.in index 54b0ced3de67..6a71cbf17e07 100644 --- a/db/sqlite3/src/Makefile.in +++ b/db/sqlite3/src/Makefile.in @@ -46,11 +46,6 @@ ifeq ($(OS_ARCH),WINNT) MODULE_OPTIMIZE_FLAGS = -O2 endif -# disable PGO for Sun Studio -ifdef SOLARIS_SUNPRO_CC -NO_PROFILE_GUIDED_OPTIMIZE = 1 -endif - include $(topsrcdir)/config/rules.mk # next line allows use of MOZ_OBJDIR in .mozconfig with older gcc on BeOS, maybe others diff --git a/db/sqlite3/src/moz.build b/db/sqlite3/src/moz.build index 962ce496be06..76fb9360d4a8 100644 --- a/db/sqlite3/src/moz.build +++ b/db/sqlite3/src/moz.build @@ -62,3 +62,7 @@ if CONFIG['OS_TARGET'] == 'Android': if CONFIG['OS_ARCH'] == 'WINNT' and CONFIG['MOZ_MEMORY']: DEFINES['HAVE_MALLOC_USABLE_SIZE'] = True DEFINES['SQLITE_WITHOUT_MSIZE'] = True + +# disable PGO for Sun Studio +if CONFIG['SOLARIS_SUNPRO_CC']: + NO_PGO = True diff --git a/dom/base/ScriptSettings.cpp b/dom/base/ScriptSettings.cpp new file mode 100644 index 000000000000..c4ebb9aa888e --- /dev/null +++ b/dom/base/ScriptSettings.cpp @@ -0,0 +1,191 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim: ft=cpp tw=78 sw=2 et ts=2 +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/ThreadLocal.h" +#include "mozilla/Assertions.h" + +#include "jsapi.h" +#include "xpcpublic.h" +#include "nsIGlobalObject.h" +#include "nsIScriptGlobalObject.h" +#include "nsIScriptContext.h" +#include "nsContentUtils.h" +#include "nsTArray.h" + +namespace mozilla { +namespace dom { + +class ScriptSettingsStack; +static mozilla::ThreadLocal sScriptSettingsTLS; + +ScriptSettingsStackEntry ScriptSettingsStackEntry::SystemSingleton; + +class ScriptSettingsStack { +public: + static ScriptSettingsStack& Ref() { + return *sScriptSettingsTLS.get(); + } + ScriptSettingsStack() {}; + + void Push(ScriptSettingsStackEntry* aSettings) { + // The bottom-most entry must always be a candidate entry point. + MOZ_ASSERT_IF(mStack.Length() == 0 || mStack.LastElement()->IsSystemSingleton(), + aSettings->mIsCandidateEntryPoint); + mStack.AppendElement(aSettings); + } + + void PushSystem() { + mStack.AppendElement(&ScriptSettingsStackEntry::SystemSingleton); + } + + void Pop() { + MOZ_ASSERT(mStack.Length() > 0); + mStack.RemoveElementAt(mStack.Length() - 1); + } + + nsIGlobalObject* Incumbent() { + if (!mStack.Length()) { + return nullptr; + } + return mStack.LastElement()->mGlobalObject; + } + + nsIGlobalObject* EntryPoint() { + if (!mStack.Length()) + return nullptr; + for (int i = mStack.Length() - 1; i >= 0; --i) { + if (mStack[i]->mIsCandidateEntryPoint) { + return mStack[i]->mGlobalObject; + } + } + MOZ_ASSUME_UNREACHABLE("Non-empty stack should always have an entry point"); + } + +private: + // These pointers are caller-owned. + nsTArray mStack; +}; + +void +InitScriptSettings() +{ + if (!sScriptSettingsTLS.initialized()) { + bool success = sScriptSettingsTLS.init(); + if (!success) { + MOZ_CRASH(); + } + } + + ScriptSettingsStack* ptr = new ScriptSettingsStack(); + sScriptSettingsTLS.set(ptr); +} + +void DestroyScriptSettings() +{ + ScriptSettingsStack* ptr = sScriptSettingsTLS.get(); + MOZ_ASSERT(ptr); + sScriptSettingsTLS.set(nullptr); + delete ptr; +} + +// Note: When we're ready to expose it, GetEntryGlobal will look similar to +// GetIncumbentGlobal below. + +nsIGlobalObject* +GetIncumbentGlobal() +{ + // We need the current JSContext in order to check the JS for + // scripted frames that may have appeared since anyone last + // manipulated the stack. If it's null, that means that there + // must be no entry point on the stack, and therefore no incumbent + // global either. + JSContext *cx = nsContentUtils::GetCurrentJSContextForThread(); + if (!cx) { + MOZ_ASSERT(ScriptSettingsStack::Ref().EntryPoint() == nullptr); + return nullptr; + } + + // See what the JS engine has to say. If we've got a scripted caller + // override in place, the JS engine will lie to us and pretend that + // there's nothing on the JS stack, which will cause us to check the + // incumbent script stack below. + JS::RootedScript script(cx); + if (JS_DescribeScriptedCaller(cx, &script, nullptr)) { + JS::RootedObject global(cx, JS_GetGlobalFromScript(script)); + MOZ_ASSERT(global); + return xpc::GetNativeForGlobal(global); + } + + // Ok, nothing from the JS engine. Let's use whatever's on the + // explicit stack. + return ScriptSettingsStack::Ref().Incumbent(); +} + +AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject, + bool aIsMainThread, + JSContext* aCx) + : mStack(ScriptSettingsStack::Ref()) + , mEntry(aGlobalObject, /* aCandidate = */ true) +{ + MOZ_ASSERT(aGlobalObject); + if (!aCx) { + // If the caller didn't provide a cx, hunt one down. This isn't exactly + // fast, but the callers that care about performance can pass an explicit + // cx for now. Eventually, the whole cx pushing thing will go away + // entirely. + MOZ_ASSERT(aIsMainThread, "cx is mandatory off-main-thread"); + nsCOMPtr sgo = do_QueryInterface(aGlobalObject); + if (sgo && sgo->GetScriptContext()) { + aCx = sgo->GetScriptContext()->GetNativeContext(); + } + if (!aCx) { + aCx = nsContentUtils::GetSafeJSContext(); + } + } + if (aIsMainThread) { + mCxPusher.Push(aCx); + } + mAc.construct(aCx, aGlobalObject->GetGlobalJSObject()); + mStack.Push(&mEntry); +} + +AutoEntryScript::~AutoEntryScript() +{ + MOZ_ASSERT(mStack.Incumbent() == mEntry.mGlobalObject); + mStack.Pop(); +} + +AutoIncumbentScript::AutoIncumbentScript(nsIGlobalObject* aGlobalObject) + : mStack(ScriptSettingsStack::Ref()) + , mEntry(aGlobalObject, /* aCandidate = */ false) + , mCallerOverride(nsContentUtils::GetCurrentJSContextForThread()) +{ + mStack.Push(&mEntry); +} + +AutoIncumbentScript::~AutoIncumbentScript() +{ + MOZ_ASSERT(mStack.Incumbent() == mEntry.mGlobalObject); + mStack.Pop(); +} + +AutoSystemCaller::AutoSystemCaller(bool aIsMainThread) + : mStack(ScriptSettingsStack::Ref()) +{ + if (aIsMainThread) { + mCxPusher.PushNull(); + } + mStack.PushSystem(); +} + +AutoSystemCaller::~AutoSystemCaller() +{ + mStack.Pop(); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/base/ScriptSettings.h b/dom/base/ScriptSettings.h new file mode 100644 index 000000000000..fd0f44b4e97b --- /dev/null +++ b/dom/base/ScriptSettings.h @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim: ft=cpp tw=78 sw=2 et ts=2 +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Utilities for managing the script settings object stack defined in webapps */ + +#ifndef mozilla_dom_ScriptSettings_h +#define mozilla_dom_ScriptSettings_h + +#include "nsCxPusher.h" +#include "MainThreadUtils.h" +#include "nsIGlobalObject.h" + +#include "mozilla/Maybe.h" + +class nsIGlobalObject; + +namespace mozilla { +namespace dom { + +/* + * System-wide setup/teardown routines. Init and Destroy should be invoked + * once each, at startup and shutdown (respectively). + */ +void InitScriptSettings(); +void DestroyScriptSettings(); + +// Note: We don't yet expose GetEntryGlobal, because in order for it to be +// correct, we first need to replace a bunch of explicit cx pushing in the +// browser with AutoEntryScript. But GetIncumbentGlobal is simpler, because it +// can mostly be inferred from the JS stack. +nsIGlobalObject* GetIncumbentGlobal(); + +class ScriptSettingsStack; +struct ScriptSettingsStackEntry { + nsCOMPtr mGlobalObject; + bool mIsCandidateEntryPoint; + + ScriptSettingsStackEntry(nsIGlobalObject *aGlobal, bool aCandidate) + : mGlobalObject(aGlobal) + , mIsCandidateEntryPoint(aCandidate) + { + MOZ_ASSERT(mGlobalObject); + MOZ_ASSERT(mGlobalObject->GetGlobalJSObject(), + "Must have an actual JS global for the duration on the stack"); + MOZ_ASSERT(JS_IsGlobalObject(mGlobalObject->GetGlobalJSObject()), + "No outer windows allowed"); + } + + ~ScriptSettingsStackEntry() { + // We must have an actual JS global for the entire time this is on the stack. + MOZ_ASSERT_IF(mGlobalObject, mGlobalObject->GetGlobalJSObject()); + } + + bool IsSystemSingleton() { return this == &SystemSingleton; } + static ScriptSettingsStackEntry SystemSingleton; + +private: + ScriptSettingsStackEntry() : mGlobalObject(nullptr) + , mIsCandidateEntryPoint(true) + {} +}; + +/* + * A class that represents a new script entry point. + */ +class AutoEntryScript { +public: + AutoEntryScript(nsIGlobalObject* aGlobalObject, + bool aIsMainThread = NS_IsMainThread(), + // Note: aCx is mandatory off-main-thread. + JSContext* aCx = nullptr); + ~AutoEntryScript(); + +private: + dom::ScriptSettingsStack& mStack; + dom::ScriptSettingsStackEntry mEntry; + nsCxPusher mCxPusher; + mozilla::Maybe mAc; // This can de-Maybe-fy when mCxPusher + // goes away. +}; + +/* + * A class that can be used to force a particular incumbent script on the stack. + */ +class AutoIncumbentScript { +public: + AutoIncumbentScript(nsIGlobalObject* aGlobalObject); + ~AutoIncumbentScript(); +private: + dom::ScriptSettingsStack& mStack; + dom::ScriptSettingsStackEntry mEntry; + JS::AutoHideScriptedCaller mCallerOverride; +}; + +/* + * A class used for C++ to indicate that existing entry and incumbent scripts + * should not apply to anything in scope, and that callees should act as if + * they were invoked "from C++". + */ +class AutoSystemCaller { +public: + AutoSystemCaller(bool aIsMainThread = NS_IsMainThread()); + ~AutoSystemCaller(); +private: + dom::ScriptSettingsStack& mStack; + nsCxPusher mCxPusher; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_ScriptSettings_h diff --git a/dom/base/moz.build b/dom/base/moz.build index 95b3f93d6442..dc8c53494e92 100644 --- a/dom/base/moz.build +++ b/dom/base/moz.build @@ -59,6 +59,7 @@ EXPORTS.mozilla.dom += [ 'MessagePortList.h', 'Navigator.h', 'ScreenOrientation.h', + 'ScriptSettings.h', 'StructuredCloneTags.h', 'URL.h', ] @@ -94,6 +95,7 @@ UNIFIED_SOURCES += [ 'nsWindowMemoryReporter.cpp', 'nsWindowRoot.cpp', 'nsWrapperCache.cpp', + 'ScriptSettings.cpp', 'URL.cpp', 'WindowNamedPropertiesHandler.cpp', ] diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp index 2be72a77ff0e..7edc40bf2dbd 100644 --- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -93,6 +93,7 @@ using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::layers; using namespace mozilla::widget; +using namespace mozilla::gfx; class gfxContext; @@ -1385,8 +1386,8 @@ nsDOMWindowUtils::NodesFromRect(float aX, float aY, aIgnoreRootScrollFrame, aFlushLayout, aReturn); } -static already_AddRefed -CanvasToImageSurface(nsIDOMHTMLCanvasElement* aCanvas) +static TemporaryRef +CanvasToDataSourceSurface(nsIDOMHTMLCanvasElement* aCanvas) { nsCOMPtr node = do_QueryInterface(aCanvas); if (!node) { @@ -1397,9 +1398,8 @@ CanvasToImageSurface(nsIDOMHTMLCanvasElement* aCanvas) "An nsINode that implements nsIDOMHTMLCanvasElement should " "be an element."); nsLayoutUtils::SurfaceFromElementResult result = - nsLayoutUtils::SurfaceFromElement(node->AsElement(), - nsLayoutUtils::SFE_WANT_IMAGE_SURFACE); - return result.mSurface.forget().downcast(); + nsLayoutUtils::SurfaceFromElement(node->AsElement()); + return result.mSourceSurface->GetDataSurface(); } NS_IMETHODIMP @@ -1417,8 +1417,8 @@ nsDOMWindowUtils::CompareCanvases(nsIDOMHTMLCanvasElement *aCanvas1, retVal == nullptr) return NS_ERROR_FAILURE; - nsRefPtr img1 = CanvasToImageSurface(aCanvas1); - nsRefPtr img2 = CanvasToImageSurface(aCanvas2); + RefPtr img1 = CanvasToDataSourceSurface(aCanvas1); + RefPtr img2 = CanvasToDataSourceSurface(aCanvas2); if (img1 == nullptr || img2 == nullptr || img1->GetSize() != img2->GetSize() || @@ -1426,12 +1426,12 @@ nsDOMWindowUtils::CompareCanvases(nsIDOMHTMLCanvasElement *aCanvas1, return NS_ERROR_FAILURE; int v; - gfxIntSize size = img1->GetSize(); + IntSize size = img1->GetSize(); uint32_t stride = img1->Stride(); // we can optimize for the common all-pass case if (stride == (uint32_t) size.width * 4) { - v = memcmp(img1->Data(), img2->Data(), size.width * size.height * 4); + v = memcmp(img1->GetData(), img2->GetData(), size.width * size.height * 4); if (v == 0) { if (aMaxDifference) *aMaxDifference = 0; @@ -1444,8 +1444,8 @@ nsDOMWindowUtils::CompareCanvases(nsIDOMHTMLCanvasElement *aCanvas1, uint32_t different = 0; for (int j = 0; j < size.height; j++) { - unsigned char *p1 = img1->Data() + j*stride; - unsigned char *p2 = img2->Data() + j*stride; + unsigned char *p1 = img1->GetData() + j*stride; + unsigned char *p2 = img2->GetData() + j*stride; v = memcmp(p1, p2, stride); if (v) { diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 193a68646169..b8bf96e9298a 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -43,6 +43,7 @@ #include "nsReadableUtils.h" #include "nsDOMClassInfo.h" #include "nsJSEnvironment.h" +#include "ScriptSettings.h" #include "mozilla/Preferences.h" #include "mozilla/Likely.h" @@ -209,6 +210,7 @@ #include "mozilla/dom/WindowBinding.h" #include "nsITabChild.h" #include "nsIDOMMediaQueryList.h" +#include "mozilla/dom/ScriptSettings.h" #ifdef MOZ_WEBSPEECH #include "mozilla/dom/SpeechSynthesis.h" @@ -4944,8 +4946,9 @@ nsGlobalWindow::RequestAnimationFrame(const JS::Value& aCallback, return NS_ERROR_INVALID_ARG; } + JS::Rooted callbackObj(cx, &aCallback.toObject()); nsRefPtr callback = - new FrameRequestCallback(&aCallback.toObject()); + new FrameRequestCallback(callbackObj, GetIncumbentGlobal()); ErrorResult rv; *aHandle = RequestAnimationFrame(*callback, rv); @@ -11211,18 +11214,18 @@ nsGlobalWindow::OpenInternal(const nsAString& aUrl, const nsAString& aName, aDialog, aNavigate, argv, getter_AddRefs(domReturn)); } else { - // Push a null JSContext here so that the window watcher won't screw us + // Force a system caller here so that the window watcher won't screw us // up. We do NOT want this case looking at the JS context on the stack // when searching. Compare comments on // nsIDOMWindow::OpenWindow and nsIWindowWatcher::OpenWindow. // Note: Because nsWindowWatcher is so broken, it's actually important - // that we don't push a null cx here, because that screws it up when it - // tries to compute the caller principal to associate with dialog + // that we don't force a system caller here, because that screws it up + // when it tries to compute the caller principal to associate with dialog // arguments. That whole setup just really needs to be rewritten. :-( - nsCxPusher pusher; + Maybe asc; if (!aContentModal) { - pusher.PushNull(); + asc.construct(); } @@ -13229,10 +13232,10 @@ nsGlobalWindow::DisableNetworkEvent(uint32_t aType) NS_IMETHODIMP nsGlobalWindow::SetOn##name_(JSContext *cx, \ const JS::Value &v) { \ nsRefPtr handler; \ - JSObject *callable; \ + JS::Rooted callable(cx); \ if (v.isObject() && \ JS_ObjectIsCallable(cx, callable = &v.toObject())) { \ - handler = new EventHandlerNonNull(callable); \ + handler = new EventHandlerNonNull(callable, GetIncumbentGlobal()); \ } \ SetOn##name_(handler); \ return NS_OK; \ @@ -13259,10 +13262,10 @@ nsGlobalWindow::DisableNetworkEvent(uint32_t aType) } \ \ nsRefPtr handler; \ - JSObject *callable; \ + JS::Rooted callable(cx); \ if (v.isObject() && \ JS_ObjectIsCallable(cx, callable = &v.toObject())) { \ - handler = new OnErrorEventHandlerNonNull(callable); \ + handler = new OnErrorEventHandlerNonNull(callable, GetIncumbentGlobal()); \ } \ elm->SetEventHandler(handler); \ return NS_OK; \ @@ -13290,10 +13293,10 @@ nsGlobalWindow::DisableNetworkEvent(uint32_t aType) } \ \ nsRefPtr handler; \ - JSObject *callable; \ + JS::Rooted callable(cx); \ if (v.isObject() && \ JS_ObjectIsCallable(cx, callable = &v.toObject())) { \ - handler = new OnBeforeUnloadEventHandlerNonNull(callable); \ + handler = new OnBeforeUnloadEventHandlerNonNull(callable, GetIncumbentGlobal()); \ } \ elm->SetEventHandler(handler); \ return NS_OK; \ diff --git a/dom/base/nsJSTimeoutHandler.cpp b/dom/base/nsJSTimeoutHandler.cpp index 8fa5d843813f..57448916e172 100644 --- a/dom/base/nsJSTimeoutHandler.cpp +++ b/dom/base/nsJSTimeoutHandler.cpp @@ -360,7 +360,7 @@ nsJSScriptTimeoutHandler::Init(nsGlobalWindow *aWindow, bool *aIsInterval, mozilla::HoldJSObjects(this); - mFunction = new Function(funobj); + mFunction = new Function(funobj, GetIncumbentGlobal()); // Create our arg array. argc is the number of arguments passed // to setTimeout or setInterval; the first two are our callback diff --git a/dom/bindings/BindingUtils.cpp b/dom/bindings/BindingUtils.cpp index df3de176fcda..6dce37c1ed1b 100644 --- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -27,6 +27,7 @@ #include "nsPrintfCString.h" #include "prprf.h" +#include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/DOMError.h" #include "mozilla/dom/DOMErrorBinding.h" #include "mozilla/dom/HTMLObjectElement.h" @@ -2015,12 +2016,12 @@ ConstructJSImplementation(JSContext* aCx, const char* aContractId, return nullptr; } - // Make sure to have nothing on the JS context stack while creating and + // Make sure to divorce ourselves from the calling JS while creating and // initializing the object, so exceptions from that will get reported // properly, since those are never exceptions that a spec wants to be thrown. - { // Scope for the nsCxPusher - nsCxPusher pusher; - pusher.PushNull(); + { + AutoSystemCaller asc; + // Get the XPCOM component containing the JS implementation. nsCOMPtr implISupports = do_CreateInstance(aContractId); if (!implISupports) { diff --git a/dom/bindings/CallbackFunction.h b/dom/bindings/CallbackFunction.h index 0ad0892bcc58..63a48af53744 100644 --- a/dom/bindings/CallbackFunction.h +++ b/dom/bindings/CallbackFunction.h @@ -25,8 +25,9 @@ namespace dom { class CallbackFunction : public CallbackObject { public: - explicit CallbackFunction(JSObject* aCallable) - : CallbackObject(aCallable) + explicit CallbackFunction(JS::Handle aCallable, + nsIGlobalObject* aIncumbentGlobal) + : CallbackObject(aCallable, aIncumbentGlobal) { MOZ_ASSERT(JS_ObjectIsCallable(nullptr, mCallback)); } diff --git a/dom/bindings/CallbackInterface.h b/dom/bindings/CallbackInterface.h index 574d4243f7a3..f6f36057851a 100644 --- a/dom/bindings/CallbackInterface.h +++ b/dom/bindings/CallbackInterface.h @@ -24,8 +24,9 @@ namespace dom { class CallbackInterface : public CallbackObject { public: - explicit CallbackInterface(JSObject* aCallback) - : CallbackObject(aCallback) + explicit CallbackInterface(JS::Handle aCallback, + nsIGlobalObject *aIncumbentGlobal) + : CallbackObject(aCallback, aIncumbentGlobal) { } diff --git a/dom/bindings/CallbackObject.cpp b/dom/bindings/CallbackObject.cpp index b64aff5d5b1d..325135e0e754 100644 --- a/dom/bindings/CallbackObject.cpp +++ b/dom/bindings/CallbackObject.cpp @@ -19,6 +19,7 @@ #include "xpcprivate.h" #include "WorkerPrivate.h" #include "nsGlobalWindow.h" +#include "WorkerScope.h" namespace mozilla { namespace dom { @@ -35,15 +36,17 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(CallbackObject) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CallbackObject) tmp->DropCallback(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncumbentGlobal) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CallbackObject) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncumbentGlobal) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CallbackObject) NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCallback) NS_IMPL_CYCLE_COLLECTION_TRACE_END -CallbackObject::CallSetup::CallSetup(JS::Handle aCallback, +CallbackObject::CallSetup::CallSetup(CallbackObject* aCallback, ErrorResult& aRv, ExceptionHandling aExceptionHandling, JSCompartment* aCompartment) @@ -63,8 +66,9 @@ CallbackObject::CallSetup::CallSetup(JS::Handle aCallback, // callable. // First, find the real underlying callback. - JSObject* realCallback = js::UncheckedUnwrap(aCallback); + JSObject* realCallback = js::UncheckedUnwrap(aCallback->CallbackPreserveColor()); JSContext* cx = nullptr; + nsIGlobalObject* globalObject = nullptr; if (mIsMainThread) { // Now get the global and JSContext for this callback. @@ -85,18 +89,33 @@ CallbackObject::CallSetup::CallSetup(JS::Handle aCallback, // This happens - Removing it causes // test_bug293235.xul to go orange. : nsContentUtils::GetSafeJSContext(); + globalObject = win; } else { - // No DOM Window. Use the SafeJSContext. + // No DOM Window. Store the global and use the SafeJSContext. + JSObject* glob = js::GetGlobalForObjectCrossCompartment(realCallback); + globalObject = xpc::GetNativeForGlobal(glob); + MOZ_ASSERT(globalObject); cx = nsContentUtils::GetSafeJSContext(); } - - // Make sure our JSContext is pushed on the stack. - mCxPusher.Push(cx); } else { cx = workers::GetCurrentThreadJSContext(); + globalObject = workers::GetCurrentThreadWorkerPrivate()->GlobalScope(); } - // Unmark the callable, and stick it in a Rooted before it can go gray again. + // Bail out if there's no useful global. This seems to happen intermittently + // on gaia-ui tests, probably because nsInProcessTabChildGlobal is returning + // null in some kind of teardown state. + if (!globalObject->GetGlobalJSObject()) { + return; + } + + mAutoEntryScript.construct(globalObject, mIsMainThread, cx); + if (aCallback->IncumbentGlobalOrNull()) { + mAutoIncumbentScript.construct(aCallback->IncumbentGlobalOrNull()); + } + + // Unmark the callable (by invoking Callback() and not the CallbackPreserveColor() + // variant), and stick it in a Rooted before it can go gray again. // Nothing before us in this function can trigger a CC, so it's safe to wait // until here it do the unmark. This allows us to order the following two // operations _after_ the Push() above, which lets us take advantage of the @@ -104,15 +123,14 @@ CallbackObject::CallSetup::CallSetup(JS::Handle aCallback, // // We can do this even though we're not in the right compartment yet, because // Rooted<> does not care about compartments. - JS::ExposeObjectToActiveJS(aCallback); - mRootedCallable.construct(cx, aCallback); + mRootedCallable.construct(cx, aCallback->Callback()); if (mIsMainThread) { // Check that it's ok to run this callback at all. - // Make sure to unwrap aCallback before passing it in to get the global of - // the callback object, not the wrapper. + // Make sure to use realCallback to get the global of the callback object, + // not the wrapper. bool allowed = nsContentUtils::GetSecurityManager()-> - ScriptAllowed(js::GetGlobalForObjectCrossCompartment(js::UncheckedUnwrap(aCallback))); + ScriptAllowed(js::GetGlobalForObjectCrossCompartment(realCallback)); if (!allowed) { return; @@ -120,7 +138,11 @@ CallbackObject::CallSetup::CallSetup(JS::Handle aCallback, } // Enter the compartment of our callback, so we can actually work with it. - mAc.construct(cx, aCallback); + // + // Note that if the callback is a wrapper, this will not be the same + // compartment that we ended up in with mAutoEntryScript above, because the + // entry point is based off of the unwrapped callback (realCallback). + mAc.construct(cx, mRootedCallable.ref()); // And now we're ready to go. mCx = cx; @@ -194,17 +216,11 @@ CallbackObject::CallSetup::~CallSetup() // But be careful: it might not have been constructed at all! mAc.destroyIfConstructed(); - // XXXbz For that matter why do we need to manually call ScriptEvaluated at - // all? nsCxPusher::Pop will do that nowadays if !mScriptIsRunning, so the - // concerns from bug 295983 don't seem relevant anymore. Do we want to make - // sure it's still called when !mScriptIsRunning? I guess play it safe for - // now and do what CallEventHandler did, which is call always. - - // Popping an nsCxPusher is safe even if it never got pushed. - mCxPusher.Pop(); + mAutoIncumbentScript.destroyIfConstructed(); + mAutoEntryScript.destroyIfConstructed(); // It is important that this is the last thing we do, after leaving the - // compartment and popping the context. + // compartment and undoing all our entry/incumbent script changes if (mIsMainThread) { nsContentUtils::LeaveMicroTask(); } diff --git a/dom/bindings/CallbackObject.h b/dom/bindings/CallbackObject.h index 854286784194..8511d6849f16 100644 --- a/dom/bindings/CallbackObject.h +++ b/dom/bindings/CallbackObject.h @@ -24,8 +24,8 @@ #include "mozilla/Assertions.h" #include "mozilla/ErrorResult.h" #include "mozilla/HoldDropJSObjects.h" +#include "mozilla/dom/ScriptSettings.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" #include "nsWrapperCache.h" #include "nsJSEnvironment.h" #include "xpcpublic.h" @@ -45,9 +45,13 @@ public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CallbackObject) - explicit CallbackObject(JSObject* aCallback) + // The caller may pass a global object which will act as an override for the + // incumbent script settings object when the callback is invoked (overriding + // the entry point computed from aCallback). If no override is required, the + // caller should pass null. + explicit CallbackObject(JS::Handle aCallback, nsIGlobalObject *aIncumbentGlobal) { - Init(aCallback); + Init(aCallback, aIncumbentGlobal); } virtual ~CallbackObject() @@ -76,6 +80,11 @@ public: return JS::Handle::fromMarkedLocation(mCallback.address()); } + nsIGlobalObject* IncumbentGlobalOrNull() const + { + return mIncumbentGlobal; + } + enum ExceptionHandling { // Report any exception and don't throw it to the caller code. eReportExceptions, @@ -90,17 +99,19 @@ public: protected: explicit CallbackObject(CallbackObject* aCallbackObject) { - Init(aCallbackObject->mCallback); + Init(aCallbackObject->mCallback, aCallbackObject->mIncumbentGlobal); } private: - inline void Init(JSObject* aCallback) + inline void Init(JSObject* aCallback, nsIGlobalObject* aIncumbentGlobal) { MOZ_ASSERT(aCallback && !mCallback); // Set mCallback before we hold, on the off chance that a GC could somehow // happen in there... (which would be pretty odd, granted). mCallback = aCallback; mozilla::HoldJSObjects(this); + + mIncumbentGlobal = aIncumbentGlobal; } CallbackObject(const CallbackObject&) MOZ_DELETE; @@ -116,6 +127,7 @@ protected: } JS::Heap mCallback; + nsCOMPtr mIncumbentGlobal; class MOZ_STACK_CLASS CallSetup { @@ -128,7 +140,7 @@ protected: public: // If aExceptionHandling == eRethrowContentExceptions then aCompartment // needs to be set to the caller's compartment. - CallSetup(JS::Handle aCallable, ErrorResult& aRv, + CallSetup(CallbackObject* aCallback, ErrorResult& aRv, ExceptionHandling aExceptionHandling, JSCompartment* aCompartment = nullptr); ~CallSetup(); @@ -152,17 +164,17 @@ protected: JSCompartment* mCompartment; // And now members whose construction/destruction order we need to control. - - nsCxPusher mCxPusher; + Maybe mAutoEntryScript; + Maybe mAutoIncumbentScript; // Constructed the rooter within the scope of mCxPusher above, so that it's // always within a request during its lifetime. Maybe > mRootedCallable; // Can't construct a JSAutoCompartment without a JSContext either. Also, - // Put mAc after mCxPusher so that we exit the compartment before we pop the - // JSContext. Though in practice we'll often manually order those two - // things. + // Put mAc after mAutoEntryScript so that we exit the compartment before + // we pop the JSContext. Though in practice we'll often manually order + // those two things. Maybe mAc; // An ErrorResult to possibly re-throw exceptions on and whether @@ -336,28 +348,7 @@ public: nsRefPtr callback = GetWebIDLCallback(); return callback.forget(); } - - XPCOMCallbackT* callback = GetXPCOMCallback(); - if (!callback) { - return nullptr; - } - - nsCOMPtr wrappedJS = do_QueryInterface(callback); - if (!wrappedJS) { - return nullptr; - } - - AutoSafeJSContext cx; - - JS::Rooted obj(cx, wrappedJS->GetJSObject()); - if (!obj) { - return nullptr; - } - - JSAutoCompartment ac(cx, obj); - - nsRefPtr newCallback = new WebIDLCallbackT(obj); - return newCallback.forget(); + return nullptr; } private: diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index 88eb729aed8d..d9d1ef26ee68 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -3396,7 +3396,10 @@ for (uint32_t i = 0; i < length; ++i) { else: declType = CGGeneric("OwningNonNull<%s>" % name) conversion = ( - "${declName} = new %s(&${val}.toObject());\n" % name) + "{ // Scope for tempRoot\n" + " JS::Rooted tempRoot(cx, &${val}.toObject());\n" + " ${declName} = new %s(tempRoot, mozilla::dom::GetIncumbentGlobal());\n" + "}" % name) template = wrapObjectTemplate(conversion, type, "${declName} = nullptr", @@ -3729,7 +3732,10 @@ for (uint32_t i = 0; i < length; ++i) { else: declType = CGGeneric("OwningNonNull<%s>" % name) conversion = ( - " ${declName} = new %s(&${val}.toObject());\n" % name) + "{ // Scope for tempRoot\n" + " JS::Rooted tempRoot(cx, &${val}.toObject());\n" + " ${declName} = new %s(tempRoot, mozilla::dom::GetIncumbentGlobal());\n" + "}\n" % name) if allowTreatNonCallableAsNull and type.treatNonCallableAsNull(): haveCallable = "JS_ObjectIsCallable(cx, &${val}.toObject())" @@ -10549,7 +10555,7 @@ class CGJSImplClass(CGBindingImplClass): decorators = "MOZ_FINAL" destructor = None - baseConstructors=["mImpl(new %s(aJSImplObject))" % jsImplName(descriptor.name), + baseConstructors=["mImpl(new %s(aJSImplObject, /* aIncumbentGlobal = */ nullptr))" % jsImplName(descriptor.name), "mParent(aParent)"] parentInterface = descriptor.interface.parent while parentInterface: @@ -10676,12 +10682,12 @@ class CGCallback(CGClass): def getConstructors(self): return [ClassConstructor( - [Argument("JSObject*", "aCallback")], + [Argument("JS::Handle", "aCallback"), Argument("nsIGlobalObject*", "aIncumbentGlobal")], bodyInHeader=True, visibility="public", explicit=True, baseConstructors=[ - "%s(aCallback)" % self.baseName + "%s(aCallback, aIncumbentGlobal)" % self.baseName, ])] def getMethodImpls(self, method): @@ -10706,7 +10712,7 @@ class CGCallback(CGClass): argsWithoutThis = list(args) args.insert(0, Argument("const T&", "thisObj")) - setupCall = ("CallSetup s(CallbackPreserveColor(), aRv, aExceptionHandling);\n" + setupCall = ("CallSetup s(this, aRv, aExceptionHandling);\n" "if (!s.GetContext()) {\n" " aRv.Throw(NS_ERROR_UNEXPECTED);\n" " return${errorReturn};\n" @@ -10996,7 +11002,7 @@ class CallbackMember(CGNativeMember): if self.needThisHandling: # It's been done for us already return "" - callSetup = "CallSetup s(CallbackPreserveColor(), aRv" + callSetup = "CallSetup s(this, aRv" if self.rethrowContentException: # getArgs doesn't add the aExceptionHandling argument but does add # aCompartment for us. diff --git a/dom/ipc/TabChild.cpp b/dom/ipc/TabChild.cpp index 31e2257d6342..2ce740927236 100644 --- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -2531,6 +2531,7 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TabChildGlobal) NS_INTERFACE_MAP_ENTRY(nsISyncMessageSender) NS_INTERFACE_MAP_ENTRY(nsIContentFrameMessageManager) NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal) + NS_INTERFACE_MAP_ENTRY(nsIGlobalObject) NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(ContentFrameMessageManager) NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper) @@ -2595,10 +2596,20 @@ TabChildGlobal::GetJSContextForEventHandlers() return nsContentUtils::GetSafeJSContext(); } -nsIPrincipal* +nsIPrincipal* TabChildGlobal::GetPrincipal() { if (!mTabChild) return nullptr; return mTabChild->GetPrincipal(); } + +JSObject* +TabChildGlobal::GetGlobalJSObject() +{ + NS_ENSURE_TRUE(mTabChild, nullptr); + nsCOMPtr ref = mTabChild->GetGlobal(); + NS_ENSURE_TRUE(ref, nullptr); + return ref->GetJSObject(); +} + diff --git a/dom/ipc/TabChild.h b/dom/ipc/TabChild.h index a8463cf6e728..7323dce45383 100644 --- a/dom/ipc/TabChild.h +++ b/dom/ipc/TabChild.h @@ -51,7 +51,8 @@ class ClonedMessageData; class TabChildGlobal : public nsDOMEventTargetHelper, public nsIContentFrameMessageManager, - public nsIScriptObjectPrincipal + public nsIScriptObjectPrincipal, + public nsIGlobalObject { public: TabChildGlobal(TabChild* aTabChild); @@ -127,6 +128,7 @@ public: virtual JSContext* GetJSContextForEventHandlers() MOZ_OVERRIDE; virtual nsIPrincipal* GetPrincipal() MOZ_OVERRIDE; + virtual JSObject* GetGlobalJSObject() MOZ_OVERRIDE; nsCOMPtr mMessageManager; TabChild* mTabChild; diff --git a/dom/locales/en-US/chrome/layout/css.properties b/dom/locales/en-US/chrome/layout/css.properties index 8b86170c7cab..e6a59a565363 100644 --- a/dom/locales/en-US/chrome/layout/css.properties +++ b/dom/locales/en-US/chrome/layout/css.properties @@ -144,3 +144,14 @@ PEExpectedNoneOrURL=Expected 'none' or URL but found '%1$S'. PEExpectedNoneOrURLOrFilterFunction=Expected 'none', URL, or filter function but found '%1$S'. PEExpectedNonnegativeNP=Expected non-negative number or percentage. PEFilterFunctionArgumentsParsingError=Error in parsing arguments for filter function. +PEVariableEOF=variable +PEVariableEmpty=Expected variable value but found '%1$S'. +PEValueWithVariablesParsingError=Error in parsing value for '%1$S' after substituting variables. +PEValueWithVariablesFallbackInherit=Falling back to 'inherit'. +PEValueWithVariablesFallbackInitial=Falling back to 'initial'. +PEInvalidVariableReference=Property contained reference to invalid variable. +PEInvalidVariableTokenFallback=Found invalid token '%1$S' at top level of variable reference fallback. +PEExpectedVariableNameEOF=identifier for variable name +PEExpectedVariableName=Expected identifier for variable name but found '%1$S'. +PEExpectedVariableFallback=Expected variable reference fallback after ','. +PEExpectedVariableCommaOrCloseParen=Expected ',' or ')' after variable name in variable reference but found '%1$S'. diff --git a/dom/mobilemessage/src/gonk/MobileMessageDB.jsm b/dom/mobilemessage/src/gonk/MobileMessageDB.jsm new file mode 100644 index 000000000000..55cea192e61f --- /dev/null +++ b/dom/mobilemessage/src/gonk/MobileMessageDB.jsm @@ -0,0 +1,3213 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/PhoneNumberUtils.jsm"); +Cu.importGlobalProperties(["indexedDB"]); + +var RIL = {}; +Cu.import("resource://gre/modules/ril_consts.js", RIL); + +const RIL_GETMESSAGESCURSOR_CID = + Components.ID("{484d1ad8-840e-4782-9dc4-9ebc4d914937}"); +const RIL_GETTHREADSCURSOR_CID = + Components.ID("{95ee7c3e-d6f2-4ec4-ade5-0c453c036d35}"); + +const DEBUG = false; +const DISABLE_MMS_GROUPING_FOR_RECEIVING = true; + + +const DB_VERSION = 20; +const MESSAGE_STORE_NAME = "sms"; +const THREAD_STORE_NAME = "thread"; +const PARTICIPANT_STORE_NAME = "participant"; +const MOST_RECENT_STORE_NAME = "most-recent"; + +const DELIVERY_SENDING = "sending"; +const DELIVERY_SENT = "sent"; +const DELIVERY_RECEIVED = "received"; +const DELIVERY_NOT_DOWNLOADED = "not-downloaded"; +const DELIVERY_ERROR = "error"; + +const DELIVERY_STATUS_NOT_APPLICABLE = "not-applicable"; +const DELIVERY_STATUS_SUCCESS = "success"; +const DELIVERY_STATUS_PENDING = "pending"; +const DELIVERY_STATUS_ERROR = "error"; + +const MESSAGE_CLASS_NORMAL = "normal"; + +const FILTER_TIMESTAMP = "timestamp"; +const FILTER_NUMBERS = "numbers"; +const FILTER_DELIVERY = "delivery"; +const FILTER_READ = "read"; + +// We can´t create an IDBKeyCursor with a boolean, so we need to use numbers +// instead. +const FILTER_READ_UNREAD = 0; +const FILTER_READ_READ = 1; + +const READ_ONLY = "readonly"; +const READ_WRITE = "readwrite"; +const PREV = "prev"; +const NEXT = "next"; + +const COLLECT_ID_END = 0; +const COLLECT_ID_ERROR = -1; +const COLLECT_TIMESTAMP_UNUSED = 0; + +XPCOMUtils.defineLazyServiceGetter(this, "gMobileMessageService", + "@mozilla.org/mobilemessage/mobilemessageservice;1", + "nsIMobileMessageService"); + +XPCOMUtils.defineLazyServiceGetter(this, "gMMSService", + "@mozilla.org/mms/rilmmsservice;1", + "nsIMmsService"); + +XPCOMUtils.defineLazyGetter(this, "MMS", function () { + let MMS = {}; + Cu.import("resource://gre/modules/MmsPduHelper.jsm", MMS); + return MMS; +}); + +/** + * MobileMessageDB + */ +this.MobileMessageDB = function() {}; +MobileMessageDB.prototype = { + dbName: null, + dbVersion: null, + + /** + * Cache the DB here. + */ + db: null, + + /** + * Last sms/mms object store key value in the database. + */ + lastMessageId: 0, + + /** + * Prepare the database. This may include opening the database and upgrading + * it to the latest schema version. + * + * @param callback + * Function that takes an error and db argument. It is called when + * the database is ready to use or if an error occurs while preparing + * the database. + * + * @return (via callback) a database ready for use. + */ + ensureDB: function ensureDB(callback) { + if (this.db) { + if (DEBUG) debug("ensureDB: already have a database, returning early."); + callback(null, this.db); + return; + } + + let self = this; + function gotDB(db) { + self.db = db; + callback(null, db); + } + + let request = indexedDB.open(this.dbName, this.dbVersion); + request.onsuccess = function (event) { + if (DEBUG) debug("Opened database:", self.dbName, self.dbVersion); + gotDB(event.target.result); + }; + request.onupgradeneeded = function (event) { + if (DEBUG) { + debug("Database needs upgrade:", self.dbName, + event.oldVersion, event.newVersion); + debug("Correct new database version:", event.newVersion == self.dbVersion); + } + + let db = event.target.result; + + let currentVersion = event.oldVersion; + + function update(currentVersion) { + let next = update.bind(self, currentVersion + 1); + + switch (currentVersion) { + case 0: + if (DEBUG) debug("New database"); + self.createSchema(db, next); + break; + case 1: + if (DEBUG) debug("Upgrade to version 2. Including `read` index"); + self.upgradeSchema(event.target.transaction, next); + break; + case 2: + if (DEBUG) debug("Upgrade to version 3. Fix existing entries."); + self.upgradeSchema2(event.target.transaction, next); + break; + case 3: + if (DEBUG) debug("Upgrade to version 4. Add quick threads view."); + self.upgradeSchema3(db, event.target.transaction, next); + break; + case 4: + if (DEBUG) debug("Upgrade to version 5. Populate quick threads view."); + self.upgradeSchema4(event.target.transaction, next); + break; + case 5: + if (DEBUG) debug("Upgrade to version 6. Use PhonenumberJS."); + self.upgradeSchema5(event.target.transaction, next); + break; + case 6: + if (DEBUG) debug("Upgrade to version 7. Use multiple entry indexes."); + self.upgradeSchema6(event.target.transaction, next); + break; + case 7: + if (DEBUG) debug("Upgrade to version 8. Add participant/thread stores."); + self.upgradeSchema7(db, event.target.transaction, next); + break; + case 8: + if (DEBUG) debug("Upgrade to version 9. Add transactionId index for incoming MMS."); + self.upgradeSchema8(event.target.transaction, next); + break; + case 9: + if (DEBUG) debug("Upgrade to version 10. Upgrade type if it's not existing."); + self.upgradeSchema9(event.target.transaction, next); + break; + case 10: + if (DEBUG) debug("Upgrade to version 11. Add last message type into threadRecord."); + self.upgradeSchema10(event.target.transaction, next); + break; + case 11: + if (DEBUG) debug("Upgrade to version 12. Add envelopeId index for outgoing MMS."); + self.upgradeSchema11(event.target.transaction, next); + break; + case 12: + if (DEBUG) debug("Upgrade to version 13. Replaced deliveryStatus by deliveryInfo."); + self.upgradeSchema12(event.target.transaction, next); + break; + case 13: + if (DEBUG) debug("Upgrade to version 14. Fix the wrong participants."); + self.upgradeSchema13(event.target.transaction, next); + break; + case 14: + if (DEBUG) debug("Upgrade to version 15. Add deliveryTimestamp."); + self.upgradeSchema14(event.target.transaction, next); + break; + case 15: + if (DEBUG) debug("Upgrade to version 16. Add ICC ID for each message."); + self.upgradeSchema15(event.target.transaction, next); + break; + case 16: + if (DEBUG) debug("Upgrade to version 17. Add isReadReportSent for incoming MMS."); + self.upgradeSchema16(event.target.transaction, next); + break; + case 17: + if (DEBUG) debug("Upgrade to version 18. Add last message subject into threadRecord."); + self.upgradeSchema17(event.target.transaction, next); + break; + case 18: + if (DEBUG) debug("Upgrade to version 19. Add pid for incoming SMS."); + self.upgradeSchema18(event.target.transaction, next); + break; + case 19: + if (DEBUG) debug("Upgrade to version 20. Add readStatus and readTimestamp."); + self.upgradeSchema19(event.target.transaction, next); + break; + case 20: + // This will need to be moved for each new version + if (DEBUG) debug("Upgrade finished."); + break; + default: + event.target.transaction.abort(); + callback("Old database version: " + event.oldVersion, null); + break; + } + } + + update(currentVersion); + }; + request.onerror = function (event) { + //TODO look at event.target.Code and change error constant accordingly + callback("Error opening database!", null); + }; + request.onblocked = function (event) { + callback("Opening database request is blocked.", null); + }; + }, + + /** + * Start a new transaction. + * + * @param txn_type + * Type of transaction (e.g. READ_WRITE) + * @param callback + * Function to call when the transaction is available. It will + * be invoked with the transaction and opened object stores. + * @param storeNames + * Names of the stores to open. + */ + newTxn: function newTxn(txn_type, callback, storeNames) { + if (!storeNames) { + storeNames = [MESSAGE_STORE_NAME]; + } + if (DEBUG) debug("Opening transaction for object stores: " + storeNames); + this.ensureDB(function (error, db) { + if (error) { + if (DEBUG) debug("Could not open database: " + error); + callback(error); + return; + } + let txn = db.transaction(storeNames, txn_type); + if (DEBUG) debug("Started transaction " + txn + " of type " + txn_type); + if (DEBUG) { + txn.oncomplete = function oncomplete(event) { + debug("Transaction " + txn + " completed."); + }; + txn.onerror = function onerror(event) { + //TODO check event.target.errorCode and show an appropiate error + // message according to it. + debug("Error occurred during transaction: " + event.target.errorCode); + }; + } + let stores; + if (storeNames.length == 1) { + if (DEBUG) debug("Retrieving object store " + storeNames[0]); + stores = txn.objectStore(storeNames[0]); + } else { + stores = []; + for each (let storeName in storeNames) { + if (DEBUG) debug("Retrieving object store " + storeName); + stores.push(txn.objectStore(storeName)); + } + } + callback(null, txn, stores); + }); + }, + + /** + * Initialize this MobileMessageDB. + * + * @param aDbName + * A string name for that database. + * @param aDbVersion + * The version that mmdb should upgrade to. 0 for the lastest version. + * @param aCallback + * A function when either the initialization transaction is completed + * or any error occurs. Should take only one argument -- null when + * initialized with success or the error object otherwise. + */ + init: function init(aDbName, aDbVersion, aCallback) { + this.dbName = aDbName; + this.dbVersion = aDbVersion || DB_VERSION; + + let self = this; + this.newTxn(READ_ONLY, function(error, txn, messageStore){ + if (error) { + if (aCallback) { + aCallback(error); + } + return; + } + + if (aCallback) { + txn.oncomplete = function() { + aCallback(null); + }; + } + + // In order to get the highest key value, we open a key cursor in reverse + // order and get only the first pointed value. + let request = messageStore.openCursor(null, PREV); + request.onsuccess = function onsuccess(event) { + let cursor = event.target.result; + if (!cursor) { + if (DEBUG) { + debug("Could not get the last key from mobile message database. " + + "Probably empty database"); + } + return; + } + self.lastMessageId = cursor.key || 0; + if (DEBUG) debug("Last assigned message ID was " + self.lastMessageId); + }; + request.onerror = function onerror(event) { + if (DEBUG) { + debug("Could not get the last key from mobile message database " + + event.target.errorCode); + } + }; + }); + }, + + close: function close() { + if (!this.db) { + return; + } + + this.db.close(); + this.db = null; + this.lastMessageId = 0; + }, + + /** + * Sometimes user might reboot or remove battery while sending/receiving + * message. This is function set the status of message records to error. + */ + updatePendingTransactionToError: + function updatePendingTransactionToError(aError) { + if (aError) { + return; + } + + this.newTxn(READ_WRITE, function (error, txn, messageStore) { + if (error) { + return; + } + + let deliveryIndex = messageStore.index("delivery"); + + // Set all 'delivery: sending' records to 'delivery: error' and 'deliveryStatus: + // error'. + let keyRange = IDBKeyRange.bound([DELIVERY_SENDING, 0], [DELIVERY_SENDING, ""]); + let cursorRequestSending = deliveryIndex.openCursor(keyRange); + cursorRequestSending.onsuccess = function(event) { + let messageCursor = event.target.result; + if (!messageCursor) { + return; + } + + let messageRecord = messageCursor.value; + + // Set delivery to error. + messageRecord.delivery = DELIVERY_ERROR; + messageRecord.deliveryIndex = [DELIVERY_ERROR, messageRecord.timestamp]; + + if (messageRecord.type == "sms") { + messageRecord.deliveryStatus = DELIVERY_STATUS_ERROR; + } else { + // Set delivery status to error. + for (let i = 0; i < messageRecord.deliveryInfo.length; i++) { + messageRecord.deliveryInfo[i].deliveryStatus = DELIVERY_STATUS_ERROR; + } + } + + messageCursor.update(messageRecord); + messageCursor.continue(); + }; + + // Set all 'delivery: not-downloaded' and 'deliveryStatus: pending' + // records to 'delivery: not-downloaded' and 'deliveryStatus: error'. + keyRange = IDBKeyRange.bound([DELIVERY_NOT_DOWNLOADED, 0], [DELIVERY_NOT_DOWNLOADED, ""]); + let cursorRequestNotDownloaded = deliveryIndex.openCursor(keyRange); + cursorRequestNotDownloaded.onsuccess = function(event) { + let messageCursor = event.target.result; + if (!messageCursor) { + return; + } + + let messageRecord = messageCursor.value; + + // We have no "not-downloaded" SMS messages. + if (messageRecord.type == "sms") { + messageCursor.continue(); + return; + } + + // Set delivery status to error. + let deliveryInfo = messageRecord.deliveryInfo; + if (deliveryInfo.length == 1 && + deliveryInfo[0].deliveryStatus == DELIVERY_STATUS_PENDING) { + deliveryInfo[0].deliveryStatus = DELIVERY_STATUS_ERROR; + } + + messageCursor.update(messageRecord); + messageCursor.continue(); + }; + }); + }, + + /** + * Create the initial database schema. + * + * TODO need to worry about number normalization somewhere... + * TODO full text search on body??? + */ + createSchema: function createSchema(db, next) { + // This messageStore holds the main mobile message data. + let messageStore = db.createObjectStore(MESSAGE_STORE_NAME, { keyPath: "id" }); + messageStore.createIndex("timestamp", "timestamp", { unique: false }); + if (DEBUG) debug("Created object stores and indexes"); + next(); + }, + + /** + * Upgrade to the corresponding database schema version. + */ + upgradeSchema: function upgradeSchema(transaction, next) { + let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); + messageStore.createIndex("read", "read", { unique: false }); + next(); + }, + + upgradeSchema2: function upgradeSchema2(transaction, next) { + let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); + messageStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + if (!cursor) { + next(); + return; + } + + let messageRecord = cursor.value; + messageRecord.messageClass = MESSAGE_CLASS_NORMAL; + messageRecord.deliveryStatus = DELIVERY_STATUS_NOT_APPLICABLE; + cursor.update(messageRecord); + cursor.continue(); + }; + }, + + upgradeSchema3: function upgradeSchema3(db, transaction, next) { + // Delete redundant "id" index. + let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); + if (messageStore.indexNames.contains("id")) { + messageStore.deleteIndex("id"); + } + + /** + * This mostRecentStore can be used to quickly construct a thread view of + * the mobile message database. Each entry looks like this: + * + * { senderOrReceiver: (primary key), + * id: , + * timestamp: , + * body: , + * unreadCount: } + * + */ + let mostRecentStore = db.createObjectStore(MOST_RECENT_STORE_NAME, + { keyPath: "senderOrReceiver" }); + mostRecentStore.createIndex("timestamp", "timestamp"); + next(); + }, + + upgradeSchema4: function upgradeSchema4(transaction, next) { + let threads = {}; + let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); + let mostRecentStore = transaction.objectStore(MOST_RECENT_STORE_NAME); + + messageStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + if (!cursor) { + for (let thread in threads) { + mostRecentStore.put(threads[thread]); + } + next(); + return; + } + + let messageRecord = cursor.value; + let contact = messageRecord.sender || messageRecord.receiver; + + if (contact in threads) { + let thread = threads[contact]; + if (!messageRecord.read) { + thread.unreadCount++; + } + if (messageRecord.timestamp > thread.timestamp) { + thread.id = messageRecord.id; + thread.body = messageRecord.body; + thread.timestamp = messageRecord.timestamp; + } + } else { + threads[contact] = { + senderOrReceiver: contact, + id: messageRecord.id, + timestamp: messageRecord.timestamp, + body: messageRecord.body, + unreadCount: messageRecord.read ? 0 : 1 + }; + } + cursor.continue(); + }; + }, + + upgradeSchema5: function upgradeSchema5(transaction, next) { + // Don't perform any upgrade. See Bug 819560. + next(); + }, + + upgradeSchema6: function upgradeSchema6(transaction, next) { + let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); + + // Delete "delivery" index. + if (messageStore.indexNames.contains("delivery")) { + messageStore.deleteIndex("delivery"); + } + // Delete "sender" index. + if (messageStore.indexNames.contains("sender")) { + messageStore.deleteIndex("sender"); + } + // Delete "receiver" index. + if (messageStore.indexNames.contains("receiver")) { + messageStore.deleteIndex("receiver"); + } + // Delete "read" index. + if (messageStore.indexNames.contains("read")) { + messageStore.deleteIndex("read"); + } + + // Create new "delivery", "number" and "read" indexes. + messageStore.createIndex("delivery", "deliveryIndex"); + messageStore.createIndex("number", "numberIndex", { multiEntry: true }); + messageStore.createIndex("read", "readIndex"); + + // Populate new "deliverIndex", "numberIndex" and "readIndex" attributes. + messageStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + if (!cursor) { + next(); + return; + } + + let messageRecord = cursor.value; + let timestamp = messageRecord.timestamp; + messageRecord.deliveryIndex = [messageRecord.delivery, timestamp]; + messageRecord.numberIndex = [ + [messageRecord.sender, timestamp], + [messageRecord.receiver, timestamp] + ]; + messageRecord.readIndex = [messageRecord.read, timestamp]; + cursor.update(messageRecord); + cursor.continue(); + }; + }, + + /** + * Add participant/thread stores. + * + * The message store now saves original phone numbers/addresses input from + * content to message records. No normalization is made. + * + * For filtering messages by phone numbers, it first looks up corresponding + * participant IDs from participant table and fetch message records with + * matching keys defined in per record "participantIds" field. + * + * For message threading, messages with the same participant ID array are put + * in the same thread. So updating "unreadCount", "lastMessageId" and + * "lastTimestamp" are through the "threadId" carried by per message record. + * Fetching threads list is now simply walking through the thread sotre. The + * "mostRecentStore" is dropped. + */ + upgradeSchema7: function upgradeSchema7(db, transaction, next) { + /** + * This "participant" object store keeps mappings of multiple phone numbers + * of the same recipient to an integer participant id. Each entry looks + * like: + * + * { id: (primary key), + * addresses: } + */ + let participantStore = db.createObjectStore(PARTICIPANT_STORE_NAME, + { keyPath: "id", + autoIncrement: true }); + participantStore.createIndex("addresses", "addresses", { multiEntry: true }); + + /** + * This "threads" object store keeps mappings from an integer thread id to + * ids of the participants of that message thread. Each entry looks like: + * + * { id: (primary key), + * participantIds: , + * participantAddresses: , + * lastMessageId: , + * lastTimestamp: , + * subject: , + * unreadCount: } + * + */ + let threadStore = db.createObjectStore(THREAD_STORE_NAME, + { keyPath: "id", + autoIncrement: true }); + threadStore.createIndex("participantIds", "participantIds"); + threadStore.createIndex("lastTimestamp", "lastTimestamp"); + + /** + * Replace "numberIndex" with "participantIdsIndex" and create an additional + * "threadId". "numberIndex" will be removed later. + */ + let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); + messageStore.createIndex("threadId", "threadIdIndex"); + messageStore.createIndex("participantIds", "participantIdsIndex", + { multiEntry: true }); + + // Now populate participantStore & threadStore. + let mostRecentStore = transaction.objectStore(MOST_RECENT_STORE_NAME); + let self = this; + let mostRecentRequest = mostRecentStore.openCursor(); + mostRecentRequest.onsuccess = function(event) { + let mostRecentCursor = event.target.result; + if (!mostRecentCursor) { + db.deleteObjectStore(MOST_RECENT_STORE_NAME); + + // No longer need the "number" index in messageStore, use + // "participantIds" index instead. + messageStore.deleteIndex("number"); + next(); + return; + } + + let mostRecentRecord = mostRecentCursor.value; + + // Each entry in mostRecentStore is supposed to be a unique thread, so we + // retrieve the records out and insert its "senderOrReceiver" column as a + // new record in participantStore. + let number = mostRecentRecord.senderOrReceiver; + self.findParticipantRecordByAddress(participantStore, number, true, + function (participantRecord) { + // Also create a new record in threadStore. + let threadRecord = { + participantIds: [participantRecord.id], + participantAddresses: [number], + lastMessageId: mostRecentRecord.id, + lastTimestamp: mostRecentRecord.timestamp, + subject: mostRecentRecord.body, + unreadCount: mostRecentRecord.unreadCount, + }; + let addThreadRequest = threadStore.add(threadRecord); + addThreadRequest.onsuccess = function (event) { + threadRecord.id = event.target.result; + + let numberRange = IDBKeyRange.bound([number, 0], [number, ""]); + let messageRequest = messageStore.index("number") + .openCursor(numberRange, NEXT); + messageRequest.onsuccess = function (event) { + let messageCursor = event.target.result; + if (!messageCursor) { + // No more message records, check next most recent record. + mostRecentCursor.continue(); + return; + } + + let messageRecord = messageCursor.value; + // Check whether the message really belongs to this thread. + let matchSenderOrReceiver = false; + if (messageRecord.delivery == DELIVERY_RECEIVED) { + if (messageRecord.sender == number) { + matchSenderOrReceiver = true; + } + } else if (messageRecord.receiver == number) { + matchSenderOrReceiver = true; + } + if (!matchSenderOrReceiver) { + // Check next message record. + messageCursor.continue(); + return; + } + + messageRecord.threadId = threadRecord.id; + messageRecord.threadIdIndex = [threadRecord.id, + messageRecord.timestamp]; + messageRecord.participantIdsIndex = [ + [participantRecord.id, messageRecord.timestamp] + ]; + messageCursor.update(messageRecord); + // Check next message record. + messageCursor.continue(); + }; + messageRequest.onerror = function () { + // Error in fetching message records, check next most recent record. + mostRecentCursor.continue(); + }; + }; + addThreadRequest.onerror = function () { + // Error in fetching message records, check next most recent record. + mostRecentCursor.continue(); + }; + }); + }; + }, + + /** + * Add transactionId index for MMS. + */ + upgradeSchema8: function upgradeSchema8(transaction, next) { + let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); + + // Delete "transactionId" index. + if (messageStore.indexNames.contains("transactionId")) { + messageStore.deleteIndex("transactionId"); + } + + // Create new "transactionId" indexes. + messageStore.createIndex("transactionId", "transactionIdIndex", { unique: true }); + + // Populate new "transactionIdIndex" attributes. + messageStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + if (!cursor) { + next(); + return; + } + + let messageRecord = cursor.value; + if ("mms" == messageRecord.type && + (DELIVERY_NOT_DOWNLOADED == messageRecord.delivery || + DELIVERY_RECEIVED == messageRecord.delivery)) { + messageRecord.transactionIdIndex = + messageRecord.headers["x-mms-transaction-id"]; + cursor.update(messageRecord); + } + cursor.continue(); + }; + }, + + upgradeSchema9: function upgradeSchema9(transaction, next) { + let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); + + // Update type attributes. + messageStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + if (!cursor) { + next(); + return; + } + + let messageRecord = cursor.value; + if (messageRecord.type == undefined) { + messageRecord.type = "sms"; + cursor.update(messageRecord); + } + cursor.continue(); + }; + }, + + upgradeSchema10: function upgradeSchema10(transaction, next) { + let threadStore = transaction.objectStore(THREAD_STORE_NAME); + + // Add 'lastMessageType' to each thread record. + threadStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + if (!cursor) { + next(); + return; + } + + let threadRecord = cursor.value; + let lastMessageId = threadRecord.lastMessageId; + let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); + let request = messageStore.mozGetAll(lastMessageId); + + request.onsuccess = function onsuccess() { + let messageRecord = request.result[0]; + if (!messageRecord) { + if (DEBUG) debug("Message ID " + lastMessageId + " not found"); + return; + } + if (messageRecord.id != lastMessageId) { + if (DEBUG) { + debug("Requested message ID (" + lastMessageId + ") is different from" + + " the one we got"); + } + return; + } + threadRecord.lastMessageType = messageRecord.type; + cursor.update(threadRecord); + cursor.continue(); + }; + + request.onerror = function onerror(event) { + if (DEBUG) { + if (event.target) { + debug("Caught error on transaction", event.target.errorCode); + } + } + cursor.continue(); + }; + }; + }, + + /** + * Add envelopeId index for MMS. + */ + upgradeSchema11: function upgradeSchema11(transaction, next) { + let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); + + // Delete "envelopeId" index. + if (messageStore.indexNames.contains("envelopeId")) { + messageStore.deleteIndex("envelopeId"); + } + + // Create new "envelopeId" indexes. + messageStore.createIndex("envelopeId", "envelopeIdIndex", { unique: true }); + + // Populate new "envelopeIdIndex" attributes. + messageStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + if (!cursor) { + next(); + return; + } + + let messageRecord = cursor.value; + if (messageRecord.type == "mms" && + messageRecord.delivery == DELIVERY_SENT) { + messageRecord.envelopeIdIndex = messageRecord.headers["message-id"]; + cursor.update(messageRecord); + } + cursor.continue(); + }; + }, + + /** + * Replace deliveryStatus by deliveryInfo. + */ + upgradeSchema12: function upgradeSchema12(transaction, next) { + let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); + + messageStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + if (!cursor) { + next(); + return; + } + + let messageRecord = cursor.value; + if (messageRecord.type == "mms") { + messageRecord.deliveryInfo = []; + + if (messageRecord.deliveryStatus.length == 1 && + (messageRecord.delivery == DELIVERY_NOT_DOWNLOADED || + messageRecord.delivery == DELIVERY_RECEIVED)) { + messageRecord.deliveryInfo.push({ + receiver: null, + deliveryStatus: messageRecord.deliveryStatus[0] }); + } else { + for (let i = 0; i < messageRecord.deliveryStatus.length; i++) { + messageRecord.deliveryInfo.push({ + receiver: messageRecord.receivers[i], + deliveryStatus: messageRecord.deliveryStatus[i] }); + } + } + delete messageRecord.deliveryStatus; + cursor.update(messageRecord); + } + cursor.continue(); + }; + }, + + /** + * Fix the wrong participants. + */ + upgradeSchema13: function upgradeSchema13(transaction, next) { + let participantStore = transaction.objectStore(PARTICIPANT_STORE_NAME); + let threadStore = transaction.objectStore(THREAD_STORE_NAME); + let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); + let self = this; + + let isInvalid = function (participantRecord) { + let entries = []; + for (let addr of participantRecord.addresses) { + entries.push({ + normalized: addr, + parsed: PhoneNumberUtils.parseWithMCC(addr, null) + }) + } + for (let ix = 0 ; ix < entries.length - 1; ix++) { + let entry1 = entries[ix]; + for (let iy = ix + 1 ; iy < entries.length; iy ++) { + let entry2 = entries[iy]; + if (!self.matchPhoneNumbers(entry1.normalized, entry1.parsed, + entry2.normalized, entry2.parsed)) { + return true; + } + } + } + return false; + }; + + let invalidParticipantIds = []; + participantStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + if (cursor) { + let participantRecord = cursor.value; + // Check if this participant record is valid + if (isInvalid(participantRecord)) { + invalidParticipantIds.push(participantRecord.id); + cursor.delete(); + } + cursor.continue(); + return; + } + + // Participant store cursor iteration done. + if (!invalidParticipantIds.length) { + next(); + return; + } + + // Find affected thread. + let wrongThreads = []; + threadStore.openCursor().onsuccess = function(event) { + let threadCursor = event.target.result; + if (threadCursor) { + let threadRecord = threadCursor.value; + let participantIds = threadRecord.participantIds; + let foundInvalid = false; + for (let invalidParticipantId of invalidParticipantIds) { + if (participantIds.indexOf(invalidParticipantId) != -1) { + foundInvalid = true; + break; + } + } + if (foundInvalid) { + wrongThreads.push(threadRecord.id); + threadCursor.delete(); + } + threadCursor.continue(); + return; + } + + if (!wrongThreads.length) { + next(); + return; + } + // Use recursive function to avoid we add participant twice. + (function createUpdateThreadAndParticipant(ix) { + let threadId = wrongThreads[ix]; + let range = IDBKeyRange.bound([threadId, 0], [threadId, ""]); + messageStore.index("threadId").openCursor(range).onsuccess = function(event) { + let messageCursor = event.target.result; + if (!messageCursor) { + ix++; + if (ix === wrongThreads.length) { + next(); + return; + } + createUpdateThreadAndParticipant(ix); + return; + } + + let messageRecord = messageCursor.value; + let timestamp = messageRecord.timestamp; + let threadParticipants = []; + // Recaculate the thread participants of received message. + if (messageRecord.delivery === DELIVERY_RECEIVED || + messageRecord.delivery === DELIVERY_NOT_DOWNLOADED) { + threadParticipants.push(messageRecord.sender); + if (messageRecord.type == "mms") { + this.fillReceivedMmsThreadParticipants(messageRecord, threadParticipants); + } + } + // Recaculate the thread participants of sent messages and error + // messages. In error sms messages, we don't have error received sms. + // In received MMS, we don't update the error to deliver field but + // deliverStatus. So we only consider sent message in DELIVERY_ERROR. + else if (messageRecord.delivery === DELIVERY_SENT || + messageRecord.delivery === DELIVERY_ERROR) { + if (messageRecord.type == "sms") { + threadParticipants = [messageRecord.receiver]; + } else if (messageRecord.type == "mms") { + threadParticipants = messageRecord.receivers; + } + } + self.findThreadRecordByParticipants(threadStore, participantStore, + threadParticipants, true, + function (threadRecord, + participantIds) { + if (!participantIds) { + debug("participantIds is empty!"); + return; + } + + let timestamp = messageRecord.timestamp; + // Setup participantIdsIndex. + messageRecord.participantIdsIndex = []; + for each (let id in participantIds) { + messageRecord.participantIdsIndex.push([id, timestamp]); + } + if (threadRecord) { + let needsUpdate = false; + + if (threadRecord.lastTimestamp <= timestamp) { + threadRecord.lastTimestamp = timestamp; + threadRecord.subject = messageRecord.body; + threadRecord.lastMessageId = messageRecord.id; + threadRecord.lastMessageType = messageRecord.type; + needsUpdate = true; + } + + if (!messageRecord.read) { + threadRecord.unreadCount++; + needsUpdate = true; + } + + if (needsUpdate) { + threadStore.put(threadRecord); + } + messageRecord.threadId = threadRecord.id; + messageRecord.threadIdIndex = [threadRecord.id, timestamp]; + messageCursor.update(messageRecord); + messageCursor.continue(); + return; + } + + let threadRecord = { + participantIds: participantIds, + participantAddresses: threadParticipants, + lastMessageId: messageRecord.id, + lastTimestamp: timestamp, + subject: messageRecord.body, + unreadCount: messageRecord.read ? 0 : 1, + lastMessageType: messageRecord.type + }; + threadStore.add(threadRecord).onsuccess = function (event) { + let threadId = event.target.result; + // Setup threadId & threadIdIndex. + messageRecord.threadId = threadId; + messageRecord.threadIdIndex = [threadId, timestamp]; + messageCursor.update(messageRecord); + messageCursor.continue(); + }; + }); + }; + })(0); + }; + }; + }, + + /** + * Add deliveryTimestamp. + */ + upgradeSchema14: function upgradeSchema14(transaction, next) { + let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); + + messageStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + if (!cursor) { + next(); + return; + } + + let messageRecord = cursor.value; + if (messageRecord.type == "sms") { + messageRecord.deliveryTimestamp = 0; + } else if (messageRecord.type == "mms") { + let deliveryInfo = messageRecord.deliveryInfo; + for (let i = 0; i < deliveryInfo.length; i++) { + deliveryInfo[i].deliveryTimestamp = 0; + } + } + cursor.update(messageRecord); + cursor.continue(); + }; + }, + + /** + * Add ICC ID. + */ + upgradeSchema15: function upgradeSchema15(transaction, next) { + let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); + messageStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + if (!cursor) { + next(); + return; + } + + let messageRecord = cursor.value; + messageRecord.iccId = null; + cursor.update(messageRecord); + cursor.continue(); + }; + }, + + /** + * Add isReadReportSent for incoming MMS. + */ + upgradeSchema16: function upgradeSchema16(transaction, next) { + let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); + + // Update type attributes. + messageStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + if (!cursor) { + next(); + return; + } + + let messageRecord = cursor.value; + if (messageRecord.type == "mms") { + messageRecord.isReadReportSent = false; + cursor.update(messageRecord); + } + cursor.continue(); + }; + }, + + upgradeSchema17: function upgradeSchema17(transaction, next) { + let threadStore = transaction.objectStore(THREAD_STORE_NAME); + let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); + + // Add 'lastMessageSubject' to each thread record. + threadStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + if (!cursor) { + next(); + return; + } + + let threadRecord = cursor.value; + // We have defined 'threadRecord.subject' in upgradeSchema7(), but it + // actually means 'threadRecord.body'. Swap the two values first. + threadRecord.body = threadRecord.subject; + delete threadRecord.subject; + + // Only MMS supports subject so assign null for non-MMS one. + if (threadRecord.lastMessageType != "mms") { + threadRecord.lastMessageSubject = null; + cursor.update(threadRecord); + + cursor.continue(); + return; + } + + messageStore.get(threadRecord.lastMessageId).onsuccess = function(event) { + let messageRecord = event.target.result; + let subject = messageRecord.headers.subject; + threadRecord.lastMessageSubject = subject || null; + cursor.update(threadRecord); + + cursor.continue(); + }; + }; + }, + + /** + * Add pid for incoming SMS. + */ + upgradeSchema18: function upgradeSchema18(transaction, next) { + let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); + + messageStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + if (!cursor) { + next(); + return; + } + + let messageRecord = cursor.value; + if (messageRecord.type == "sms") { + messageRecord.pid = RIL.PDU_PID_DEFAULT; + cursor.update(messageRecord); + } + cursor.continue(); + }; + }, + + /** + * Add readStatus and readTimestamp. + */ + upgradeSchema19: function upgradeSchema19(transaction, next) { + let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); + messageStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + if (!cursor) { + next(); + return; + } + + let messageRecord = cursor.value; + if (messageRecord.type == "sms") { + cursor.continue(); + return; + } + + // We can always retrieve transaction id from + // |messageRecord.headers["x-mms-transaction-id"]|. + if (messageRecord.hasOwnProperty("transactionId")) { + delete messageRecord.transactionId; + } + + // xpconnect gives "undefined" for an unassigned argument of an interface + // method. + if (messageRecord.envelopeIdIndex === "undefined") { + delete messageRecord.envelopeIdIndex; + } + + // Convert some header fields that were originally decoded as BooleanValue + // to numeric enums. + for (let field of ["x-mms-cancel-status", + "x-mms-sender-visibility", + "x-mms-read-status"]) { + let value = messageRecord.headers[field]; + if (value !== undefined) { + messageRecord.headers[field] = value ? 128 : 129; + } + } + + // For all sent and received MMS messages, we have to add their + // |readStatus| and |readTimestamp| attributes in |deliveryInfo| array. + let readReportRequested = + messageRecord.headers["x-mms-read-report"] || false; + for (let element of messageRecord.deliveryInfo) { + element.readStatus = readReportRequested + ? MMS.DOM_READ_STATUS_PENDING + : MMS.DOM_READ_STATUS_NOT_APPLICABLE; + element.readTimestamp = 0; + } + + cursor.update(messageRecord); + cursor.continue(); + }; + }, + + matchParsedPhoneNumbers: function matchParsedPhoneNumbers(addr1, parsedAddr1, + addr2, parsedAddr2) { + if ((parsedAddr1.internationalNumber && + parsedAddr1.internationalNumber === parsedAddr2.internationalNumber) || + (parsedAddr1.nationalNumber && + parsedAddr1.nationalNumber === parsedAddr2.nationalNumber)) { + return true; + } + + if (parsedAddr1.countryName != parsedAddr2.countryName) { + return false; + } + + let ssPref = "dom.phonenumber.substringmatching." + parsedAddr1.countryName; + if (Services.prefs.getPrefType(ssPref) != Ci.nsIPrefBranch.PREF_INT) { + return false; + } + + let val = Services.prefs.getIntPref(ssPref); + return addr1.length > val && + addr2.length > val && + addr1.slice(-val) === addr2.slice(-val); + }, + + matchPhoneNumbers: function matchPhoneNumbers(addr1, parsedAddr1, addr2, parsedAddr2) { + if (parsedAddr1 && parsedAddr2) { + return this.matchParsedPhoneNumbers(addr1, parsedAddr1, addr2, parsedAddr2); + } + + if (parsedAddr1) { + parsedAddr2 = PhoneNumberUtils.parseWithCountryName(addr2, parsedAddr1.countryName); + if (parsedAddr2) { + return this.matchParsedPhoneNumbers(addr1, parsedAddr1, addr2, parsedAddr2); + } + + return false; + } + + if (parsedAddr2) { + parsedAddr1 = PhoneNumberUtils.parseWithCountryName(addr1, parsedAddr2.countryName); + if (parsedAddr1) { + return this.matchParsedPhoneNumbers(addr1, parsedAddr1, addr2, parsedAddr2); + } + } + + return false; + }, + + createDomMessageFromRecord: function createDomMessageFromRecord(aMessageRecord) { + if (DEBUG) { + debug("createDomMessageFromRecord: " + JSON.stringify(aMessageRecord)); + } + if (aMessageRecord.type == "sms") { + return gMobileMessageService.createSmsMessage(aMessageRecord.id, + aMessageRecord.threadId, + aMessageRecord.iccId, + aMessageRecord.delivery, + aMessageRecord.deliveryStatus, + aMessageRecord.sender, + aMessageRecord.receiver, + aMessageRecord.body, + aMessageRecord.messageClass, + aMessageRecord.timestamp, + aMessageRecord.deliveryTimestamp, + aMessageRecord.read); + } else if (aMessageRecord.type == "mms") { + let headers = aMessageRecord["headers"]; + if (DEBUG) { + debug("MMS: headers: " + JSON.stringify(headers)); + } + + let subject = headers["subject"]; + if (subject == undefined) { + subject = ""; + } + + let smil = ""; + let attachments = []; + let parts = aMessageRecord.parts; + if (parts) { + for (let i = 0; i < parts.length; i++) { + let part = parts[i]; + if (DEBUG) { + debug("MMS: part[" + i + "]: " + JSON.stringify(part)); + } + // Sometimes the part is incomplete because the device reboots when + // downloading MMS. Don't need to expose this part to the content. + if (!part) { + continue; + } + + let partHeaders = part["headers"]; + let partContent = part["content"]; + // Don't need to make the SMIL part if it's present. + if (partHeaders["content-type"]["media"] == "application/smil") { + smil = partContent; + continue; + } + attachments.push({ + "id": partHeaders["content-id"], + "location": partHeaders["content-location"], + "content": partContent + }); + } + } + let expiryDate = 0; + if (headers["x-mms-expiry"] != undefined) { + expiryDate = aMessageRecord.timestamp + headers["x-mms-expiry"] * 1000; + } + let readReportRequested = headers["x-mms-read-report"] || false; + return gMobileMessageService.createMmsMessage(aMessageRecord.id, + aMessageRecord.threadId, + aMessageRecord.iccId, + aMessageRecord.delivery, + aMessageRecord.deliveryInfo, + aMessageRecord.sender, + aMessageRecord.receivers, + aMessageRecord.timestamp, + aMessageRecord.read, + subject, + smil, + attachments, + expiryDate, + readReportRequested); + } + }, + + findParticipantRecordByAddress: function findParticipantRecordByAddress( + aParticipantStore, aAddress, aCreate, aCallback) { + if (DEBUG) { + debug("findParticipantRecordByAddress(" + + JSON.stringify(aAddress) + ", " + aCreate + ")"); + } + + // Two types of input number to match here, international(+886987654321), + // and local(0987654321) types. The "nationalNumber" parsed from + // phonenumberutils will be "987654321" in this case. + + // Normalize address before searching for participant record. + let normalizedAddress = PhoneNumberUtils.normalize(aAddress, false); + let allPossibleAddresses = [normalizedAddress]; + let parsedAddress = PhoneNumberUtils.parse(normalizedAddress); + if (parsedAddress && parsedAddress.internationalNumber && + allPossibleAddresses.indexOf(parsedAddress.internationalNumber) < 0) { + // We only stores international numbers into participant store because + // the parsed national number doesn't contain country info and may + // duplicate in different country. + allPossibleAddresses.push(parsedAddress.internationalNumber); + } + if (DEBUG) { + debug("findParticipantRecordByAddress: allPossibleAddresses = " + + JSON.stringify(allPossibleAddresses)); + } + + // Make a copy here because we may need allPossibleAddresses again. + let needles = allPossibleAddresses.slice(0); + let request = aParticipantStore.index("addresses").get(needles.pop()); + request.onsuccess = (function onsuccess(event) { + let participantRecord = event.target.result; + // 1) First try matching through "addresses" index of participant store. + // If we're lucky, return the fetched participant record. + if (participantRecord) { + if (DEBUG) { + debug("findParticipantRecordByAddress: got " + + JSON.stringify(participantRecord)); + } + aCallback(participantRecord); + return; + } + + // Try next possible address again. + if (needles.length) { + let request = aParticipantStore.index("addresses").get(needles.pop()); + request.onsuccess = onsuccess.bind(this); + return; + } + + // 2) Traverse throught all participants and check all alias addresses. + aParticipantStore.openCursor().onsuccess = (function (event) { + let cursor = event.target.result; + if (!cursor) { + // Have traversed whole object store but still in vain. + if (!aCreate) { + aCallback(null); + return; + } + + let participantRecord = { addresses: [normalizedAddress] }; + let addRequest = aParticipantStore.add(participantRecord); + addRequest.onsuccess = function (event) { + participantRecord.id = event.target.result; + if (DEBUG) { + debug("findParticipantRecordByAddress: created " + + JSON.stringify(participantRecord)); + } + aCallback(participantRecord); + }; + return; + } + + let participantRecord = cursor.value; + for (let storedAddress of participantRecord.addresses) { + let parsedStoredAddress = PhoneNumberUtils.parseWithMCC(storedAddress, null); + let match = this.matchPhoneNumbers(normalizedAddress, parsedAddress, + storedAddress, parsedStoredAddress); + if (!match) { + // 3) Else we fail to match current stored participant record. + continue; + } + // Match! + if (aCreate) { + // In a READ-WRITE transaction, append one more possible address for + // this participant record. + participantRecord.addresses = + participantRecord.addresses.concat(allPossibleAddresses); + cursor.update(participantRecord); + } + + if (DEBUG) { + debug("findParticipantRecordByAddress: match " + + JSON.stringify(cursor.value)); + } + aCallback(participantRecord); + return; + } + + // Check next participant record if available. + cursor.continue(); + }).bind(this); + }).bind(this); + }, + + findParticipantIdsByAddresses: function findParticipantIdsByAddresses( + aParticipantStore, aAddresses, aCreate, aSkipNonexistent, aCallback) { + if (DEBUG) { + debug("findParticipantIdsByAddresses(" + + JSON.stringify(aAddresses) + ", " + + aCreate + ", " + aSkipNonexistent + ")"); + } + + if (!aAddresses || !aAddresses.length) { + if (DEBUG) debug("findParticipantIdsByAddresses: returning null"); + aCallback(null); + return; + } + + let self = this; + (function findParticipantId(index, result) { + if (index >= aAddresses.length) { + // Sort numerically. + result.sort(function (a, b) { + return a - b; + }); + if (DEBUG) debug("findParticipantIdsByAddresses: returning " + result); + aCallback(result); + return; + } + + self.findParticipantRecordByAddress(aParticipantStore, + aAddresses[index++], aCreate, + function (participantRecord) { + if (!participantRecord) { + if (!aSkipNonexistent) { + if (DEBUG) debug("findParticipantIdsByAddresses: returning null"); + aCallback(null); + return; + } + } else if (result.indexOf(participantRecord.id) < 0) { + result.push(participantRecord.id); + } + findParticipantId(index, result); + }); + }) (0, []); + }, + + findThreadRecordByParticipants: function findThreadRecordByParticipants( + aThreadStore, aParticipantStore, aAddresses, + aCreateParticipants, aCallback) { + if (DEBUG) { + debug("findThreadRecordByParticipants(" + JSON.stringify(aAddresses) + + ", " + aCreateParticipants + ")"); + } + this.findParticipantIdsByAddresses(aParticipantStore, aAddresses, + aCreateParticipants, false, + function (participantIds) { + if (!participantIds) { + if (DEBUG) debug("findThreadRecordByParticipants: returning null"); + aCallback(null, null); + return; + } + // Find record from thread store. + let request = aThreadStore.index("participantIds").get(participantIds); + request.onsuccess = function (event) { + let threadRecord = event.target.result; + if (DEBUG) { + debug("findThreadRecordByParticipants: return " + + JSON.stringify(threadRecord)); + } + aCallback(threadRecord, participantIds); + }; + }); + }, + + newTxnWithCallback: function newTxnWithCallback(aCallback, aFunc, aStoreNames) { + let self = this; + this.newTxn(READ_WRITE, function(aError, aTransaction, aStores) { + let notifyResult = function(aRv, aMessageRecord) { + if (!aCallback) { + return; + } + let domMessage = + aMessageRecord && self.createDomMessageFromRecord(aMessageRecord); + aCallback.notify(aRv, domMessage); + }; + + if (aError) { + // TODO bug 832140 check event.target.errorCode + notifyResult(Cr.NS_ERROR_FAILURE, null); + return; + } + + let capture = {}; + aTransaction.oncomplete = function(event) { + notifyResult(Cr.NS_OK, capture.messageRecord); + }; + aTransaction.onabort = function(event) { + // TODO bug 832140 check event.target.errorCode + notifyResult(Cr.NS_ERROR_FAILURE, null); + }; + + aFunc(capture, aStores); + }, aStoreNames); + }, + + saveRecord: function saveRecord(aMessageRecord, aAddresses, aCallback) { + if (DEBUG) debug("Going to store " + JSON.stringify(aMessageRecord)); + + let self = this; + this.newTxn(READ_WRITE, function(error, txn, stores) { + let notifyResult = function(aRv, aMessageRecord) { + if (!aCallback) { + return; + } + let domMessage = + aMessageRecord && self.createDomMessageFromRecord(aMessageRecord); + aCallback.notify(aRv, domMessage); + }; + + if (error) { + // TODO bug 832140 check event.target.errorCode + notifyResult(Cr.NS_ERROR_FAILURE, null); + return; + } + + txn.oncomplete = function oncomplete(event) { + if (aMessageRecord.id > self.lastMessageId) { + self.lastMessageId = aMessageRecord.id; + } + notifyResult(Cr.NS_OK, aMessageRecord); + }; + txn.onabort = function onabort(event) { + // TODO bug 832140 check event.target.errorCode + notifyResult(Cr.NS_ERROR_FAILURE, null); + }; + + let messageStore = stores[0]; + let participantStore = stores[1]; + let threadStore = stores[2]; + self.replaceShortMessageOnSave(txn, messageStore, participantStore, + threadStore, aMessageRecord, aAddresses); + }, [MESSAGE_STORE_NAME, PARTICIPANT_STORE_NAME, THREAD_STORE_NAME]); + }, + + replaceShortMessageOnSave: + function replaceShortMessageOnSave(aTransaction, aMessageStore, + aParticipantStore, aThreadStore, + aMessageRecord, aAddresses) { + let isReplaceTypePid = (aMessageRecord.pid) && + ((aMessageRecord.pid >= RIL.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_1 && + aMessageRecord.pid <= RIL.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_7) || + aMessageRecord.pid == RIL.PDU_PID_RETURN_CALL_MESSAGE); + + if (aMessageRecord.type != "sms" || + aMessageRecord.delivery != DELIVERY_RECEIVED || + !isReplaceTypePid) { + this.realSaveRecord(aTransaction, aMessageStore, aParticipantStore, + aThreadStore, aMessageRecord, aAddresses); + return; + } + + // 3GPP TS 23.040 subclause 9.2.3.9 "TP-Protocol-Identifier (TP-PID)": + // + // ... the MS shall check the originating address and replace any + // existing stored message having the same Protocol Identifier code + // and originating address with the new short message and other + // parameter values. If there is no message to be replaced, the MS + // shall store the message in the normal way. ... it is recommended + // that the SC address should not be checked by the MS." + let self = this; + this.findParticipantRecordByAddress(aParticipantStore, + aMessageRecord.sender, false, + function(participantRecord) { + if (!participantRecord) { + self.realSaveRecord(aTransaction, aMessageStore, aParticipantStore, + aThreadStore, aMessageRecord, aAddresses); + return; + } + + let participantId = participantRecord.id; + let range = IDBKeyRange.bound([participantId, 0], [participantId, ""]); + let request = aMessageStore.index("participantIds").openCursor(range); + request.onsuccess = function onsuccess(event) { + let cursor = event.target.result; + if (!cursor) { + self.realSaveRecord(aTransaction, aMessageStore, aParticipantStore, + aThreadStore, aMessageRecord, aAddresses); + return; + } + + // A message record with same participantId found. + // Verify matching criteria. + let foundMessageRecord = cursor.value; + if (foundMessageRecord.type != "sms" || + foundMessageRecord.sender != aMessageRecord.sender || + foundMessageRecord.pid != aMessageRecord.pid) { + cursor.continue(); + return; + } + + // Match! Now replace that found message record with current one. + aMessageRecord.id = foundMessageRecord.id; + self.realSaveRecord(aTransaction, aMessageStore, aParticipantStore, + aThreadStore, aMessageRecord, aAddresses); + }; + }); + }, + + realSaveRecord: function realSaveRecord(aTransaction, aMessageStore, + aParticipantStore, aThreadStore, + aMessageRecord, aAddresses) { + let self = this; + this.findThreadRecordByParticipants(aThreadStore, aParticipantStore, + aAddresses, true, + function(threadRecord, participantIds) { + if (!participantIds) { + aTransaction.abort(); + return; + } + + let isOverriding = (aMessageRecord.id !== undefined); + if (!isOverriding) { + // |self.lastMessageId| is only updated in |txn.oncomplete|. + aMessageRecord.id = self.lastMessageId + 1; + } + + let timestamp = aMessageRecord.timestamp; + let insertMessageRecord = function(threadId) { + // Setup threadId & threadIdIndex. + aMessageRecord.threadId = threadId; + aMessageRecord.threadIdIndex = [threadId, timestamp]; + // Setup participantIdsIndex. + aMessageRecord.participantIdsIndex = []; + for each (let id in participantIds) { + aMessageRecord.participantIdsIndex.push([id, timestamp]); + } + + if (!isOverriding) { + // Really add to message store. + aMessageStore.put(aMessageRecord); + return; + } + + // If we're going to override an old message, we need to update the + // info of the original thread containing the overridden message. + // To get the original thread ID and read status of the overridden + // message record, we need to retrieve it before overriding it. + aMessageStore.get(aMessageRecord.id).onsuccess = function(event) { + let oldMessageRecord = event.target.result; + aMessageStore.put(aMessageRecord); + if (oldMessageRecord) { + self.updateThreadByMessageChange(aMessageStore, + aThreadStore, + oldMessageRecord.threadId, + aMessageRecord.id, + oldMessageRecord.read); + } + }; + }; + + if (threadRecord) { + let needsUpdate = false; + + if (threadRecord.lastTimestamp <= timestamp) { + let lastMessageSubject; + if (aMessageRecord.type == "mms") { + lastMessageSubject = aMessageRecord.headers.subject; + } + threadRecord.lastMessageSubject = lastMessageSubject || null; + threadRecord.lastTimestamp = timestamp; + threadRecord.body = aMessageRecord.body; + threadRecord.lastMessageId = aMessageRecord.id; + threadRecord.lastMessageType = aMessageRecord.type; + needsUpdate = true; + } + + if (!aMessageRecord.read) { + threadRecord.unreadCount++; + needsUpdate = true; + } + + if (needsUpdate) { + aThreadStore.put(threadRecord); + } + + insertMessageRecord(threadRecord.id); + return; + } + + let lastMessageSubject; + if (aMessageRecord.type == "mms") { + lastMessageSubject = aMessageRecord.headers.subject; + } + + threadRecord = { + participantIds: participantIds, + participantAddresses: aAddresses, + lastMessageId: aMessageRecord.id, + lastTimestamp: timestamp, + lastMessageSubject: lastMessageSubject || null, + body: aMessageRecord.body, + unreadCount: aMessageRecord.read ? 0 : 1, + lastMessageType: aMessageRecord.type, + }; + aThreadStore.add(threadRecord).onsuccess = function(event) { + let threadId = event.target.result; + insertMessageRecord(threadId); + }; + }); + }, + + forEachMatchedMmsDeliveryInfo: + function forEachMatchedMmsDeliveryInfo(aDeliveryInfo, aNeedle, aCallback) { + + let typedAddress = { + type: MMS.Address.resolveType(aNeedle), + address: aNeedle + }; + let normalizedAddress, parsedAddress; + if (typedAddress.type === "PLMN") { + normalizedAddress = PhoneNumberUtils.normalize(aNeedle, false); + parsedAddress = PhoneNumberUtils.parse(normalizedAddress); + } + + for (let element of aDeliveryInfo) { + let typedStoredAddress = { + type: MMS.Address.resolveType(element.receiver), + address: element.receiver + }; + if (typedAddress.type !== typedStoredAddress.type) { + // Not even my type. Skip. + continue; + } + + if (typedAddress.address == typedStoredAddress.address) { + // Have a direct match. + aCallback(element); + continue; + } + + if (typedAddress.type !== "PLMN") { + // Address type other than "PLMN" must have direct match. Or, skip. + continue; + } + + // Both are of "PLMN" type. + let normalizedStoredAddress = + PhoneNumberUtils.normalize(element.receiver, false); + let parsedStoredAddress = + PhoneNumberUtils.parseWithMCC(normalizedStoredAddress, null); + if (this.matchPhoneNumbers(normalizedAddress, parsedAddress, + normalizedStoredAddress, parsedStoredAddress)) { + aCallback(element); + } + } + }, + + updateMessageDeliveryById: function updateMessageDeliveryById( + id, type, receiver, delivery, deliveryStatus, envelopeId, callback) { + if (DEBUG) { + debug("Setting message's delivery by " + type + " = "+ id + + " receiver: " + receiver + + " delivery: " + delivery + + " deliveryStatus: " + deliveryStatus + + " envelopeId: " + envelopeId); + } + + let self = this; + this.newTxnWithCallback(callback, function(aCapture, aMessageStore) { + let getRequest; + if (type === "messageId") { + getRequest = aMessageStore.get(id); + } else if (type === "envelopeId") { + getRequest = aMessageStore.index("envelopeId").get(id); + } + + getRequest.onsuccess = function onsuccess(event) { + let messageRecord = event.target.result; + if (!messageRecord) { + if (DEBUG) debug("type = " + id + " is not found"); + throw Cr.NS_ERROR_FAILURE; + } + + let isRecordUpdated = false; + + // Update |messageRecord.delivery| if needed. + if (delivery && messageRecord.delivery != delivery) { + messageRecord.delivery = delivery; + messageRecord.deliveryIndex = [delivery, messageRecord.timestamp]; + isRecordUpdated = true; + } + + // Attempt to update |deliveryStatus| and |deliveryTimestamp| of: + // - the |messageRecord| for SMS. + // - the element(s) in |messageRecord.deliveryInfo| for MMS. + if (deliveryStatus) { + // A callback for updating the deliveyStatus/deliveryTimestamp of + // each target. + let updateFunc = function(aTarget) { + if (aTarget.deliveryStatus == deliveryStatus) { + return; + } + + aTarget.deliveryStatus = deliveryStatus; + + // Update |deliveryTimestamp| if it's successfully delivered. + if (deliveryStatus == DELIVERY_STATUS_SUCCESS) { + aTarget.deliveryTimestamp = Date.now(); + } + + isRecordUpdated = true; + }; + + if (messageRecord.type == "sms") { + updateFunc(messageRecord); + } else if (messageRecord.type == "mms") { + if (!receiver) { + // If the receiver is specified, we only need to update the + // element(s) in deliveryInfo that match the same receiver. + messageRecord.deliveryInfo.forEach(updateFunc); + } else { + self.forEachMatchedMmsDeliveryInfo(messageRecord.deliveryInfo, + receiver, updateFunc); + } + } + } + + // Update |messageRecord.envelopeIdIndex| if needed. + if (envelopeId) { + if (messageRecord.envelopeIdIndex != envelopeId) { + messageRecord.envelopeIdIndex = envelopeId; + isRecordUpdated = true; + } + } + + aCapture.messageRecord = messageRecord; + if (!isRecordUpdated) { + if (DEBUG) { + debug("The values of delivery, deliveryStatus and envelopeId " + + "don't need to be updated."); + } + return; + } + + if (DEBUG) { + debug("The delivery, deliveryStatus or envelopeId are updated."); + } + aMessageStore.put(messageRecord); + }; + }); + }, + + fillReceivedMmsThreadParticipants: function fillReceivedMmsThreadParticipants(aMessage, threadParticipants) { + let receivers = aMessage.receivers; + // If we don't want to disable the MMS grouping for receiving, we need to + // add the receivers (excluding the user's own number) to the participants + // for creating the thread. Some cases might be investigated as below: + // + // 1. receivers.length == 0 + // This usually happens when receiving an MMS notification indication + // which doesn't carry any receivers. + // 2. receivers.length == 1 + // If the receivers contain single phone number, we don't need to + // add it into participants because we know that number is our own. + // 3. receivers.length >= 2 + // If the receivers contain multiple phone numbers, we need to add all + // of them but not the user's own number into participants. + if (DISABLE_MMS_GROUPING_FOR_RECEIVING || receivers.length < 2) { + return; + } + let isSuccess = false; + let slicedReceivers = receivers.slice(); + if (aMessage.msisdn) { + let found = slicedReceivers.indexOf(aMessage.msisdn); + if (found !== -1) { + isSuccess = true; + slicedReceivers.splice(found, 1); + } + } + + if (!isSuccess) { + // For some SIMs we cannot retrieve the vaild MSISDN (i.e. the user's + // own phone number), so we cannot correcly exclude the user's own + // number from the receivers, thus wrongly building the thread index. + if (DEBUG) debug("Error! Cannot strip out user's own phone number!"); + } + + threadParticipants = threadParticipants.concat(slicedReceivers); + }, + + updateThreadByMessageChange: function updateThreadByMessageChange(messageStore, + threadStore, + threadId, + messageId, + messageRead) { + threadStore.get(threadId).onsuccess = function(event) { + // This must exist. + let threadRecord = event.target.result; + if (DEBUG) debug("Updating thread record " + JSON.stringify(threadRecord)); + + if (!messageRead) { + threadRecord.unreadCount--; + } + + if (threadRecord.lastMessageId == messageId) { + // Check most recent sender/receiver. + let range = IDBKeyRange.bound([threadId, 0], [threadId, ""]); + let request = messageStore.index("threadId") + .openCursor(range, PREV); + request.onsuccess = function(event) { + let cursor = event.target.result; + if (!cursor) { + if (DEBUG) { + debug("Deleting mru entry for thread id " + threadId); + } + threadStore.delete(threadId); + return; + } + + let nextMsg = cursor.value; + let lastMessageSubject; + if (nextMsg.type == "mms") { + lastMessageSubject = nextMsg.headers.subject; + } + threadRecord.lastMessageSubject = lastMessageSubject || null; + threadRecord.lastMessageId = nextMsg.id; + threadRecord.lastTimestamp = nextMsg.timestamp; + threadRecord.body = nextMsg.body; + threadRecord.lastMessageType = nextMsg.type; + if (DEBUG) { + debug("Updating mru entry: " + + JSON.stringify(threadRecord)); + } + threadStore.put(threadRecord); + }; + } else if (!messageRead) { + // Shortcut, just update the unread count. + if (DEBUG) { + debug("Updating unread count for thread id " + threadId + ": " + + (threadRecord.unreadCount + 1) + " -> " + + threadRecord.unreadCount); + } + threadStore.put(threadRecord); + } + }; + }, + + /** + * nsIRilMobileMessageDatabaseService API + */ + + saveReceivedMessage: function saveReceivedMessage(aMessage, aCallback) { + if ((aMessage.type != "sms" && aMessage.type != "mms") || + (aMessage.type == "sms" && (aMessage.messageClass == undefined || + aMessage.sender == undefined)) || + (aMessage.type == "mms" && (aMessage.delivery == undefined || + aMessage.deliveryStatus == undefined || + !Array.isArray(aMessage.receivers))) || + aMessage.timestamp == undefined) { + if (aCallback) { + aCallback.notify(Cr.NS_ERROR_FAILURE, null); + } + return; + } + + let threadParticipants; + if (aMessage.type == "mms") { + if (aMessage.headers.from) { + aMessage.sender = aMessage.headers.from.address; + } else { + aMessage.sender = "anonymous"; + } + + threadParticipants = [aMessage.sender]; + this.fillReceivedMmsThreadParticipants(aMessage, threadParticipants); + } else { // SMS + threadParticipants = [aMessage.sender]; + } + + let timestamp = aMessage.timestamp; + + // Adding needed indexes and extra attributes for internal use. + // threadIdIndex & participantIdsIndex are filled in saveRecord(). + aMessage.readIndex = [FILTER_READ_UNREAD, timestamp]; + aMessage.read = FILTER_READ_UNREAD; + + if (aMessage.type == "mms") { + aMessage.transactionIdIndex = aMessage.headers["x-mms-transaction-id"]; + aMessage.isReadReportSent = false; + + // As a receiver, we don't need to care about the delivery status of + // others, so we put a single element with self's phone number in the + // |deliveryInfo| array. + aMessage.deliveryInfo = [{ + receiver: aMessage.phoneNumber, + deliveryStatus: aMessage.deliveryStatus, + deliveryTimestamp: 0, + readStatus: MMS.DOM_READ_STATUS_NOT_APPLICABLE, + readTimestamp: 0, + }]; + + delete aMessage.deliveryStatus; + } + + if (aMessage.type == "sms") { + aMessage.delivery = DELIVERY_RECEIVED; + aMessage.deliveryStatus = DELIVERY_STATUS_SUCCESS; + aMessage.deliveryTimestamp = 0; + + if (aMessage.pid == undefined) { + aMessage.pid = RIL.PDU_PID_DEFAULT; + } + } + aMessage.deliveryIndex = [aMessage.delivery, timestamp]; + + this.saveRecord(aMessage, threadParticipants, aCallback); + }, + + saveSendingMessage: function saveSendingMessage(aMessage, aCallback) { + if ((aMessage.type != "sms" && aMessage.type != "mms") || + (aMessage.type == "sms" && aMessage.receiver == undefined) || + (aMessage.type == "mms" && !Array.isArray(aMessage.receivers)) || + aMessage.deliveryStatusRequested == undefined || + aMessage.timestamp == undefined) { + if (aCallback) { + aCallback.notify(Cr.NS_ERROR_FAILURE, null); + } + return; + } + + // Set |aMessage.deliveryStatus|. Note that for MMS record + // it must be an array of strings; For SMS, it's a string. + let deliveryStatus = aMessage.deliveryStatusRequested + ? DELIVERY_STATUS_PENDING + : DELIVERY_STATUS_NOT_APPLICABLE; + if (aMessage.type == "sms") { + aMessage.deliveryStatus = deliveryStatus; + // If |deliveryTimestamp| is not specified, use 0 as default. + if (aMessage.deliveryTimestamp == undefined) { + aMessage.deliveryTimestamp = 0; + } + } else if (aMessage.type == "mms") { + let receivers = aMessage.receivers + if (!Array.isArray(receivers)) { + if (DEBUG) { + debug("Need receivers for MMS. Fail to save the sending message."); + } + if (aCallback) { + aCallback.notify(Cr.NS_ERROR_FAILURE, null); + } + return; + } + let readStatus = aMessage.headers["x-mms-read-report"] + ? MMS.DOM_READ_STATUS_PENDING + : MMS.DOM_READ_STATUS_NOT_APPLICABLE; + aMessage.deliveryInfo = []; + for (let i = 0; i < receivers.length; i++) { + aMessage.deliveryInfo.push({ + receiver: receivers[i], + deliveryStatus: deliveryStatus, + deliveryTimestamp: 0, + readStatus: readStatus, + readTimestamp: 0, + }); + } + } + + let timestamp = aMessage.timestamp; + + // Adding needed indexes and extra attributes for internal use. + // threadIdIndex & participantIdsIndex are filled in saveRecord(). + aMessage.deliveryIndex = [DELIVERY_SENDING, timestamp]; + aMessage.readIndex = [FILTER_READ_READ, timestamp]; + aMessage.delivery = DELIVERY_SENDING; + aMessage.messageClass = MESSAGE_CLASS_NORMAL; + aMessage.read = FILTER_READ_READ; + + let addresses; + if (aMessage.type == "sms") { + addresses = [aMessage.receiver]; + } else if (aMessage.type == "mms") { + addresses = aMessage.receivers; + } + this.saveRecord(aMessage, addresses, aCallback); + }, + + setMessageDeliveryByMessageId: function setMessageDeliveryByMessageId( + messageId, receiver, delivery, deliveryStatus, envelopeId, callback) { + this.updateMessageDeliveryById(messageId, "messageId", + receiver, delivery, deliveryStatus, + envelopeId, callback); + + }, + + setMessageDeliveryStatusByEnvelopeId: + function setMessageDeliveryStatusByEnvelopeId(aEnvelopeId, aReceiver, + aDeliveryStatus, aCallback) { + this.updateMessageDeliveryById(aEnvelopeId, "envelopeId", aReceiver, null, + aDeliveryStatus, null, aCallback); + }, + + setMessageReadStatusByEnvelopeId: + function setMessageReadStatusByEnvelopeId(aEnvelopeId, aReceiver, + aReadStatus, aCallback) { + if (DEBUG) { + debug("Setting message's read status by envelopeId = " + aEnvelopeId + + ", receiver: " + aReceiver + ", readStatus: " + aReadStatus); + } + + let self = this; + this.newTxnWithCallback(aCallback, function(aCapture, aMessageStore) { + let getRequest = aMessageStore.index("envelopeId").get(aEnvelopeId); + getRequest.onsuccess = function onsuccess(event) { + let messageRecord = event.target.result; + if (!messageRecord) { + if (DEBUG) debug("envelopeId '" + aEnvelopeId + "' not found"); + throw Cr.NS_ERROR_FAILURE; + } + + aCapture.messageRecord = messageRecord; + + let isRecordUpdated = false; + self.forEachMatchedMmsDeliveryInfo(messageRecord.deliveryInfo, + aReceiver, function(aEntry) { + if (aEntry.readStatus == aReadStatus) { + return; + } + + aEntry.readStatus = aReadStatus; + if (aReadStatus == MMS.DOM_READ_STATUS_SUCCESS) { + aEntry.readTimestamp = Date.now(); + } else { + aEntry.readTimestamp = 0; + } + isRecordUpdated = true; + }); + + if (!isRecordUpdated) { + if (DEBUG) { + debug("The values of readStatus don't need to be updated."); + } + return; + } + + if (DEBUG) { + debug("The readStatus is updated."); + } + aMessageStore.put(messageRecord); + }; + }); + }, + + getMessageRecordByTransactionId: function getMessageRecordByTransactionId(aTransactionId, aCallback) { + if (DEBUG) debug("Retrieving message with transaction ID " + aTransactionId); + let self = this; + this.newTxn(READ_ONLY, function (error, txn, messageStore) { + if (error) { + if (DEBUG) debug(error); + aCallback.notify(Ci.nsIMobileMessageCallback.INTERNAL_ERROR, null, null); + return; + } + let request = messageStore.index("transactionId").get(aTransactionId); + + txn.oncomplete = function oncomplete(event) { + if (DEBUG) debug("Transaction " + txn + " completed."); + let messageRecord = request.result; + if (!messageRecord) { + if (DEBUG) debug("Transaction ID " + aTransactionId + " not found"); + aCallback.notify(Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR, null, null); + return; + } + // In this case, we don't need a dom message. Just pass null to the + // third argument. + aCallback.notify(Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR, + messageRecord, null); + }; + + txn.onerror = function onerror(event) { + if (DEBUG) { + if (event.target) { + debug("Caught error on transaction", event.target.errorCode); + } + } + aCallback.notify(Ci.nsIMobileMessageCallback.INTERNAL_ERROR, null, null); + }; + }); + }, + + getMessageRecordById: function getMessageRecordById(aMessageId, aCallback) { + if (DEBUG) debug("Retrieving message with ID " + aMessageId); + let self = this; + this.newTxn(READ_ONLY, function (error, txn, messageStore) { + if (error) { + if (DEBUG) debug(error); + aCallback.notify(Ci.nsIMobileMessageCallback.INTERNAL_ERROR, null, null); + return; + } + let request = messageStore.mozGetAll(aMessageId); + + txn.oncomplete = function oncomplete() { + if (DEBUG) debug("Transaction " + txn + " completed."); + if (request.result.length > 1) { + if (DEBUG) debug("Got too many results for id " + aMessageId); + aCallback.notify(Ci.nsIMobileMessageCallback.UNKNOWN_ERROR, null, null); + return; + } + let messageRecord = request.result[0]; + if (!messageRecord) { + if (DEBUG) debug("Message ID " + aMessageId + " not found"); + aCallback.notify(Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR, null, null); + return; + } + if (messageRecord.id != aMessageId) { + if (DEBUG) { + debug("Requested message ID (" + aMessageId + ") is " + + "different from the one we got"); + } + aCallback.notify(Ci.nsIMobileMessageCallback.UNKNOWN_ERROR, null, null); + return; + } + let domMessage = self.createDomMessageFromRecord(messageRecord); + aCallback.notify(Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR, + messageRecord, domMessage); + }; + + txn.onerror = function onerror(event) { + if (DEBUG) { + if (event.target) { + debug("Caught error on transaction", event.target.errorCode); + } + } + aCallback.notify(Ci.nsIMobileMessageCallback.INTERNAL_ERROR, null, null); + }; + }); + }, + + /** + * nsIMobileMessageDatabaseService API + */ + + getMessage: function getMessage(aMessageId, aRequest) { + if (DEBUG) debug("Retrieving message with ID " + aMessageId); + let notifyCallback = { + notify: function notify(aRv, aMessageRecord, aDomMessage) { + if (Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR == aRv) { + aRequest.notifyMessageGot(aDomMessage); + return; + } + aRequest.notifyGetMessageFailed(aRv, null); + } + }; + this.getMessageRecordById(aMessageId, notifyCallback); + }, + + deleteMessage: function deleteMessage(messageIds, length, aRequest) { + if (DEBUG) debug("deleteMessage: message ids " + JSON.stringify(messageIds)); + let deleted = []; + let self = this; + this.newTxn(READ_WRITE, function (error, txn, stores) { + if (error) { + if (DEBUG) debug("deleteMessage: failed to open transaction"); + aRequest.notifyDeleteMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR); + return; + } + txn.onerror = function onerror(event) { + if (DEBUG) debug("Caught error on transaction", event.target.errorCode); + //TODO look at event.target.errorCode, pick appropriate error constant + aRequest.notifyDeleteMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR); + }; + + const messageStore = stores[0]; + const threadStore = stores[1]; + + txn.oncomplete = function oncomplete(event) { + if (DEBUG) debug("Transaction " + txn + " completed."); + aRequest.notifyMessageDeleted(deleted, length); + }; + + for (let i = 0; i < length; i++) { + let messageId = messageIds[i]; + deleted[i] = false; + messageStore.get(messageId).onsuccess = function(messageIndex, event) { + let messageRecord = event.target.result; + let messageId = messageIds[messageIndex]; + if (messageRecord) { + if (DEBUG) debug("Deleting message id " + messageId); + + // First actually delete the message. + messageStore.delete(messageId).onsuccess = function(event) { + if (DEBUG) debug("Message id " + messageId + " deleted"); + deleted[messageIndex] = true; + + // Then update unread count and most recent message. + self.updateThreadByMessageChange(messageStore, + threadStore, + messageRecord.threadId, + messageId, + messageRecord.read); + + Services.obs.notifyObservers(null, + "mobile-message-deleted", + JSON.stringify({ id: messageId })); + }; + } else if (DEBUG) { + debug("Message id " + messageId + " does not exist"); + } + }.bind(null, i); + } + }, [MESSAGE_STORE_NAME, THREAD_STORE_NAME]); + }, + + createMessageCursor: function createMessageCursor(filter, reverse, callback) { + if (DEBUG) { + debug("Creating a message cursor. Filters:" + + " startDate: " + filter.startDate + + " endDate: " + filter.endDate + + " delivery: " + filter.delivery + + " numbers: " + filter.numbers + + " read: " + filter.read + + " threadId: " + filter.threadId + + " reverse: " + reverse); + } + + let cursor = new GetMessagesCursor(this, callback); + + let self = this; + self.newTxn(READ_ONLY, function (error, txn, stores) { + let collector = cursor.collector; + let collect = collector.collect.bind(collector); + FilterSearcherHelper.transact(self, txn, error, filter, reverse, collect); + }, [MESSAGE_STORE_NAME, PARTICIPANT_STORE_NAME]); + + return cursor; + }, + + markMessageRead: function markMessageRead(messageId, value, aSendReadReport, aRequest) { + if (DEBUG) debug("Setting message " + messageId + " read to " + value); + this.newTxn(READ_WRITE, function (error, txn, stores) { + if (error) { + if (DEBUG) debug(error); + aRequest.notifyMarkMessageReadFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR); + return; + } + + txn.onerror = function onerror(event) { + if (DEBUG) debug("Caught error on transaction ", event.target.errorCode); + aRequest.notifyMarkMessageReadFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR); + }; + + let messageStore = stores[0]; + let threadStore = stores[1]; + messageStore.get(messageId).onsuccess = function onsuccess(event) { + let messageRecord = event.target.result; + if (!messageRecord) { + if (DEBUG) debug("Message ID " + messageId + " not found"); + aRequest.notifyMarkMessageReadFailed(Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR); + return; + } + + if (messageRecord.id != messageId) { + if (DEBUG) { + debug("Retrieve message ID (" + messageId + ") is " + + "different from the one we got"); + } + aRequest.notifyMarkMessageReadFailed(Ci.nsIMobileMessageCallback.UNKNOWN_ERROR); + return; + } + + // If the value to be set is the same as the current message `read` + // value, we just notify successfully. + if (messageRecord.read == value) { + if (DEBUG) debug("The value of messageRecord.read is already " + value); + aRequest.notifyMessageMarkedRead(messageRecord.read); + return; + } + + messageRecord.read = value ? FILTER_READ_READ : FILTER_READ_UNREAD; + messageRecord.readIndex = [messageRecord.read, messageRecord.timestamp]; + let readReportMessageId, readReportTo; + if (aSendReadReport && + messageRecord.type == "mms" && + messageRecord.delivery == DELIVERY_RECEIVED && + messageRecord.read == FILTER_READ_READ && + !messageRecord.isReadReportSent) { + messageRecord.isReadReportSent = true; + + let from = messageRecord.headers["from"]; + readReportTo = from && from.address; + readReportMessageId = messageRecord.headers["message-id"]; + } + + if (DEBUG) debug("Message.read set to: " + value); + messageStore.put(messageRecord).onsuccess = function onsuccess(event) { + if (DEBUG) { + debug("Update successfully completed. Message: " + + JSON.stringify(event.target.result)); + } + + // Now update the unread count. + let threadId = messageRecord.threadId; + + threadStore.get(threadId).onsuccess = function(event) { + let threadRecord = event.target.result; + threadRecord.unreadCount += value ? -1 : 1; + if (DEBUG) { + debug("Updating unreadCount for thread id " + threadId + ": " + + (value ? + threadRecord.unreadCount + 1 : + threadRecord.unreadCount - 1) + + " -> " + threadRecord.unreadCount); + } + threadStore.put(threadRecord).onsuccess = function(event) { + if(readReportMessageId && readReportTo) { + gMMSService.sendReadReport(readReportMessageId, + readReportTo, + messageRecord.iccId); + } + aRequest.notifyMessageMarkedRead(messageRecord.read); + }; + }; + }; + }; + }, [MESSAGE_STORE_NAME, THREAD_STORE_NAME]); + }, + + createThreadCursor: function createThreadCursor(callback) { + if (DEBUG) debug("Getting thread list"); + + let cursor = new GetThreadsCursor(this, callback); + this.newTxn(READ_ONLY, function (error, txn, threadStore) { + let collector = cursor.collector; + if (error) { + if (DEBUG) debug(error); + collector.collect(null, COLLECT_ID_ERROR, COLLECT_TIMESTAMP_UNUSED); + return; + } + txn.onerror = function onerror(event) { + if (DEBUG) debug("Caught error on transaction ", event.target.errorCode); + collector.collect(null, COLLECT_ID_ERROR, COLLECT_TIMESTAMP_UNUSED); + }; + let request = threadStore.index("lastTimestamp").openKeyCursor(); + request.onsuccess = function(event) { + let cursor = event.target.result; + if (cursor) { + if (collector.collect(txn, cursor.primaryKey, cursor.key)) { + cursor.continue(); + } + } else { + collector.collect(txn, COLLECT_ID_END, COLLECT_TIMESTAMP_UNUSED); + } + }; + }, [THREAD_STORE_NAME]); + + return cursor; + } +}; + +let FilterSearcherHelper = { + + /** + * @param index + * The name of a message store index to filter on. + * @param range + * A IDBKeyRange. + * @param direction + * NEXT or PREV. + * @param txn + * Ongoing IDBTransaction context object. + * @param collect + * Result colletor function. It takes three parameters -- txn, message + * id, and message timestamp. + */ + filterIndex: function filterIndex(index, range, direction, txn, collect) { + let messageStore = txn.objectStore(MESSAGE_STORE_NAME); + let request = messageStore.index(index).openKeyCursor(range, direction); + request.onsuccess = function onsuccess(event) { + let cursor = event.target.result; + // Once the cursor has retrieved all keys that matches its key range, + // the filter search is done. + if (cursor) { + let timestamp = Array.isArray(cursor.key) ? cursor.key[1] : cursor.key; + if (collect(txn, cursor.primaryKey, timestamp)) { + cursor.continue(); + } + } else { + collect(txn, COLLECT_ID_END, COLLECT_TIMESTAMP_UNUSED); + } + }; + request.onerror = function onerror(event) { + if (DEBUG && event) debug("IDBRequest error " + event.target.errorCode); + collect(txn, COLLECT_ID_ERROR, COLLECT_TIMESTAMP_UNUSED); + }; + }, + + /** + * Explicitly fiter message on the timestamp index. + * + * @param startDate + * Timestamp of the starting date. + * @param endDate + * Timestamp of the ending date. + * @param direction + * NEXT or PREV. + * @param txn + * Ongoing IDBTransaction context object. + * @param collect + * Result colletor function. It takes three parameters -- txn, message + * id, and message timestamp. + */ + filterTimestamp: function filterTimestamp(startDate, endDate, direction, txn, + collect) { + let range = null; + if (startDate != null && endDate != null) { + range = IDBKeyRange.bound(startDate.getTime(), endDate.getTime()); + } else if (startDate != null) { + range = IDBKeyRange.lowerBound(startDate.getTime()); + } else if (endDate != null) { + range = IDBKeyRange.upperBound(endDate.getTime()); + } + this.filterIndex("timestamp", range, direction, txn, collect); + }, + + /** + * Instanciate a filtering transaction. + * + * @param mmdb + * A MobileMessageDB. + * @param txn + * Ongoing IDBTransaction context object. + * @param error + * Previous error while creating the transaction. + * @param filter + * A SmsFilter object. + * @param reverse + * A boolean value indicating whether we should filter message in + * reversed order. + * @param collect + * Result colletor function. It takes three parameters -- txn, message + * id, and message timestamp. + */ + transact: function transact(mmdb, txn, error, filter, reverse, collect) { + if (error) { + //TODO look at event.target.errorCode, pick appropriate error constant. + if (DEBUG) debug("IDBRequest error " + error.target.errorCode); + collect(txn, COLLECT_ID_ERROR, COLLECT_TIMESTAMP_UNUSED); + return; + } + + let direction = reverse ? PREV : NEXT; + + // We support filtering by date range only (see `else` block below) or by + // number/delivery status/read status with an optional date range. + if (filter.delivery == null && + filter.numbers == null && + filter.read == null && + filter.threadId == null) { + // Filtering by date range only. + if (DEBUG) { + debug("filter.timestamp " + filter.startDate + ", " + filter.endDate); + } + + this.filterTimestamp(filter.startDate, filter.endDate, direction, txn, + collect); + return; + } + + // Numeric 0 is smaller than any time stamp, and empty string is larger + // than all numeric values. + let startDate = 0, endDate = ""; + if (filter.startDate != null) { + startDate = filter.startDate.getTime(); + } + if (filter.endDate != null) { + endDate = filter.endDate.getTime(); + } + + let single, intersectionCollector; + { + let num = 0; + if (filter.delivery) num++; + if (filter.numbers) num++; + if (filter.read != undefined) num++; + if (filter.threadId != undefined) num++; + single = (num == 1); + } + + if (!single) { + intersectionCollector = new IntersectionResultsCollector(collect, reverse); + } + + // Retrieve the keys from the 'delivery' index that matches the value of + // filter.delivery. + if (filter.delivery) { + if (DEBUG) debug("filter.delivery " + filter.delivery); + let delivery = filter.delivery; + let range = IDBKeyRange.bound([delivery, startDate], [delivery, endDate]); + this.filterIndex("delivery", range, direction, txn, + single ? collect : intersectionCollector.newContext()); + } + + // Retrieve the keys from the 'read' index that matches the value of + // filter.read. + if (filter.read != undefined) { + if (DEBUG) debug("filter.read " + filter.read); + let read = filter.read ? FILTER_READ_READ : FILTER_READ_UNREAD; + let range = IDBKeyRange.bound([read, startDate], [read, endDate]); + this.filterIndex("read", range, direction, txn, + single ? collect : intersectionCollector.newContext()); + } + + // Retrieve the keys from the 'threadId' index that matches the value of + // filter.threadId. + if (filter.threadId != undefined) { + if (DEBUG) debug("filter.threadId " + filter.threadId); + let threadId = filter.threadId; + let range = IDBKeyRange.bound([threadId, startDate], [threadId, endDate]); + this.filterIndex("threadId", range, direction, txn, + single ? collect : intersectionCollector.newContext()); + } + + // Retrieve the keys from the 'sender' and 'receiver' indexes that + // match the values of filter.numbers + if (filter.numbers) { + if (DEBUG) debug("filter.numbers " + filter.numbers.join(", ")); + + if (!single) { + collect = intersectionCollector.newContext(); + } + + let participantStore = txn.objectStore(PARTICIPANT_STORE_NAME); + mmdb.findParticipantIdsByAddresses(participantStore, filter.numbers, + false, true, + (function (participantIds) { + if (!participantIds || !participantIds.length) { + // Oops! No such participant at all. + + collect(txn, COLLECT_ID_END, COLLECT_TIMESTAMP_UNUSED); + return; + } + + if (participantIds.length == 1) { + let id = participantIds[0]; + let range = IDBKeyRange.bound([id, startDate], [id, endDate]); + this.filterIndex("participantIds", range, direction, txn, collect); + return; + } + + let unionCollector = new UnionResultsCollector(collect); + + this.filterTimestamp(filter.startDate, filter.endDate, direction, txn, + unionCollector.newTimestampContext()); + + for (let i = 0; i < participantIds.length; i++) { + let id = participantIds[i]; + let range = IDBKeyRange.bound([id, startDate], [id, endDate]); + this.filterIndex("participantIds", range, direction, txn, + unionCollector.newContext()); + } + }).bind(this)); + } + } +}; + +function ResultsCollector() { + this.results = []; + this.done = false; +} +ResultsCollector.prototype = { + results: null, + requestWaiting: null, + done: null, + + /** + * Queue up passed id, reply if necessary. + * + * @param txn + * Ongoing IDBTransaction context object. + * @param id + * COLLECT_ID_END(0) for no more results, COLLECT_ID_ERROR(-1) for + * errors and valid otherwise. + * @param timestamp + * We assume this function is always called in timestamp order. So + * this parameter is actually unused. + * + * @return true if expects more. false otherwise. + */ + collect: function collect(txn, id, timestamp) { + if (this.done) { + return false; + } + + if (DEBUG) { + debug("collect: message ID = " + id); + } + if (id) { + // Queue up any id but '0' and replies later accordingly. + this.results.push(id); + } + if (id <= 0) { + // No more processing on '0' or negative values passed. + this.done = true; + } + + if (!this.requestWaiting) { + if (DEBUG) debug("Cursor.continue() not called yet"); + return !this.done; + } + + // We assume there is only one request waiting throughout the message list + // retrieving process. So we don't bother continuing to process further + // waiting requests here. This assumption comes from DOMCursor::Continue() + // implementation. + let callback = this.requestWaiting; + this.requestWaiting = null; + + this.drip(txn, callback); + + return !this.done; + }, + + /** + * Callback right away with the first queued result entry if the filtering is + * done. Or queue up the request and callback when a new entry is available. + * + * @param callback + * A callback function that accepts a numeric id. + */ + squeeze: function squeeze(callback) { + if (this.requestWaiting) { + throw new Error("Already waiting for another request!"); + } + + if (!this.done) { + // Database transaction ongoing, let it reply for us so that we won't get + // blocked by the existing transaction. + this.requestWaiting = callback; + return; + } + + this.drip(null, callback); + }, + + /** + * @param txn + * Ongoing IDBTransaction context object or null. + * @param callback + * A callback function that accepts a numeric id. + */ + drip: function drip(txn, callback) { + if (!this.results.length) { + if (DEBUG) debug("No messages matching the filter criteria"); + callback(txn, COLLECT_ID_END); + return; + } + + if (this.results[0] < 0) { + // An previous error found. Keep the answer in results so that we can + // reply INTERNAL_ERROR for further requests. + if (DEBUG) debug("An previous error found"); + callback(txn, COLLECT_ID_ERROR); + return; + } + + let firstMessageId = this.results.shift(); + callback(txn, firstMessageId); + } +}; + +function IntersectionResultsCollector(collect, reverse) { + this.cascadedCollect = collect; + this.reverse = reverse; + this.contexts = []; +} +IntersectionResultsCollector.prototype = { + cascadedCollect: null, + reverse: false, + contexts: null, + + /** + * Queue up {id, timestamp} pairs, find out intersections and report to + * |cascadedCollect|. Return true if it is still possible to have another match. + */ + collect: function collect(contextIndex, txn, id, timestamp) { + if (DEBUG) { + debug("IntersectionResultsCollector: " + + contextIndex + ", " + id + ", " + timestamp); + } + + let contexts = this.contexts; + let context = contexts[contextIndex]; + + if (id < 0) { + // Act as no more matched records. + id = 0; + } + if (!id) { + context.done = true; + + if (!context.results.length) { + // Already empty, can't have further intersection results. + return this.cascadedCollect(txn, COLLECT_ID_END, COLLECT_TIMESTAMP_UNUSED); + } + + for (let i = 0; i < contexts.length; i++) { + if (!contexts[i].done) { + // Don't call |this.cascadedCollect| because |context.results| might not + // be empty, so other contexts might still have a chance here. + return false; + } + } + + // It was the last processing context and is no more processing. + return this.cascadedCollect(txn, COLLECT_ID_END, COLLECT_TIMESTAMP_UNUSED); + } + + // Search id in other existing results. If no other results has it, + // and A) the last timestamp is smaller-equal to current timestamp, + // we wait for further results; either B) record timestamp is larger + // then current timestamp or C) no more processing for a filter, then we + // drop this id because there can't be a match anymore. + for (let i = 0; i < contexts.length; i++) { + if (i == contextIndex) { + continue; + } + + let ctx = contexts[i]; + let results = ctx.results; + let found = false; + for (let j = 0; j < results.length; j++) { + let result = results[j]; + if (result.id == id) { + found = true; + break; + } + if ((!this.reverse && (result.timestamp > timestamp)) || + (this.reverse && (result.timestamp < timestamp))) { + // B) Cannot find a match anymore. Drop. + return true; + } + } + + if (!found) { + if (ctx.done) { + // C) Cannot find a match anymore. Drop. + if (results.length) { + let lastResult = results[results.length - 1]; + if ((!this.reverse && (lastResult.timestamp >= timestamp)) || + (this.reverse && (lastResult.timestamp <= timestamp))) { + // Still have a chance to get another match. Return true. + return true; + } + } + + // Impossible to find another match because all results in ctx have + // timestamps smaller than timestamp. + context.done = true; + return this.cascadedCollect(txn, COLLECT_ID_END, COLLECT_TIMESTAMP_UNUSED); + } + + // A) Pending. + context.results.push({ + id: id, + timestamp: timestamp + }); + return true; + } + } + + // Now id is found in all other results. Report it. + return this.cascadedCollect(txn, id, timestamp); + }, + + newContext: function newContext() { + let contextIndex = this.contexts.length; + this.contexts.push({ + results: [], + done: false + }); + return this.collect.bind(this, contextIndex); + } +}; + +function UnionResultsCollector(collect) { + this.cascadedCollect = collect; + this.contexts = [{ + // Timestamp. + processing: 1, + results: [] + }, { + processing: 0, + results: [] + }]; +} +UnionResultsCollector.prototype = { + cascadedCollect: null, + contexts: null, + + collect: function collect(contextIndex, txn, id, timestamp) { + if (DEBUG) { + debug("UnionResultsCollector: " + + contextIndex + ", " + id + ", " + timestamp); + } + + let contexts = this.contexts; + let context = contexts[contextIndex]; + + if (id < 0) { + // Act as no more matched records. + id = 0; + } + if (id) { + if (!contextIndex) { + // Timestamp. + context.results.push({ + id: id, + timestamp: timestamp + }); + } else { + context.results.push(id); + } + return true; + } + + context.processing -= 1; + if (contexts[0].processing || contexts[1].processing) { + // At least one queue is still processing, but we got here because + // current cursor gives 0 as id meaning no more messages are + // available. Return false here to stop further cursor.continue() calls. + return false; + } + + let tres = contexts[0].results; + let qres = contexts[1].results; + tres = tres.filter(function (element) { + return qres.indexOf(element.id) != -1; + }); + + for (let i = 0; i < tres.length; i++) { + this.cascadedCollect(txn, tres[i].id, tres[i].timestamp); + } + this.cascadedCollect(txn, COLLECT_ID_END, COLLECT_TIMESTAMP_UNUSED); + + return false; + }, + + newTimestampContext: function newTimestampContext() { + return this.collect.bind(this, 0); + }, + + newContext: function newContext() { + this.contexts[1].processing++; + return this.collect.bind(this, 1); + } +}; + +function GetMessagesCursor(mmdb, callback) { + this.mmdb = mmdb; + this.callback = callback; + this.collector = new ResultsCollector(); + + this.handleContinue(); // Trigger first run. +} +GetMessagesCursor.prototype = { + classID: RIL_GETMESSAGESCURSOR_CID, + QueryInterface: XPCOMUtils.generateQI([Ci.nsICursorContinueCallback]), + + mmdb: null, + callback: null, + collector: null, + + getMessageTxn: function getMessageTxn(messageStore, messageId) { + if (DEBUG) debug ("Fetching message " + messageId); + + let getRequest = messageStore.get(messageId); + let self = this; + getRequest.onsuccess = function onsuccess(event) { + if (DEBUG) { + debug("notifyNextMessageInListGot - messageId: " + messageId); + } + let domMessage = + self.mmdb.createDomMessageFromRecord(event.target.result); + self.callback.notifyCursorResult(domMessage); + }; + getRequest.onerror = function onerror(event) { + if (DEBUG) { + debug("notifyCursorError - messageId: " + messageId); + } + self.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR); + }; + }, + + notify: function notify(txn, messageId) { + if (!messageId) { + this.callback.notifyCursorDone(); + return; + } + + if (messageId < 0) { + this.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR); + return; + } + + // When filter transaction is not yet completed, we're called with current + // ongoing transaction object. + if (txn) { + let messageStore = txn.objectStore(MESSAGE_STORE_NAME); + this.getMessageTxn(messageStore, messageId); + return; + } + + // Or, we have to open another transaction ourselves. + let self = this; + this.mmdb.newTxn(READ_ONLY, function (error, txn, messageStore) { + if (error) { + self.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR); + return; + } + self.getMessageTxn(messageStore, messageId); + }, [MESSAGE_STORE_NAME]); + }, + + // nsICursorContinueCallback + + handleContinue: function handleContinue() { + if (DEBUG) debug("Getting next message in list"); + this.collector.squeeze(this.notify.bind(this)); + } +}; + +function GetThreadsCursor(mmdb, callback) { + this.mmdb = mmdb; + this.callback = callback; + this.collector = new ResultsCollector(); + + this.handleContinue(); // Trigger first run. +} +GetThreadsCursor.prototype = { + classID: RIL_GETTHREADSCURSOR_CID, + QueryInterface: XPCOMUtils.generateQI([Ci.nsICursorContinueCallback]), + + mmdb: null, + callback: null, + collector: null, + + getThreadTxn: function getThreadTxn(threadStore, threadId) { + if (DEBUG) debug ("Fetching thread " + threadId); + + let getRequest = threadStore.get(threadId); + let self = this; + getRequest.onsuccess = function onsuccess(event) { + let threadRecord = event.target.result; + if (DEBUG) { + debug("notifyCursorResult: " + JSON.stringify(threadRecord)); + } + let thread = + gMobileMessageService.createThread(threadRecord.id, + threadRecord.participantAddresses, + threadRecord.lastTimestamp, + threadRecord.lastMessageSubject || "", + threadRecord.body, + threadRecord.unreadCount, + threadRecord.lastMessageType); + self.callback.notifyCursorResult(thread); + }; + getRequest.onerror = function onerror(event) { + if (DEBUG) { + debug("notifyCursorError - threadId: " + threadId); + } + self.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR); + }; + }, + + notify: function notify(txn, threadId) { + if (!threadId) { + this.callback.notifyCursorDone(); + return; + } + + if (threadId < 0) { + this.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR); + return; + } + + // When filter transaction is not yet completed, we're called with current + // ongoing transaction object. + if (txn) { + let threadStore = txn.objectStore(THREAD_STORE_NAME); + this.getThreadTxn(threadStore, threadId); + return; + } + + // Or, we have to open another transaction ourselves. + let self = this; + this.mmdb.newTxn(READ_ONLY, function (error, txn, threadStore) { + if (error) { + self.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR); + return; + } + self.getThreadTxn(threadStore, threadId); + }, [THREAD_STORE_NAME]); + }, + + // nsICursorContinueCallback + + handleContinue: function handleContinue() { + if (DEBUG) debug("Getting next thread in list"); + this.collector.squeeze(this.notify.bind(this)); + } +} + +this.EXPORTED_SYMBOLS = [ + 'MobileMessageDB' +]; + +function debug() { + dump("MobileMessageDB: " + Array.slice(arguments).join(" ") + "\n"); +} diff --git a/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js b/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js index d56b18381d9b..b1dca1460b4e 100644 --- a/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js +++ b/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js @@ -8,77 +8,16 @@ const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/PhoneNumberUtils.jsm"); -Cu.importGlobalProperties(["indexedDB"]); -var RIL = {}; -Cu.import("resource://gre/modules/ril_consts.js", RIL); +let MMDB = {}; +Cu.import("resource://gre/modules/MobileMessageDB.jsm", MMDB); const RIL_MOBILEMESSAGEDATABASESERVICE_CONTRACTID = "@mozilla.org/mobilemessage/rilmobilemessagedatabaseservice;1"; const RIL_MOBILEMESSAGEDATABASESERVICE_CID = Components.ID("{29785f90-6b5b-11e2-9201-3b280170b2ec}"); -const RIL_GETMESSAGESCURSOR_CID = - Components.ID("{484d1ad8-840e-4782-9dc4-9ebc4d914937}"); -const RIL_GETTHREADSCURSOR_CID = - Components.ID("{95ee7c3e-d6f2-4ec4-ade5-0c453c036d35}"); - -const DEBUG = false; -const DISABLE_MMS_GROUPING_FOR_RECEIVING = true; - const DB_NAME = "sms"; -const DB_VERSION = 20; -const MESSAGE_STORE_NAME = "sms"; -const THREAD_STORE_NAME = "thread"; -const PARTICIPANT_STORE_NAME = "participant"; -const MOST_RECENT_STORE_NAME = "most-recent"; - -const DELIVERY_SENDING = "sending"; -const DELIVERY_SENT = "sent"; -const DELIVERY_RECEIVED = "received"; -const DELIVERY_NOT_DOWNLOADED = "not-downloaded"; -const DELIVERY_ERROR = "error"; - -const DELIVERY_STATUS_NOT_APPLICABLE = "not-applicable"; -const DELIVERY_STATUS_SUCCESS = "success"; -const DELIVERY_STATUS_PENDING = "pending"; -const DELIVERY_STATUS_ERROR = "error"; - -const MESSAGE_CLASS_NORMAL = "normal"; - -const FILTER_TIMESTAMP = "timestamp"; -const FILTER_NUMBERS = "numbers"; -const FILTER_DELIVERY = "delivery"; -const FILTER_READ = "read"; - -// We can´t create an IDBKeyCursor with a boolean, so we need to use numbers -// instead. -const FILTER_READ_UNREAD = 0; -const FILTER_READ_READ = 1; - -const READ_ONLY = "readonly"; -const READ_WRITE = "readwrite"; -const PREV = "prev"; -const NEXT = "next"; - -const COLLECT_ID_END = 0; -const COLLECT_ID_ERROR = -1; -const COLLECT_TIMESTAMP_UNUSED = 0; - -XPCOMUtils.defineLazyServiceGetter(this, "gMobileMessageService", - "@mozilla.org/mobilemessage/mobilemessageservice;1", - "nsIMobileMessageService"); - -XPCOMUtils.defineLazyServiceGetter(this, "gMMSService", - "@mozilla.org/mms/rilmmsservice;1", - "nsIMmsService"); - -XPCOMUtils.defineLazyGetter(this, "MMS", function () { - let MMS = {}; - Cu.import("resource://gre/modules/MmsPduHelper.jsm", MMS); - return MMS; -}); /** * MobileMessageDatabaseService @@ -88,34 +27,9 @@ function MobileMessageDatabaseService() { // by the time IndexedDB queries for it off the main thread. (See bug 743635.) Services.dirsvc.get("ProfD", Ci.nsIFile); - let that = this; - this.newTxn(READ_ONLY, function(error, txn, messageStore){ - if (error) { - return; - } - // In order to get the highest key value, we open a key cursor in reverse - // order and get only the first pointed value. - let request = messageStore.openCursor(null, PREV); - request.onsuccess = function onsuccess(event) { - let cursor = event.target.result; - if (!cursor) { - if (DEBUG) { - debug("Could not get the last key from mobile message database. " + - "Probably empty database"); - } - return; - } - that.lastMessageId = cursor.key || 0; - if (DEBUG) debug("Last assigned message ID was " + that.lastMessageId); - }; - request.onerror = function onerror(event) { - if (DEBUG) { - debug("Could not get the last key from mobile message database " + - event.target.errorCode); - } - }; - }); - this.updatePendingTransactionToError(); + let mmdb = new MMDB.MobileMessageDB(); + mmdb.init(DB_NAME, 0, mmdb.updatePendingTransactionToError.bind(mmdb)); + this.mmdb = mmdb; } MobileMessageDatabaseService.prototype = { @@ -125,3065 +39,78 @@ MobileMessageDatabaseService.prototype = { Ci.nsIObserver]), /** - * Cache the DB here. + * MobileMessageDB instance. */ - db: null, - - /** - * Last sms/mms object store key value in the database. - */ - lastMessageId: 0, + mmdb: null, /** * nsIObserver */ observe: function observe() {}, - /** - * Prepare the database. This may include opening the database and upgrading - * it to the latest schema version. - * - * @param callback - * Function that takes an error and db argument. It is called when - * the database is ready to use or if an error occurs while preparing - * the database. - * - * @return (via callback) a database ready for use. - */ - ensureDB: function ensureDB(callback) { - if (this.db) { - if (DEBUG) debug("ensureDB: already have a database, returning early."); - callback(null, this.db); - return; - } - - let self = this; - function gotDB(db) { - self.db = db; - callback(null, db); - } - - let request = indexedDB.open(DB_NAME, DB_VERSION); - request.onsuccess = function (event) { - if (DEBUG) debug("Opened database:", DB_NAME, DB_VERSION); - gotDB(event.target.result); - }; - request.onupgradeneeded = function (event) { - if (DEBUG) { - debug("Database needs upgrade:", DB_NAME, - event.oldVersion, event.newVersion); - debug("Correct new database version:", event.newVersion == DB_VERSION); - } - - let db = event.target.result; - - let currentVersion = event.oldVersion; - - function update(currentVersion) { - let next = update.bind(self, currentVersion + 1); - - switch (currentVersion) { - case 0: - if (DEBUG) debug("New database"); - self.createSchema(db, next); - break; - case 1: - if (DEBUG) debug("Upgrade to version 2. Including `read` index"); - self.upgradeSchema(event.target.transaction, next); - break; - case 2: - if (DEBUG) debug("Upgrade to version 3. Fix existing entries."); - self.upgradeSchema2(event.target.transaction, next); - break; - case 3: - if (DEBUG) debug("Upgrade to version 4. Add quick threads view."); - self.upgradeSchema3(db, event.target.transaction, next); - break; - case 4: - if (DEBUG) debug("Upgrade to version 5. Populate quick threads view."); - self.upgradeSchema4(event.target.transaction, next); - break; - case 5: - if (DEBUG) debug("Upgrade to version 6. Use PhonenumberJS."); - self.upgradeSchema5(event.target.transaction, next); - break; - case 6: - if (DEBUG) debug("Upgrade to version 7. Use multiple entry indexes."); - self.upgradeSchema6(event.target.transaction, next); - break; - case 7: - if (DEBUG) debug("Upgrade to version 8. Add participant/thread stores."); - self.upgradeSchema7(db, event.target.transaction, next); - break; - case 8: - if (DEBUG) debug("Upgrade to version 9. Add transactionId index for incoming MMS."); - self.upgradeSchema8(event.target.transaction, next); - break; - case 9: - if (DEBUG) debug("Upgrade to version 10. Upgrade type if it's not existing."); - self.upgradeSchema9(event.target.transaction, next); - break; - case 10: - if (DEBUG) debug("Upgrade to version 11. Add last message type into threadRecord."); - self.upgradeSchema10(event.target.transaction, next); - break; - case 11: - if (DEBUG) debug("Upgrade to version 12. Add envelopeId index for outgoing MMS."); - self.upgradeSchema11(event.target.transaction, next); - break; - case 12: - if (DEBUG) debug("Upgrade to version 13. Replaced deliveryStatus by deliveryInfo."); - self.upgradeSchema12(event.target.transaction, next); - break; - case 13: - if (DEBUG) debug("Upgrade to version 14. Fix the wrong participants."); - self.upgradeSchema13(event.target.transaction, next); - break; - case 14: - if (DEBUG) debug("Upgrade to version 15. Add deliveryTimestamp."); - self.upgradeSchema14(event.target.transaction, next); - break; - case 15: - if (DEBUG) debug("Upgrade to version 16. Add ICC ID for each message."); - self.upgradeSchema15(event.target.transaction, next); - break; - case 16: - if (DEBUG) debug("Upgrade to version 17. Add isReadReportSent for incoming MMS."); - self.upgradeSchema16(event.target.transaction, next); - break; - case 17: - if (DEBUG) debug("Upgrade to version 18. Add last message subject into threadRecord."); - self.upgradeSchema17(event.target.transaction, next); - break; - case 18: - if (DEBUG) debug("Upgrade to version 19. Add pid for incoming SMS."); - self.upgradeSchema18(event.target.transaction, next); - break; - case 19: - if (DEBUG) debug("Upgrade to version 20. Add readStatus and readTimestamp."); - self.upgradeSchema19(event.target.transaction, next); - break; - case 20: - // This will need to be moved for each new version - if (DEBUG) debug("Upgrade finished."); - break; - default: - event.target.transaction.abort(); - callback("Old database version: " + event.oldVersion, null); - break; - } - } - - update(currentVersion); - }; - request.onerror = function (event) { - //TODO look at event.target.Code and change error constant accordingly - callback("Error opening database!", null); - }; - request.onblocked = function (event) { - callback("Opening database request is blocked.", null); - }; - }, - - /** - * Start a new transaction. - * - * @param txn_type - * Type of transaction (e.g. READ_WRITE) - * @param callback - * Function to call when the transaction is available. It will - * be invoked with the transaction and opened object stores. - * @param storeNames - * Names of the stores to open. - */ - newTxn: function newTxn(txn_type, callback, storeNames) { - if (!storeNames) { - storeNames = [MESSAGE_STORE_NAME]; - } - if (DEBUG) debug("Opening transaction for object stores: " + storeNames); - this.ensureDB(function (error, db) { - if (error) { - if (DEBUG) debug("Could not open database: " + error); - callback(error); - return; - } - let txn = db.transaction(storeNames, txn_type); - if (DEBUG) debug("Started transaction " + txn + " of type " + txn_type); - if (DEBUG) { - txn.oncomplete = function oncomplete(event) { - debug("Transaction " + txn + " completed."); - }; - txn.onerror = function onerror(event) { - //TODO check event.target.errorCode and show an appropiate error - // message according to it. - debug("Error occurred during transaction: " + event.target.errorCode); - }; - } - let stores; - if (storeNames.length == 1) { - if (DEBUG) debug("Retrieving object store " + storeNames[0]); - stores = txn.objectStore(storeNames[0]); - } else { - stores = []; - for each (let storeName in storeNames) { - if (DEBUG) debug("Retrieving object store " + storeName); - stores.push(txn.objectStore(storeName)); - } - } - callback(null, txn, stores); - }); - }, - - /** - * Sometimes user might reboot or remove battery while sending/receiving - * message. This is function set the status of message records to error. - */ - updatePendingTransactionToError: function updatePendingTransactionToError() { - this.newTxn(READ_WRITE, function (error, txn, messageStore) { - if (DEBUG) { - txn.onerror = function onerror(event) { - debug("updatePendingTransactionToError fail, event = " + event); - }; - } - - let deliveryIndex = messageStore.index("delivery"); - - // Set all 'delivery: sending' records to 'delivery: error' and 'deliveryStatus: - // error'. - let keyRange = IDBKeyRange.bound([DELIVERY_SENDING, 0], [DELIVERY_SENDING, ""]); - let cursorRequestSending = deliveryIndex.openCursor(keyRange); - cursorRequestSending.onsuccess = function(event) { - let messageCursor = event.target.result; - if (!messageCursor) { - return; - } - - let messageRecord = messageCursor.value; - - // Set delivery to error. - messageRecord.delivery = DELIVERY_ERROR; - messageRecord.deliveryIndex = [DELIVERY_ERROR, messageRecord.timestamp]; - - if (messageRecord.type == "sms") { - messageRecord.deliveryStatus = DELIVERY_STATUS_ERROR; - } else { - // Set delivery status to error. - for (let i = 0; i < messageRecord.deliveryInfo.length; i++) { - messageRecord.deliveryInfo[i].deliveryStatus = DELIVERY_STATUS_ERROR; - } - } - - messageCursor.update(messageRecord); - messageCursor.continue(); - }; - - // Set all 'delivery: not-downloaded' and 'deliveryStatus: pending' - // records to 'delivery: not-downloaded' and 'deliveryStatus: error'. - keyRange = IDBKeyRange.bound([DELIVERY_NOT_DOWNLOADED, 0], [DELIVERY_NOT_DOWNLOADED, ""]); - let cursorRequestNotDownloaded = deliveryIndex.openCursor(keyRange); - cursorRequestNotDownloaded.onsuccess = function(event) { - let messageCursor = event.target.result; - if (!messageCursor) { - return; - } - - let messageRecord = messageCursor.value; - - // We have no "not-downloaded" SMS messages. - if (messageRecord.type == "sms") { - messageCursor.continue(); - return; - } - - // Set delivery status to error. - let deliveryInfo = messageRecord.deliveryInfo; - if (deliveryInfo.length == 1 && - deliveryInfo[0].deliveryStatus == DELIVERY_STATUS_PENDING) { - deliveryInfo[0].deliveryStatus = DELIVERY_STATUS_ERROR; - } - - messageCursor.update(messageRecord); - messageCursor.continue(); - }; - }); - }, - - /** - * Create the initial database schema. - * - * TODO need to worry about number normalization somewhere... - * TODO full text search on body??? - */ - createSchema: function createSchema(db, next) { - // This messageStore holds the main mobile message data. - let messageStore = db.createObjectStore(MESSAGE_STORE_NAME, { keyPath: "id" }); - messageStore.createIndex("timestamp", "timestamp", { unique: false }); - if (DEBUG) debug("Created object stores and indexes"); - next(); - }, - - /** - * Upgrade to the corresponding database schema version. - */ - upgradeSchema: function upgradeSchema(transaction, next) { - let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); - messageStore.createIndex("read", "read", { unique: false }); - next(); - }, - - upgradeSchema2: function upgradeSchema2(transaction, next) { - let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); - messageStore.openCursor().onsuccess = function(event) { - let cursor = event.target.result; - if (!cursor) { - next(); - return; - } - - let messageRecord = cursor.value; - messageRecord.messageClass = MESSAGE_CLASS_NORMAL; - messageRecord.deliveryStatus = DELIVERY_STATUS_NOT_APPLICABLE; - cursor.update(messageRecord); - cursor.continue(); - }; - }, - - upgradeSchema3: function upgradeSchema3(db, transaction, next) { - // Delete redundant "id" index. - let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); - if (messageStore.indexNames.contains("id")) { - messageStore.deleteIndex("id"); - } - - /** - * This mostRecentStore can be used to quickly construct a thread view of - * the mobile message database. Each entry looks like this: - * - * { senderOrReceiver: (primary key), - * id: , - * timestamp: , - * body: , - * unreadCount: } - * - */ - let mostRecentStore = db.createObjectStore(MOST_RECENT_STORE_NAME, - { keyPath: "senderOrReceiver" }); - mostRecentStore.createIndex("timestamp", "timestamp"); - next(); - }, - - upgradeSchema4: function upgradeSchema4(transaction, next) { - let threads = {}; - let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); - let mostRecentStore = transaction.objectStore(MOST_RECENT_STORE_NAME); - - messageStore.openCursor().onsuccess = function(event) { - let cursor = event.target.result; - if (!cursor) { - for (let thread in threads) { - mostRecentStore.put(threads[thread]); - } - next(); - return; - } - - let messageRecord = cursor.value; - let contact = messageRecord.sender || messageRecord.receiver; - - if (contact in threads) { - let thread = threads[contact]; - if (!messageRecord.read) { - thread.unreadCount++; - } - if (messageRecord.timestamp > thread.timestamp) { - thread.id = messageRecord.id; - thread.body = messageRecord.body; - thread.timestamp = messageRecord.timestamp; - } - } else { - threads[contact] = { - senderOrReceiver: contact, - id: messageRecord.id, - timestamp: messageRecord.timestamp, - body: messageRecord.body, - unreadCount: messageRecord.read ? 0 : 1 - }; - } - cursor.continue(); - }; - }, - - upgradeSchema5: function upgradeSchema5(transaction, next) { - // Don't perform any upgrade. See Bug 819560. - next(); - }, - - upgradeSchema6: function upgradeSchema6(transaction, next) { - let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); - - // Delete "delivery" index. - if (messageStore.indexNames.contains("delivery")) { - messageStore.deleteIndex("delivery"); - } - // Delete "sender" index. - if (messageStore.indexNames.contains("sender")) { - messageStore.deleteIndex("sender"); - } - // Delete "receiver" index. - if (messageStore.indexNames.contains("receiver")) { - messageStore.deleteIndex("receiver"); - } - // Delete "read" index. - if (messageStore.indexNames.contains("read")) { - messageStore.deleteIndex("read"); - } - - // Create new "delivery", "number" and "read" indexes. - messageStore.createIndex("delivery", "deliveryIndex"); - messageStore.createIndex("number", "numberIndex", { multiEntry: true }); - messageStore.createIndex("read", "readIndex"); - - // Populate new "deliverIndex", "numberIndex" and "readIndex" attributes. - messageStore.openCursor().onsuccess = function(event) { - let cursor = event.target.result; - if (!cursor) { - next(); - return; - } - - let messageRecord = cursor.value; - let timestamp = messageRecord.timestamp; - messageRecord.deliveryIndex = [messageRecord.delivery, timestamp]; - messageRecord.numberIndex = [ - [messageRecord.sender, timestamp], - [messageRecord.receiver, timestamp] - ]; - messageRecord.readIndex = [messageRecord.read, timestamp]; - cursor.update(messageRecord); - cursor.continue(); - }; - }, - - /** - * Add participant/thread stores. - * - * The message store now saves original phone numbers/addresses input from - * content to message records. No normalization is made. - * - * For filtering messages by phone numbers, it first looks up corresponding - * participant IDs from participant table and fetch message records with - * matching keys defined in per record "participantIds" field. - * - * For message threading, messages with the same participant ID array are put - * in the same thread. So updating "unreadCount", "lastMessageId" and - * "lastTimestamp" are through the "threadId" carried by per message record. - * Fetching threads list is now simply walking through the thread sotre. The - * "mostRecentStore" is dropped. - */ - upgradeSchema7: function upgradeSchema7(db, transaction, next) { - /** - * This "participant" object store keeps mappings of multiple phone numbers - * of the same recipient to an integer participant id. Each entry looks - * like: - * - * { id: (primary key), - * addresses: } - */ - let participantStore = db.createObjectStore(PARTICIPANT_STORE_NAME, - { keyPath: "id", - autoIncrement: true }); - participantStore.createIndex("addresses", "addresses", { multiEntry: true }); - - /** - * This "threads" object store keeps mappings from an integer thread id to - * ids of the participants of that message thread. Each entry looks like: - * - * { id: (primary key), - * participantIds: , - * participantAddresses: , - * lastMessageId: , - * lastTimestamp: , - * subject: , - * unreadCount: } - * - */ - let threadStore = db.createObjectStore(THREAD_STORE_NAME, - { keyPath: "id", - autoIncrement: true }); - threadStore.createIndex("participantIds", "participantIds"); - threadStore.createIndex("lastTimestamp", "lastTimestamp"); - - /** - * Replace "numberIndex" with "participantIdsIndex" and create an additional - * "threadId". "numberIndex" will be removed later. - */ - let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); - messageStore.createIndex("threadId", "threadIdIndex"); - messageStore.createIndex("participantIds", "participantIdsIndex", - { multiEntry: true }); - - // Now populate participantStore & threadStore. - let mostRecentStore = transaction.objectStore(MOST_RECENT_STORE_NAME); - let self = this; - let mostRecentRequest = mostRecentStore.openCursor(); - mostRecentRequest.onsuccess = function(event) { - let mostRecentCursor = event.target.result; - if (!mostRecentCursor) { - db.deleteObjectStore(MOST_RECENT_STORE_NAME); - - // No longer need the "number" index in messageStore, use - // "participantIds" index instead. - messageStore.deleteIndex("number"); - next(); - return; - } - - let mostRecentRecord = mostRecentCursor.value; - - // Each entry in mostRecentStore is supposed to be a unique thread, so we - // retrieve the records out and insert its "senderOrReceiver" column as a - // new record in participantStore. - let number = mostRecentRecord.senderOrReceiver; - self.findParticipantRecordByAddress(participantStore, number, true, - function (participantRecord) { - // Also create a new record in threadStore. - let threadRecord = { - participantIds: [participantRecord.id], - participantAddresses: [number], - lastMessageId: mostRecentRecord.id, - lastTimestamp: mostRecentRecord.timestamp, - subject: mostRecentRecord.body, - unreadCount: mostRecentRecord.unreadCount, - }; - let addThreadRequest = threadStore.add(threadRecord); - addThreadRequest.onsuccess = function (event) { - threadRecord.id = event.target.result; - - let numberRange = IDBKeyRange.bound([number, 0], [number, ""]); - let messageRequest = messageStore.index("number") - .openCursor(numberRange, NEXT); - messageRequest.onsuccess = function (event) { - let messageCursor = event.target.result; - if (!messageCursor) { - // No more message records, check next most recent record. - mostRecentCursor.continue(); - return; - } - - let messageRecord = messageCursor.value; - // Check whether the message really belongs to this thread. - let matchSenderOrReceiver = false; - if (messageRecord.delivery == DELIVERY_RECEIVED) { - if (messageRecord.sender == number) { - matchSenderOrReceiver = true; - } - } else if (messageRecord.receiver == number) { - matchSenderOrReceiver = true; - } - if (!matchSenderOrReceiver) { - // Check next message record. - messageCursor.continue(); - return; - } - - messageRecord.threadId = threadRecord.id; - messageRecord.threadIdIndex = [threadRecord.id, - messageRecord.timestamp]; - messageRecord.participantIdsIndex = [ - [participantRecord.id, messageRecord.timestamp] - ]; - messageCursor.update(messageRecord); - // Check next message record. - messageCursor.continue(); - }; - messageRequest.onerror = function () { - // Error in fetching message records, check next most recent record. - mostRecentCursor.continue(); - }; - }; - addThreadRequest.onerror = function () { - // Error in fetching message records, check next most recent record. - mostRecentCursor.continue(); - }; - }); - }; - }, - - /** - * Add transactionId index for MMS. - */ - upgradeSchema8: function upgradeSchema8(transaction, next) { - let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); - - // Delete "transactionId" index. - if (messageStore.indexNames.contains("transactionId")) { - messageStore.deleteIndex("transactionId"); - } - - // Create new "transactionId" indexes. - messageStore.createIndex("transactionId", "transactionIdIndex", { unique: true }); - - // Populate new "transactionIdIndex" attributes. - messageStore.openCursor().onsuccess = function(event) { - let cursor = event.target.result; - if (!cursor) { - next(); - return; - } - - let messageRecord = cursor.value; - if ("mms" == messageRecord.type && - (DELIVERY_NOT_DOWNLOADED == messageRecord.delivery || - DELIVERY_RECEIVED == messageRecord.delivery)) { - messageRecord.transactionIdIndex = - messageRecord.headers["x-mms-transaction-id"]; - cursor.update(messageRecord); - } - cursor.continue(); - }; - }, - - upgradeSchema9: function upgradeSchema9(transaction, next) { - let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); - - // Update type attributes. - messageStore.openCursor().onsuccess = function(event) { - let cursor = event.target.result; - if (!cursor) { - next(); - return; - } - - let messageRecord = cursor.value; - if (messageRecord.type == undefined) { - messageRecord.type = "sms"; - cursor.update(messageRecord); - } - cursor.continue(); - }; - }, - - upgradeSchema10: function upgradeSchema10(transaction, next) { - let threadStore = transaction.objectStore(THREAD_STORE_NAME); - - // Add 'lastMessageType' to each thread record. - threadStore.openCursor().onsuccess = function(event) { - let cursor = event.target.result; - if (!cursor) { - next(); - return; - } - - let threadRecord = cursor.value; - let lastMessageId = threadRecord.lastMessageId; - let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); - let request = messageStore.mozGetAll(lastMessageId); - - request.onsuccess = function onsuccess() { - let messageRecord = request.result[0]; - if (!messageRecord) { - if (DEBUG) debug("Message ID " + lastMessageId + " not found"); - return; - } - if (messageRecord.id != lastMessageId) { - if (DEBUG) { - debug("Requested message ID (" + lastMessageId + ") is different from" + - " the one we got"); - } - return; - } - threadRecord.lastMessageType = messageRecord.type; - cursor.update(threadRecord); - cursor.continue(); - }; - - request.onerror = function onerror(event) { - if (DEBUG) { - if (event.target) { - debug("Caught error on transaction", event.target.errorCode); - } - } - cursor.continue(); - }; - }; - }, - - /** - * Add envelopeId index for MMS. - */ - upgradeSchema11: function upgradeSchema11(transaction, next) { - let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); - - // Delete "envelopeId" index. - if (messageStore.indexNames.contains("envelopeId")) { - messageStore.deleteIndex("envelopeId"); - } - - // Create new "envelopeId" indexes. - messageStore.createIndex("envelopeId", "envelopeIdIndex", { unique: true }); - - // Populate new "envelopeIdIndex" attributes. - messageStore.openCursor().onsuccess = function(event) { - let cursor = event.target.result; - if (!cursor) { - next(); - return; - } - - let messageRecord = cursor.value; - if (messageRecord.type == "mms" && - messageRecord.delivery == DELIVERY_SENT) { - messageRecord.envelopeIdIndex = messageRecord.headers["message-id"]; - cursor.update(messageRecord); - } - cursor.continue(); - }; - }, - - /** - * Replace deliveryStatus by deliveryInfo. - */ - upgradeSchema12: function upgradeSchema12(transaction, next) { - let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); - - messageStore.openCursor().onsuccess = function(event) { - let cursor = event.target.result; - if (!cursor) { - next(); - return; - } - - let messageRecord = cursor.value; - if (messageRecord.type == "mms") { - messageRecord.deliveryInfo = []; - - if (messageRecord.deliveryStatus.length == 1 && - (messageRecord.delivery == DELIVERY_NOT_DOWNLOADED || - messageRecord.delivery == DELIVERY_RECEIVED)) { - messageRecord.deliveryInfo.push({ - receiver: null, - deliveryStatus: messageRecord.deliveryStatus[0] }); - } else { - for (let i = 0; i < messageRecord.deliveryStatus.length; i++) { - messageRecord.deliveryInfo.push({ - receiver: messageRecord.receivers[i], - deliveryStatus: messageRecord.deliveryStatus[i] }); - } - } - delete messageRecord.deliveryStatus; - cursor.update(messageRecord); - } - cursor.continue(); - }; - }, - - /** - * Fix the wrong participants. - */ - upgradeSchema13: function upgradeSchema13(transaction, next) { - let participantStore = transaction.objectStore(PARTICIPANT_STORE_NAME); - let threadStore = transaction.objectStore(THREAD_STORE_NAME); - let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); - let self = this; - - let isInvalid = function (participantRecord) { - let entries = []; - for (let addr of participantRecord.addresses) { - entries.push({ - normalized: addr, - parsed: PhoneNumberUtils.parseWithMCC(addr, null) - }) - } - for (let ix = 0 ; ix < entries.length - 1; ix++) { - let entry1 = entries[ix]; - for (let iy = ix + 1 ; iy < entries.length; iy ++) { - let entry2 = entries[iy]; - if (!self.matchPhoneNumbers(entry1.normalized, entry1.parsed, - entry2.normalized, entry2.parsed)) { - return true; - } - } - } - return false; - }; - - let invalidParticipantIds = []; - participantStore.openCursor().onsuccess = function(event) { - let cursor = event.target.result; - if (cursor) { - let participantRecord = cursor.value; - // Check if this participant record is valid - if (isInvalid(participantRecord)) { - invalidParticipantIds.push(participantRecord.id); - cursor.delete(); - } - cursor.continue(); - return; - } - - // Participant store cursor iteration done. - if (!invalidParticipantIds.length) { - next(); - return; - } - - // Find affected thread. - let wrongThreads = []; - threadStore.openCursor().onsuccess = function(event) { - let threadCursor = event.target.result; - if (threadCursor) { - let threadRecord = threadCursor.value; - let participantIds = threadRecord.participantIds; - let foundInvalid = false; - for (let invalidParticipantId of invalidParticipantIds) { - if (participantIds.indexOf(invalidParticipantId) != -1) { - foundInvalid = true; - break; - } - } - if (foundInvalid) { - wrongThreads.push(threadRecord.id); - threadCursor.delete(); - } - threadCursor.continue(); - return; - } - - if (!wrongThreads.length) { - next(); - return; - } - // Use recursive function to avoid we add participant twice. - (function createUpdateThreadAndParticipant(ix) { - let threadId = wrongThreads[ix]; - let range = IDBKeyRange.bound([threadId, 0], [threadId, ""]); - messageStore.index("threadId").openCursor(range).onsuccess = function(event) { - let messageCursor = event.target.result; - if (!messageCursor) { - ix++; - if (ix === wrongThreads.length) { - next(); - return; - } - createUpdateThreadAndParticipant(ix); - return; - } - - let messageRecord = messageCursor.value; - let timestamp = messageRecord.timestamp; - let threadParticipants = []; - // Recaculate the thread participants of received message. - if (messageRecord.delivery === DELIVERY_RECEIVED || - messageRecord.delivery === DELIVERY_NOT_DOWNLOADED) { - threadParticipants.push(messageRecord.sender); - if (messageRecord.type == "mms") { - this.fillReceivedMmsThreadParticipants(messageRecord, threadParticipants); - } - } - // Recaculate the thread participants of sent messages and error - // messages. In error sms messages, we don't have error received sms. - // In received MMS, we don't update the error to deliver field but - // deliverStatus. So we only consider sent message in DELIVERY_ERROR. - else if (messageRecord.delivery === DELIVERY_SENT || - messageRecord.delivery === DELIVERY_ERROR) { - if (messageRecord.type == "sms") { - threadParticipants = [messageRecord.receiver]; - } else if (messageRecord.type == "mms") { - threadParticipants = messageRecord.receivers; - } - } - self.findThreadRecordByParticipants(threadStore, participantStore, - threadParticipants, true, - function (threadRecord, - participantIds) { - if (!participantIds) { - debug("participantIds is empty!"); - return; - } - - let timestamp = messageRecord.timestamp; - // Setup participantIdsIndex. - messageRecord.participantIdsIndex = []; - for each (let id in participantIds) { - messageRecord.participantIdsIndex.push([id, timestamp]); - } - if (threadRecord) { - let needsUpdate = false; - - if (threadRecord.lastTimestamp <= timestamp) { - threadRecord.lastTimestamp = timestamp; - threadRecord.subject = messageRecord.body; - threadRecord.lastMessageId = messageRecord.id; - threadRecord.lastMessageType = messageRecord.type; - needsUpdate = true; - } - - if (!messageRecord.read) { - threadRecord.unreadCount++; - needsUpdate = true; - } - - if (needsUpdate) { - threadStore.put(threadRecord); - } - messageRecord.threadId = threadRecord.id; - messageRecord.threadIdIndex = [threadRecord.id, timestamp]; - messageCursor.update(messageRecord); - messageCursor.continue(); - return; - } - - let threadRecord = { - participantIds: participantIds, - participantAddresses: threadParticipants, - lastMessageId: messageRecord.id, - lastTimestamp: timestamp, - subject: messageRecord.body, - unreadCount: messageRecord.read ? 0 : 1, - lastMessageType: messageRecord.type - }; - threadStore.add(threadRecord).onsuccess = function (event) { - let threadId = event.target.result; - // Setup threadId & threadIdIndex. - messageRecord.threadId = threadId; - messageRecord.threadIdIndex = [threadId, timestamp]; - messageCursor.update(messageRecord); - messageCursor.continue(); - }; - }); - }; - })(0); - }; - }; - }, - - /** - * Add deliveryTimestamp. - */ - upgradeSchema14: function upgradeSchema14(transaction, next) { - let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); - - messageStore.openCursor().onsuccess = function(event) { - let cursor = event.target.result; - if (!cursor) { - next(); - return; - } - - let messageRecord = cursor.value; - if (messageRecord.type == "sms") { - messageRecord.deliveryTimestamp = 0; - } else if (messageRecord.type == "mms") { - let deliveryInfo = messageRecord.deliveryInfo; - for (let i = 0; i < deliveryInfo.length; i++) { - deliveryInfo[i].deliveryTimestamp = 0; - } - } - cursor.update(messageRecord); - cursor.continue(); - }; - }, - - /** - * Add ICC ID. - */ - upgradeSchema15: function upgradeSchema15(transaction, next) { - let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); - messageStore.openCursor().onsuccess = function(event) { - let cursor = event.target.result; - if (!cursor) { - next(); - return; - } - - let messageRecord = cursor.value; - messageRecord.iccId = null; - cursor.update(messageRecord); - cursor.continue(); - }; - }, - - /** - * Add isReadReportSent for incoming MMS. - */ - upgradeSchema16: function upgradeSchema16(transaction, next) { - let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); - - // Update type attributes. - messageStore.openCursor().onsuccess = function(event) { - let cursor = event.target.result; - if (!cursor) { - next(); - return; - } - - let messageRecord = cursor.value; - if (messageRecord.type == "mms") { - messageRecord.isReadReportSent = false; - cursor.update(messageRecord); - } - cursor.continue(); - }; - }, - - upgradeSchema17: function upgradeSchema17(transaction, next) { - let threadStore = transaction.objectStore(THREAD_STORE_NAME); - let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); - - // Add 'lastMessageSubject' to each thread record. - threadStore.openCursor().onsuccess = function(event) { - let cursor = event.target.result; - if (!cursor) { - next(); - return; - } - - let threadRecord = cursor.value; - // We have defined 'threadRecord.subject' in upgradeSchema7(), but it - // actually means 'threadRecord.body'. Swap the two values first. - threadRecord.body = threadRecord.subject; - delete threadRecord.subject; - - // Only MMS supports subject so assign null for non-MMS one. - if (threadRecord.lastMessageType != "mms") { - threadRecord.lastMessageSubject = null; - cursor.update(threadRecord); - - cursor.continue(); - return; - } - - messageStore.get(threadRecord.lastMessageId).onsuccess = function(event) { - let messageRecord = event.target.result; - let subject = messageRecord.headers.subject; - threadRecord.lastMessageSubject = subject || null; - cursor.update(threadRecord); - - cursor.continue(); - }; - }; - }, - - /** - * Add pid for incoming SMS. - */ - upgradeSchema18: function upgradeSchema18(transaction, next) { - let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); - - messageStore.openCursor().onsuccess = function(event) { - let cursor = event.target.result; - if (!cursor) { - next(); - return; - } - - let messageRecord = cursor.value; - if (messageRecord.type == "sms") { - messageRecord.pid = RIL.PDU_PID_DEFAULT; - cursor.update(messageRecord); - } - cursor.continue(); - }; - }, - - /** - * Add readStatus and readTimestamp. - */ - upgradeSchema19: function upgradeSchema19(transaction, next) { - let messageStore = transaction.objectStore(MESSAGE_STORE_NAME); - messageStore.openCursor().onsuccess = function(event) { - let cursor = event.target.result; - if (!cursor) { - next(); - return; - } - - let messageRecord = cursor.value; - if (messageRecord.type == "sms") { - cursor.continue(); - return; - } - - // We can always retrieve transaction id from - // |messageRecord.headers["x-mms-transaction-id"]|. - if (messageRecord.hasOwnProperty("transactionId")) { - delete messageRecord.transactionId; - } - - // xpconnect gives "undefined" for an unassigned argument of an interface - // method. - if (messageRecord.envelopeIdIndex === "undefined") { - delete messageRecord.envelopeIdIndex; - } - - // Convert some header fields that were originally decoded as BooleanValue - // to numeric enums. - for (let field of ["x-mms-cancel-status", - "x-mms-sender-visibility", - "x-mms-read-status"]) { - let value = messageRecord.headers[field]; - if (value !== undefined) { - messageRecord.headers[field] = value ? 128 : 129; - } - } - - // For all sent and received MMS messages, we have to add their - // |readStatus| and |readTimestamp| attributes in |deliveryInfo| array. - let readReportRequested = - messageRecord.headers["x-mms-read-report"] || false; - for (let element of messageRecord.deliveryInfo) { - element.readStatus = readReportRequested - ? MMS.DOM_READ_STATUS_PENDING - : MMS.DOM_READ_STATUS_NOT_APPLICABLE; - element.readTimestamp = 0; - } - - cursor.update(messageRecord); - cursor.continue(); - }; - }, - - matchParsedPhoneNumbers: function matchParsedPhoneNumbers(addr1, parsedAddr1, - addr2, parsedAddr2) { - if ((parsedAddr1.internationalNumber && - parsedAddr1.internationalNumber === parsedAddr2.internationalNumber) || - (parsedAddr1.nationalNumber && - parsedAddr1.nationalNumber === parsedAddr2.nationalNumber)) { - return true; - } - - if (parsedAddr1.countryName != parsedAddr2.countryName) { - return false; - } - - let ssPref = "dom.phonenumber.substringmatching." + parsedAddr1.countryName; - if (Services.prefs.getPrefType(ssPref) != Ci.nsIPrefBranch.PREF_INT) { - return false; - } - - let val = Services.prefs.getIntPref(ssPref); - return addr1.length > val && - addr2.length > val && - addr1.slice(-val) === addr2.slice(-val); - }, - - matchPhoneNumbers: function matchPhoneNumbers(addr1, parsedAddr1, addr2, parsedAddr2) { - if (parsedAddr1 && parsedAddr2) { - return this.matchParsedPhoneNumbers(addr1, parsedAddr1, addr2, parsedAddr2); - } - - if (parsedAddr1) { - parsedAddr2 = PhoneNumberUtils.parseWithCountryName(addr2, parsedAddr1.countryName); - if (parsedAddr2) { - return this.matchParsedPhoneNumbers(addr1, parsedAddr1, addr2, parsedAddr2); - } - - return false; - } - - if (parsedAddr2) { - parsedAddr1 = PhoneNumberUtils.parseWithCountryName(addr1, parsedAddr2.countryName); - if (parsedAddr1) { - return this.matchParsedPhoneNumbers(addr1, parsedAddr1, addr2, parsedAddr2); - } - } - - return false; - }, - - createDomMessageFromRecord: function createDomMessageFromRecord(aMessageRecord) { - if (DEBUG) { - debug("createDomMessageFromRecord: " + JSON.stringify(aMessageRecord)); - } - if (aMessageRecord.type == "sms") { - return gMobileMessageService.createSmsMessage(aMessageRecord.id, - aMessageRecord.threadId, - aMessageRecord.iccId, - aMessageRecord.delivery, - aMessageRecord.deliveryStatus, - aMessageRecord.sender, - aMessageRecord.receiver, - aMessageRecord.body, - aMessageRecord.messageClass, - aMessageRecord.timestamp, - aMessageRecord.deliveryTimestamp, - aMessageRecord.read); - } else if (aMessageRecord.type == "mms") { - let headers = aMessageRecord["headers"]; - if (DEBUG) { - debug("MMS: headers: " + JSON.stringify(headers)); - } - - let subject = headers["subject"]; - if (subject == undefined) { - subject = ""; - } - - let smil = ""; - let attachments = []; - let parts = aMessageRecord.parts; - if (parts) { - for (let i = 0; i < parts.length; i++) { - let part = parts[i]; - if (DEBUG) { - debug("MMS: part[" + i + "]: " + JSON.stringify(part)); - } - // Sometimes the part is incomplete because the device reboots when - // downloading MMS. Don't need to expose this part to the content. - if (!part) { - continue; - } - - let partHeaders = part["headers"]; - let partContent = part["content"]; - // Don't need to make the SMIL part if it's present. - if (partHeaders["content-type"]["media"] == "application/smil") { - smil = partContent; - continue; - } - attachments.push({ - "id": partHeaders["content-id"], - "location": partHeaders["content-location"], - "content": partContent - }); - } - } - let expiryDate = 0; - if (headers["x-mms-expiry"] != undefined) { - expiryDate = aMessageRecord.timestamp + headers["x-mms-expiry"] * 1000; - } - let readReportRequested = headers["x-mms-read-report"] || false; - return gMobileMessageService.createMmsMessage(aMessageRecord.id, - aMessageRecord.threadId, - aMessageRecord.iccId, - aMessageRecord.delivery, - aMessageRecord.deliveryInfo, - aMessageRecord.sender, - aMessageRecord.receivers, - aMessageRecord.timestamp, - aMessageRecord.read, - subject, - smil, - attachments, - expiryDate, - readReportRequested); - } - }, - - findParticipantRecordByAddress: function findParticipantRecordByAddress( - aParticipantStore, aAddress, aCreate, aCallback) { - if (DEBUG) { - debug("findParticipantRecordByAddress(" - + JSON.stringify(aAddress) + ", " + aCreate + ")"); - } - - // Two types of input number to match here, international(+886987654321), - // and local(0987654321) types. The "nationalNumber" parsed from - // phonenumberutils will be "987654321" in this case. - - // Normalize address before searching for participant record. - let normalizedAddress = PhoneNumberUtils.normalize(aAddress, false); - let allPossibleAddresses = [normalizedAddress]; - let parsedAddress = PhoneNumberUtils.parse(normalizedAddress); - if (parsedAddress && parsedAddress.internationalNumber && - allPossibleAddresses.indexOf(parsedAddress.internationalNumber) < 0) { - // We only stores international numbers into participant store because - // the parsed national number doesn't contain country info and may - // duplicate in different country. - allPossibleAddresses.push(parsedAddress.internationalNumber); - } - if (DEBUG) { - debug("findParticipantRecordByAddress: allPossibleAddresses = " + - JSON.stringify(allPossibleAddresses)); - } - - // Make a copy here because we may need allPossibleAddresses again. - let needles = allPossibleAddresses.slice(0); - let request = aParticipantStore.index("addresses").get(needles.pop()); - request.onsuccess = (function onsuccess(event) { - let participantRecord = event.target.result; - // 1) First try matching through "addresses" index of participant store. - // If we're lucky, return the fetched participant record. - if (participantRecord) { - if (DEBUG) { - debug("findParticipantRecordByAddress: got " - + JSON.stringify(participantRecord)); - } - aCallback(participantRecord); - return; - } - - // Try next possible address again. - if (needles.length) { - let request = aParticipantStore.index("addresses").get(needles.pop()); - request.onsuccess = onsuccess.bind(this); - return; - } - - // 2) Traverse throught all participants and check all alias addresses. - aParticipantStore.openCursor().onsuccess = (function (event) { - let cursor = event.target.result; - if (!cursor) { - // Have traversed whole object store but still in vain. - if (!aCreate) { - aCallback(null); - return; - } - - let participantRecord = { addresses: [normalizedAddress] }; - let addRequest = aParticipantStore.add(participantRecord); - addRequest.onsuccess = function (event) { - participantRecord.id = event.target.result; - if (DEBUG) { - debug("findParticipantRecordByAddress: created " - + JSON.stringify(participantRecord)); - } - aCallback(participantRecord); - }; - return; - } - - let participantRecord = cursor.value; - for (let storedAddress of participantRecord.addresses) { - let parsedStoredAddress = PhoneNumberUtils.parseWithMCC(storedAddress, null); - let match = this.matchPhoneNumbers(normalizedAddress, parsedAddress, - storedAddress, parsedStoredAddress); - if (!match) { - // 3) Else we fail to match current stored participant record. - continue; - } - // Match! - if (aCreate) { - // In a READ-WRITE transaction, append one more possible address for - // this participant record. - participantRecord.addresses = - participantRecord.addresses.concat(allPossibleAddresses); - cursor.update(participantRecord); - } - - if (DEBUG) { - debug("findParticipantRecordByAddress: match " - + JSON.stringify(cursor.value)); - } - aCallback(participantRecord); - return; - } - - // Check next participant record if available. - cursor.continue(); - }).bind(this); - }).bind(this); - }, - - findParticipantIdsByAddresses: function findParticipantIdsByAddresses( - aParticipantStore, aAddresses, aCreate, aSkipNonexistent, aCallback) { - if (DEBUG) { - debug("findParticipantIdsByAddresses(" - + JSON.stringify(aAddresses) + ", " - + aCreate + ", " + aSkipNonexistent + ")"); - } - - if (!aAddresses || !aAddresses.length) { - if (DEBUG) debug("findParticipantIdsByAddresses: returning null"); - aCallback(null); - return; - } - - let self = this; - (function findParticipantId(index, result) { - if (index >= aAddresses.length) { - // Sort numerically. - result.sort(function (a, b) { - return a - b; - }); - if (DEBUG) debug("findParticipantIdsByAddresses: returning " + result); - aCallback(result); - return; - } - - self.findParticipantRecordByAddress(aParticipantStore, - aAddresses[index++], aCreate, - function (participantRecord) { - if (!participantRecord) { - if (!aSkipNonexistent) { - if (DEBUG) debug("findParticipantIdsByAddresses: returning null"); - aCallback(null); - return; - } - } else if (result.indexOf(participantRecord.id) < 0) { - result.push(participantRecord.id); - } - findParticipantId(index, result); - }); - }) (0, []); - }, - - findThreadRecordByParticipants: function findThreadRecordByParticipants( - aThreadStore, aParticipantStore, aAddresses, - aCreateParticipants, aCallback) { - if (DEBUG) { - debug("findThreadRecordByParticipants(" + JSON.stringify(aAddresses) - + ", " + aCreateParticipants + ")"); - } - this.findParticipantIdsByAddresses(aParticipantStore, aAddresses, - aCreateParticipants, false, - function (participantIds) { - if (!participantIds) { - if (DEBUG) debug("findThreadRecordByParticipants: returning null"); - aCallback(null, null); - return; - } - // Find record from thread store. - let request = aThreadStore.index("participantIds").get(participantIds); - request.onsuccess = function (event) { - let threadRecord = event.target.result; - if (DEBUG) { - debug("findThreadRecordByParticipants: return " - + JSON.stringify(threadRecord)); - } - aCallback(threadRecord, participantIds); - }; - }); - }, - - newTxnWithCallback: function newTxnWithCallback(aCallback, aFunc, aStoreNames) { - let self = this; - this.newTxn(READ_WRITE, function(aError, aTransaction, aStores) { - let notifyResult = function(aRv, aMessageRecord) { - if (!aCallback) { - return; - } - let domMessage = - aMessageRecord && self.createDomMessageFromRecord(aMessageRecord); - aCallback.notify(aRv, domMessage); - }; - - if (aError) { - // TODO bug 832140 check event.target.errorCode - notifyResult(Cr.NS_ERROR_FAILURE, null); - return; - } - - let capture = {}; - aTransaction.oncomplete = function(event) { - notifyResult(Cr.NS_OK, capture.messageRecord); - }; - aTransaction.onabort = function(event) { - // TODO bug 832140 check event.target.errorCode - notifyResult(Cr.NS_ERROR_FAILURE, null); - }; - - aFunc(capture, aStores); - }, aStoreNames); - }, - - saveRecord: function saveRecord(aMessageRecord, aAddresses, aCallback) { - if (DEBUG) debug("Going to store " + JSON.stringify(aMessageRecord)); - - let self = this; - this.newTxn(READ_WRITE, function(error, txn, stores) { - let notifyResult = function(aRv, aMessageRecord) { - if (!aCallback) { - return; - } - let domMessage = - aMessageRecord && self.createDomMessageFromRecord(aMessageRecord); - aCallback.notify(aRv, domMessage); - }; - - if (error) { - // TODO bug 832140 check event.target.errorCode - notifyResult(Cr.NS_ERROR_FAILURE, null); - return; - } - - txn.oncomplete = function oncomplete(event) { - if (aMessageRecord.id > self.lastMessageId) { - self.lastMessageId = aMessageRecord.id; - } - notifyResult(Cr.NS_OK, aMessageRecord); - }; - txn.onabort = function onabort(event) { - // TODO bug 832140 check event.target.errorCode - notifyResult(Cr.NS_ERROR_FAILURE, null); - }; - - let messageStore = stores[0]; - let participantStore = stores[1]; - let threadStore = stores[2]; - self.replaceShortMessageOnSave(txn, messageStore, participantStore, - threadStore, aMessageRecord, aAddresses); - }, [MESSAGE_STORE_NAME, PARTICIPANT_STORE_NAME, THREAD_STORE_NAME]); - }, - - replaceShortMessageOnSave: - function replaceShortMessageOnSave(aTransaction, aMessageStore, - aParticipantStore, aThreadStore, - aMessageRecord, aAddresses) { - let isReplaceTypePid = (aMessageRecord.pid) && - ((aMessageRecord.pid >= RIL.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_1 && - aMessageRecord.pid <= RIL.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_7) || - aMessageRecord.pid == RIL.PDU_PID_RETURN_CALL_MESSAGE); - - if (aMessageRecord.type != "sms" || - aMessageRecord.delivery != DELIVERY_RECEIVED || - !isReplaceTypePid) { - this.realSaveRecord(aTransaction, aMessageStore, aParticipantStore, - aThreadStore, aMessageRecord, aAddresses); - return; - } - - // 3GPP TS 23.040 subclause 9.2.3.9 "TP-Protocol-Identifier (TP-PID)": - // - // ... the MS shall check the originating address and replace any - // existing stored message having the same Protocol Identifier code - // and originating address with the new short message and other - // parameter values. If there is no message to be replaced, the MS - // shall store the message in the normal way. ... it is recommended - // that the SC address should not be checked by the MS." - let self = this; - this.findParticipantRecordByAddress(aParticipantStore, - aMessageRecord.sender, false, - function(participantRecord) { - if (!participantRecord) { - self.realSaveRecord(aTransaction, aMessageStore, aParticipantStore, - aThreadStore, aMessageRecord, aAddresses); - return; - } - - let participantId = participantRecord.id; - let range = IDBKeyRange.bound([participantId, 0], [participantId, ""]); - let request = aMessageStore.index("participantIds").openCursor(range); - request.onsuccess = function onsuccess(event) { - let cursor = event.target.result; - if (!cursor) { - self.realSaveRecord(aTransaction, aMessageStore, aParticipantStore, - aThreadStore, aMessageRecord, aAddresses); - return; - } - - // A message record with same participantId found. - // Verify matching criteria. - let foundMessageRecord = cursor.value; - if (foundMessageRecord.type != "sms" || - foundMessageRecord.sender != aMessageRecord.sender || - foundMessageRecord.pid != aMessageRecord.pid) { - cursor.continue(); - return; - } - - // Match! Now replace that found message record with current one. - aMessageRecord.id = foundMessageRecord.id; - self.realSaveRecord(aTransaction, aMessageStore, aParticipantStore, - aThreadStore, aMessageRecord, aAddresses); - }; - }); - }, - - realSaveRecord: function realSaveRecord(aTransaction, aMessageStore, - aParticipantStore, aThreadStore, - aMessageRecord, aAddresses) { - let self = this; - this.findThreadRecordByParticipants(aThreadStore, aParticipantStore, - aAddresses, true, - function(threadRecord, participantIds) { - if (!participantIds) { - aTransaction.abort(); - return; - } - - let isOverriding = (aMessageRecord.id !== undefined); - if (!isOverriding) { - // |self.lastMessageId| is only updated in |txn.oncomplete|. - aMessageRecord.id = self.lastMessageId + 1; - } - - let timestamp = aMessageRecord.timestamp; - let insertMessageRecord = function(threadId) { - // Setup threadId & threadIdIndex. - aMessageRecord.threadId = threadId; - aMessageRecord.threadIdIndex = [threadId, timestamp]; - // Setup participantIdsIndex. - aMessageRecord.participantIdsIndex = []; - for each (let id in participantIds) { - aMessageRecord.participantIdsIndex.push([id, timestamp]); - } - - if (!isOverriding) { - // Really add to message store. - aMessageStore.put(aMessageRecord); - return; - } - - // If we're going to override an old message, we need to update the - // info of the original thread containing the overridden message. - // To get the original thread ID and read status of the overridden - // message record, we need to retrieve it before overriding it. - aMessageStore.get(aMessageRecord.id).onsuccess = function(event) { - let oldMessageRecord = event.target.result; - aMessageStore.put(aMessageRecord); - if (oldMessageRecord) { - self.updateThreadByMessageChange(aMessageStore, - aThreadStore, - oldMessageRecord.threadId, - aMessageRecord.id, - oldMessageRecord.read); - } - }; - }; - - if (threadRecord) { - let needsUpdate = false; - - if (threadRecord.lastTimestamp <= timestamp) { - let lastMessageSubject; - if (aMessageRecord.type == "mms") { - lastMessageSubject = aMessageRecord.headers.subject; - } - threadRecord.lastMessageSubject = lastMessageSubject || null; - threadRecord.lastTimestamp = timestamp; - threadRecord.body = aMessageRecord.body; - threadRecord.lastMessageId = aMessageRecord.id; - threadRecord.lastMessageType = aMessageRecord.type; - needsUpdate = true; - } - - if (!aMessageRecord.read) { - threadRecord.unreadCount++; - needsUpdate = true; - } - - if (needsUpdate) { - aThreadStore.put(threadRecord); - } - - insertMessageRecord(threadRecord.id); - return; - } - - let lastMessageSubject; - if (aMessageRecord.type == "mms") { - lastMessageSubject = aMessageRecord.headers.subject; - } - - threadRecord = { - participantIds: participantIds, - participantAddresses: aAddresses, - lastMessageId: aMessageRecord.id, - lastTimestamp: timestamp, - lastMessageSubject: lastMessageSubject || null, - body: aMessageRecord.body, - unreadCount: aMessageRecord.read ? 0 : 1, - lastMessageType: aMessageRecord.type, - }; - aThreadStore.add(threadRecord).onsuccess = function(event) { - let threadId = event.target.result; - insertMessageRecord(threadId); - }; - }); - }, - - forEachMatchedMmsDeliveryInfo: - function forEachMatchedMmsDeliveryInfo(aDeliveryInfo, aNeedle, aCallback) { - - let typedAddress = { - type: MMS.Address.resolveType(aNeedle), - address: aNeedle - }; - let normalizedAddress, parsedAddress; - if (typedAddress.type === "PLMN") { - normalizedAddress = PhoneNumberUtils.normalize(aNeedle, false); - parsedAddress = PhoneNumberUtils.parse(normalizedAddress); - } - - for (let element of aDeliveryInfo) { - let typedStoredAddress = { - type: MMS.Address.resolveType(element.receiver), - address: element.receiver - }; - if (typedAddress.type !== typedStoredAddress.type) { - // Not even my type. Skip. - continue; - } - - if (typedAddress.address == typedStoredAddress.address) { - // Have a direct match. - aCallback(element); - continue; - } - - if (typedAddress.type !== "PLMN") { - // Address type other than "PLMN" must have direct match. Or, skip. - continue; - } - - // Both are of "PLMN" type. - let normalizedStoredAddress = - PhoneNumberUtils.normalize(element.receiver, false); - let parsedStoredAddress = - PhoneNumberUtils.parseWithMCC(normalizedStoredAddress, null); - if (this.matchPhoneNumbers(normalizedAddress, parsedAddress, - normalizedStoredAddress, parsedStoredAddress)) { - aCallback(element); - } - } - }, - - updateMessageDeliveryById: function updateMessageDeliveryById( - id, type, receiver, delivery, deliveryStatus, envelopeId, callback) { - if (DEBUG) { - debug("Setting message's delivery by " + type + " = "+ id - + " receiver: " + receiver - + " delivery: " + delivery - + " deliveryStatus: " + deliveryStatus - + " envelopeId: " + envelopeId); - } - - let self = this; - this.newTxnWithCallback(callback, function(aCapture, aMessageStore) { - let getRequest; - if (type === "messageId") { - getRequest = aMessageStore.get(id); - } else if (type === "envelopeId") { - getRequest = aMessageStore.index("envelopeId").get(id); - } - - getRequest.onsuccess = function onsuccess(event) { - let messageRecord = event.target.result; - if (!messageRecord) { - if (DEBUG) debug("type = " + id + " is not found"); - throw Cr.NS_ERROR_FAILURE; - } - - let isRecordUpdated = false; - - // Update |messageRecord.delivery| if needed. - if (delivery && messageRecord.delivery != delivery) { - messageRecord.delivery = delivery; - messageRecord.deliveryIndex = [delivery, messageRecord.timestamp]; - isRecordUpdated = true; - } - - // Attempt to update |deliveryStatus| and |deliveryTimestamp| of: - // - the |messageRecord| for SMS. - // - the element(s) in |messageRecord.deliveryInfo| for MMS. - if (deliveryStatus) { - // A callback for updating the deliveyStatus/deliveryTimestamp of - // each target. - let updateFunc = function(aTarget) { - if (aTarget.deliveryStatus == deliveryStatus) { - return; - } - - aTarget.deliveryStatus = deliveryStatus; - - // Update |deliveryTimestamp| if it's successfully delivered. - if (deliveryStatus == DELIVERY_STATUS_SUCCESS) { - aTarget.deliveryTimestamp = Date.now(); - } - - isRecordUpdated = true; - }; - - if (messageRecord.type == "sms") { - updateFunc(messageRecord); - } else if (messageRecord.type == "mms") { - if (!receiver) { - // If the receiver is specified, we only need to update the - // element(s) in deliveryInfo that match the same receiver. - messageRecord.deliveryInfo.forEach(updateFunc); - } else { - self.forEachMatchedMmsDeliveryInfo(messageRecord.deliveryInfo, - receiver, updateFunc); - } - } - } - - // Update |messageRecord.envelopeIdIndex| if needed. - if (envelopeId) { - if (messageRecord.envelopeIdIndex != envelopeId) { - messageRecord.envelopeIdIndex = envelopeId; - isRecordUpdated = true; - } - } - - aCapture.messageRecord = messageRecord; - if (!isRecordUpdated) { - if (DEBUG) { - debug("The values of delivery, deliveryStatus and envelopeId " + - "don't need to be updated."); - } - return; - } - - if (DEBUG) { - debug("The delivery, deliveryStatus or envelopeId are updated."); - } - aMessageStore.put(messageRecord); - }; - }); - }, - - fillReceivedMmsThreadParticipants: function fillReceivedMmsThreadParticipants(aMessage, threadParticipants) { - let receivers = aMessage.receivers; - // If we don't want to disable the MMS grouping for receiving, we need to - // add the receivers (excluding the user's own number) to the participants - // for creating the thread. Some cases might be investigated as below: - // - // 1. receivers.length == 0 - // This usually happens when receiving an MMS notification indication - // which doesn't carry any receivers. - // 2. receivers.length == 1 - // If the receivers contain single phone number, we don't need to - // add it into participants because we know that number is our own. - // 3. receivers.length >= 2 - // If the receivers contain multiple phone numbers, we need to add all - // of them but not the user's own number into participants. - if (DISABLE_MMS_GROUPING_FOR_RECEIVING || receivers.length < 2) { - return; - } - let isSuccess = false; - let slicedReceivers = receivers.slice(); - if (aMessage.msisdn) { - let found = slicedReceivers.indexOf(aMessage.msisdn); - if (found !== -1) { - isSuccess = true; - slicedReceivers.splice(found, 1); - } - } - - if (!isSuccess) { - // For some SIMs we cannot retrieve the vaild MSISDN (i.e. the user's - // own phone number), so we cannot correcly exclude the user's own - // number from the receivers, thus wrongly building the thread index. - if (DEBUG) debug("Error! Cannot strip out user's own phone number!"); - } - - threadParticipants = threadParticipants.concat(slicedReceivers); - }, - - updateThreadByMessageChange: function updateThreadByMessageChange(messageStore, - threadStore, - threadId, - messageId, - messageRead) { - threadStore.get(threadId).onsuccess = function(event) { - // This must exist. - let threadRecord = event.target.result; - if (DEBUG) debug("Updating thread record " + JSON.stringify(threadRecord)); - - if (!messageRead) { - threadRecord.unreadCount--; - } - - if (threadRecord.lastMessageId == messageId) { - // Check most recent sender/receiver. - let range = IDBKeyRange.bound([threadId, 0], [threadId, ""]); - let request = messageStore.index("threadId") - .openCursor(range, PREV); - request.onsuccess = function(event) { - let cursor = event.target.result; - if (!cursor) { - if (DEBUG) { - debug("Deleting mru entry for thread id " + threadId); - } - threadStore.delete(threadId); - return; - } - - let nextMsg = cursor.value; - let lastMessageSubject; - if (nextMsg.type == "mms") { - lastMessageSubject = nextMsg.headers.subject; - } - threadRecord.lastMessageSubject = lastMessageSubject || null; - threadRecord.lastMessageId = nextMsg.id; - threadRecord.lastTimestamp = nextMsg.timestamp; - threadRecord.body = nextMsg.body; - threadRecord.lastMessageType = nextMsg.type; - if (DEBUG) { - debug("Updating mru entry: " + - JSON.stringify(threadRecord)); - } - threadStore.put(threadRecord); - }; - } else if (!messageRead) { - // Shortcut, just update the unread count. - if (DEBUG) { - debug("Updating unread count for thread id " + threadId + ": " + - (threadRecord.unreadCount + 1) + " -> " + - threadRecord.unreadCount); - } - threadStore.put(threadRecord); - } - }; - }, - /** * nsIRilMobileMessageDatabaseService API */ - saveReceivedMessage: function saveReceivedMessage(aMessage, aCallback) { - if ((aMessage.type != "sms" && aMessage.type != "mms") || - (aMessage.type == "sms" && (aMessage.messageClass == undefined || - aMessage.sender == undefined)) || - (aMessage.type == "mms" && (aMessage.delivery == undefined || - aMessage.deliveryStatus == undefined || - !Array.isArray(aMessage.receivers))) || - aMessage.timestamp == undefined) { - if (aCallback) { - aCallback.notify(Cr.NS_ERROR_FAILURE, null); - } - return; - } - - let threadParticipants; - if (aMessage.type == "mms") { - if (aMessage.headers.from) { - aMessage.sender = aMessage.headers.from.address; - } else { - aMessage.sender = "anonymous"; - } - - threadParticipants = [aMessage.sender]; - this.fillReceivedMmsThreadParticipants(aMessage, threadParticipants); - } else { // SMS - threadParticipants = [aMessage.sender]; - } - - let timestamp = aMessage.timestamp; - - // Adding needed indexes and extra attributes for internal use. - // threadIdIndex & participantIdsIndex are filled in saveRecord(). - aMessage.readIndex = [FILTER_READ_UNREAD, timestamp]; - aMessage.read = FILTER_READ_UNREAD; - - if (aMessage.type == "mms") { - aMessage.transactionIdIndex = aMessage.headers["x-mms-transaction-id"]; - aMessage.isReadReportSent = false; - - // As a receiver, we don't need to care about the delivery status of - // others, so we put a single element with self's phone number in the - // |deliveryInfo| array. - aMessage.deliveryInfo = [{ - receiver: aMessage.phoneNumber, - deliveryStatus: aMessage.deliveryStatus, - deliveryTimestamp: 0, - readStatus: MMS.DOM_READ_STATUS_NOT_APPLICABLE, - readTimestamp: 0, - }]; - - delete aMessage.deliveryStatus; - } - - if (aMessage.type == "sms") { - aMessage.delivery = DELIVERY_RECEIVED; - aMessage.deliveryStatus = DELIVERY_STATUS_SUCCESS; - aMessage.deliveryTimestamp = 0; - - if (aMessage.pid == undefined) { - aMessage.pid = RIL.PDU_PID_DEFAULT; - } - } - aMessage.deliveryIndex = [aMessage.delivery, timestamp]; - - this.saveRecord(aMessage, threadParticipants, aCallback); + saveReceivedMessage: function(aMessage, aCallback) { + this.mmdb.saveReceivedMessage(aMessage, aCallback); }, - saveSendingMessage: function saveSendingMessage(aMessage, aCallback) { - if ((aMessage.type != "sms" && aMessage.type != "mms") || - (aMessage.type == "sms" && aMessage.receiver == undefined) || - (aMessage.type == "mms" && !Array.isArray(aMessage.receivers)) || - aMessage.deliveryStatusRequested == undefined || - aMessage.timestamp == undefined) { - if (aCallback) { - aCallback.notify(Cr.NS_ERROR_FAILURE, null); - } - return; - } - - // Set |aMessage.deliveryStatus|. Note that for MMS record - // it must be an array of strings; For SMS, it's a string. - let deliveryStatus = aMessage.deliveryStatusRequested - ? DELIVERY_STATUS_PENDING - : DELIVERY_STATUS_NOT_APPLICABLE; - if (aMessage.type == "sms") { - aMessage.deliveryStatus = deliveryStatus; - // If |deliveryTimestamp| is not specified, use 0 as default. - if (aMessage.deliveryTimestamp == undefined) { - aMessage.deliveryTimestamp = 0; - } - } else if (aMessage.type == "mms") { - let receivers = aMessage.receivers - if (!Array.isArray(receivers)) { - if (DEBUG) { - debug("Need receivers for MMS. Fail to save the sending message."); - } - if (aCallback) { - aCallback.notify(Cr.NS_ERROR_FAILURE, null); - } - return; - } - let readStatus = aMessage.headers["x-mms-read-report"] - ? MMS.DOM_READ_STATUS_PENDING - : MMS.DOM_READ_STATUS_NOT_APPLICABLE; - aMessage.deliveryInfo = []; - for (let i = 0; i < receivers.length; i++) { - aMessage.deliveryInfo.push({ - receiver: receivers[i], - deliveryStatus: deliveryStatus, - deliveryTimestamp: 0, - readStatus: readStatus, - readTimestamp: 0, - }); - } - } - - let timestamp = aMessage.timestamp; - - // Adding needed indexes and extra attributes for internal use. - // threadIdIndex & participantIdsIndex are filled in saveRecord(). - aMessage.deliveryIndex = [DELIVERY_SENDING, timestamp]; - aMessage.readIndex = [FILTER_READ_READ, timestamp]; - aMessage.delivery = DELIVERY_SENDING; - aMessage.messageClass = MESSAGE_CLASS_NORMAL; - aMessage.read = FILTER_READ_READ; - - let addresses; - if (aMessage.type == "sms") { - addresses = [aMessage.receiver]; - } else if (aMessage.type == "mms") { - addresses = aMessage.receivers; - } - this.saveRecord(aMessage, addresses, aCallback); + saveSendingMessage: function(aMessage, aCallback) { + this.mmdb.saveSendingMessage(aMessage, aCallback); }, - setMessageDeliveryByMessageId: function setMessageDeliveryByMessageId( - messageId, receiver, delivery, deliveryStatus, envelopeId, callback) { - this.updateMessageDeliveryById(messageId, "messageId", - receiver, delivery, deliveryStatus, - envelopeId, callback); - + setMessageDeliveryByMessageId: function(aMessageId, aReceiver, aDelivery, + aDeliveryStatus, aEnvelopeId, + aCallback) { + this.mmdb.updateMessageDeliveryById(aMessageId, "messageId", aReceiver, + aDelivery, aDeliveryStatus, + aEnvelopeId, aCallback); }, - setMessageDeliveryStatusByEnvelopeId: - function setMessageDeliveryStatusByEnvelopeId(aEnvelopeId, aReceiver, - aDeliveryStatus, aCallback) { - this.updateMessageDeliveryById(aEnvelopeId, "envelopeId", aReceiver, null, - aDeliveryStatus, null, aCallback); + setMessageDeliveryStatusByEnvelopeId: function(aEnvelopeId, aReceiver, + aDeliveryStatus, aCallback) { + this.mmdb.updateMessageDeliveryById(aEnvelopeId, "envelopeId", aReceiver, + null, aDeliveryStatus, null, aCallback); }, - setMessageReadStatusByEnvelopeId: - function setMessageReadStatusByEnvelopeId(aEnvelopeId, aReceiver, - aReadStatus, aCallback) { - if (DEBUG) { - debug("Setting message's read status by envelopeId = " + aEnvelopeId + - ", receiver: " + aReceiver + ", readStatus: " + aReadStatus); - } - - let self = this; - this.newTxnWithCallback(aCallback, function(aCapture, aMessageStore) { - let getRequest = aMessageStore.index("envelopeId").get(aEnvelopeId); - getRequest.onsuccess = function onsuccess(event) { - let messageRecord = event.target.result; - if (!messageRecord) { - if (DEBUG) debug("envelopeId '" + aEnvelopeId + "' not found"); - throw Cr.NS_ERROR_FAILURE; - } - - aCapture.messageRecord = messageRecord; - - let isRecordUpdated = false; - self.forEachMatchedMmsDeliveryInfo(messageRecord.deliveryInfo, - aReceiver, function(aEntry) { - if (aEntry.readStatus == aReadStatus) { - return; - } - - aEntry.readStatus = aReadStatus; - if (aReadStatus == MMS.DOM_READ_STATUS_SUCCESS) { - aEntry.readTimestamp = Date.now(); - } else { - aEntry.readTimestamp = 0; - } - isRecordUpdated = true; - }); - - if (!isRecordUpdated) { - if (DEBUG) { - debug("The values of readStatus don't need to be updated."); - } - return; - } - - if (DEBUG) { - debug("The readStatus is updated."); - } - aMessageStore.put(messageRecord); - }; - }); + setMessageReadStatusByEnvelopeId: function(aEnvelopeId, aReceiver, + aReadStatus, aCallback) { + this.mmdb.setMessageReadStatusByEnvelopeId(aEnvelopeId, aReceiver, + aReadStatus, aCallback); }, - getMessageRecordByTransactionId: function getMessageRecordByTransactionId(aTransactionId, aCallback) { - if (DEBUG) debug("Retrieving message with transaction ID " + aTransactionId); - let self = this; - this.newTxn(READ_ONLY, function (error, txn, messageStore) { - if (error) { - if (DEBUG) debug(error); - aCallback.notify(Ci.nsIMobileMessageCallback.INTERNAL_ERROR, null, null); - return; - } - let request = messageStore.index("transactionId").get(aTransactionId); - - txn.oncomplete = function oncomplete(event) { - if (DEBUG) debug("Transaction " + txn + " completed."); - let messageRecord = request.result; - if (!messageRecord) { - if (DEBUG) debug("Transaction ID " + aTransactionId + " not found"); - aCallback.notify(Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR, null, null); - return; - } - // In this case, we don't need a dom message. Just pass null to the - // third argument. - aCallback.notify(Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR, - messageRecord, null); - }; - - txn.onerror = function onerror(event) { - if (DEBUG) { - if (event.target) { - debug("Caught error on transaction", event.target.errorCode); - } - } - aCallback.notify(Ci.nsIMobileMessageCallback.INTERNAL_ERROR, null, null); - }; - }); + getMessageRecordByTransactionId: function(aTransactionId, aCallback) { + this.mmdb.getMessageRecordByTransactionId(aTransactionId, aCallback); }, - getMessageRecordById: function getMessageRecordById(aMessageId, aCallback) { - if (DEBUG) debug("Retrieving message with ID " + aMessageId); - let self = this; - this.newTxn(READ_ONLY, function (error, txn, messageStore) { - if (error) { - if (DEBUG) debug(error); - aCallback.notify(Ci.nsIMobileMessageCallback.INTERNAL_ERROR, null, null); - return; - } - let request = messageStore.mozGetAll(aMessageId); - - txn.oncomplete = function oncomplete() { - if (DEBUG) debug("Transaction " + txn + " completed."); - if (request.result.length > 1) { - if (DEBUG) debug("Got too many results for id " + aMessageId); - aCallback.notify(Ci.nsIMobileMessageCallback.UNKNOWN_ERROR, null, null); - return; - } - let messageRecord = request.result[0]; - if (!messageRecord) { - if (DEBUG) debug("Message ID " + aMessageId + " not found"); - aCallback.notify(Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR, null, null); - return; - } - if (messageRecord.id != aMessageId) { - if (DEBUG) { - debug("Requested message ID (" + aMessageId + ") is " + - "different from the one we got"); - } - aCallback.notify(Ci.nsIMobileMessageCallback.UNKNOWN_ERROR, null, null); - return; - } - let domMessage = self.createDomMessageFromRecord(messageRecord); - aCallback.notify(Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR, - messageRecord, domMessage); - }; - - txn.onerror = function onerror(event) { - if (DEBUG) { - if (event.target) { - debug("Caught error on transaction", event.target.errorCode); - } - } - aCallback.notify(Ci.nsIMobileMessageCallback.INTERNAL_ERROR, null, null); - }; - }); + getMessageRecordById: function(aMessageId, aCallback) { + this.mmdb.getMessageRecordById(aMessageId, aCallback); }, /** * nsIMobileMessageDatabaseService API */ - getMessage: function getMessage(aMessageId, aRequest) { - if (DEBUG) debug("Retrieving message with ID " + aMessageId); - let notifyCallback = { - notify: function notify(aRv, aMessageRecord, aDomMessage) { - if (Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR == aRv) { - aRequest.notifyMessageGot(aDomMessage); - return; - } - aRequest.notifyGetMessageFailed(aRv, null); - } - }; - this.getMessageRecordById(aMessageId, notifyCallback); + getMessage: function(aMessageId, aRequest) { + this.mmdb.getMessage(aMessageId, aRequest); }, - deleteMessage: function deleteMessage(messageIds, length, aRequest) { - if (DEBUG) debug("deleteMessage: message ids " + JSON.stringify(messageIds)); - let deleted = []; - let self = this; - this.newTxn(READ_WRITE, function (error, txn, stores) { - if (error) { - if (DEBUG) debug("deleteMessage: failed to open transaction"); - aRequest.notifyDeleteMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR); - return; - } - txn.onerror = function onerror(event) { - if (DEBUG) debug("Caught error on transaction", event.target.errorCode); - //TODO look at event.target.errorCode, pick appropriate error constant - aRequest.notifyDeleteMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR); - }; - - const messageStore = stores[0]; - const threadStore = stores[1]; - - txn.oncomplete = function oncomplete(event) { - if (DEBUG) debug("Transaction " + txn + " completed."); - aRequest.notifyMessageDeleted(deleted, length); - }; - - for (let i = 0; i < length; i++) { - let messageId = messageIds[i]; - deleted[i] = false; - messageStore.get(messageId).onsuccess = function(messageIndex, event) { - let messageRecord = event.target.result; - let messageId = messageIds[messageIndex]; - if (messageRecord) { - if (DEBUG) debug("Deleting message id " + messageId); - - // First actually delete the message. - messageStore.delete(messageId).onsuccess = function(event) { - if (DEBUG) debug("Message id " + messageId + " deleted"); - deleted[messageIndex] = true; - - // Then update unread count and most recent message. - self.updateThreadByMessageChange(messageStore, - threadStore, - messageRecord.threadId, - messageId, - messageRecord.read); - - Services.obs.notifyObservers(null, - "mobile-message-deleted", - JSON.stringify({ id: messageId })); - }; - } else if (DEBUG) { - debug("Message id " + messageId + " does not exist"); - } - }.bind(null, i); - } - }, [MESSAGE_STORE_NAME, THREAD_STORE_NAME]); + deleteMessage: function(aMessageIds, aLength, aRequest) { + this.mmdb.deleteMessage(aMessageIds, aLength, aRequest); }, - createMessageCursor: function createMessageCursor(filter, reverse, callback) { - if (DEBUG) { - debug("Creating a message cursor. Filters:" + - " startDate: " + filter.startDate + - " endDate: " + filter.endDate + - " delivery: " + filter.delivery + - " numbers: " + filter.numbers + - " read: " + filter.read + - " threadId: " + filter.threadId + - " reverse: " + reverse); - } - - let cursor = new GetMessagesCursor(this, callback); - - let self = this; - self.newTxn(READ_ONLY, function (error, txn, stores) { - let collector = cursor.collector; - let collect = collector.collect.bind(collector); - FilterSearcherHelper.transact(self, txn, error, filter, reverse, collect); - }, [MESSAGE_STORE_NAME, PARTICIPANT_STORE_NAME]); - - return cursor; + createMessageCursor: function(aFilter, aReverse, aCallback) { + return this.mmdb.createMessageCursor(aFilter, aReverse, aCallback); }, - markMessageRead: function markMessageRead(messageId, value, aSendReadReport, aRequest) { - if (DEBUG) debug("Setting message " + messageId + " read to " + value); - this.newTxn(READ_WRITE, function (error, txn, stores) { - if (error) { - if (DEBUG) debug(error); - aRequest.notifyMarkMessageReadFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR); - return; - } - - txn.onerror = function onerror(event) { - if (DEBUG) debug("Caught error on transaction ", event.target.errorCode); - aRequest.notifyMarkMessageReadFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR); - }; - - let messageStore = stores[0]; - let threadStore = stores[1]; - messageStore.get(messageId).onsuccess = function onsuccess(event) { - let messageRecord = event.target.result; - if (!messageRecord) { - if (DEBUG) debug("Message ID " + messageId + " not found"); - aRequest.notifyMarkMessageReadFailed(Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR); - return; - } - - if (messageRecord.id != messageId) { - if (DEBUG) { - debug("Retrieve message ID (" + messageId + ") is " + - "different from the one we got"); - } - aRequest.notifyMarkMessageReadFailed(Ci.nsIMobileMessageCallback.UNKNOWN_ERROR); - return; - } - - // If the value to be set is the same as the current message `read` - // value, we just notify successfully. - if (messageRecord.read == value) { - if (DEBUG) debug("The value of messageRecord.read is already " + value); - aRequest.notifyMessageMarkedRead(messageRecord.read); - return; - } - - messageRecord.read = value ? FILTER_READ_READ : FILTER_READ_UNREAD; - messageRecord.readIndex = [messageRecord.read, messageRecord.timestamp]; - let readReportMessageId, readReportTo; - if (aSendReadReport && - messageRecord.type == "mms" && - messageRecord.delivery == DELIVERY_RECEIVED && - messageRecord.read == FILTER_READ_READ && - !messageRecord.isReadReportSent) { - messageRecord.isReadReportSent = true; - - let from = messageRecord.headers["from"]; - readReportTo = from && from.address; - readReportMessageId = messageRecord.headers["message-id"]; - } - - if (DEBUG) debug("Message.read set to: " + value); - messageStore.put(messageRecord).onsuccess = function onsuccess(event) { - if (DEBUG) { - debug("Update successfully completed. Message: " + - JSON.stringify(event.target.result)); - } - - // Now update the unread count. - let threadId = messageRecord.threadId; - - threadStore.get(threadId).onsuccess = function(event) { - let threadRecord = event.target.result; - threadRecord.unreadCount += value ? -1 : 1; - if (DEBUG) { - debug("Updating unreadCount for thread id " + threadId + ": " + - (value ? - threadRecord.unreadCount + 1 : - threadRecord.unreadCount - 1) + - " -> " + threadRecord.unreadCount); - } - threadStore.put(threadRecord).onsuccess = function(event) { - if(readReportMessageId && readReportTo) { - gMMSService.sendReadReport(readReportMessageId, - readReportTo, - messageRecord.iccId); - } - aRequest.notifyMessageMarkedRead(messageRecord.read); - }; - }; - }; - }; - }, [MESSAGE_STORE_NAME, THREAD_STORE_NAME]); + markMessageRead: function(aMessageId, aValue, aSendReadReport, aRequest) { + this.mmdb.markMessageRead(aMessageId, aValue, aSendReadReport, aRequest); }, - createThreadCursor: function createThreadCursor(callback) { - if (DEBUG) debug("Getting thread list"); - - let cursor = new GetThreadsCursor(this, callback); - this.newTxn(READ_ONLY, function (error, txn, threadStore) { - let collector = cursor.collector; - if (error) { - if (DEBUG) debug(error); - collector.collect(null, COLLECT_ID_ERROR, COLLECT_TIMESTAMP_UNUSED); - return; - } - txn.onerror = function onerror(event) { - if (DEBUG) debug("Caught error on transaction ", event.target.errorCode); - collector.collect(null, COLLECT_ID_ERROR, COLLECT_TIMESTAMP_UNUSED); - }; - let request = threadStore.index("lastTimestamp").openKeyCursor(); - request.onsuccess = function(event) { - let cursor = event.target.result; - if (cursor) { - if (collector.collect(txn, cursor.primaryKey, cursor.key)) { - cursor.continue(); - } - } else { - collector.collect(txn, COLLECT_ID_END, COLLECT_TIMESTAMP_UNUSED); - } - }; - }, [THREAD_STORE_NAME]); - - return cursor; + createThreadCursor: function(aCallback) { + return this.mmdb.createThreadCursor(aCallback); } }; -let FilterSearcherHelper = { - - /** - * @param index - * The name of a message store index to filter on. - * @param range - * A IDBKeyRange. - * @param direction - * NEXT or PREV. - * @param txn - * Ongoing IDBTransaction context object. - * @param collect - * Result colletor function. It takes three parameters -- txn, message - * id, and message timestamp. - */ - filterIndex: function filterIndex(index, range, direction, txn, collect) { - let messageStore = txn.objectStore(MESSAGE_STORE_NAME); - let request = messageStore.index(index).openKeyCursor(range, direction); - request.onsuccess = function onsuccess(event) { - let cursor = event.target.result; - // Once the cursor has retrieved all keys that matches its key range, - // the filter search is done. - if (cursor) { - let timestamp = Array.isArray(cursor.key) ? cursor.key[1] : cursor.key; - if (collect(txn, cursor.primaryKey, timestamp)) { - cursor.continue(); - } - } else { - collect(txn, COLLECT_ID_END, COLLECT_TIMESTAMP_UNUSED); - } - }; - request.onerror = function onerror(event) { - if (DEBUG && event) debug("IDBRequest error " + event.target.errorCode); - collect(txn, COLLECT_ID_ERROR, COLLECT_TIMESTAMP_UNUSED); - }; - }, - - /** - * Explicitly fiter message on the timestamp index. - * - * @param startDate - * Timestamp of the starting date. - * @param endDate - * Timestamp of the ending date. - * @param direction - * NEXT or PREV. - * @param txn - * Ongoing IDBTransaction context object. - * @param collect - * Result colletor function. It takes three parameters -- txn, message - * id, and message timestamp. - */ - filterTimestamp: function filterTimestamp(startDate, endDate, direction, txn, - collect) { - let range = null; - if (startDate != null && endDate != null) { - range = IDBKeyRange.bound(startDate.getTime(), endDate.getTime()); - } else if (startDate != null) { - range = IDBKeyRange.lowerBound(startDate.getTime()); - } else if (endDate != null) { - range = IDBKeyRange.upperBound(endDate.getTime()); - } - this.filterIndex("timestamp", range, direction, txn, collect); - }, - - /** - * Instanciate a filtering transaction. - * - * @param service - * A MobileMessageDatabaseService. Used to create - * @param txn - * Ongoing IDBTransaction context object. - * @param error - * Previous error while creating the transaction. - * @param filter - * A SmsFilter object. - * @param reverse - * A boolean value indicating whether we should filter message in - * reversed order. - * @param collect - * Result colletor function. It takes three parameters -- txn, message - * id, and message timestamp. - */ - transact: function transact(service, txn, error, filter, reverse, collect) { - if (error) { - //TODO look at event.target.errorCode, pick appropriate error constant. - if (DEBUG) debug("IDBRequest error " + error.target.errorCode); - collect(txn, COLLECT_ID_ERROR, COLLECT_TIMESTAMP_UNUSED); - return; - } - - let direction = reverse ? PREV : NEXT; - - // We support filtering by date range only (see `else` block below) or by - // number/delivery status/read status with an optional date range. - if (filter.delivery == null && - filter.numbers == null && - filter.read == null && - filter.threadId == null) { - // Filtering by date range only. - if (DEBUG) { - debug("filter.timestamp " + filter.startDate + ", " + filter.endDate); - } - - this.filterTimestamp(filter.startDate, filter.endDate, direction, txn, - collect); - return; - } - - // Numeric 0 is smaller than any time stamp, and empty string is larger - // than all numeric values. - let startDate = 0, endDate = ""; - if (filter.startDate != null) { - startDate = filter.startDate.getTime(); - } - if (filter.endDate != null) { - endDate = filter.endDate.getTime(); - } - - let single, intersectionCollector; - { - let num = 0; - if (filter.delivery) num++; - if (filter.numbers) num++; - if (filter.read != undefined) num++; - if (filter.threadId != undefined) num++; - single = (num == 1); - } - - if (!single) { - intersectionCollector = new IntersectionResultsCollector(collect, reverse); - } - - // Retrieve the keys from the 'delivery' index that matches the value of - // filter.delivery. - if (filter.delivery) { - if (DEBUG) debug("filter.delivery " + filter.delivery); - let delivery = filter.delivery; - let range = IDBKeyRange.bound([delivery, startDate], [delivery, endDate]); - this.filterIndex("delivery", range, direction, txn, - single ? collect : intersectionCollector.newContext()); - } - - // Retrieve the keys from the 'read' index that matches the value of - // filter.read. - if (filter.read != undefined) { - if (DEBUG) debug("filter.read " + filter.read); - let read = filter.read ? FILTER_READ_READ : FILTER_READ_UNREAD; - let range = IDBKeyRange.bound([read, startDate], [read, endDate]); - this.filterIndex("read", range, direction, txn, - single ? collect : intersectionCollector.newContext()); - } - - // Retrieve the keys from the 'threadId' index that matches the value of - // filter.threadId. - if (filter.threadId != undefined) { - if (DEBUG) debug("filter.threadId " + filter.threadId); - let threadId = filter.threadId; - let range = IDBKeyRange.bound([threadId, startDate], [threadId, endDate]); - this.filterIndex("threadId", range, direction, txn, - single ? collect : intersectionCollector.newContext()); - } - - // Retrieve the keys from the 'sender' and 'receiver' indexes that - // match the values of filter.numbers - if (filter.numbers) { - if (DEBUG) debug("filter.numbers " + filter.numbers.join(", ")); - - if (!single) { - collect = intersectionCollector.newContext(); - } - - let participantStore = txn.objectStore(PARTICIPANT_STORE_NAME); - service.findParticipantIdsByAddresses(participantStore, filter.numbers, - false, true, - (function (participantIds) { - if (!participantIds || !participantIds.length) { - // Oops! No such participant at all. - - collect(txn, COLLECT_ID_END, COLLECT_TIMESTAMP_UNUSED); - return; - } - - if (participantIds.length == 1) { - let id = participantIds[0]; - let range = IDBKeyRange.bound([id, startDate], [id, endDate]); - this.filterIndex("participantIds", range, direction, txn, collect); - return; - } - - let unionCollector = new UnionResultsCollector(collect); - - this.filterTimestamp(filter.startDate, filter.endDate, direction, txn, - unionCollector.newTimestampContext()); - - for (let i = 0; i < participantIds.length; i++) { - let id = participantIds[i]; - let range = IDBKeyRange.bound([id, startDate], [id, endDate]); - this.filterIndex("participantIds", range, direction, txn, - unionCollector.newContext()); - } - }).bind(this)); - } - } -}; - -function ResultsCollector() { - this.results = []; - this.done = false; -} -ResultsCollector.prototype = { - results: null, - requestWaiting: null, - done: null, - - /** - * Queue up passed id, reply if necessary. - * - * @param txn - * Ongoing IDBTransaction context object. - * @param id - * COLLECT_ID_END(0) for no more results, COLLECT_ID_ERROR(-1) for - * errors and valid otherwise. - * @param timestamp - * We assume this function is always called in timestamp order. So - * this parameter is actually unused. - * - * @return true if expects more. false otherwise. - */ - collect: function collect(txn, id, timestamp) { - if (this.done) { - return false; - } - - if (DEBUG) { - debug("collect: message ID = " + id); - } - if (id) { - // Queue up any id but '0' and replies later accordingly. - this.results.push(id); - } - if (id <= 0) { - // No more processing on '0' or negative values passed. - this.done = true; - } - - if (!this.requestWaiting) { - if (DEBUG) debug("Cursor.continue() not called yet"); - return !this.done; - } - - // We assume there is only one request waiting throughout the message list - // retrieving process. So we don't bother continuing to process further - // waiting requests here. This assumption comes from DOMCursor::Continue() - // implementation. - let callback = this.requestWaiting; - this.requestWaiting = null; - - this.drip(txn, callback); - - return !this.done; - }, - - /** - * Callback right away with the first queued result entry if the filtering is - * done. Or queue up the request and callback when a new entry is available. - * - * @param callback - * A callback function that accepts a numeric id. - */ - squeeze: function squeeze(callback) { - if (this.requestWaiting) { - throw new Error("Already waiting for another request!"); - } - - if (!this.done) { - // Database transaction ongoing, let it reply for us so that we won't get - // blocked by the existing transaction. - this.requestWaiting = callback; - return; - } - - this.drip(null, callback); - }, - - /** - * @param txn - * Ongoing IDBTransaction context object or null. - * @param callback - * A callback function that accepts a numeric id. - */ - drip: function drip(txn, callback) { - if (!this.results.length) { - if (DEBUG) debug("No messages matching the filter criteria"); - callback(txn, COLLECT_ID_END); - return; - } - - if (this.results[0] < 0) { - // An previous error found. Keep the answer in results so that we can - // reply INTERNAL_ERROR for further requests. - if (DEBUG) debug("An previous error found"); - callback(txn, COLLECT_ID_ERROR); - return; - } - - let firstMessageId = this.results.shift(); - callback(txn, firstMessageId); - } -}; - -function IntersectionResultsCollector(collect, reverse) { - this.cascadedCollect = collect; - this.reverse = reverse; - this.contexts = []; -} -IntersectionResultsCollector.prototype = { - cascadedCollect: null, - reverse: false, - contexts: null, - - /** - * Queue up {id, timestamp} pairs, find out intersections and report to - * |cascadedCollect|. Return true if it is still possible to have another match. - */ - collect: function collect(contextIndex, txn, id, timestamp) { - if (DEBUG) { - debug("IntersectionResultsCollector: " - + contextIndex + ", " + id + ", " + timestamp); - } - - let contexts = this.contexts; - let context = contexts[contextIndex]; - - if (id < 0) { - // Act as no more matched records. - id = 0; - } - if (!id) { - context.done = true; - - if (!context.results.length) { - // Already empty, can't have further intersection results. - return this.cascadedCollect(txn, COLLECT_ID_END, COLLECT_TIMESTAMP_UNUSED); - } - - for (let i = 0; i < contexts.length; i++) { - if (!contexts[i].done) { - // Don't call |this.cascadedCollect| because |context.results| might not - // be empty, so other contexts might still have a chance here. - return false; - } - } - - // It was the last processing context and is no more processing. - return this.cascadedCollect(txn, COLLECT_ID_END, COLLECT_TIMESTAMP_UNUSED); - } - - // Search id in other existing results. If no other results has it, - // and A) the last timestamp is smaller-equal to current timestamp, - // we wait for further results; either B) record timestamp is larger - // then current timestamp or C) no more processing for a filter, then we - // drop this id because there can't be a match anymore. - for (let i = 0; i < contexts.length; i++) { - if (i == contextIndex) { - continue; - } - - let ctx = contexts[i]; - let results = ctx.results; - let found = false; - for (let j = 0; j < results.length; j++) { - let result = results[j]; - if (result.id == id) { - found = true; - break; - } - if ((!this.reverse && (result.timestamp > timestamp)) || - (this.reverse && (result.timestamp < timestamp))) { - // B) Cannot find a match anymore. Drop. - return true; - } - } - - if (!found) { - if (ctx.done) { - // C) Cannot find a match anymore. Drop. - if (results.length) { - let lastResult = results[results.length - 1]; - if ((!this.reverse && (lastResult.timestamp >= timestamp)) || - (this.reverse && (lastResult.timestamp <= timestamp))) { - // Still have a chance to get another match. Return true. - return true; - } - } - - // Impossible to find another match because all results in ctx have - // timestamps smaller than timestamp. - context.done = true; - return this.cascadedCollect(txn, COLLECT_ID_END, COLLECT_TIMESTAMP_UNUSED); - } - - // A) Pending. - context.results.push({ - id: id, - timestamp: timestamp - }); - return true; - } - } - - // Now id is found in all other results. Report it. - return this.cascadedCollect(txn, id, timestamp); - }, - - newContext: function newContext() { - let contextIndex = this.contexts.length; - this.contexts.push({ - results: [], - done: false - }); - return this.collect.bind(this, contextIndex); - } -}; - -function UnionResultsCollector(collect) { - this.cascadedCollect = collect; - this.contexts = [{ - // Timestamp. - processing: 1, - results: [] - }, { - processing: 0, - results: [] - }]; -} -UnionResultsCollector.prototype = { - cascadedCollect: null, - contexts: null, - - collect: function collect(contextIndex, txn, id, timestamp) { - if (DEBUG) { - debug("UnionResultsCollector: " - + contextIndex + ", " + id + ", " + timestamp); - } - - let contexts = this.contexts; - let context = contexts[contextIndex]; - - if (id < 0) { - // Act as no more matched records. - id = 0; - } - if (id) { - if (!contextIndex) { - // Timestamp. - context.results.push({ - id: id, - timestamp: timestamp - }); - } else { - context.results.push(id); - } - return true; - } - - context.processing -= 1; - if (contexts[0].processing || contexts[1].processing) { - // At least one queue is still processing, but we got here because - // current cursor gives 0 as id meaning no more messages are - // available. Return false here to stop further cursor.continue() calls. - return false; - } - - let tres = contexts[0].results; - let qres = contexts[1].results; - tres = tres.filter(function (element) { - return qres.indexOf(element.id) != -1; - }); - - for (let i = 0; i < tres.length; i++) { - this.cascadedCollect(txn, tres[i].id, tres[i].timestamp); - } - this.cascadedCollect(txn, COLLECT_ID_END, COLLECT_TIMESTAMP_UNUSED); - - return false; - }, - - newTimestampContext: function newTimestampContext() { - return this.collect.bind(this, 0); - }, - - newContext: function newContext() { - this.contexts[1].processing++; - return this.collect.bind(this, 1); - } -}; - -function GetMessagesCursor(service, callback) { - this.service = service; - this.callback = callback; - this.collector = new ResultsCollector(); - - this.handleContinue(); // Trigger first run. -} -GetMessagesCursor.prototype = { - classID: RIL_GETMESSAGESCURSOR_CID, - QueryInterface: XPCOMUtils.generateQI([Ci.nsICursorContinueCallback]), - - service: null, - callback: null, - collector: null, - - getMessageTxn: function getMessageTxn(messageStore, messageId) { - if (DEBUG) debug ("Fetching message " + messageId); - - let getRequest = messageStore.get(messageId); - let self = this; - getRequest.onsuccess = function onsuccess(event) { - if (DEBUG) { - debug("notifyNextMessageInListGot - messageId: " + messageId); - } - let domMessage = - self.service.createDomMessageFromRecord(event.target.result); - self.callback.notifyCursorResult(domMessage); - }; - getRequest.onerror = function onerror(event) { - if (DEBUG) { - debug("notifyCursorError - messageId: " + messageId); - } - self.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR); - }; - }, - - notify: function notify(txn, messageId) { - if (!messageId) { - this.callback.notifyCursorDone(); - return; - } - - if (messageId < 0) { - this.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR); - return; - } - - // When filter transaction is not yet completed, we're called with current - // ongoing transaction object. - if (txn) { - let messageStore = txn.objectStore(MESSAGE_STORE_NAME); - this.getMessageTxn(messageStore, messageId); - return; - } - - // Or, we have to open another transaction ourselves. - let self = this; - this.service.newTxn(READ_ONLY, function (error, txn, messageStore) { - if (error) { - self.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR); - return; - } - self.getMessageTxn(messageStore, messageId); - }, [MESSAGE_STORE_NAME]); - }, - - // nsICursorContinueCallback - - handleContinue: function handleContinue() { - if (DEBUG) debug("Getting next message in list"); - this.collector.squeeze(this.notify.bind(this)); - } -}; - -function GetThreadsCursor(service, callback) { - this.service = service; - this.callback = callback; - this.collector = new ResultsCollector(); - - this.handleContinue(); // Trigger first run. -} -GetThreadsCursor.prototype = { - classID: RIL_GETTHREADSCURSOR_CID, - QueryInterface: XPCOMUtils.generateQI([Ci.nsICursorContinueCallback]), - - service: null, - callback: null, - collector: null, - - getThreadTxn: function getThreadTxn(threadStore, threadId) { - if (DEBUG) debug ("Fetching thread " + threadId); - - let getRequest = threadStore.get(threadId); - let self = this; - getRequest.onsuccess = function onsuccess(event) { - let threadRecord = event.target.result; - if (DEBUG) { - debug("notifyCursorResult: " + JSON.stringify(threadRecord)); - } - let thread = - gMobileMessageService.createThread(threadRecord.id, - threadRecord.participantAddresses, - threadRecord.lastTimestamp, - threadRecord.lastMessageSubject || "", - threadRecord.body, - threadRecord.unreadCount, - threadRecord.lastMessageType); - self.callback.notifyCursorResult(thread); - }; - getRequest.onerror = function onerror(event) { - if (DEBUG) { - debug("notifyCursorError - threadId: " + threadId); - } - self.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR); - }; - }, - - notify: function notify(txn, threadId) { - if (!threadId) { - this.callback.notifyCursorDone(); - return; - } - - if (threadId < 0) { - this.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR); - return; - } - - // When filter transaction is not yet completed, we're called with current - // ongoing transaction object. - if (txn) { - let threadStore = txn.objectStore(THREAD_STORE_NAME); - this.getThreadTxn(threadStore, threadId); - return; - } - - // Or, we have to open another transaction ourselves. - let self = this; - this.service.newTxn(READ_ONLY, function (error, txn, threadStore) { - if (error) { - self.callback.notifyCursorError(Ci.nsIMobileMessageCallback.INTERNAL_ERROR); - return; - } - self.getThreadTxn(threadStore, threadId); - }, [THREAD_STORE_NAME]); - }, - - // nsICursorContinueCallback - - handleContinue: function handleContinue() { - if (DEBUG) debug("Getting next thread in list"); - this.collector.squeeze(this.notify.bind(this)); - } -} - this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MobileMessageDatabaseService]); - -function debug() { - dump("MobileMessageDatabaseService: " + Array.slice(arguments).join(" ") + "\n"); -} diff --git a/dom/mobilemessage/src/moz.build b/dom/mobilemessage/src/moz.build index 3bca3c816f6b..3bd98ba75679 100644 --- a/dom/mobilemessage/src/moz.build +++ b/dom/mobilemessage/src/moz.build @@ -22,6 +22,7 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk' and CONFIG['MOZ_B2G_RIL']: EXTRA_JS_MODULES = [ 'gonk/mms_consts.js', 'gonk/MmsPduHelper.jsm', + 'gonk/MobileMessageDB.jsm', 'gonk/wap_consts.js', 'gonk/WspPduHelper.jsm', ] diff --git a/dom/mobilemessage/tests/marionette/head.js b/dom/mobilemessage/tests/marionette/head.js index ff931c20f440..fd2572e36074 100644 --- a/dom/mobilemessage/tests/marionette/head.js +++ b/dom/mobilemessage/tests/marionette/head.js @@ -8,7 +8,7 @@ let Promise = Cu.import("resource://gre/modules/Promise.jsm").Promise; /* Push required permissions and test if |navigator.mozMobileMessage| exists. * Resolve if it does, reject otherwise. * - * Forfill params: + * Fulfill params: * manager -- an reference to navigator.mozMobileMessage. * * Reject params: (none) @@ -47,7 +47,7 @@ function ensureMobileMessage() { /* Send a SMS message to a single receiver. Resolve if it succeeds, reject * otherwise. * - * Forfill params: + * Fulfill params: * message -- the sent SmsMessage. * * Reject params: @@ -75,7 +75,7 @@ function sendSmsWithSuccess(aReceiver, aText) { /* Send a MMS message with specified parameters. Resolve if it fails, reject * otherwise. * - * Forfill params: + * Fulfill params: * message -- the failed MmsMessage * * Reject params: (none) @@ -102,7 +102,7 @@ function sendMmsWithFailure(aMmsParameters) { /* Retrieve messages from database. * - * Forfill params: + * Fulfill params: * messages -- an array of {Sms,Mms}Message instances. * * Reject params: @@ -138,7 +138,7 @@ function getMessages(aFilter, aReverse) { /* Retrieve all messages from database. * - * Forfill params: + * Fulfill params: * messages -- an array of {Sms,Mms}Message instances. * * Reject params: @@ -152,7 +152,7 @@ function getAllMessages() { /* Retrieve all threads from database. * - * Forfill params: + * Fulfill params: * threads -- an array of MozMobileMessageThread instances. * * Reject params: @@ -181,7 +181,7 @@ function getAllThreads() { /* Retrieve a single specified thread from database. * - * Forfill params: + * Fulfill params: * thread -- a MozMobileMessageThread instance. * * Reject params: @@ -206,7 +206,7 @@ function getThreadById(aThreadId) { /* Delete messages specified from database. * - * Forfill params: + * Fulfill params: * result -- an array of boolean values indicating whether delesion was * actually performed on the message record with corresponding id. * @@ -236,7 +236,7 @@ function deleteMessagesById(aMessageIds) { /* Delete messages specified from database. * - * Forfill params: + * Fulfill params: * result -- an array of boolean values indicating whether delesion was * actually performed on the message record with corresponding id. * @@ -254,7 +254,7 @@ function deleteMessages(aMessages) { /* Delete all messages from database. * - * Forfill params: + * Fulfill params: * ids -- an array of numeric values identifying those deleted * {Sms,Mms}Messages. * @@ -275,7 +275,7 @@ let pendingEmulatorCmdCount = 0; * end, so here comes with the pending counter. Resolve when the emulator * gives positive response, and reject otherwise. * - * Forfill params: + * Fulfill params: * result -- an array of emulator response lines. * * Reject params: @@ -303,7 +303,7 @@ function runEmulatorCmdSafe(aCommand) { /* Send simple text SMS to emulator. * - * Forfill params: + * Fulfill params: * result -- an array of emulator response lines. * * Reject params: @@ -318,7 +318,7 @@ function sendTextSmsToEmulator(aFrom, aText) { /* Send raw SMS TPDU to emulator. * - * Forfill params: + * Fulfill params: * result -- an array of emulator response lines. * * Reject params: @@ -331,6 +331,62 @@ function sendRawSmsToEmulator(aPdu) { return runEmulatorCmdSafe(command); } +/* Name space for MobileMessageDB.jsm. Only initialized after first call to + * newMobileMessageDB. + */ +let MMDB; + +// Create a new MobileMessageDB instance. +function newMobileMessageDB() { + if (!MMDB) { + MMDB = Cu.import("resource://gre/modules/MobileMessageDB.jsm", {}); + is(typeof MMDB.MobileMessageDB, "function", "MMDB.MobileMessageDB"); + } + + let mmdb = new MMDB.MobileMessageDB(); + ok(mmdb, "MobileMessageDB instance"); + return mmdb; +} + +/* Initialize a MobileMessageDB. Resolve if initialized with success, reject + * otherwise. + * + * Fulfill params: a MobileMessageDB instance. + * Reject params: a MobileMessageDB instance. + * + * @param aMmdb + * A MobileMessageDB instance. + * @param aDbName + * A string name for that database. + * @param aDbVersion + * The version that MobileMessageDB should upgrade to. 0 for the lastest + * version. + * + * @return A deferred promise. + */ +function initMobileMessageDB(aMmdb, aDbName, aDbVersion) { + let deferred = Promise.defer(); + + aMmdb.init(aDbName, aDbVersion, function(aError) { + if (aError) { + deferred.reject(aMmdb); + } else { + deferred.resolve(aMmdb); + } + }); + + return deferred.promise; +} + +/* Close a MobileMessageDB. + * + * @return The passed MobileMessageDB instance. + */ +function closeMobileMessageDB(aMmdb) { + aMmdb.close(); + return aMmdb; +} + /* Create a new array of id attribute of input messages. * * @param aMessages an array of {Sms,Mms}Message instances. @@ -345,6 +401,23 @@ function messagesToIds(aMessages) { return ids; } +// A reference to a nsIUUIDGenerator service. +let uuidGenerator; + +/* Generate a new UUID. + * + * @return A UUID string. + */ +function newUUID() { + if (!uuidGenerator) { + uuidGenerator = Cc["@mozilla.org/uuid-generator;1"] + .getService(Ci.nsIUUIDGenerator); + ok(uuidGenerator, "uuidGenerator"); + } + + return uuidGenerator.generateUUID().toString(); +} + /* Flush permission settings and call |finish()|. */ function cleanUp() { @@ -360,13 +433,20 @@ function cleanUp() { }); } -function startTestCommon(aTestCaseMain) { - ensureMobileMessage() - .then(deleteAllMessages) - .then(aTestCaseMain) - .then(deleteAllMessages) - .then(cleanUp, function() { - ok(false, 'promise rejects during test.'); - cleanUp(); - }); +function startTestBase(aTestCaseMain) { + Promise.resolve() + .then(aTestCaseMain) + .then(cleanUp, function() { + ok(false, 'promise rejects during test.'); + cleanUp(); + }); +} + +function startTestCommon(aTestCaseMain) { + startTestBase(function() { + return ensureMobileMessage() + .then(deleteAllMessages) + .then(aTestCaseMain) + .then(deleteAllMessages); + }); } diff --git a/dom/mobilemessage/tests/marionette/manifest.ini b/dom/mobilemessage/tests/marionette/manifest.ini index 7b8d618ccd1d..bb7316194ce4 100644 --- a/dom/mobilemessage/tests/marionette/manifest.ini +++ b/dom/mobilemessage/tests/marionette/manifest.ini @@ -39,5 +39,6 @@ qemu = true [test_smsc_address.js] [test_dsds_default_service_id.js] [test_thread_subject.js] +[test_mmdb_new.js] [test_mmdb_setmessagedeliverybyid_sms.js] [test_replace_short_message_type.js] diff --git a/dom/mobilemessage/tests/marionette/test_mmdb_new.js b/dom/mobilemessage/tests/marionette/test_mmdb_new.js new file mode 100644 index 000000000000..da34b13ca9d4 --- /dev/null +++ b/dom/mobilemessage/tests/marionette/test_mmdb_new.js @@ -0,0 +1,34 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = 'head.js'; + +startTestBase(function testCaseMain() { + log("Test init MobileMessageDB"); + + let mmdb = newMobileMessageDB(); + let dbName = "test_mmdb_new"; + let dbVersion = 0; + let check = function() { + is(mmdb.dbName, dbName, "dbName"); + if (!dbVersion) { + ok(mmdb.dbVersion, "dbVersion"); + dbVersion = mmdb.dbVersion; + } else { + is(mmdb.dbVersion, dbVersion, "dbVersion"); + } + }; + + return initMobileMessageDB(mmdb, dbName, dbVersion) + .then(check) + .then(closeMobileMessageDB.bind(null, mmdb)) + .then(check) + .then(function() { + log("Test re-init and close."); + return initMobileMessageDB(mmdb, dbName, dbVersion); + }) + .then(check) + .then(closeMobileMessageDB.bind(null, mmdb)) + .then(check); +}); diff --git a/dom/plugins/base/android/ANPSystem.cpp b/dom/plugins/base/android/ANPSystem.cpp index 098153e87bc9..7d9c17b7f612 100644 --- a/dom/plugins/base/android/ANPSystem.cpp +++ b/dom/plugins/base/android/ANPSystem.cpp @@ -50,33 +50,20 @@ anp_system_getApplicationDataDirectory() return anp_system_getApplicationDataDirectory(nullptr); } -jclass anp_system_loadJavaClass(NPP instance, const char* className) +jclass anp_system_loadJavaClass(NPP instance, const char* classNameStr) { LOG("%s", __PRETTY_FUNCTION__); - JNIEnv* env = GetJNIForThread(); - if (!env) - return nullptr; - - jclass cls = env->FindClass("org/mozilla/gecko/GeckoAppShell"); - jmethodID method = env->GetStaticMethodID(cls, - "loadPluginClass", - "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Class;"); - - // pass libname and classname, gotta create java strings nsNPAPIPluginInstance* pinst = static_cast(instance->ndata); mozilla::PluginPRLibrary* lib = static_cast(pinst->GetPlugin()->GetLibrary()); - nsCString libName; - lib->GetLibraryPath(libName); + NS_ConvertUTF8toUTF16 className(classNameStr); - jstring jclassName = env->NewStringUTF(className); - jstring jlibName = env->NewStringUTF(libName.get()); - jobject obj = env->CallStaticObjectMethod(cls, method, jclassName, jlibName); - env->DeleteLocalRef(jlibName); - env->DeleteLocalRef(jclassName); - env->DeleteLocalRef(cls); - return reinterpret_cast(obj); + nsCString libNameUtf8; + lib->GetLibraryPath(libNameUtf8); + NS_ConvertUTF8toUTF16 libName(libNameUtf8); + + return GeckoAppShell::LoadPluginClass(className, libName); } void anp_system_setPowerState(NPP instance, ANPPowerState powerState) diff --git a/dom/src/geolocation/nsGeolocation.cpp b/dom/src/geolocation/nsGeolocation.cpp index 314c4f7360e6..bc652bb5293b 100644 --- a/dom/src/geolocation/nsGeolocation.cpp +++ b/dom/src/geolocation/nsGeolocation.cpp @@ -15,7 +15,6 @@ #include "nsServiceManagerUtils.h" #include "nsContentUtils.h" #include "nsContentPermissionHelper.h" -#include "nsCxPusher.h" #include "nsIDocument.h" #include "nsIObserverService.h" #include "nsPIDOMWindow.h" @@ -295,10 +294,6 @@ PositionError::WrapObject(JSContext* aCx, JS::Handle aScope) void PositionError::NotifyCallback(const GeoPositionErrorCallback& aCallback) { - // Ensure that the proper context is on the stack (bug 452762) - nsCxPusher pusher; - pusher.PushNull(); - nsAutoMicroTask mt; if (aCallback.HasWebIDLCallback()) { PositionErrorCallback* callback = aCallback.GetWebIDLCallback(); @@ -530,9 +525,6 @@ nsGeolocationRequest::SendLocation(nsIDOMGeoPosition* aPosition) Shutdown(); } - // Ensure that the proper context is on the stack (bug 452762) - nsCxPusher pusher; - pusher.PushNull(); nsAutoMicroTask mt; if (mCallback.HasWebIDLCallback()) { ErrorResult err; diff --git a/extensions/widgetutils/Makefile.in b/extensions/widgetutils/Makefile.in deleted file mode 100644 index 9a5d0d5d1ed2..000000000000 --- a/extensions/widgetutils/Makefile.in +++ /dev/null @@ -1,18 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - - -INSTALL_EXTENSION_ID = widgetutils@extensions.mozilla.org -DIST_FILES = install.rdf -USE_EXTENSION_MANIFEST = 1 -XPI_PKGNAME = widgetutils-$(MOZ_APP_VERSION) - -XULAPP_DEFINES = \ - -DTHUNDERBIRD_VERSION=$(THUNDERBIRD_VERSION) \ - -DEXTENSION_VERSION=$(MOZILLA_VERSION) \ - $(NULL) - -ifdef TARGET_XPCOM_ABI -XULAPP_DEFINES += -DEM_ABI=$(OS_TARGET)_$(TARGET_XPCOM_ABI) -endif diff --git a/extensions/widgetutils/install.rdf b/extensions/widgetutils/install.rdf deleted file mode 100644 index b9ca2cb4b166..000000000000 --- a/extensions/widgetutils/install.rdf +++ /dev/null @@ -1,44 +0,0 @@ - - - - -#filter substitution - - - - widgetutils@extensions.mozilla.org - @EXTENSION_VERSION@ - -#ifdef EM_ABI - @EM_ABI@ -#endif - - - - - {ec8030f7-c20a-464f-9b0e-13a3a9e97384} - @FIREFOX_VERSION@ - @FIREFOX_VERSION@ - - - - - - - {3550f703-e582-4d05-9a08-453d09bdfdc6} - @THUNDERBIRD_VERSION@ - @THUNDERBIRD_VERSION@ - - - - - WidgetUtils service - Provide scrolling by mouse feature. - mozilla.org - Oleg Romashin <romaxa@gmail.com> - http://hg.mozilla.org - - diff --git a/extensions/widgetutils/moz.build b/extensions/widgetutils/moz.build deleted file mode 100644 index 962510f78c0a..000000000000 --- a/extensions/widgetutils/moz.build +++ /dev/null @@ -1,9 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -DIRS += ['src'] - -XPI_NAME = 'widgetutils' diff --git a/extensions/widgetutils/src/Makefile.in b/extensions/widgetutils/src/Makefile.in deleted file mode 100644 index f778a3eadb26..000000000000 --- a/extensions/widgetutils/src/Makefile.in +++ /dev/null @@ -1,10 +0,0 @@ -# vim:set ts=8 sw=8 sts=8 noet: -# 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/. - -EXTRA_DSO_LDOPTS = \ - $(XPCOM_GLUE_LDOPTS) \ - $(NSPR_LIBS) \ - $(MOZ_COMPONENT_LIBS) \ - $(NULL) diff --git a/extensions/widgetutils/src/moz.build b/extensions/widgetutils/src/moz.build deleted file mode 100644 index 12b160cdf55d..000000000000 --- a/extensions/widgetutils/src/moz.build +++ /dev/null @@ -1,20 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -SOURCES += [ - 'nsWidgetUtils.cpp', -] - -LIBRARY_NAME = 'widgetutils' - -IS_COMPONENT = True - -XPI_NAME = 'widgetutils' - -if CONFIG['TARGET_XPCOM_ABI']: - FINAL_TARGET += '/platform/%(OS_TARGET)s_%(TARGET_XPCOM_ABI)s' % CONFIG - -FORCE_SHARED_LIB = True diff --git a/extensions/widgetutils/src/nsWidgetUtils.cpp b/extensions/widgetutils/src/nsWidgetUtils.cpp deleted file mode 100644 index 1071ea6fcfb0..000000000000 --- a/extensions/widgetutils/src/nsWidgetUtils.cpp +++ /dev/null @@ -1,537 +0,0 @@ -/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim:set ts=2 sw=2 sts=2 tw=80 et cindent: */ -/* 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 "nsCURILoader.h" -#include "nsICategoryManager.h" -#include "nsIDOMDocument.h" -#include "nsIDOMHTMLElement.h" -#include "nsIDOMHTMLIFrameElement.h" -#include "nsIDOMNode.h" -#include "nsIDOMNodeList.h" -#include "nsIDOMWindow.h" -#include "nsIDOMWindowCollection.h" -#include "nsIDocument.h" -#include "nsIGenericFactory.h" -#include "nsIObserver.h" -#include "nsIPresShell.h" -#include "nsIStyleSheetService.h" -#include "nsIWebProgress.h" -#include "nsIWebProgressListener.h" -#include "nsIWindowWatcher.h" -#include "nsNetUtil.h" -#include "nsRect.h" -#include "nsString.h" -#include "nsWeakReference.h" -#include "nsIWebBrowser.h" -#include "nsIObserverService.h" -#include "nsPIDOMWindow.h" -#include "nsIDOMWindow.h" -#include "nsIDOMCompositionListener.h" -#include "nsIDOMTextListener.h" -#include "nsIDOMMouseEvent.h" -#include "nsIDOMWheelEvent.h" -#include "nsView.h" -#include "nsViewManager.h" -#include "nsIContentPolicy.h" -#include "nsIDocShellTreeItem.h" -#include "nsIContent.h" -#include "nsITimer.h" -#include "mozilla/MouseEvents.h" - -using namespace mozilla; - -const int MIN_INT =((int) (1 << (sizeof(int) * 8 - 1))); - -static int g_lastX=MIN_INT; -static int g_lastY=MIN_INT; -static int32_t g_panning = 0; -static bool g_is_scrollable = false; - -#define EM_MULT 16. -#define NS_FRAME_HAS_RELATIVE_SIZE 0x01000000 -#define NS_FRAME_HAS_OPTIMIZEDVIEW 0x02000000 -#define BEHAVIOR_ACCEPT nsIPermissionManager::ALLOW_ACTION -#define BEHAVIOR_REJECT nsIPermissionManager::DENY_ACTION -#define BEHAVIOR_NOFOREIGN 3 -#define NUMBER_OF_TYPES 13 - -// TODO auto reload nsWidgetUtils in C. -class nsWidgetUtils : public nsIObserver, - public nsIDOMEventListener, - public nsIContentPolicy, - public nsSupportsWeakReference -{ -public: - nsWidgetUtils(); - virtual ~nsWidgetUtils(); - - NS_DECL_ISUPPORTS - NS_DECL_NSIDOMEVENTLISTENER - NS_DECL_NSIOBSERVER - NS_DECL_NSICONTENTPOLICY - -private: - nsresult Init(void); - void RemoveWindowListeners(nsIDOMWindow *aDOMWin); - EventTarget* GetChromeEventHandler(nsIDOMWindow *aDOMWin); - void AttachWindowListeners(nsIDOMWindow *aDOMWin); - bool IsXULNode(nsIDOMNode *aNode, uint32_t *aType = 0); - nsresult GetDOMWindowByNode(nsIDOMNode *aNode, nsIDOMWindow * *aDOMWindow); - nsresult UpdateFromEvent(nsIDOMEvent *aDOMEvent); - nsresult MouseDown(nsIDOMEvent* aDOMEvent); - nsresult MouseUp(nsIDOMEvent* aDOMEvent); - nsresult MouseMove(nsIDOMEvent* aDOMEvent); - - static void StopPanningCallback(nsITimer *timer, void *closure); - - nsCOMPtr mWidget; - nsRefPtr mViewManager; - nsCOMPtr mTimer; -}; - -nsWidgetUtils::nsWidgetUtils() -{ - Init(); -} - -NS_IMETHODIMP -nsWidgetUtils::Init() -{ - nsresult rv; - nsCOMPtr obsSvc = - do_GetService("@mozilla.org/observer-service;1"); - NS_ENSURE_STATE(obsSvc); - - rv = obsSvc->AddObserver(this, "domwindowopened", false); - NS_ENSURE_SUCCESS(rv, rv); - rv = obsSvc->AddObserver(this, "domwindowclosed", false); - NS_ENSURE_SUCCESS(rv, rv); - mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); -} - -nsresult -nsWidgetUtils::UpdateFromEvent(nsIDOMEvent *aDOMEvent) -{ - nsCOMPtr mouseEvent = do_QueryInterface(aDOMEvent); - if (!mouseEvent) - return NS_OK; - - mouseEvent->GetScreenX(&g_lastX); - mouseEvent->GetScreenY(&g_lastY); - - nsCOMPtr mWindow; - nsCOMPtr mNode; - nsCOMPtr mOrigNode; - - uint32_t type = 0; - nsDOMEvent* event = aDOMEvent->InternalDOMEvent(); - bool isXul = false; - { - nsCOMPtr eventOrigTarget = event->GetOriginalTarget(); - if (eventOrigTarget) - mOrigNode = do_QueryInterface(eventOrigTarget); - isXul = IsXULNode(mOrigNode, &type); - - } - if (isXul) - return NS_ERROR_FAILURE; - - nsCOMPtr eventTarget = event->GetTarget(); - if (eventTarget) - mNode = do_QueryInterface(eventTarget); - - if (!mNode) - return NS_OK; - - GetDOMWindowByNode(mNode, getter_AddRefs(mWindow)); - if (!mWindow) - return NS_OK; - nsCOMPtr doc; - nsCOMPtr domDoc; - mWindow->GetDocument(getter_AddRefs(domDoc)); - doc = do_QueryInterface(domDoc); - if (!doc) return NS_OK; - // the only case where there could be more shells in printpreview - nsIPresShell *shell = doc->GetShell(); - NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE); - mViewManager = shell->GetViewManager(); - NS_ENSURE_TRUE(mViewManager, NS_ERROR_FAILURE); - mViewManager->GetRootWidget(getter_AddRefs(mWidget)); - NS_ENSURE_TRUE(mWidget, NS_ERROR_FAILURE); - return NS_OK; -} - -nsresult -nsWidgetUtils::MouseDown(nsIDOMEvent* aDOMEvent) -{ - g_is_scrollable = false; - // Return TRUE from your signal handler to mark the event as consumed. - if (NS_FAILED(UpdateFromEvent(aDOMEvent))) - return NS_OK; - g_is_scrollable = true; - if (g_is_scrollable) { - aDOMEvent->StopPropagation(); - aDOMEvent->PreventDefault(); - } - return NS_OK; -} - -/* static */ void -nsWidgetUtils::StopPanningCallback(nsITimer *timer, void *closure) -{ - g_panning = false; -} - -nsresult -nsWidgetUtils::MouseUp(nsIDOMEvent* aDOMEvent) -{ - nsCOMPtr mouseEvent; - mouseEvent = do_QueryInterface(aDOMEvent); - if (!mouseEvent) - return NS_OK; - // Return TRUE from your signal handler to mark the event as consumed. - g_lastX = MIN_INT; - g_lastY = MIN_INT; - g_is_scrollable = false; - if (g_panning) { - aDOMEvent->StopPropagation(); - aDOMEvent->PreventDefault(); - nsresult rv; - if (mTimer) { - rv = mTimer->InitWithFuncCallback(nsWidgetUtils::StopPanningCallback, - nullptr, 500, nsITimer::TYPE_ONE_SHOT); - if (NS_SUCCEEDED(rv)) - return NS_OK; - } - g_panning = false; - } - return NS_OK; -} - -nsresult -nsWidgetUtils::MouseMove(nsIDOMEvent* aDOMEvent) -{ - if (!g_is_scrollable) return NS_OK; - - nsCOMPtr mouseEvent = do_QueryInterface(aDOMEvent); - if (!mouseEvent) - return NS_OK; - int x, y; - ((nsIDOMMouseEvent*)mouseEvent)->GetScreenX(&x); - ((nsIDOMMouseEvent*)mouseEvent)->GetScreenY(&y); - - int dx = g_lastX - x; - int dy = g_lastY - y; - if(g_lastX == MIN_INT || g_lastY == MIN_INT) - return NS_OK; - - nsView* aView = mViewManager->GetRootView(); - if (!aView) - if (NS_FAILED(UpdateFromEvent(aDOMEvent))) - return NS_OK; - - nsEventStatus status; - WidgetWheelEvent wheelEvent(true, NS_WHEEL_WHEEL, mWidget); - wheelEvent.deltaMode = nsIDOMWheelEvent::DOM_DELTA_LINE; - wheelEvent.deltaX = wheelEvent.lineOrPageDeltaX = dx; - wheelEvent.deltaY = wheelEvent.lineOrPageDeltaY = dy; - mViewManager->DispatchEvent(&wheelEvent, aView, &status); - if (status != nsEventStatus_eIgnore) { - if (dx > 5 || dy > 5) { - g_panning = true; - } - g_lastX = x; - g_lastY = y; - } - - if (g_panning) { - aDOMEvent->StopPropagation(); - aDOMEvent->PreventDefault(); - } - - return NS_OK; -} - -// nsIContentPolicy Implementation -NS_IMETHODIMP -nsWidgetUtils::ShouldLoad(uint32_t aContentType, - nsIURI *aContentLocation, - nsIURI *aRequestingLocation, - nsISupports *aRequestingContext, - const nsACString &aMimeGuess, - nsISupports *aExtra, - int16_t *aDecision) -{ - *aDecision = nsIContentPolicy::ACCEPT; - nsresult rv; - - if (aContentType != nsIContentPolicy::TYPE_DOCUMENT) - return NS_OK; - - // we can't do anything without this - if (!aContentLocation) - return NS_OK; - - nsAutoCString scheme; - rv = aContentLocation->GetScheme(scheme); - nsAutoCString lscheme; - ToLowerCase(scheme, lscheme); - if (!lscheme.EqualsLiteral("ftp") && - !lscheme.EqualsLiteral("http") && - !lscheme.EqualsLiteral("https")) - return NS_OK; - if (g_panning > 0) - *aDecision = nsIContentPolicy::REJECT_REQUEST; - return NS_OK; -} - -NS_IMETHODIMP -nsWidgetUtils::HandleEvent(nsIDOMEvent* aDOMEvent) -{ - nsAutoString eventType; - aEvent->GetType(eventType); - - if (eventType.EqualsLiteral("mousedown")) { - return MouseDown(aEvent); - } - if (eventType.EqualsLiteral("mouseup")) { - return MouseUp(aEvent); - } - if (eventType.EqualsLiteral("mousemove")) { - return MouseMove(aEvent); - } - - return NS_OK; -} - -NS_IMETHODIMP -nsWidgetUtils::ShouldProcess(uint32_t aContentType, - nsIURI *aContentLocation, - nsIURI *aRequestingLocation, - nsISupports *aRequestingContext, - const nsACString &aMimeGuess, - nsISupports *aExtra, - int16_t *aDecision) -{ - *aDecision = nsIContentPolicy::ACCEPT; - return NS_OK; -} - -bool -nsWidgetUtils::IsXULNode(nsIDOMNode *aNode, uint32_t *aType) -{ - bool retval = false; - if (!aNode) return retval; - - nsString sorigNode; - aNode->GetNodeName(sorigNode); - if (sorigNode.EqualsLiteral("#document")) - return retval; - retval = StringBeginsWith(sorigNode, NS_LITERAL_STRING("xul:")); - - if (!aType) return retval; - - if (sorigNode.EqualsLiteral("xul:thumb") - || sorigNode.EqualsLiteral("xul:vbox") - || sorigNode.EqualsLiteral("xul:spacer")) - *aType = false; // Magic - else if (sorigNode.EqualsLiteral("xul:slider")) - *aType = 2; // Magic - else if (sorigNode.EqualsLiteral("xul:scrollbarbutton")) - *aType = 3; // Magic - - return retval; -} - -nsresult -nsWidgetUtils::GetDOMWindowByNode(nsIDOMNode* aNode, nsIDOMWindow** aDOMWindow) -{ - nsCOMPtr nodeDoc; - nsresult rv = aNode->GetOwnerDocument(getter_AddRefs(nodeDoc)); - NS_ENSURE_SUCCESS(rv, rv); - NS_ENSURE_TRUE(nodeDoc, NS_ERROR_NULL_POINTER); - - nsCOMPtr window; - rv = nodeDoc->GetDefaultView(getter_AddRefs(window)); - NS_ENSURE_SUCCESS(rv, rv); - NS_ENSURE_TRUE(window, NS_ERROR_NULL_POINTER); - window.forget(aDOMWindow); - return rv; -} - -EventTarget* -nsWidgetUtils::GetChromeEventHandler(nsIDOMWindow* aDOMWin) -{ - nsCOMPtr privateDOMWindow = do_QueryInterface(aDOMWin); - return privateDOMWindow ? privateDOMWindow->GetChromeEventHandler() : nullptr; -} - -void -nsWidgetUtils::RemoveWindowListeners(nsIDOMWindow *aDOMWin) -{ - nsresult rv; - EventTarget* chromeEventHandler = GetChromeEventHandler(aDOMWin); - if (!chromeEventHandler) { - return; - } - - // Use capturing, otherwise the normal find next will get activated when ours should - - // Remove DOM Text listener for IME text events - chromeEventHandler->RemoveEventListener(NS_LITERAL_STRING("mousedown"), - this, false); - chromeEventHandler->RemoveEventListener(NS_LITERAL_STRING("mouseup"), - this, false); - chromeEventHandler->RemoveEventListener(NS_LITERAL_STRING("mousemove"), - this, false); -} - -void -nsWidgetUtils::AttachWindowListeners(nsIDOMWindow *aDOMWin) -{ - nsresult rv; - EventHandler* chromeEventHandler = GetChromeEventHandler(aDOMWin); - if (!chromeEventHandler) { - return; - } - - // Use capturing, otherwise the normal find next will get activated when ours should - - // Attach menu listeners, this will help us ignore keystrokes meant for menus - chromeEventHandler->AddEventListener(NS_LITERAL_STRING("mousedown"), this, - false, false); - chromeEventHandler->AddEventListener(NS_LITERAL_STRING("mouseup"), this, - false, false); - chromeEventHandler->AddEventListener(NS_LITERAL_STRING("mousemove"), this, - false, false); -} - -nsWidgetUtils::~nsWidgetUtils() -{ -} - -NS_IMPL_ISUPPORTS4(nsWidgetUtils, - nsIObserver, - nsIDOMEventListener, - nsIContentPolicy, - nsISupportsWeakReference) - -NS_IMETHODIMP -nsWidgetUtils::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *aData) -{ - nsresult rv; - if (!strcmp(aTopic,"domwindowopened")) - { - nsCOMPtr chromeWindow = do_QueryInterface(aSubject); - if (chromeWindow) - AttachWindowListeners(chromeWindow); - return NS_OK; - } - - if (!strcmp(aTopic,"domwindowclosed")) - { - nsCOMPtr chromeWindow = do_QueryInterface(aSubject); - RemoveWindowListeners(chromeWindow); - return NS_OK; - } - - return NS_OK; -} - -//------------------------------------------------------------------------------ -// XPCOM REGISTRATION BELOW -//------------------------------------------------------------------------------ - -#define WidgetUtils_CID \ -{ 0x0ced17b6, 0x96ed, 0x4030, \ -{0xa1, 0x34, 0x77, 0xcb, 0x66, 0x10, 0xa8, 0xf6} } - -#define WidgetUtils_ContractID "@mozilla.org/extensions/widgetutils;1" - -static NS_METHOD WidgetUtilsRegistration(nsIComponentManager *aCompMgr, - nsIFile *aPath, - const char *registryLocation, - const char *componentType, - const nsModuleComponentInfo *info) -{ - nsresult rv; - - nsCOMPtr servman = do_QueryInterface((nsISupports*)aCompMgr, &rv); - if (NS_FAILED(rv)) - return rv; - - nsCOMPtr catman; - servman->GetServiceByContractID(NS_CATEGORYMANAGER_CONTRACTID, - NS_GET_IID(nsICategoryManager), - getter_AddRefs(catman)); - - if (NS_FAILED(rv)) - return rv; - - char* previous = nullptr; - rv = catman->AddCategoryEntry("app-startup", - "WidgetUtils", - WidgetUtils_ContractID, - true, - true, - &previous); - if (previous) - nsMemory::Free(previous); - rv = catman->AddCategoryEntry("content-policy", - "WidgetUtils", - WidgetUtils_ContractID, - true, - true, - &previous); - if (previous) - nsMemory::Free(previous); - - return rv; -} - -static NS_METHOD WidgetUtilsUnregistration(nsIComponentManager *aCompMgr, - nsIFile *aPath, - const char *registryLocation, - const nsModuleComponentInfo *info) -{ - nsresult rv; - - nsCOMPtr servman = do_QueryInterface((nsISupports*)aCompMgr, &rv); - if (NS_FAILED(rv)) - return rv; - - nsCOMPtr catman; - servman->GetServiceByContractID(NS_CATEGORYMANAGER_CONTRACTID, - NS_GET_IID(nsICategoryManager), - getter_AddRefs(catman)); - - if (NS_FAILED(rv)) - return rv; - - rv = catman->DeleteCategoryEntry("app-startup", - "WidgetUtils", - true); - rv = catman->DeleteCategoryEntry("content-policy", - "WidgetUtils", - true); - - return rv; -} - -NS_GENERIC_FACTORY_CONSTRUCTOR(nsWidgetUtils) - - static const nsModuleComponentInfo components[] = -{ - { "nsWidgetUtilsService", - WidgetUtils_CID, - WidgetUtils_ContractID, - nsWidgetUtilsConstructor, - WidgetUtilsRegistration, - WidgetUtilsUnregistration - } -}; - -NS_IMPL_NSGETMODULE(nsWidgetUtilsModule, components) diff --git a/gfx/2d/DrawTargetD2D.cpp b/gfx/2d/DrawTargetD2D.cpp index 6b7f21edd06c..bae29fdc9bf9 100644 --- a/gfx/2d/DrawTargetD2D.cpp +++ b/gfx/2d/DrawTargetD2D.cpp @@ -764,27 +764,37 @@ void DrawTargetD2D::ClearRect(const Rect &aRect) { MarkChanged(); + PushClipRect(aRect); - FlushTransformToRT(); PopAllClips(); AutoSaveRestoreClippedOut restoreClippedOut(this); - restoreClippedOut.Save(); + D2D1_RECT_F clipRect; + bool isPixelAligned; + bool pushedClip = false; + if (mTransform.IsRectilinear() && + GetDeviceSpaceClipRect(clipRect, isPixelAligned)) { + if (mTransformDirty || + !mTransform.IsIdentity()) { + mRT->SetTransform(D2D1::IdentityMatrix()); + mTransformDirty = true; + } - bool needsClip = false; - - needsClip = aRect.x > 0 || aRect.y > 0 || - aRect.XMost() < mSize.width || - aRect.YMost() < mSize.height; - - if (needsClip) { - mRT->PushAxisAlignedClip(D2DRect(aRect), D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); + mRT->PushAxisAlignedClip(clipRect, isPixelAligned ? D2D1_ANTIALIAS_MODE_ALIASED : D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); + pushedClip = true; + } else { + FlushTransformToRT(); + restoreClippedOut.Save(); } + mRT->Clear(D2D1::ColorF(0, 0.0f)); - if (needsClip) { + + if (pushedClip) { mRT->PopAxisAlignedClip(); } + + PopClip(); return; } @@ -1773,9 +1783,40 @@ IntersectRect(const D2D1_RECT_F& aRect1, const D2D1_RECT_F& aRect2) result.top = max(aRect1.top, aRect2.top); result.right = min(aRect1.right, aRect2.right); result.bottom = min(aRect1.bottom, aRect2.bottom); + + result.right = max(result.right, result.left); + result.bottom = max(result.bottom, result.top); + return result; } +bool +DrawTargetD2D::GetDeviceSpaceClipRect(D2D1_RECT_F& aClipRect, bool& aIsPixelAligned) +{ + if (!mPushedClips.size()) { + return false; + } + + std::vector::iterator iter = mPushedClips.begin(); + if (iter->mPath) { + return false; + } + aClipRect = iter->mBounds; + aIsPixelAligned = iter->mIsPixelAligned; + + iter++; + for (;iter != mPushedClips.end(); iter++) { + if (iter->mPath) { + return false; + } + aClipRect = IntersectRect(aClipRect, iter->mBounds); + if (!iter->mIsPixelAligned) { + aIsPixelAligned = false; + } + } + return true; +} + TemporaryRef DrawTargetD2D::GetClippedGeometry(IntRect *aClipBounds) { diff --git a/gfx/2d/DrawTargetD2D.h b/gfx/2d/DrawTargetD2D.h index 1cf4fa1a3946..460823ad2ed0 100644 --- a/gfx/2d/DrawTargetD2D.h +++ b/gfx/2d/DrawTargetD2D.h @@ -205,6 +205,8 @@ private: // bounds to correctly reflect the total clip. This is in device space. TemporaryRef GetClippedGeometry(IntRect *aClipBounds); + bool GetDeviceSpaceClipRect(D2D1_RECT_F& aClipRect, bool& aIsPixelAligned); + TemporaryRef CreateBrushForPattern(const Pattern &aPattern, Float aAlpha = 1.0f); TemporaryRef CreateGradientTexture(const GradientStopsD2D *aStops); diff --git a/gfx/gl/GLBlitTextureImageHelper.cpp b/gfx/gl/GLBlitTextureImageHelper.cpp index c0eb63f8c4c6..42208f19356f 100644 --- a/gfx/gl/GLBlitTextureImageHelper.cpp +++ b/gfx/gl/GLBlitTextureImageHelper.cpp @@ -8,6 +8,7 @@ #include "GLUploadHelpers.h" #include "DecomposeIntoNoRepeatTriangles.h" #include "GLContext.h" +#include "ScopedGLHelpers.h" #include "nsRect.h" #include "gfx2DGlue.h" #include "gfxUtils.h" @@ -143,7 +144,8 @@ GLBlitTextureImageHelper::BlitTextureImage(TextureImage *aSrc, const nsIntRect& } } - TextureImage::ScopedBindTexture texBind(aSrc, LOCAL_GL_TEXTURE0); + ScopedBindTextureUnit autoTexUnit(mGL, LOCAL_GL_TEXTURE0); + ScopedBindTexture autoTex(mGL, aSrc->GetTextureID()); mGL->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0); diff --git a/gfx/gl/GLContext.cpp b/gfx/gl/GLContext.cpp index 5401b3156e0e..35203f814eaf 100644 --- a/gfx/gl/GLContext.cpp +++ b/gfx/gl/GLContext.cpp @@ -1189,24 +1189,6 @@ GLContext::ListHasExtension(const GLubyte *extensions, const char *extension) return false; } -void GLContext::ApplyFilterToBoundTexture(GraphicsFilter aFilter) -{ - ApplyFilterToBoundTexture(LOCAL_GL_TEXTURE_2D, aFilter); -} - -void GLContext::ApplyFilterToBoundTexture(GLuint aTarget, - GraphicsFilter aFilter) -{ - if (aFilter == GraphicsFilter::FILTER_NEAREST) { - fTexParameteri(aTarget, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_NEAREST); - fTexParameteri(aTarget, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_NEAREST); - } else { - fTexParameteri(aTarget, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_LINEAR); - fTexParameteri(aTarget, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_LINEAR); - } -} - - void GLContext::DetermineCaps() { diff --git a/gfx/gl/GLContext.h b/gfx/gl/GLContext.h index a6c4bd1a7bdd..4ebfe1bb3395 100644 --- a/gfx/gl/GLContext.h +++ b/gfx/gl/GLContext.h @@ -2482,25 +2482,6 @@ public: */ virtual bool ReleaseTexImage() { return false; } - /** - * Applies aFilter to the texture currently bound to GL_TEXTURE_2D. - */ - void ApplyFilterToBoundTexture(GraphicsFilter aFilter); - - /** - * Applies aFilter to the texture currently bound to aTarget. - */ - void ApplyFilterToBoundTexture(GLuint aTarget, - GraphicsFilter aFilter); - - virtual bool BindExternalBuffer(GLuint texture, void* buffer) { return false; } - virtual bool UnbindExternalBuffer(GLuint texture) { return false; } - -#ifdef MOZ_WIDGET_GONK - virtual EGLImage CreateEGLImageForNativeBuffer(void* buffer) = 0; - virtual void DestroyEGLImage(EGLImage image) = 0; -#endif - // Before reads from offscreen texture void GuaranteeResolve(); diff --git a/gfx/gl/GLContextProviderEGL.cpp b/gfx/gl/GLContextProviderEGL.cpp index 2e0d8143486e..ba6b9f2c46e2 100644 --- a/gfx/gl/GLContextProviderEGL.cpp +++ b/gfx/gl/GLContextProviderEGL.cpp @@ -408,25 +408,6 @@ public: return true; } -#ifdef MOZ_WIDGET_GONK - EGLImage CreateEGLImageForNativeBuffer(void* buffer) MOZ_OVERRIDE - { - EGLint attrs[] = { - LOCAL_EGL_IMAGE_PRESERVED, LOCAL_EGL_TRUE, - LOCAL_EGL_NONE, LOCAL_EGL_NONE - }; - return sEGLLibrary.fCreateImage(EGL_DISPLAY(), - EGL_NO_CONTEXT, - LOCAL_EGL_NATIVE_BUFFER_ANDROID, - buffer, attrs); - } - - void DestroyEGLImage(EGLImage image) MOZ_OVERRIDE - { - sEGLLibrary.fDestroyImage(EGL_DISPLAY(), image); - } -#endif - virtual void SetEGLSurfaceOverride(EGLSurface surf) MOZ_OVERRIDE { if (Screen()) { /* Blit `draw` to `read` if we need to, before we potentially juggle diff --git a/gfx/gl/GLContextProviderGLX.cpp b/gfx/gl/GLContextProviderGLX.cpp index 672e5eb242a4..e5b96c87f0e4 100644 --- a/gfx/gl/GLContextProviderGLX.cpp +++ b/gfx/gl/GLContextProviderGLX.cpp @@ -58,7 +58,7 @@ GLXLibrary::SelectLibrary(const ContextFlags& aFlags) } // Check that we have at least version aMajor.aMinor . -bool +bool GLXLibrary::GLXVersionCheck(int aMajor, int aMinor) { return aMajor < mGLXMajorVersion || @@ -244,7 +244,7 @@ GLXLibrary::EnsureInitialized(LibType libType) } if (HasExtension(extensionsStr, "GLX_EXT_texture_from_pixmap") && - GLLibraryLoader::LoadSymbols(mOGLLibrary, symbols_texturefrompixmap, + GLLibraryLoader::LoadSymbols(mOGLLibrary, symbols_texturefrompixmap, (GLLibraryLoader::PlatformLookupFunction)&xGetProcAddress)) { #ifdef MOZ_WIDGET_GTK @@ -278,7 +278,7 @@ GLXLibrary::SupportsTextureFromPixmap(gfxASurface* aSurface) if (!EnsureInitialized(mLibType)) { return false; } - + if (aSurface->GetType() != gfxSurfaceTypeXlib || !mUseTextureFromPixmap) { return false; } @@ -286,7 +286,7 @@ GLXLibrary::SupportsTextureFromPixmap(gfxASurface* aSurface) return true; } -GLXPixmap +GLXPixmap GLXLibrary::CreatePixmap(gfxASurface* aSurface) { if (!SupportsTextureFromPixmap(aSurface)) { @@ -320,7 +320,7 @@ GLXLibrary::CreatePixmap(gfxASurface* aSurface) attribs, &numConfigs)); - // Find an fbconfig that matches the pixel format used on the Pixmap. + // Find an fbconfig that matches the pixel format used on the Pixmap. int matchIndex = -1; unsigned long redMask = static_cast(direct.redMask) << direct.red; @@ -436,7 +436,7 @@ GLXLibrary::DestroyPixmap(GLXPixmap aPixmap) void GLXLibrary::BindTexImage(GLXPixmap aPixmap) -{ +{ if (!mUseTextureFromPixmap) { return; } @@ -508,7 +508,7 @@ GLXLibrary::AfterGLXCall() #define BEFORE_GLX_CALL do { \ sGLXLibrary[gCurrLib].BeforeGLXCall(); \ } while (0) - + #define AFTER_GLX_CALL do { \ sGLXLibrary[gCurrLib].AfterGLXCall(); \ } while (0) @@ -519,8 +519,8 @@ GLXLibrary::AfterGLXCall() #define AFTER_GLX_CALL do { } while(0) #endif - -void + +void GLXLibrary::xDestroyContext(Display* display, GLXContext context) { BEFORE_GLX_CALL; @@ -528,9 +528,9 @@ GLXLibrary::xDestroyContext(Display* display, GLXContext context) AFTER_GLX_CALL; } -Bool -GLXLibrary::xMakeCurrent(Display* display, - GLXDrawable drawable, +Bool +GLXLibrary::xMakeCurrent(Display* display, + GLXDrawable drawable, GLXContext context) { BEFORE_GLX_CALL; @@ -539,7 +539,7 @@ GLXLibrary::xMakeCurrent(Display* display, return result; } -GLXContext +GLXContext GLXLibrary::xGetCurrentContext() { BEFORE_GLX_CALL; @@ -548,7 +548,7 @@ GLXLibrary::xGetCurrentContext() return result; } -/* static */ void* +/* static */ void* GLXLibrary::xGetProcAddress(const char *procName) { BEFORE_GLX_CALL; @@ -558,9 +558,9 @@ GLXLibrary::xGetProcAddress(const char *procName) } GLXFBConfig* -GLXLibrary::xChooseFBConfig(Display* display, - int screen, - const int *attrib_list, +GLXLibrary::xChooseFBConfig(Display* display, + int screen, + const int *attrib_list, int *nelements) { BEFORE_GLX_CALL; @@ -569,9 +569,9 @@ GLXLibrary::xChooseFBConfig(Display* display, return result; } -GLXFBConfig* -GLXLibrary::xGetFBConfigs(Display* display, - int screen, +GLXFBConfig* +GLXLibrary::xGetFBConfigs(Display* display, + int screen, int *nelements) { BEFORE_GLX_CALL; @@ -579,16 +579,16 @@ GLXLibrary::xGetFBConfigs(Display* display, AFTER_GLX_CALL; return result; } - + GLXContext -GLXLibrary::xCreateNewContext(Display* display, - GLXFBConfig config, - int render_type, - GLXContext share_list, +GLXLibrary::xCreateNewContext(Display* display, + GLXFBConfig config, + int render_type, + GLXContext share_list, Bool direct) { BEFORE_GLX_CALL; - GLXContext result = xCreateNewContextInternal(display, config, + GLXContext result = xCreateNewContextInternal(display, config, render_type, share_list, direct); AFTER_GLX_CALL; @@ -647,7 +647,7 @@ GLXLibrary::xQueryServerString(Display *display, } GLXPixmap -GLXLibrary::xCreatePixmap(Display *display, +GLXLibrary::xCreatePixmap(Display *display, GLXFBConfig config, Pixmap pixmap, const int *attrib_list) @@ -710,7 +710,7 @@ GLXLibrary::xReleaseTexImage(Display *display, AFTER_GLX_CALL; } -void +void GLXLibrary::xWaitGL() { BEFORE_GLX_CALL; @@ -727,16 +727,16 @@ GLXLibrary::xWaitX() } GLXContext -GLXLibrary::xCreateContextAttribs(Display* display, - GLXFBConfig config, - GLXContext share_list, +GLXLibrary::xCreateContextAttribs(Display* display, + GLXFBConfig config, + GLXContext share_list, Bool direct, const int* attrib_list) { BEFORE_GLX_CALL; - GLXContext result = xCreateContextAttribsInternal(display, - config, - share_list, + GLXContext result = xCreateContextAttribsInternal(display, + config, + share_list, direct, attrib_list); AFTER_GLX_CALL; @@ -905,7 +905,7 @@ TRY_AGAIN_NO_SHARING: switch(aType) { case NativeGLContext: return mContext; - + case NativeThebesSurface: return mPixmap; @@ -1012,13 +1012,13 @@ GLContextProviderGLX::CreateForWindow(nsIWidget *aWidget) // performance might be suboptimal. But using the existing visual // is a relatively safe intermediate step. - Display *display = (Display*)aWidget->GetNativeData(NS_NATIVE_DISPLAY); + Display *display = (Display*)aWidget->GetNativeData(NS_NATIVE_DISPLAY); int xscreen = DefaultScreen(display); Window window = GET_NATIVE_WINDOW(aWidget); int numConfigs; ScopedXFree cfgs; - if (sDefGLXLib.IsATI() || + if (sDefGLXLib.IsATI() || !sDefGLXLib.GLXVersionCheck(1, 3)) { const int attribs[] = { LOCAL_GLX_DOUBLEBUFFER, False, diff --git a/gfx/gl/GLTextureImage.cpp b/gfx/gl/GLTextureImage.cpp index e6c345c85dc2..36ec89565f39 100644 --- a/gfx/gl/GLTextureImage.cpp +++ b/gfx/gl/GLTextureImage.cpp @@ -205,13 +205,6 @@ BasicTextureImage::BindTexture(GLenum aTextureUnit) mGLContext->fActiveTexture(LOCAL_GL_TEXTURE0); } -void -BasicTextureImage::ApplyFilter() -{ - mGLContext->ApplyFilterToBoundTexture(mFilter); -} - - already_AddRefed BasicTextureImage::GetSurfaceForUpdate(const gfxIntSize& aSize, ImageFormat aFmt) { @@ -637,12 +630,6 @@ TiledTextureImage::BindTexture(GLenum aTextureUnit) mImages[mCurrentImage]->BindTexture(aTextureUnit); } -void -TiledTextureImage::ApplyFilter() -{ - mGL->ApplyFilterToBoundTexture(mFilter); -} - /* * Resize, trying to reuse tiles. The reuse strategy is to decide on reuse per * column. A tile on a column is reused if it hasn't changed size, otherwise it @@ -745,16 +732,6 @@ uint32_t TiledTextureImage::GetTileCount() return mImages.Length(); } -TextureImage::ScopedBindTexture::ScopedBindTexture(TextureImage* aTexture, - GLenum aTextureUnit) - : mTexture(aTexture) -{ - if (mTexture) { - MOZ_ASSERT(aTextureUnit >= LOCAL_GL_TEXTURE0); - mTexture->BindTexture(aTextureUnit); - } -} - already_AddRefed CreateBasicTextureImage(GLContext* aGL, const nsIntSize& aSize, diff --git a/gfx/gl/GLTextureImage.h b/gfx/gl/GLTextureImage.h index 52676dfb61af..efda20554860 100644 --- a/gfx/gl/GLTextureImage.h +++ b/gfx/gl/GLTextureImage.h @@ -185,41 +185,6 @@ public: const gfx::IntPoint* aSrcOffset = nullptr); virtual void BindTexture(GLenum aTextureUnit) = 0; - virtual void ReleaseTexture() {} - - void BindTextureAndApplyFilter(GLenum aTextureUnit) { - BindTexture(aTextureUnit); - ApplyFilter(); - } - - class ScopedBindTexture - { - public: - ScopedBindTexture(TextureImage *aTexture, GLenum aTextureUnit); - - ~ScopedBindTexture() - { - if (mTexture) { - mTexture->ReleaseTexture(); - } - } - - protected: - TextureImage *mTexture; - }; - - class ScopedBindTextureAndApplyFilter - : public ScopedBindTexture - { - public: - ScopedBindTextureAndApplyFilter(TextureImage *aTexture, GLenum aTextureUnit) : - ScopedBindTexture(aTexture, aTextureUnit) - { - if (mTexture) { - mTexture->ApplyFilter(); - } - } - }; /** * Returns the image format of the texture. Only valid after a matching @@ -247,12 +212,6 @@ public: void SetFilter(GraphicsFilter aFilter) { mFilter = aFilter; } - /** - * Applies this TextureImage's filter, assuming that its texture is - * the currently bound texture. - */ - virtual void ApplyFilter() = 0; - protected: friend class GLContext; @@ -345,9 +304,7 @@ public: virtual void Resize(const nsIntSize& aSize); - virtual void ApplyFilter(); protected: - GLuint mTexture; TextureState mTextureState; nsRefPtr mGLContext; @@ -390,7 +347,6 @@ public: virtual bool DirectUpdate(gfxASurface* aSurf, const nsIntRegion& aRegion, const nsIntPoint& aFrom = nsIntPoint(0,0)); virtual bool InUpdate() const { return mInUpdate; } virtual void BindTexture(GLenum); - virtual void ApplyFilter(); protected: virtual gfx::IntRect GetSrcTileRect(); diff --git a/gfx/gl/TextureImageEGL.cpp b/gfx/gl/TextureImageEGL.cpp index 4f18aabfeead..f8277271db01 100644 --- a/gfx/gl/TextureImageEGL.cpp +++ b/gfx/gl/TextureImageEGL.cpp @@ -311,12 +311,6 @@ TextureImageEGL::DestroyEGLSurface(void) mSurface = nullptr; } -void -TextureImageEGL::ApplyFilter() -{ - mGLContext->ApplyFilterToBoundTexture(mFilter); -} - already_AddRefed CreateTextureImageEGL(GLContext *gl, const nsIntSize& aSize, @@ -357,4 +351,4 @@ TileGenFuncEGL(GLContext *gl, } } -} \ No newline at end of file +} diff --git a/gfx/gl/TextureImageEGL.h b/gfx/gl/TextureImageEGL.h index e12e68a0c6a3..71e99e1ddc06 100644 --- a/gfx/gl/TextureImageEGL.h +++ b/gfx/gl/TextureImageEGL.h @@ -75,8 +75,6 @@ protected: TextureState mTextureState; bool mBound; - - virtual void ApplyFilter(); }; already_AddRefed @@ -97,4 +95,4 @@ TileGenFuncEGL(GLContext *gl, } } -#endif // TEXTUREIMAGEEGL_H_ \ No newline at end of file +#endif // TEXTUREIMAGEEGL_H_ diff --git a/gfx/layers/AtomicRefCountedWithFinalize.h b/gfx/layers/AtomicRefCountedWithFinalize.h new file mode 100644 index 000000000000..3bb00b1b16d3 --- /dev/null +++ b/gfx/layers/AtomicRefCountedWithFinalize.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZILLA_ATOMICREFCOUNTEDWITHFINALIZE_H_ +#define MOZILLA_ATOMICREFCOUNTEDWITHFINALIZE_H_ + +#include "mozilla/RefPtr.h" + +namespace mozilla { + +template +class AtomicRefCountedWithFinalize +{ + protected: + AtomicRefCountedWithFinalize() + : mRefCount(0) + {} + + ~AtomicRefCountedWithFinalize() {} + + public: + void AddRef() { + MOZ_ASSERT(mRefCount >= 0); + ++mRefCount; + } + + void Release() { + MOZ_ASSERT(mRefCount > 0); + if (0 == --mRefCount) { +#ifdef DEBUG + mRefCount = detail::DEAD; +#endif + T* derived = static_cast(this); + derived->Finalize(); + delete derived; + } + } + +private: + Atomic mRefCount; +}; + +} + +#endif diff --git a/gfx/layers/ImageContainer.cpp b/gfx/layers/ImageContainer.cpp index f43330ed4517..8d51ce7c6944 100644 --- a/gfx/layers/ImageContainer.cpp +++ b/gfx/layers/ImageContainer.cpp @@ -217,6 +217,7 @@ ImageContainer::ClearAllImages() ImageBridgeChild::FlushAllImages(mImageClient, this, false); return; } + ReentrantMonitorAutoEnter mon(mReentrantMonitor); SetCurrentImageInternal(nullptr); } diff --git a/gfx/layers/Layers.cpp b/gfx/layers/Layers.cpp index 6091347e7916..1aa0a3b90e17 100644 --- a/gfx/layers/Layers.cpp +++ b/gfx/layers/Layers.cpp @@ -214,7 +214,7 @@ Layer::ClearAnimations() Mutated(); } -static nsCSSValueList* +static nsCSSValueSharedList* CreateCSSValueList(const InfallibleTArray& aFunctions) { nsAutoPtr result; @@ -337,7 +337,7 @@ CreateCSSValueList(const InfallibleTArray& aFunctions) result = new nsCSSValueList(); result->mValue.SetNoneValue(); } - return result.forget(); + return new nsCSSValueSharedList(result.forget()); } void @@ -385,13 +385,11 @@ Layer::SetAnimations(const AnimationArray& aAnimations) if (segment.endState().type() == Animatable::TArrayOfTransformFunction) { const InfallibleTArray& startFunctions = segment.startState().get_ArrayOfTransformFunction(); - startValue->SetAndAdoptCSSValueListValue(CreateCSSValueList(startFunctions), - nsStyleAnimation::eUnit_Transform); + startValue->SetTransformValue(CreateCSSValueList(startFunctions)); const InfallibleTArray& endFunctions = segment.endState().get_ArrayOfTransformFunction(); - endValue->SetAndAdoptCSSValueListValue(CreateCSSValueList(endFunctions), - nsStyleAnimation::eUnit_Transform); + endValue->SetTransformValue(CreateCSSValueList(endFunctions)); } else { NS_ASSERTION(segment.endState().type() == Animatable::Tfloat, "Unknown Animatable type"); diff --git a/gfx/layers/Makefile.in b/gfx/layers/Makefile.in index 5f4d9c633e67..f9f5f9578e55 100644 --- a/gfx/layers/Makefile.in +++ b/gfx/layers/Makefile.in @@ -25,13 +25,3 @@ CXXFLAGS += \ $(NULL) CXXFLAGS += $(MOZ_CAIRO_CFLAGS) $(TK_CFLAGS) - -ifdef _MSC_VER -ifeq ($(CPU_ARCH),x86_64) -# Workaround compiler bug (Bug 795594) -NO_PROFILE_GUIDED_OPTIMIZE := \ - LayerTreeInvalidation.cpp \ - Layers.cpp \ - $(NULL) -endif -endif diff --git a/gfx/layers/basic/MacIOSurfaceTextureHostBasic.cpp b/gfx/layers/basic/MacIOSurfaceTextureHostBasic.cpp index 33ea46705338..83f19baad829 100644 --- a/gfx/layers/basic/MacIOSurfaceTextureHostBasic.cpp +++ b/gfx/layers/basic/MacIOSurfaceTextureHostBasic.cpp @@ -35,11 +35,10 @@ MacIOSurfaceTextureSourceBasic::GetFormat() const } MacIOSurfaceTextureHostBasic::MacIOSurfaceTextureHostBasic( - uint64_t aID, TextureFlags aFlags, const SurfaceDescriptorMacIOSurface& aDescriptor ) - : TextureHost(aID, aFlags) + : TextureHost(aFlags) { mSurface = MacIOSurface::LookupSurface(aDescriptor.surface(), aDescriptor.scaleFactor(), diff --git a/gfx/layers/basic/MacIOSurfaceTextureHostBasic.h b/gfx/layers/basic/MacIOSurfaceTextureHostBasic.h index 747c8eb29bb3..06375de7ae35 100644 --- a/gfx/layers/basic/MacIOSurfaceTextureHostBasic.h +++ b/gfx/layers/basic/MacIOSurfaceTextureHostBasic.h @@ -56,9 +56,8 @@ protected: class MacIOSurfaceTextureHostBasic : public TextureHost { public: - MacIOSurfaceTextureHostBasic(uint64_t aID, - TextureFlags aFlags, - const SurfaceDescriptorMacIOSurface& aDescriptor); + MacIOSurfaceTextureHostBasic(TextureFlags aFlags, + const SurfaceDescriptorMacIOSurface& aDescriptor); virtual void SetCompositor(Compositor* aCompositor) MOZ_OVERRIDE; diff --git a/gfx/layers/basic/TextureHostBasic.cpp b/gfx/layers/basic/TextureHostBasic.cpp index 9d8c76e62f8f..867b134e468e 100644 --- a/gfx/layers/basic/TextureHostBasic.cpp +++ b/gfx/layers/basic/TextureHostBasic.cpp @@ -13,8 +13,7 @@ namespace mozilla { namespace layers { TemporaryRef -CreateTextureHostBasic(uint64_t aID, - const SurfaceDescriptor& aDesc, +CreateTextureHostBasic(const SurfaceDescriptor& aDesc, ISurfaceAllocator* aDeallocator, TextureFlags aFlags) { @@ -24,12 +23,12 @@ CreateTextureHostBasic(uint64_t aID, case SurfaceDescriptor::TSurfaceDescriptorMacIOSurface: { const SurfaceDescriptorMacIOSurface& desc = aDesc.get_SurfaceDescriptorMacIOSurface(); - result = new MacIOSurfaceTextureHostBasic(aID, aFlags, desc); + result = new MacIOSurfaceTextureHostBasic(aFlags, desc); break; } #endif default: { - result = CreateBackendIndependentTextureHost(aID, aDesc, aDeallocator, aFlags); + result = CreateBackendIndependentTextureHost(aDesc, aDeallocator, aFlags); break; } } diff --git a/gfx/layers/client/CanvasClient.cpp b/gfx/layers/client/CanvasClient.cpp index 43db6d9de9d6..26553b3e5b94 100644 --- a/gfx/layers/client/CanvasClient.cpp +++ b/gfx/layers/client/CanvasClient.cpp @@ -58,7 +58,7 @@ CanvasClient2D::Update(gfx::IntSize aSize, ClientCanvasLayer* aLayer) { if (mBuffer && (mBuffer->IsImmutable() || mBuffer->GetSize() != aSize)) { - RemoveTextureClient(mBuffer); + mBuffer->ForceRemove(); mBuffer = nullptr; } @@ -106,14 +106,6 @@ CanvasClient2D::CreateBufferTextureClient(gfx::SurfaceFormat aFormat, TextureFla mTextureInfo.mTextureFlags | aFlags); } -void -CanvasClient2D::OnActorDestroy() -{ - if (mBuffer) { - mBuffer->OnActorDestroy(); - } -} - void DeprecatedCanvasClient2D::Updated() { @@ -165,14 +157,6 @@ DeprecatedCanvasClient2D::Update(gfx::IntSize aSize, ClientCanvasLayer* aLayer) mDeprecatedTextureClient->Unlock(); } -void -DeprecatedCanvasClient2D::OnActorDestroy() -{ - if (mDeprecatedTextureClient) { - mDeprecatedTextureClient->OnActorDestroy(); - } -} - void DeprecatedCanvasClientSurfaceStream::Updated() { @@ -241,13 +225,5 @@ DeprecatedCanvasClientSurfaceStream::Update(gfx::IntSize aSize, ClientCanvasLaye aLayer->Painted(); } -void -DeprecatedCanvasClientSurfaceStream::OnActorDestroy() -{ - if (mDeprecatedTextureClient) { - mDeprecatedTextureClient->OnActorDestroy(); - } -} - } } diff --git a/gfx/layers/client/CanvasClient.h b/gfx/layers/client/CanvasClient.h index 195ce3b562ff..e0d534b00d34 100644 --- a/gfx/layers/client/CanvasClient.h +++ b/gfx/layers/client/CanvasClient.h @@ -91,8 +91,6 @@ public: mBuffer = nullptr; } - virtual void OnActorDestroy() MOZ_OVERRIDE; - private: RefPtr mBuffer; }; @@ -117,8 +115,6 @@ public: mDeprecatedTextureClient->SetDescriptorFromReply(aDescriptor); } - virtual void OnActorDestroy() MOZ_OVERRIDE; - private: RefPtr mDeprecatedTextureClient; }; @@ -145,8 +141,6 @@ public: mDeprecatedTextureClient->SetDescriptorFromReply(aDescriptor); } - virtual void OnActorDestroy() MOZ_OVERRIDE; - private: RefPtr mDeprecatedTextureClient; }; diff --git a/gfx/layers/client/ClientLayerManager.cpp b/gfx/layers/client/ClientLayerManager.cpp index 30261f3615d5..9270cea2a3fb 100644 --- a/gfx/layers/client/ClientLayerManager.cpp +++ b/gfx/layers/client/ClientLayerManager.cpp @@ -368,17 +368,6 @@ ClientLayerManager::ForwardTransaction() ->SetDescriptorFromReply(ots.textureId(), ots.image()); break; } - case EditReply::TReplyTextureRemoved: { - // XXX - to manage reuse of gralloc buffers, we'll need to add some - // glue code here to find the TextureClient and invoke a callback to - // let the camera know that the gralloc buffer is not used anymore on - // the compositor side and that it can reuse it. - const ReplyTextureRemoved& rep = reply.get_ReplyTextureRemoved(); - CompositableClient* compositable - = static_cast(rep.compositableChild())->GetCompositableClient(); - compositable->OnReplyTextureRemoved(rep.textureId()); - break; - } default: NS_RUNTIMEABORT("not reached"); diff --git a/gfx/layers/client/CompositableClient.cpp b/gfx/layers/client/CompositableClient.cpp index 63984f396e85..630068967b07 100644 --- a/gfx/layers/client/CompositableClient.cpp +++ b/gfx/layers/client/CompositableClient.cpp @@ -24,8 +24,7 @@ namespace mozilla { namespace layers { CompositableClient::CompositableClient(CompositableForwarder* aForwarder) -: mNextTextureID(1) -, mCompositableChild(nullptr) +: mCompositableChild(nullptr) , mForwarder(aForwarder) { MOZ_COUNT_CTOR(CompositableClient); @@ -36,23 +35,6 @@ CompositableClient::~CompositableClient() { MOZ_COUNT_DTOR(CompositableClient); Destroy(); - - FlushTexturesToRemoveCallbacks(); - MOZ_ASSERT(mTexturesToRemove.Length() == 0, "would leak textures pending for deletion"); -} - -void -CompositableClient::FlushTexturesToRemoveCallbacks() -{ - std::map::iterator it - = mTexturesToRemoveCallbacks.begin(); - std::map::iterator stop - = mTexturesToRemoveCallbacks.end(); - for (; it != stop; ++it) { - it->second->DeallocateSharedData(GetForwarder()); - delete it->second; - } - mTexturesToRemoveCallbacks.clear(); } LayersBackend @@ -251,70 +233,16 @@ CompositableClient::CreateTextureClientForDrawing(SurfaceFormat aFormat, return result; } -uint64_t -CompositableClient::NextTextureID() -{ - ++mNextTextureID; - // 0 is always an invalid ID - if (mNextTextureID == 0) { - ++mNextTextureID; - } - - return mNextTextureID; -} - bool CompositableClient::AddTextureClient(TextureClient* aClient) { - aClient->SetID(NextTextureID()); - return mForwarder->AddTexture(this, aClient); -} - -void -CompositableClient::RemoveTextureClient(TextureClient* aClient) -{ - MOZ_ASSERT(aClient); - mTexturesToRemove.AppendElement(TextureIDAndFlags(aClient->GetID(), - aClient->GetFlags())); - if (aClient->GetFlags() & TEXTURE_DEALLOCATE_CLIENT) { - TextureClientData* data = aClient->DropTextureData(); - if (data) { - mTexturesToRemoveCallbacks[aClient->GetID()] = data; - } - } - aClient->ClearID(); - aClient->MarkInvalid(); -} - -void -CompositableClient::OnReplyTextureRemoved(uint64_t aTextureID) -{ - std::map::iterator it - = mTexturesToRemoveCallbacks.find(aTextureID); - if (it != mTexturesToRemoveCallbacks.end()) { - it->second->DeallocateSharedData(GetForwarder()); - delete it->second; - mTexturesToRemoveCallbacks.erase(it); - } + return aClient->InitIPDLActor(mForwarder); } void CompositableClient::OnTransaction() { - for (unsigned i = 0; i < mTexturesToRemove.Length(); ++i) { - const TextureIDAndFlags& texture = mTexturesToRemove[i]; - mForwarder->RemoveTexture(this, texture.mID, texture.mFlags); - } - mTexturesToRemove.Clear(); } - -void -CompositableChild::ActorDestroy(ActorDestroyReason why) -{ - if (mCompositableClient && why == AbnormalShutdown) { - mCompositableClient->OnActorDestroy(); - } -} } // namespace layers } // namespace mozilla diff --git a/gfx/layers/client/CompositableClient.h b/gfx/layers/client/CompositableClient.h index b5a50cb366c4..47fbc71fb397 100644 --- a/gfx/layers/client/CompositableClient.h +++ b/gfx/layers/client/CompositableClient.h @@ -132,12 +132,6 @@ public: */ virtual bool AddTextureClient(TextureClient* aClient); - /** - * Tells the Compositor to delete the TextureHost corresponding to this - * TextureClient. - */ - virtual void RemoveTextureClient(TextureClient* aClient); - /** * A hook for the Compositable to execute whatever it held off for next transaction. */ @@ -148,47 +142,11 @@ public: */ virtual void OnDetach() {} - /** - * When texture deallocation must happen on the client side, we need to first - * ensure that the compositor has already let go of the data in order - * to safely deallocate it. - * - * This is implemented by registering a callback to postpone deallocation or - * recycling of the shared data. - * - * This hook is called when the compositor notifies the client that it is not - * holding any more references to the shared data so that this compositable - * can run the corresponding callback. - */ - void OnReplyTextureRemoved(uint64_t aTextureID); - - /** - * Run all he registered callbacks (see the comment for OnReplyTextureRemoved). - * Only call this if you know what you are doing. - */ - void FlushTexturesToRemoveCallbacks(); - - /** - * Our IPDL actor is being destroyed, get rid of any shmem resources now. - */ - virtual void OnActorDestroy() = 0; - protected: - // return the next texture ID - uint64_t NextTextureID(); - - struct TextureIDAndFlags { - TextureIDAndFlags(uint64_t aID, TextureFlags aFlags) - : mID(aID), mFlags(aFlags) {} - uint64_t mID; - TextureFlags mFlags; - }; - // The textures to destroy in the next transaction; - nsTArray mTexturesToRemove; - std::map mTexturesToRemoveCallbacks; - uint64_t mNextTextureID; CompositableChild* mCompositableChild; CompositableForwarder* mForwarder; + + friend class CompositableChild; }; /** @@ -222,7 +180,11 @@ public: return mCompositableClient; } - virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE; + virtual void ActorDestroy(ActorDestroyReason) MOZ_OVERRIDE { + if (mCompositableClient) { + mCompositableClient->mCompositableChild = nullptr; + } + } void SetAsyncID(uint64_t aID) { mID = aID; } uint64_t GetAsyncID() const diff --git a/gfx/layers/client/ContentClient.cpp b/gfx/layers/client/ContentClient.cpp index 37e09bda0279..544784812d91 100644 --- a/gfx/layers/client/ContentClient.cpp +++ b/gfx/layers/client/ContentClient.cpp @@ -148,9 +148,6 @@ ContentClientRemoteBuffer::EndPaint() // decided we didn't need one yet because the region to draw was empty. SetBufferProvider(nullptr); SetBufferProviderOnWhite(nullptr); - for (size_t i = 0; i < mOldTextures.Length(); ++i) { - RemoveTextureClient(mOldTextures[i]); - } mOldTextures.Clear(); if (mTextureClient) { @@ -298,20 +295,6 @@ ContentClientRemoteBuffer::SwapBuffers(const nsIntRegion& aFrontUpdatedRegion) mFrontAndBackBufferDiffer = true; } -void -ContentClientRemoteBuffer::OnActorDestroy() -{ - if (mTextureClient) { - mTextureClient->OnActorDestroy(); - } - if (mTextureClientOnWhite) { - mTextureClientOnWhite->OnActorDestroy(); - } - for (size_t i = 0; i < mOldTextures.Length(); ++i) { - mOldTextures[i]->OnActorDestroy(); - } -} - void DeprecatedContentClientRemoteBuffer::DestroyBuffers() { @@ -497,21 +480,6 @@ DeprecatedContentClientRemoteBuffer::SwapBuffers(const nsIntRegion& aFrontUpdate } } - -void -DeprecatedContentClientRemoteBuffer::OnActorDestroy() -{ - if (mDeprecatedTextureClient) { - mDeprecatedTextureClient->OnActorDestroy(); - } - if (mDeprecatedTextureClientOnWhite) { - mDeprecatedTextureClientOnWhite->OnActorDestroy(); - } - for (size_t i = 0; i < mOldTextures.Length(); ++i) { - mOldTextures[i]->OnActorDestroy(); - } -} - void ContentClientDoubleBuffered::CreateFrontBuffer(const nsIntRect& aBufferRect) { @@ -666,26 +634,6 @@ ContentClientDoubleBuffered::UpdateDestinationFrom(const RotatedBuffer& aSource, } } -void -ContentClientDoubleBuffered::OnActorDestroy() -{ - if (mTextureClient) { - mTextureClient->OnActorDestroy(); - } - if (mTextureClientOnWhite) { - mTextureClientOnWhite->OnActorDestroy(); - } - for (size_t i = 0; i < mOldTextures.Length(); ++i) { - mOldTextures[i]->OnActorDestroy(); - } - if (mFrontClient) { - mFrontClient->OnActorDestroy(); - } - if (mFrontClientOnWhite) { - mFrontClientOnWhite->OnActorDestroy(); - } -} - DeprecatedContentClientDoubleBuffered::~DeprecatedContentClientDoubleBuffered() { if (mDeprecatedTextureClient) { @@ -786,26 +734,6 @@ DeprecatedContentClientDoubleBuffered::SwapBuffers(const nsIntRegion& aFrontUpda DeprecatedContentClientRemoteBuffer::SwapBuffers(aFrontUpdatedRegion); } -void -DeprecatedContentClientDoubleBuffered::OnActorDestroy() -{ - if (mDeprecatedTextureClient) { - mDeprecatedTextureClient->OnActorDestroy(); - } - if (mDeprecatedTextureClientOnWhite) { - mDeprecatedTextureClientOnWhite->OnActorDestroy(); - } - for (size_t i = 0; i < mOldTextures.Length(); ++i) { - mOldTextures[i]->OnActorDestroy(); - } - if (mFrontClient) { - mFrontClient->OnActorDestroy(); - } - if (mFrontClientOnWhite) { - mFrontClientOnWhite->OnActorDestroy(); - } -} - struct AutoDeprecatedTextureClient { AutoDeprecatedTextureClient() : mTexture(nullptr) diff --git a/gfx/layers/client/ContentClient.h b/gfx/layers/client/ContentClient.h index 0a43b59cdff9..bea91629112e 100644 --- a/gfx/layers/client/ContentClient.h +++ b/gfx/layers/client/ContentClient.h @@ -159,8 +159,6 @@ public: { MOZ_CRASH("Should not be called on non-remote ContentClient"); } - - virtual void OnActorDestroy() MOZ_OVERRIDE {} }; /** @@ -238,8 +236,6 @@ public: return mTextureInfo; } - virtual void OnActorDestroy() MOZ_OVERRIDE; - protected: void DestroyBuffers(); @@ -340,8 +336,6 @@ public: return mTextureInfo; } - virtual void OnActorDestroy() MOZ_OVERRIDE; - protected: void DestroyBuffers(); @@ -402,8 +396,6 @@ public: virtual void SyncFrontBufferToBackBuffer() MOZ_OVERRIDE; - virtual void OnActorDestroy() MOZ_OVERRIDE; - protected: virtual void CreateFrontBuffer(const nsIntRect& aBufferRect) MOZ_OVERRIDE; virtual void DestroyFrontBuffer() MOZ_OVERRIDE; @@ -441,8 +433,6 @@ public: virtual void SyncFrontBufferToBackBuffer() MOZ_OVERRIDE; - virtual void OnActorDestroy() MOZ_OVERRIDE; - protected: virtual void CreateFrontBufferAndNotify(const nsIntRect& aBufferRect) MOZ_OVERRIDE; virtual void DestroyFrontBuffer() MOZ_OVERRIDE; @@ -549,8 +539,6 @@ public: } } - virtual void OnActorDestroy() MOZ_OVERRIDE {} - private: enum BufferType{ diff --git a/gfx/layers/client/ImageClient.cpp b/gfx/layers/client/ImageClient.cpp index 1e20cef23d37..90476e3d5e38 100644 --- a/gfx/layers/client/ImageClient.cpp +++ b/gfx/layers/client/ImageClient.cpp @@ -101,7 +101,7 @@ void ImageClientSingle::FlushAllImages(bool aExceptFront) { if (!aExceptFront && mFrontBuffer) { - RemoveTextureClient(mFrontBuffer); + mFrontBuffer->ForceRemove(); mFrontBuffer = nullptr; } } @@ -110,11 +110,11 @@ void ImageClientBuffered::FlushAllImages(bool aExceptFront) { if (!aExceptFront && mFrontBuffer) { - RemoveTextureClient(mFrontBuffer); + mFrontBuffer->ForceRemove(); mFrontBuffer = nullptr; } if (mBackBuffer) { - RemoveTextureClient(mBackBuffer); + mBackBuffer->ForceRemove(); mBackBuffer = nullptr; } } @@ -145,7 +145,7 @@ ImageClientSingle::UpdateImage(ImageContainer* aContainer, } if (mFrontBuffer) { - RemoveTextureClient(mFrontBuffer); + mFrontBuffer->ForceRemove(); } mFrontBuffer = texture; if (!AddTextureClient(texture)) { @@ -162,7 +162,7 @@ ImageClientSingle::UpdateImage(ImageContainer* aContainer, } if (mFrontBuffer && mFrontBuffer->IsImmutable()) { - RemoveTextureClient(mFrontBuffer); + mFrontBuffer->ForceRemove(); mFrontBuffer = nullptr; } @@ -205,7 +205,7 @@ ImageClientSingle::UpdateImage(ImageContainer* aContainer, gfx::IntSize size = gfx::IntSize(image->GetSize().width, image->GetSize().height); if (mFrontBuffer) { - RemoveTextureClient(mFrontBuffer); + mFrontBuffer->ForceRemove(); mFrontBuffer = nullptr; } @@ -226,7 +226,7 @@ ImageClientSingle::UpdateImage(ImageContainer* aContainer, if (mFrontBuffer && (mFrontBuffer->IsImmutable() || mFrontBuffer->GetSize() != size)) { - RemoveTextureClient(mFrontBuffer); + mFrontBuffer->ForceRemove(); mFrontBuffer = nullptr; } @@ -280,25 +280,6 @@ ImageClientBuffered::UpdateImage(ImageContainer* aContainer, return ImageClientSingle::UpdateImage(aContainer, aContentFlags); } -void -ImageClientSingle::OnActorDestroy() -{ - if (mFrontBuffer) { - mFrontBuffer->OnActorDestroy(); - } -} - -void -ImageClientBuffered::OnActorDestroy() -{ - if (mFrontBuffer) { - mFrontBuffer->OnActorDestroy(); - } - if (mBackBuffer) { - mBackBuffer->OnActorDestroy(); - } -} - bool ImageClientSingle::AddTextureClient(TextureClient* aTexture) { @@ -484,14 +465,6 @@ ImageClientBridge::ImageClientBridge(CompositableForwarder* aFwd, { } -void -DeprecatedImageClientSingle::OnActorDestroy() -{ - if (mDeprecatedTextureClient) { - mDeprecatedTextureClient->OnActorDestroy(); - } -} - bool ImageClientBridge::UpdateImage(ImageContainer* aContainer, uint32_t aContentFlags) { diff --git a/gfx/layers/client/ImageClient.h b/gfx/layers/client/ImageClient.h index 940696ec222f..127d08e7c313 100644 --- a/gfx/layers/client/ImageClient.h +++ b/gfx/layers/client/ImageClient.h @@ -102,8 +102,6 @@ public: virtual void FlushAllImages(bool aExceptFront) MOZ_OVERRIDE; - virtual void OnActorDestroy() MOZ_OVERRIDE; - protected: RefPtr mFrontBuffer; // Some layers may want to enforce some flags to all their textures @@ -127,8 +125,6 @@ public: virtual void FlushAllImages(bool aExceptFront) MOZ_OVERRIDE; - virtual void OnActorDestroy() MOZ_OVERRIDE; - protected: RefPtr mBackBuffer; }; @@ -173,8 +169,6 @@ public: virtual already_AddRefed CreateImage(const uint32_t *aFormats, uint32_t aNumFormats) MOZ_OVERRIDE; - virtual void OnActorDestroy() MOZ_OVERRIDE; - private: RefPtr mDeprecatedTextureClient; TextureInfo mTextureInfo; @@ -216,8 +210,6 @@ public: return nullptr; } - virtual void OnActorDestroy() MOZ_OVERRIDE {} - protected: uint64_t mAsyncContainerID; ShadowableLayer* mLayer; diff --git a/gfx/layers/client/TextureClient.cpp b/gfx/layers/client/TextureClient.cpp index ca6d2bf7cb02..a84b8981ce70 100644 --- a/gfx/layers/client/TextureClient.cpp +++ b/gfx/layers/client/TextureClient.cpp @@ -19,6 +19,7 @@ #include "mozilla/layers/ShadowLayers.h" // for ShadowLayerForwarder #include "mozilla/layers/SharedPlanarYCbCrImage.h" #include "mozilla/layers/YCbCrImageDataSerializer.h" +#include "mozilla/layers/PTextureChild.h" #include "nsDebug.h" // for NS_ASSERTION, NS_WARNING, etc #include "nsTraceRefcnt.h" // for MOZ_COUNT_CTOR, etc #include "ImageContainer.h" // for PlanarYCbCrImage, etc @@ -38,6 +39,126 @@ using namespace mozilla::gfx; namespace mozilla { namespace layers { +/** + * TextureChild is the content-side incarnation of the PTexture IPDL actor. + * + * TextureChild is used to synchronize a texture client and its corresponding + * TextureHost if needed (a TextureClient that is not shared with the compositor + * does not have a TextureChild) + * + * During the deallocation phase, a TextureChild may hold its recently destroyed + * TextureClient's data until the compositor side confirmed that it is safe to + * deallocte or recycle the it. + */ +class TextureChild : public PTextureChild +{ +public: + TextureChild() + : mForwarder(nullptr) + , mTextureData(nullptr) + , mTextureClient(nullptr) + { + MOZ_COUNT_CTOR(TextureChild); + } + + ~TextureChild() + { + MOZ_COUNT_DTOR(TextureChild); + } + + bool Recv__delete__() MOZ_OVERRIDE; + + /** + * Only used during the deallocation phase iff we need synchronization between + * the client and host side for deallocation (that is, when the data is going + * to be deallocated or recycled on the client side). + */ + void SetTextureData(TextureClientData* aData) + { + mTextureData = aData; + } + + void DeleteTextureData(); + + CompositableForwarder* GetForwarder() { return mForwarder; } + + ISurfaceAllocator* GetAllocator() { return mForwarder; } + + void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE; + +private: + + CompositableForwarder* mForwarder; + TextureClientData* mTextureData; + TextureClient* mTextureClient; + + friend class TextureClient; +}; + +void +TextureChild::DeleteTextureData() +{ + if (mTextureData) { + mTextureData->DeallocateSharedData(GetAllocator()); + delete mTextureData; + mTextureData = nullptr; + } +} + +bool +TextureChild::Recv__delete__() +{ + DeleteTextureData(); + return true; +} + +void +TextureChild::ActorDestroy(ActorDestroyReason why) +{ + if (mTextureClient) { + mTextureClient->mActor = nullptr; + } +} + +// static +PTextureChild* +TextureClient::CreateIPDLActor() +{ + return new TextureChild(); +} + +// static +bool +TextureClient::DestroyIPDLActor(PTextureChild* actor) +{ + delete actor; + return true; +} + +bool +TextureClient::InitIPDLActor(CompositableForwarder* aForwarder) +{ + MOZ_ASSERT(!mActor); + MOZ_ASSERT(aForwarder); + + SurfaceDescriptor desc; + if (!ToSurfaceDescriptor(desc)) { + return false; + } + + mActor = static_cast(aForwarder->CreateEmptyTextureChild()); + mActor->mForwarder = aForwarder; + mActor->mTextureClient = this; + mShared = true; + return mActor->SendInit(desc, GetFlags()); +} + +PTextureChild* +TextureClient::GetIPDLActor() +{ + return mActor; +} + class ShmemTextureClientData : public TextureClientData { public: @@ -80,6 +201,7 @@ public: virtual void DeallocateSharedData(ISurfaceAllocator*) { delete[] mBuffer; + mBuffer = nullptr; } private: @@ -111,14 +233,43 @@ ShmemTextureClient::DropTextureData() } TextureClient::TextureClient(TextureFlags aFlags) - : mID(0) + : mActor(nullptr) , mFlags(aFlags) , mShared(false) , mValid(true) {} TextureClient::~TextureClient() -{} +{ + // All the destruction code that may lead to virtual method calls must + // be in Finalize() which is called just before the destructor. +} + +void TextureClient::ForceRemove() +{ + if (mValid && mActor) { + if (GetFlags() & TEXTURE_DEALLOCATE_CLIENT) { + mActor->SetTextureData(DropTextureData()); + mActor->SendRemoveTextureSync(); + mActor->DeleteTextureData(); + } else { + mActor->SendRemoveTexture(); + } + } + MarkInvalid(); +} + +void +TextureClient::Finalize() +{ + if (mActor) { + // this will call ForceRemove in the right thread, using a sync proxy if needed + mActor->GetForwarder()->RemoveTexture(this); + + // mActor has a raw pointer to us, mActor->mTextureClient. Null it before we die. + mActor->mTextureClient = nullptr; + } +} bool TextureClient::ShouldDeallocateInDestructor() const @@ -237,8 +388,8 @@ MemoryTextureClient::MemoryTextureClient(CompositableClient* aCompositable, MemoryTextureClient::~MemoryTextureClient() { MOZ_COUNT_DTOR(MemoryTextureClient); - if (ShouldDeallocateInDestructor() && mBuffer) { - // if the buffer has never been shared we must deallocate it or ir would + if (mBuffer && ShouldDeallocateInDestructor()) { + // if the buffer has never been shared we must deallocate it or it would // leak. GfxMemoryImageReporter::WillFree(mBuffer); delete mBuffer; @@ -413,15 +564,6 @@ DeprecatedTextureClient::~DeprecatedTextureClient() MOZ_ASSERT(mDescriptor.type() == SurfaceDescriptor::T__None, "Need to release surface!"); } -void -DeprecatedTextureClient::OnActorDestroy() -{ - if (ISurfaceAllocator::IsShmem(&mDescriptor)) { - mDescriptor = SurfaceDescriptor(); - } -} - - DeprecatedTextureClientShmem::DeprecatedTextureClientShmem(CompositableForwarder* aForwarder, const TextureInfo& aTextureInfo) : DeprecatedTextureClient(aForwarder, aTextureInfo) diff --git a/gfx/layers/client/TextureClient.h b/gfx/layers/client/TextureClient.h index 826217415a1d..8c1f2cf2ac78 100644 --- a/gfx/layers/client/TextureClient.h +++ b/gfx/layers/client/TextureClient.h @@ -24,6 +24,7 @@ #include "nsAutoPtr.h" // for nsRefPtr #include "nsCOMPtr.h" // for already_AddRefed #include "nsISupportsImpl.h" // for TextureImage::AddRef, etc +#include "mozilla/layers/AtomicRefCountedWithFinalize.h" class gfxReusableSurfaceWrapper; class gfxASurface; @@ -39,6 +40,8 @@ class CompositableClient; class PlanarYCbCrImage; class PlanarYCbCrData; class Image; +class PTextureChild; +class TextureChild; /** * TextureClient is the abstraction that allows us to share data between the @@ -111,14 +114,13 @@ public: * host side, there is nothing to do. * On the other hand, if the client data must be deallocated on the client * side, the CompositableClient will ask the TextureClient to drop its shared - * data in the form of a TextureClientData object. The compositable will keep - * this object until it has received from the host side the confirmation that - * the compositor is not using the texture and that it is completely safe to - * deallocate the shared data. + * data in the form of a TextureClientData object. This data will be kept alive + * until the host side confirms that it is not using the data anymore and that + * it is completely safe to deallocate the shared data. * * See: - * - CompositableClient::RemoveTextureClient - * - CompositableClient::OnReplyTextureRemoved + * - The PTexture IPDL protocol + * - CompositableChild in TextureClient.cpp */ class TextureClientData { public: @@ -149,7 +151,8 @@ public: * In order to send several different buffers to the compositor side, use * several TextureClients. */ -class TextureClient : public AtomicRefCounted +class TextureClient + : public AtomicRefCountedWithFinalize { public: TextureClient(TextureFlags aFlags = TEXTURE_FLAGS_DEFAULT); @@ -177,28 +180,15 @@ public: virtual bool ImplementsLocking() const { return false; } /** - * Sets this texture's ID. + * Allocate and deallocate a TextureChild actor. * - * This ID is used to match a texture client with his corresponding TextureHost. - * Only the CompositableClient should be allowed to set or clear the ID. - * Zero is always an invalid ID. - * For a given compositableClient, there can never be more than one texture - * client with the same non-zero ID. - * Texture clients from different compositables may have the same ID. + * TextureChild is an implementation detail of TextureHost that is not + * exposed to the rest of the code base. CreateIPDLActor and DestroyIPDLActor + * are for use with the maging IPDL protocols only (so that they can + * implement AllocPextureChild and DeallocPTextureChild). */ - void SetID(uint64_t aID) - { - MOZ_ASSERT(mID == 0 && aID != 0); - mID = aID; - mShared = true; - } - void ClearID() - { - MOZ_ASSERT(mID != 0); - mID = 0; - } - - uint64_t GetID() const { return mID; } + static PTextureChild* CreateIPDLActor(); + static bool DestroyIPDLActor(PTextureChild* actor); virtual bool IsAllocated() const = 0; @@ -249,9 +239,36 @@ public: */ void MarkInvalid() { mValid = false; } - // If a texture client holds a reference to shmem, it should override this - // method to forget about the shmem _without_ releasing it. - virtual void OnActorDestroy() {} + /** + * Create and init the TextureChild/Parent IPDL actor pair. + * + * Should be called only once per TextureClient. + */ + bool InitIPDLActor(CompositableForwarder* aForwarder); + + /** + * Return a pointer to the IPDLActor. + * + * This is to be used with IPDL messages only. Do not store the returned + * pointer. + */ + PTextureChild* GetIPDLActor(); + + /** + * TODO[nical] doc! + */ + void ForceRemove(); + +private: + /** + * Called once, just before the destructor. + * + * Here goes the shut-down code that uses virtual methods. + * Must only be called by Release(). + */ + void Finalize(); + + friend class AtomicRefCountedWithFinalize; protected: void AddFlags(TextureFlags aFlags) @@ -260,10 +277,12 @@ protected: mFlags |= aFlags; } - uint64_t mID; + TextureChild* mActor; TextureFlags mFlags; bool mShared; bool mValid; + + friend class TextureChild; }; /** @@ -361,11 +380,6 @@ public: ipc::Shmem& GetShmem() { return mShmem; } - virtual void OnActorDestroy() MOZ_OVERRIDE - { - mShmem = ipc::Shmem(); - } - protected: ipc::Shmem mShmem; RefPtr mAllocator; @@ -542,8 +556,6 @@ public: virtual gfxContentType GetContentType() = 0; - void OnActorDestroy(); - protected: DeprecatedTextureClient(CompositableForwarder* aForwarder, const TextureInfo& aTextureInfo); diff --git a/gfx/layers/client/TiledContentClient.h b/gfx/layers/client/TiledContentClient.h index a16e1fc23627..37637efe195c 100644 --- a/gfx/layers/client/TiledContentClient.h +++ b/gfx/layers/client/TiledContentClient.h @@ -252,14 +252,6 @@ public: static BasicTiledLayerBuffer OpenDescriptor(ISurfaceAllocator* aAllocator, const SurfaceDescriptorTiles& aDescriptor); - void OnActorDestroy() - { - for (size_t i = 0; i < mRetainedTiles.Length(); i++) { - if (mRetainedTiles[i].IsPlaceholderTile()) continue; - mRetainedTiles[i].mDeprecatedTextureClient->OnActorDestroy(); - } - } - protected: BasicTiledLayerTile ValidateTile(BasicTiledLayerTile aTile, const nsIntPoint& aTileRect, @@ -349,12 +341,6 @@ public: }; void LockCopyAndWrite(TiledBufferType aType); - virtual void OnActorDestroy() MOZ_OVERRIDE - { - mTiledBuffer.OnActorDestroy(); - mLowPrecisionTiledBuffer.OnActorDestroy(); - } - private: BasicTiledLayerBuffer mTiledBuffer; BasicTiledLayerBuffer mLowPrecisionTiledBuffer; diff --git a/gfx/layers/composite/AsyncCompositionManager.cpp b/gfx/layers/composite/AsyncCompositionManager.cpp index 95d459b8044c..1d38bc15dd5c 100644 --- a/gfx/layers/composite/AsyncCompositionManager.cpp +++ b/gfx/layers/composite/AsyncCompositionManager.cpp @@ -40,7 +40,7 @@ #endif #include "GeckoProfiler.h" -struct nsCSSValueList; +struct nsCSSValueSharedList; using namespace mozilla::dom; @@ -348,7 +348,8 @@ SampleValue(float aPortion, Animation& aAnimation, nsStyleAnimation::Value& aSta return; } - nsCSSValueList* interpolatedList = interpolatedValue.GetCSSValueListValue(); + nsCSSValueSharedList* interpolatedList = + interpolatedValue.GetCSSValueSharedListValue(); TransformData& data = aAnimation.data().get_TransformData(); nsPoint origin = data.origin(); diff --git a/gfx/layers/composite/CompositableHost.cpp b/gfx/layers/composite/CompositableHost.cpp index 04edab1be3e2..67b91ac2e084 100644 --- a/gfx/layers/composite/CompositableHost.cpp +++ b/gfx/layers/composite/CompositableHost.cpp @@ -36,82 +36,22 @@ CompositableHost::CompositableHost(const TextureInfo& aTextureInfo) CompositableHost::~CompositableHost() { MOZ_COUNT_DTOR(CompositableHost); - - RefPtr it = mFirstTexture; - while (it) { - if (!(it->GetFlags() & TEXTURE_DEALLOCATE_CLIENT)) { - it->DeallocateSharedData(); - } - it = it->GetNextSibling(); - } } void -CompositableHost::AddTextureHost(TextureHost* aTexture) +CompositableHost::UseTextureHost(TextureHost* aTexture) { - MOZ_ASSERT(aTexture); - MOZ_ASSERT(GetTextureHost(aTexture->GetID()) == nullptr, - "A texture is already present with this ID"); - RefPtr second = mFirstTexture; - mFirstTexture = aTexture; - aTexture->SetNextSibling(second); + if (!aTexture) { + return; + } + aTexture->SetCompositor(GetCompositor()); aTexture->SetCompositableBackendSpecificData(GetCompositableBackendSpecificData()); } -void -CompositableHost::RemoveTextureHost(TextureHost* aTexture) -{ - uint64_t textureID = aTexture->GetID(); - if (mFirstTexture && mFirstTexture->GetID() == textureID) { - mFirstTexture = mFirstTexture->GetNextSibling(); - aTexture->SetNextSibling(nullptr); - } - RefPtr it = mFirstTexture; - while (it) { - if (it->GetNextSibling() && - it->GetNextSibling()->GetID() == textureID) { - it->SetNextSibling(it->GetNextSibling()->GetNextSibling()); - aTexture->SetNextSibling(nullptr); - } - it = it->GetNextSibling(); - } - if (!mFirstTexture && mBackendData) { - mBackendData->ClearData(); - } -} - -TextureHost* -CompositableHost::GetTextureHost(uint64_t aTextureID) -{ - RefPtr it = mFirstTexture; - while (it) { - if (it->GetID() == aTextureID) { - return it; - } - it = it->GetNextSibling(); - } - return nullptr; -} - -void -CompositableHost::OnActorDestroy() -{ - TextureHost* it = mFirstTexture; - while (it) { - it->OnActorDestroy(); - it = it->GetNextSibling(); - } -} - void CompositableHost::SetCompositor(Compositor* aCompositor) { mCompositor = aCompositor; - RefPtr it = mFirstTexture; - while (!!it) { - it->SetCompositor(aCompositor); - it = it->GetNextSibling(); - } } bool @@ -136,13 +76,11 @@ CompositableHost::AddMaskEffect(EffectChain& aEffects, { RefPtr source; RefPtr oldHost = GetDeprecatedTextureHost(); - if (oldHost) { - oldHost->Lock(); + if (oldHost && oldHost->Lock()) { source = oldHost; } else { RefPtr host = GetAsTextureHost(); - if (host) { - host->Lock(); + if (host && host->Lock()) { source = host->GetTextureSources(); } } @@ -266,13 +204,6 @@ void CompositableParent::ActorDestroy(ActorDestroyReason why) { if (mHost) { - // XXX: sadness warning. We should be able to do this whenever we get ActorDestroy, - // not just for abnormal shutdowns (which is the only case we _need_ to - so that - // we don't double release our shmems). But, for some reason, that causes a - // crash, we don't know why. (Bug 925773). - if (why == AbnormalShutdown) { - mHost->OnActorDestroy(); - } mHost->Detach(nullptr, CompositableHost::FORCE_DETACH); } } diff --git a/gfx/layers/composite/CompositableHost.h b/gfx/layers/composite/CompositableHost.h index b77a5b29c7bc..7e7435c6f6b4 100644 --- a/gfx/layers/composite/CompositableHost.h +++ b/gfx/layers/composite/CompositableHost.h @@ -108,12 +108,6 @@ public: mBackendData = aBackendData; } - /** - * Our IPDL actor is being destroyed, get rid of any shmem resources now and - * don't worry about compositing anymore. - */ - virtual void OnActorDestroy(); - // If base class overrides, it should still call the parent implementation virtual void SetCompositor(Compositor* aCompositor); @@ -294,23 +288,13 @@ public: virtual void PrintInfo(nsACString& aTo, const char* aPrefix) { } - void AddTextureHost(TextureHost* aTexture); - virtual void UseTextureHost(TextureHost* aTexture) {} - // If a texture host is flagged for deferred removal, the compositable will - // get an option to run any cleanup code early, that is when it would have - // been run if the texture host was not marked deferred. - // If the compositable does not cleanup the texture host now, it is the - // compositable's responsibility to cleanup the texture host before the - // texture host dies. - virtual void RemoveTextureHost(TextureHost* aTexture); - TextureHost* GetTextureHost(uint64_t aTextureID); + virtual void UseTextureHost(TextureHost* aTexture); protected: TextureInfo mTextureInfo; Compositor* mCompositor; Layer* mLayer; RefPtr mBackendData; - RefPtr mFirstTexture; bool mAttached; bool mKeepAttached; }; diff --git a/gfx/layers/composite/ContentHost.cpp b/gfx/layers/composite/ContentHost.cpp index 116ac028f9fb..5d14cfdf9f4e 100644 --- a/gfx/layers/composite/ContentHost.cpp +++ b/gfx/layers/composite/ContentHost.cpp @@ -32,8 +32,6 @@ ContentHostBase::ContentHostBase(const TextureInfo& aTextureInfo) ContentHostBase::~ContentHostBase() { - DestroyTextureHost(); - DestroyTextureHostOnWhite(); } TextureHost* @@ -42,50 +40,6 @@ ContentHostBase::GetAsTextureHost() return mTextureHost; } -void -ContentHostBase::DestroyTextureHost() -{ - // The third clause in the if statement checks that we are in fact done with - // this texture. We don't want to prematurely deallocate a texture we might - // use again or double deallocate. Deallocation will happen in - // RemoveTextureHost. - // Note that GetTextureHost is linear in the number of texture hosts, but as - // long as that number is small (I expect a maximum of 6 for now) then it - // should be ok. - if (mTextureHost && - mTextureHost->GetFlags() & TEXTURE_DEALLOCATE_DEFERRED && - !GetTextureHost(mTextureHost->GetID())) { - MOZ_ASSERT(!(mTextureHost->GetFlags() & TEXTURE_DEALLOCATE_CLIENT)); - mTextureHost->DeallocateSharedData(); - } - mTextureHost = nullptr; -} - -void -ContentHostBase::DestroyTextureHostOnWhite() -{ - if (mTextureHostOnWhite && - mTextureHostOnWhite->GetFlags() & TEXTURE_DEALLOCATE_DEFERRED && - !GetTextureHost(mTextureHostOnWhite->GetID())) { - MOZ_ASSERT(!(mTextureHostOnWhite->GetFlags() & TEXTURE_DEALLOCATE_CLIENT)); - mTextureHostOnWhite->DeallocateSharedData(); - } - mTextureHostOnWhite = nullptr; -} - -void -ContentHostBase::RemoveTextureHost(TextureHost* aTexture) -{ - if ((aTexture->GetFlags() & TEXTURE_DEALLOCATE_DEFERRED) && - !(mTextureHost && mTextureHost == aTexture) && - !(mTextureHostOnWhite && mTextureHostOnWhite == aTexture)) { - MOZ_ASSERT(!(aTexture->GetFlags() & TEXTURE_DEALLOCATE_CLIENT)); - aTexture->DeallocateSharedData(); - } - - CompositableHost::RemoveTextureHost(aTexture); -} - class MOZ_STACK_CLASS AutoLockTextureHost { public: @@ -286,10 +240,10 @@ void ContentHostBase::UseTextureHost(TextureHost* aTexture) { if (aTexture->GetFlags() & TEXTURE_ON_WHITE) { - DestroyTextureHost(); + mTextureHost = nullptr; mTextureHostOnWhite = aTexture; } else { - DestroyTextureHostOnWhite(); + mTextureHostOnWhite = nullptr; mTextureHost = aTexture; } } @@ -335,18 +289,6 @@ ContentHostBase::Dump(FILE* aFile, } #endif -void -ContentHostBase::OnActorDestroy() -{ - if (mTextureHost) { - mTextureHost->OnActorDestroy(); - } - if (mTextureHostOnWhite) { - mTextureHostOnWhite->OnActorDestroy(); - } - CompositableHost::OnActorDestroy(); -} - DeprecatedContentHostBase::DeprecatedContentHostBase(const TextureInfo& aTextureInfo) : ContentHost(aTextureInfo) , mPaintWillResample(false) @@ -373,23 +315,6 @@ DeprecatedContentHostBase::DestroyFrontHost() mDeprecatedTextureHostOnWhite = nullptr; } -void -DeprecatedContentHostBase::OnActorDestroy() -{ - if (mDeprecatedTextureHost) { - mDeprecatedTextureHost->OnActorDestroy(); - } - if (mDeprecatedTextureHostOnWhite) { - mDeprecatedTextureHostOnWhite->OnActorDestroy(); - } - if (mNewFrontHost) { - mNewFrontHost->OnActorDestroy(); - } - if (mNewFrontHostOnWhite) { - mNewFrontHostOnWhite->OnActorDestroy(); - } -} - void DeprecatedContentHostBase::Composite(EffectChain& aEffectChain, float aOpacity, @@ -848,29 +773,6 @@ DeprecatedContentHostDoubleBuffered::DestroyTextures() // don't touch mDeprecatedTextureHost, we might need it for compositing } -void -DeprecatedContentHostDoubleBuffered::OnActorDestroy() -{ - if (mDeprecatedTextureHost) { - mDeprecatedTextureHost->OnActorDestroy(); - } - if (mDeprecatedTextureHostOnWhite) { - mDeprecatedTextureHostOnWhite->OnActorDestroy(); - } - if (mNewFrontHost) { - mNewFrontHost->OnActorDestroy(); - } - if (mNewFrontHostOnWhite) { - mNewFrontHostOnWhite->OnActorDestroy(); - } - if (mBackHost) { - mBackHost->OnActorDestroy(); - } - if (mBackHostOnWhite) { - mBackHostOnWhite->OnActorDestroy(); - } -} - void DeprecatedContentHostDoubleBuffered::UpdateThebes(const ThebesBufferData& aData, const nsIntRegion& aUpdated, diff --git a/gfx/layers/composite/ContentHost.h b/gfx/layers/composite/ContentHost.h index 52e540980701..fd464a746480 100644 --- a/gfx/layers/composite/ContentHost.h +++ b/gfx/layers/composite/ContentHost.h @@ -117,12 +117,8 @@ public: virtual void UseTextureHost(TextureHost* aTexture) MOZ_OVERRIDE; - virtual void RemoveTextureHost(TextureHost* aTexture) MOZ_OVERRIDE; - virtual void SetPaintWillResample(bool aResample) { mPaintWillResample = aResample; } - virtual void OnActorDestroy() MOZ_OVERRIDE; - protected: virtual nsIntPoint GetOriginOffset() { @@ -131,10 +127,6 @@ protected: bool PaintWillResample() { return mPaintWillResample; } - // These must be called before forgetting mTextureHost or mTextureHostOnWhite - void DestroyTextureHost(); - void DestroyTextureHostOnWhite(); - nsIntRect mBufferRect; nsIntPoint mBufferRotation; RefPtr mTextureHost; @@ -185,8 +177,6 @@ public: // destroy our front buffer so that we can continue to composite. virtual void DestroyTextures() = 0; - virtual void OnActorDestroy() MOZ_OVERRIDE; - protected: virtual nsIntPoint GetOriginOffset() { @@ -259,8 +249,6 @@ public: const TextureInfo& aTextureInfo) MOZ_OVERRIDE; virtual void DestroyTextures() MOZ_OVERRIDE; - virtual void OnActorDestroy() MOZ_OVERRIDE; - #ifdef MOZ_DUMP_PAINTING virtual void Dump(FILE* aFile=nullptr, const char* aPrefix="", diff --git a/gfx/layers/composite/ImageHost.cpp b/gfx/layers/composite/ImageHost.cpp index f4a70314c7cb..948972231738 100644 --- a/gfx/layers/composite/ImageHost.cpp +++ b/gfx/layers/composite/ImageHost.cpp @@ -38,16 +38,14 @@ ImageHost::~ImageHost() {} void ImageHost::UseTextureHost(TextureHost* aTexture) { - mFrontBuffer = aTexture; -} - -void -ImageHost::RemoveTextureHost(TextureHost* aTexture) -{ - CompositableHost::RemoveTextureHost(aTexture); - if (mFrontBuffer && mFrontBuffer->GetID() == aTexture->GetID()) { - mFrontBuffer = nullptr; + if (mFrontBuffer) { + // XXX - When we implement sharing textures between several compositables + // we will need to not remove the compositor if there is another compositable + // using the texture. + mFrontBuffer->SetCompositor(nullptr); } + CompositableHost::UseTextureHost(aTexture); + mFrontBuffer = aTexture; } TextureHost* @@ -74,6 +72,10 @@ ImageHost::Composite(EffectChain& aEffectChain, if (!mFrontBuffer) { return; } + + // Make sure the front buffer has a compositor + mFrontBuffer->SetCompositor(GetCompositor()); + if (!mFrontBuffer->Lock()) { NS_WARNING("failed to lock front buffer"); return; @@ -154,6 +156,15 @@ ImageHost::Composite(EffectChain& aEffectChain, mFrontBuffer->Unlock(); } +void +ImageHost::SetCompositor(Compositor* aCompositor) +{ + if (mFrontBuffer && mCompositor != aCompositor) { + mFrontBuffer->SetCompositor(aCompositor); + } + CompositableHost::SetCompositor(aCompositor); +} + void ImageHost::PrintInfo(nsACString& aTo, const char* aPrefix) { diff --git a/gfx/layers/composite/ImageHost.h b/gfx/layers/composite/ImageHost.h index 52ddbf5e7665..7855ea7c81d4 100644 --- a/gfx/layers/composite/ImageHost.h +++ b/gfx/layers/composite/ImageHost.h @@ -55,10 +55,10 @@ public: virtual void UseTextureHost(TextureHost* aTexture) MOZ_OVERRIDE; - virtual void RemoveTextureHost(TextureHost* aTexture) MOZ_OVERRIDE; - virtual TextureHost* GetAsTextureHost() MOZ_OVERRIDE; + virtual void SetCompositor(Compositor* aCompositor) MOZ_OVERRIDE; + virtual void SetPictureRect(const nsIntRect& aPictureRect) MOZ_OVERRIDE { mPictureRect = aPictureRect; @@ -67,13 +67,6 @@ public: virtual LayerRenderState GetRenderState() MOZ_OVERRIDE; - virtual void OnActorDestroy() MOZ_OVERRIDE - { - if (mFrontBuffer) { - mFrontBuffer->OnActorDestroy(); - } - } - virtual void PrintInfo(nsACString& aTo, const char* aPrefix); #ifdef MOZ_DUMP_PAINTING @@ -134,13 +127,6 @@ public: virtual void SetCompositor(Compositor* aCompositor) MOZ_OVERRIDE; - virtual void OnActorDestroy() MOZ_OVERRIDE - { - if (mDeprecatedTextureHost) { - mDeprecatedTextureHost->OnActorDestroy(); - } - } - virtual void PrintInfo(nsACString& aTo, const char* aPrefix); #ifdef MOZ_DUMP_PAINTING diff --git a/gfx/layers/composite/LayerManagerComposite.cpp b/gfx/layers/composite/LayerManagerComposite.cpp index e16fbb2d6a21..7ac217f24c61 100644 --- a/gfx/layers/composite/LayerManagerComposite.cpp +++ b/gfx/layers/composite/LayerManagerComposite.cpp @@ -726,7 +726,9 @@ LayerManagerComposite::AutoAddMaskEffect::AutoAddMaskEffect(Layer* aMaskLayer, gfx::Matrix4x4 transform; ToMatrix4x4(aMaskLayer->GetEffectiveTransform(), transform); - mCompositable->AddMaskEffect(aEffects, transform, aIs3D); + if (!mCompositable->AddMaskEffect(aEffects, transform, aIs3D)) { + mCompositable = nullptr; + } } LayerManagerComposite::AutoAddMaskEffect::~AutoAddMaskEffect() diff --git a/gfx/layers/composite/TextureHost.cpp b/gfx/layers/composite/TextureHost.cpp index 601a5b51639c..5e4da901d9ac 100644 --- a/gfx/layers/composite/TextureHost.cpp +++ b/gfx/layers/composite/TextureHost.cpp @@ -18,12 +18,68 @@ #include "nsAString.h" #include "nsAutoPtr.h" // for nsRefPtr #include "nsPrintfCString.h" // for nsPrintfCString +#include "mozilla/layers/PTextureParent.h" struct nsIntPoint; namespace mozilla { namespace layers { +/** + * TextureParent is the host-side IPDL glue between TextureClient and TextureHost. + * It is an IPDL actor just like LayerParent, CompositableParent, etc. + */ +class TextureParent : public PTextureParent +{ +public: + TextureParent(ISurfaceAllocator* aAllocator); + + ~TextureParent(); + + bool RecvInit(const SurfaceDescriptor& aSharedData, + const TextureFlags& aFlags) MOZ_OVERRIDE; + + virtual bool RecvRemoveTexture() MOZ_OVERRIDE; + + virtual bool RecvRemoveTextureSync() MOZ_OVERRIDE; + + TextureHost* GetTextureHost() { return mTextureHost; } + + void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE; + + ISurfaceAllocator* mAllocator; + RefPtr mTextureHost; +}; + +// static +PTextureParent* +TextureHost::CreateIPDLActor(ISurfaceAllocator* aAllocator) +{ + return new TextureParent(aAllocator); +} + +// static +bool +TextureHost::DestroyIPDLActor(PTextureParent* actor) +{ + delete actor; + return true; +} + +// static +bool +TextureHost::SendDeleteIPDLActor(PTextureParent* actor) +{ + return PTextureParent::Send__delete__(actor); +} + +// static +TextureHost* +TextureHost::AsTextureHost(PTextureParent* actor) +{ + return actor? static_cast(actor)->mTextureHost : nullptr; +} + // implemented in TextureOGL.cpp TemporaryRef CreateDeprecatedTextureHostOGL(SurfaceDescriptorType aDescriptorType, uint32_t aDeprecatedTextureHostFlags, @@ -81,35 +137,32 @@ DeprecatedTextureHost::CreateDeprecatedTextureHost(SurfaceDescriptorType aDescri } // implemented in TextureHostOGL.cpp -TemporaryRef CreateTextureHostOGL(uint64_t aID, - const SurfaceDescriptor& aDesc, +TemporaryRef CreateTextureHostOGL(const SurfaceDescriptor& aDesc, ISurfaceAllocator* aDeallocator, TextureFlags aFlags); // implemented in TextureHostBasic.cpp -TemporaryRef CreateTextureHostBasic(uint64_t aID, - const SurfaceDescriptor& aDesc, - ISurfaceAllocator* aDeallocator, - TextureFlags aFlags); +TemporaryRef CreateTextureHostBasic(const SurfaceDescriptor& aDesc, + ISurfaceAllocator* aDeallocator, + TextureFlags aFlags); // static TemporaryRef -TextureHost::Create(uint64_t aID, - const SurfaceDescriptor& aDesc, +TextureHost::Create(const SurfaceDescriptor& aDesc, ISurfaceAllocator* aDeallocator, TextureFlags aFlags) { switch (Compositor::GetBackend()) { case LAYERS_OPENGL: - return CreateTextureHostOGL(aID, aDesc, aDeallocator, aFlags); + return CreateTextureHostOGL(aDesc, aDeallocator, aFlags); case LAYERS_BASIC: - return CreateTextureHostBasic(aID, aDesc, aDeallocator, aFlags); + return CreateTextureHostBasic(aDesc, aDeallocator, aFlags); #ifdef MOZ_WIDGET_GONK case LAYERS_NONE: // Power on video reqests to allocate TextureHost, // when Compositor is still not present. This is a very hacky workaround. // See Bug 944420. - return CreateTextureHostOGL(aID, aDesc, aDeallocator, aFlags); + return CreateTextureHostOGL(aDesc, aDeallocator, aFlags); #endif #ifdef XP_WIN case LAYERS_D3D11: @@ -123,8 +176,7 @@ TextureHost::Create(uint64_t aID, } TemporaryRef -CreateBackendIndependentTextureHost(uint64_t aID, - const SurfaceDescriptor& aDesc, +CreateBackendIndependentTextureHost(const SurfaceDescriptor& aDesc, ISurfaceAllocator* aDeallocator, TextureFlags aFlags) { @@ -132,8 +184,7 @@ CreateBackendIndependentTextureHost(uint64_t aID, switch (aDesc.type()) { case SurfaceDescriptor::TSurfaceDescriptorShmem: { const SurfaceDescriptorShmem& descriptor = aDesc.get_SurfaceDescriptorShmem(); - result = new ShmemTextureHost(aID, - descriptor.data(), + result = new ShmemTextureHost(descriptor.data(), descriptor.format(), aDeallocator, aFlags); @@ -141,8 +192,7 @@ CreateBackendIndependentTextureHost(uint64_t aID, } case SurfaceDescriptor::TSurfaceDescriptorMemory: { const SurfaceDescriptorMemory& descriptor = aDesc.get_SurfaceDescriptorMemory(); - result = new MemoryTextureHost(aID, - reinterpret_cast(descriptor.data()), + result = new MemoryTextureHost(reinterpret_cast(descriptor.data()), descriptor.format(), aFlags); break; @@ -161,17 +211,23 @@ TextureHost::SetCompositableBackendSpecificData(CompositableBackendSpecificData* } -TextureHost::TextureHost(uint64_t aID, - TextureFlags aFlags) - : mID(aID) - , mNextTexture(nullptr) - , mFlags(aFlags) +TextureHost::TextureHost(TextureFlags aFlags) + : mFlags(aFlags) {} TextureHost::~TextureHost() { } +void TextureHost::Finalize() +{ + if (GetFlags() & TEXTURE_DEALLOCATE_DEFERRED) { + MOZ_ASSERT(!(GetFlags() & TEXTURE_DEALLOCATE_CLIENT)); + DeallocateSharedData(); + DeallocateDeviceData(); + } +} + void TextureHost::PrintInfo(nsACString& aTo, const char* aPrefix) { @@ -253,7 +309,7 @@ DeprecatedTextureHost::SwapTextures(const SurfaceDescriptor& aImage, } void -DeprecatedTextureHost::OnActorDestroy() +DeprecatedTextureHost::OnShutdown() { if (ISurfaceAllocator::IsShmem(mBuffer)) { *mBuffer = SurfaceDescriptor(); @@ -279,10 +335,9 @@ DeprecatedTextureHost::SetBuffer(SurfaceDescriptor* aBuffer, ISurfaceAllocator* mDeAllocator = aAllocator; } -BufferTextureHost::BufferTextureHost(uint64_t aID, - gfx::SurfaceFormat aFormat, +BufferTextureHost::BufferTextureHost(gfx::SurfaceFormat aFormat, TextureFlags aFlags) -: TextureHost(aID, aFlags) +: TextureHost(aFlags) , mCompositor(nullptr) , mFormat(aFormat) , mUpdateSerial(1) @@ -384,6 +439,19 @@ BufferTextureHost::MaybeUpload(nsIntRegion *aRegion) bool BufferTextureHost::Upload(nsIntRegion *aRegion) { + if (!GetBuffer()) { + // We don't have a buffer; a possible cause is that the IPDL actor + // is already dead. This inevitably happens as IPDL actors can die + // at any time, so we want to silently return in this case. + return false; + } + if (!mCompositor) { + NS_WARNING("Tried to upload without a compositor. Skipping texture upload..."); + // If we are in this situation it means we should have called SetCompositor + // earlier. It is conceivable that on certain rare conditions with async-video + // we may end up here for the first frame, but this should not happen repeatedly. + return false; + } if (mFormat == gfx::FORMAT_UNKNOWN) { NS_WARNING("BufferTextureHost: unsupported format!"); return false; @@ -494,12 +562,11 @@ BufferTextureHost::GetAsSurface() return result.forget(); } -ShmemTextureHost::ShmemTextureHost(uint64_t aID, - const ipc::Shmem& aShmem, +ShmemTextureHost::ShmemTextureHost(const ipc::Shmem& aShmem, gfx::SurfaceFormat aFormat, ISurfaceAllocator* aDeallocator, TextureFlags aFlags) -: BufferTextureHost(aID, aFormat, aFlags) +: BufferTextureHost(aFormat, aFlags) , mShmem(new ipc::Shmem(aShmem)) , mDeallocator(aDeallocator) { @@ -520,12 +587,22 @@ ShmemTextureHost::DeallocateSharedData() MOZ_ASSERT(mDeallocator, "Shared memory would leak without a ISurfaceAllocator"); mDeallocator->DeallocShmem(*mShmem); + delete mShmem; mShmem = nullptr; } } void -ShmemTextureHost::OnActorDestroy() +ShmemTextureHost::ForgetSharedData() +{ + if (mShmem) { + delete mShmem; + mShmem = nullptr; + } +} + +void +ShmemTextureHost::OnShutdown() { delete mShmem; mShmem = nullptr; @@ -536,11 +613,10 @@ uint8_t* ShmemTextureHost::GetBuffer() return mShmem ? mShmem->get() : nullptr; } -MemoryTextureHost::MemoryTextureHost(uint64_t aID, - uint8_t* aBuffer, +MemoryTextureHost::MemoryTextureHost(uint8_t* aBuffer, gfx::SurfaceFormat aFormat, TextureFlags aFlags) -: BufferTextureHost(aID, aFormat, aFlags) +: BufferTextureHost(aFormat, aFlags) , mBuffer(aBuffer) { MOZ_COUNT_CTOR(MemoryTextureHost); @@ -564,10 +640,82 @@ MemoryTextureHost::DeallocateSharedData() mBuffer = nullptr; } +void +MemoryTextureHost::ForgetSharedData() +{ + mBuffer = nullptr; +} + uint8_t* MemoryTextureHost::GetBuffer() { return mBuffer; } +TextureParent::TextureParent(ISurfaceAllocator* aAllocator) +: mAllocator(aAllocator) +{ + MOZ_COUNT_CTOR(TextureParent); +} + +TextureParent::~TextureParent() +{ + MOZ_COUNT_DTOR(TextureParent); + mTextureHost = nullptr; +} + +bool +TextureParent::RecvInit(const SurfaceDescriptor& aSharedData, + const TextureFlags& aFlags) +{ + mTextureHost = TextureHost::Create(aSharedData, + mAllocator, + aFlags); + return !!mTextureHost; +} + +bool +TextureParent::RecvRemoveTexture() +{ + return PTextureParent::Send__delete__(this); +} + +bool +TextureParent::RecvRemoveTextureSync() +{ + // we don't need to send a reply in the synchronous case since the child side + // has the guarantee that this message has been handled synchronously. + return PTextureParent::Send__delete__(this); +} + +void +TextureParent::ActorDestroy(ActorDestroyReason why) +{ + if (!mTextureHost) { + return; + } + + switch (why) { + case AncestorDeletion: + NS_WARNING("PTexture deleted after ancestor"); + // fall-through to deletion path + case Deletion: + if (!(mTextureHost->GetFlags() & TEXTURE_DEALLOCATE_CLIENT) && + !(mTextureHost->GetFlags() & TEXTURE_DEALLOCATE_DEFERRED)) { + mTextureHost->DeallocateSharedData(); + } + break; + + case NormalShutdown: + case AbnormalShutdown: + mTextureHost->OnShutdown(); + break; + + case FailedConstructor: + NS_RUNTIMEABORT("FailedConstructor isn't possible in PTexture"); + } + mTextureHost->ForgetSharedData(); + mTextureHost = nullptr; +} + } // namespace } // namespace diff --git a/gfx/layers/composite/TextureHost.h b/gfx/layers/composite/TextureHost.h index 5cf54e90f39b..85a9f1a2ff16 100644 --- a/gfx/layers/composite/TextureHost.h +++ b/gfx/layers/composite/TextureHost.h @@ -23,6 +23,7 @@ #include "nsRegion.h" // for nsIntRegion #include "nsTraceRefcnt.h" // for MOZ_COUNT_CTOR, etc #include "nscore.h" // for nsACString +#include "mozilla/layers/AtomicRefCountedWithFinalize.h" class gfxImageSurface; class gfxReusableSurfaceWrapper; @@ -46,8 +47,9 @@ class TextureSourceOGL; class TextureSourceD3D9; class TextureSourceD3D11; class TextureSourceBasic; -class TextureParent; class DataTextureSource; +class PTextureParent; +class TextureParent; /** * A view on a TextureHost where the texture is internally represented as tiles @@ -257,19 +259,28 @@ private: * The Lock/Unlock mecanism here mirrors Lock/Unlock in TextureClient. * */ -class TextureHost : public RefCounted +class TextureHost + : public AtomicRefCountedWithFinalize { + /** + * Called once, just before the destructor. + * + * Here goes the shut-down code that uses virtual methods. + * Must only be called by Release(). + */ + void Finalize(); + + friend class AtomicRefCountedWithFinalize; + public: - TextureHost(uint64_t aID, - TextureFlags aFlags); + TextureHost(TextureFlags aFlags); virtual ~TextureHost(); /** * Factory method. */ - static TemporaryRef Create(uint64_t aID, - const SurfaceDescriptor& aDesc, + static TemporaryRef Create(const SurfaceDescriptor& aDesc, ISurfaceAllocator* aDeallocator, TextureFlags aFlags); @@ -332,27 +343,15 @@ public: virtual void DeallocateSharedData() {} /** - * An ID to differentiate TextureHosts of a given CompositableHost. + * Should be overridden in order to force the TextureHost to drop all references + * to it's shared data. * - * A TextureHost and its corresponding TextureClient always have the same ID. - * TextureHosts of a given CompositableHost always have different IDs. - * TextureHosts of different CompositableHosts, may have the same ID. - * Zero is always an invalid ID. + * This is important to ensure the correctness of the deallocation protocol. */ - uint64_t GetID() const { return mID; } + virtual void ForgetSharedData() {} virtual gfx::IntSize GetSize() const = 0; - /** - * TextureHosts are kept as a linked list in their compositable - * XXX - This is just a poor man's PTexture. The purpose of this list is - * to keep TextureHost alive which should be independent from compositables. - * It will be removed when we add the PTetxure protocol (which will more - * gracefully handle the lifetime of textures). See bug 897452 - */ - TextureHost* GetNextSibling() const { return mNextTexture; } - void SetNextSibling(TextureHost* aNext) { mNextTexture = aNext; } - /** * Debug facility. * XXX - cool kids use Moz2D. See bug 882113. @@ -371,6 +370,27 @@ public: TextureFlags GetFlags() { return mFlags; } + /** + * Allocate and deallocate a TextureParent actor. + * + * TextureParent< is an implementation detail of TextureHost that is not + * exposed to the rest of the code base. CreateIPDLActor and DestroyIPDLActor + * are for use with the managing IPDL protocols only (so that they can + * implement AllocPTextureParent and DeallocPTextureParent). + */ + static PTextureParent* CreateIPDLActor(ISurfaceAllocator* aAllocator); + static bool DestroyIPDLActor(PTextureParent* actor); + + /** + * Destroy the TextureChild/Parent pair. + */ + static bool SendDeleteIPDLActor(PTextureParent* actor); + + /** + * Get the TextureHost corresponding to the actor passed in parameter. + */ + static TextureHost* AsTextureHost(PTextureParent* actor); + /** * Specific to B2G's Composer2D * XXX - more doc here @@ -386,14 +406,12 @@ public: // If a texture host holds a reference to shmem, it should override this method // to forget about the shmem _without_ releasing it. - virtual void OnActorDestroy() {} + virtual void OnShutdown() {} virtual const char *Name() { return "TextureHost"; } virtual void PrintInfo(nsACString& aTo, const char* aPrefix); protected: - uint64_t mID; - RefPtr mNextTexture; TextureFlags mFlags; RefPtr mCompositableBackendData; }; @@ -414,8 +432,7 @@ protected: class BufferTextureHost : public TextureHost { public: - BufferTextureHost(uint64_t aID, - gfx::SurfaceFormat aFormat, + BufferTextureHost(gfx::SurfaceFormat aFormat, TextureFlags aFlags); ~BufferTextureHost(); @@ -470,8 +487,7 @@ protected: class ShmemTextureHost : public BufferTextureHost { public: - ShmemTextureHost(uint64_t aID, - const mozilla::ipc::Shmem& aShmem, + ShmemTextureHost(const mozilla::ipc::Shmem& aShmem, gfx::SurfaceFormat aFormat, ISurfaceAllocator* aDeallocator, TextureFlags aFlags); @@ -480,11 +496,13 @@ public: virtual void DeallocateSharedData() MOZ_OVERRIDE; + virtual void ForgetSharedData() MOZ_OVERRIDE; + virtual uint8_t* GetBuffer() MOZ_OVERRIDE; virtual const char *Name() MOZ_OVERRIDE { return "ShmemTextureHost"; } - virtual void OnActorDestroy() MOZ_OVERRIDE; + virtual void OnShutdown() MOZ_OVERRIDE; protected: mozilla::ipc::Shmem* mShmem; @@ -500,8 +518,7 @@ protected: class MemoryTextureHost : public BufferTextureHost { public: - MemoryTextureHost(uint64_t aID, - uint8_t* aBuffer, + MemoryTextureHost(uint8_t* aBuffer, gfx::SurfaceFormat aFormat, TextureFlags aFlags); @@ -509,6 +526,8 @@ public: virtual void DeallocateSharedData() MOZ_OVERRIDE; + virtual void ForgetSharedData() MOZ_OVERRIDE; + virtual uint8_t* GetBuffer() MOZ_OVERRIDE; virtual const char *Name() MOZ_OVERRIDE { return "MemoryTextureHost"; } @@ -711,7 +730,7 @@ public: // see bug 865908 about fixing this. virtual void ForgetBuffer() {} - void OnActorDestroy(); + void OnShutdown(); protected: /** @@ -814,8 +833,7 @@ private: * Not all SurfaceDescriptor types are supported */ TemporaryRef -CreateBackendIndependentTextureHost(uint64_t aID, - const SurfaceDescriptor& aDesc, +CreateBackendIndependentTextureHost(const SurfaceDescriptor& aDesc, ISurfaceAllocator* aDeallocator, TextureFlags aFlags); diff --git a/gfx/layers/composite/TiledContentHost.h b/gfx/layers/composite/TiledContentHost.h index d281a89500ec..163a36da1537 100644 --- a/gfx/layers/composite/TiledContentHost.h +++ b/gfx/layers/composite/TiledContentHost.h @@ -119,16 +119,6 @@ public: mCompositor = aCompositor; } - void OnActorDestroy() - { - Iterator end = TilesEnd(); - for (Iterator it = TilesBegin(); it != end; ++it) { - if (it->mDeprecatedTextureHost) { - it->mDeprecatedTextureHost->OnActorDestroy(); - } - } - } - protected: TiledTexture ValidateTile(TiledTexture aTile, const nsIntPoint& aTileRect, @@ -249,12 +239,6 @@ public: Compositor* aCompositor, AttachFlags aFlags = NO_FLAGS) MOZ_OVERRIDE; - virtual void OnActorDestroy() MOZ_OVERRIDE - { - mVideoMemoryTiledBuffer.OnActorDestroy(); - mLowPrecisionVideoMemoryTiledBuffer.OnActorDestroy(); - } - #ifdef MOZ_DUMP_PAINTING virtual void Dump(FILE* aFile=nullptr, const char* aPrefix="", diff --git a/gfx/layers/ipc/CompositableForwarder.h b/gfx/layers/ipc/CompositableForwarder.h index 31d46342dc83..4c3894d43ab6 100644 --- a/gfx/layers/ipc/CompositableForwarder.h +++ b/gfx/layers/ipc/CompositableForwarder.h @@ -29,6 +29,8 @@ class ThebesBufferData; class DeprecatedTextureClient; class TextureClient; class BasicTiledLayerBuffer; +class PTextureChild; +class TextureClientData; /** * A transaction is a set of changes that happenned on the content side, that @@ -93,6 +95,14 @@ public: virtual void PaintedTiledLayerBuffer(CompositableClient* aCompositable, const SurfaceDescriptorTiles& aTiledDescriptor) = 0; + /** + * Create an unitialized TextureChild. + * + * This does not trigger the the creation of a TextureHost on the compositor + * side (see PTexture::Init). + */ + virtual PTextureChild* CreateEmptyTextureChild() = 0; + /** * Communicate to the compositor that the texture identified by aCompositable * and aTextureId has been updated to aImage. @@ -150,25 +160,10 @@ public: virtual void DestroyedThebesBuffer(const SurfaceDescriptor& aBackBufferToDestroy) = 0; /** - * Tell the compositor side to create a TextureHost that corresponds to - * aClient. + * Tell the compositor side to delete the TextureHost corresponding to the + * TextureClient passed in parameter. */ - virtual bool AddTexture(CompositableClient* aCompositable, - TextureClient* aClient) = 0; - - /** - * Tell the compositor side to delete the TextureHost corresponding to - * aTextureID. - * By default the shared Data is deallocated along with the TextureHost, but - * this behaviour can be overriden by the TextureFlags passed here. - * XXX - This is kind of bad, but for now we have to do this, because of some - * edge cases caused by the lifetime of the TextureHost being limited by the - * lifetime of the CompositableHost. We should be able to remove this flags - * parameter when we remove the lifetime constraint. - */ - virtual void RemoveTexture(CompositableClient* aCompositable, - uint64_t aTextureID, - TextureFlags aFlags) = 0; + virtual void RemoveTexture(TextureClient* aTexture) = 0; /** * Tell the CompositableHost on the compositor side what texture to use for diff --git a/gfx/layers/ipc/CompositableTransactionParent.cpp b/gfx/layers/ipc/CompositableTransactionParent.cpp index 5026bfab6e1c..6cb8f71a6550 100644 --- a/gfx/layers/ipc/CompositableTransactionParent.cpp +++ b/gfx/layers/ipc/CompositableTransactionParent.cpp @@ -222,12 +222,8 @@ CompositableParentManager::ReceiveCompositableUpdate(const CompositableOperation } case CompositableOperation::TOpUseTexture: { const OpUseTexture& op = aEdit.get_OpUseTexture(); - if (op.textureID() == 0) { - NS_WARNING("Invalid texture ID"); - break; - } CompositableHost* compositable = AsCompositable(op); - RefPtr tex = compositable->GetTextureHost(op.textureID()); + RefPtr tex = TextureHost::AsTextureHost(op.textureParent()); MOZ_ASSERT(tex.get()); compositable->UseTextureHost(tex); @@ -237,75 +233,17 @@ CompositableParentManager::ReceiveCompositableUpdate(const CompositableOperation } break; } - case CompositableOperation::TOpAddTexture: { - const OpAddTexture& op = aEdit.get_OpAddTexture(); - if (op.textureID() == 0) { - NS_WARNING("Invalid texture ID"); - break; - } - CompositableHost* compositable = AsCompositable(op); - RefPtr tex = TextureHost::Create(op.textureID(), - op.data(), - this, - op.textureFlags()); - MOZ_ASSERT(tex.get()); - tex->SetCompositor(compositable->GetCompositor()); - // set CompositableBackendSpecificData - // on gonk, create EGLImage if possible. - // create EGLImage during buffer swap could reduce the graphic driver's task - // during rendering. - compositable->AddTextureHost(tex); - MOZ_ASSERT(compositable->GetTextureHost(op.textureID()) == tex.get()); - break; - } - case CompositableOperation::TOpRemoveTexture: { - const OpRemoveTexture& op = aEdit.get_OpRemoveTexture(); - if (op.textureID() == 0) { - NS_WARNING("Invalid texture ID"); - break; - } - CompositableHost* compositable = AsCompositable(op); - - RefPtr texture = compositable->GetTextureHost(op.textureID()); - MOZ_ASSERT(texture); - - TextureFlags flags = texture->GetFlags(); - - if (!(flags & TEXTURE_DEALLOCATE_CLIENT) && - !(flags & TEXTURE_DEALLOCATE_DEFERRED)) { - texture->DeallocateSharedData(); - } - - compositable->RemoveTextureHost(texture); - - // if it is not the host that deallocates the shared data, then we need - // to notfy the client side to tell when it is safe to deallocate or - // reuse it. - if (flags & TEXTURE_DEALLOCATE_CLIENT) { - replyv.push_back(ReplyTextureRemoved(op.compositableParent(), nullptr, - op.textureID())); - } - - break; - } case CompositableOperation::TOpUpdateTexture: { const OpUpdateTexture& op = aEdit.get_OpUpdateTexture(); - if (op.textureID() == 0) { - NS_WARNING("Invalid texture ID"); - break; - } CompositableHost* compositable = AsCompositable(op); MOZ_ASSERT(compositable); - RefPtr texture = compositable->GetTextureHost(op.textureID()); + RefPtr texture = TextureHost::AsTextureHost(op.textureParent()); MOZ_ASSERT(texture); texture->Updated(op.region().type() == MaybeRegion::TnsIntRegion ? &op.region().get_nsIntRegion() : nullptr); // no region means invalidate the entire surface - - compositable->UseTextureHost(texture); - break; } diff --git a/gfx/layers/ipc/ImageBridgeChild.cpp b/gfx/layers/ipc/ImageBridgeChild.cpp index 3c015a36fe10..8c4eda634fbb 100644 --- a/gfx/layers/ipc/ImageBridgeChild.cpp +++ b/gfx/layers/ipc/ImageBridgeChild.cpp @@ -36,6 +36,7 @@ #include "nsThreadUtils.h" // for NS_IsMainThread #include "nsXULAppAPI.h" // for XRE_GetIOMessageLoop #include "mozilla/StaticPtr.h" // for StaticRefPtr +#include "mozilla/layers/TextureClient.h" struct nsIntRect; @@ -103,46 +104,12 @@ struct AutoEndTransaction { CompositableTransaction* mTxn; }; -bool -ImageBridgeChild::AddTexture(CompositableClient* aCompositable, - TextureClient* aTexture) -{ - SurfaceDescriptor descriptor; - if (!aTexture->ToSurfaceDescriptor(descriptor)) { - NS_WARNING("ImageBridge: Failed to serialize a TextureClient"); - return false; - } - mTxn->AddEdit(OpAddTexture(nullptr, aCompositable->GetIPDLActor(), - aTexture->GetID(), - descriptor, - aTexture->GetFlags())); - return true; -} - -void -ImageBridgeChild::RemoveTexture(CompositableClient* aCompositable, - uint64_t aTexture, - TextureFlags aFlags) -{ - if (aFlags & TEXTURE_DEALLOCATE_CLIENT) { - // if deallocation happens on the host side, we need the transaction - // to be synchronous. - mTxn->AddEdit(OpRemoveTexture(nullptr, aCompositable->GetIPDLActor(), - aTexture, - aFlags)); - } else { - mTxn->AddNoSwapEdit(OpRemoveTexture(nullptr, aCompositable->GetIPDLActor(), - aTexture, - aFlags)); - } -} - void ImageBridgeChild::UseTexture(CompositableClient* aCompositable, TextureClient* aTexture) { mTxn->AddNoSwapEdit(OpUseTexture(nullptr, aCompositable->GetIPDLActor(), - aTexture->GetID())); + nullptr, aTexture->GetIPDLActor())); } void @@ -153,7 +120,7 @@ ImageBridgeChild::UpdatedTexture(CompositableClient* aCompositable, MaybeRegion region = aRegion ? MaybeRegion(*aRegion) : MaybeRegion(null_t()); mTxn->AddNoSwapEdit(OpUpdateTexture(nullptr, aCompositable->GetIPDLActor(), - aTexture->GetID(), + nullptr, aTexture->GetIPDLActor(), region)); } @@ -465,7 +432,6 @@ void ImageBridgeChild::FlushAllImagesNow(ImageClient* aClient, ImageContainer* a aClient->FlushAllImages(aExceptFront); aClient->OnTransaction(); sImageBridgeChildSingleton->EndTransaction(); - aClient->FlushTexturesToRemoveCallbacks(); } void @@ -523,18 +489,6 @@ ImageBridgeChild::EndTransaction() ->SetDescriptorFromReply(ots.textureId(), ots.image()); break; } - case EditReply::TReplyTextureRemoved: { - // We receive this reply when a Texture is removed and when it is not - // the responsibility of the compositor side to deallocate memory. - // This would be, for instance, the place to implement a mechanism to - // notify the B2G camera that the gralloc buffer is not used by the - // compositor anymore and that it can be recycled. - const ReplyTextureRemoved& rep = reply.get_ReplyTextureRemoved(); - CompositableClient* compositable - = static_cast(rep.compositableChild())->GetCompositableClient(); - compositable->OnReplyTextureRemoved(rep.textureId()); - break; - } default: NS_RUNTIMEABORT("not reached"); } @@ -940,5 +894,54 @@ ImageBridgeChild::AllocGrallocBuffer(const gfxIntSize& aSize, #endif } +PTextureChild* +ImageBridgeChild::AllocPTextureChild() +{ + return TextureClient::CreateIPDLActor(); +} + +bool +ImageBridgeChild::DeallocPTextureChild(PTextureChild* actor) +{ + return TextureClient::DestroyIPDLActor(actor); +} + +PTextureChild* +ImageBridgeChild::CreateEmptyTextureChild() +{ + return SendPTextureConstructor(); +} + +static void RemoveTextureSync(TextureClient* aTexture, ReentrantMonitor* aBarrier, bool* aDone) +{ + aTexture->ForceRemove(); + + ReentrantMonitorAutoEnter autoMon(*aBarrier); + *aDone = true; + aBarrier->NotifyAll(); +} + +void ImageBridgeChild::RemoveTexture(TextureClient* aTexture) +{ + if (InImageBridgeChildThread()) { + aTexture->ForceRemove(); + return; + } + + ReentrantMonitor barrier("RemoveTexture Lock"); + ReentrantMonitorAutoEnter autoMon(barrier); + bool done = false; + + sImageBridgeChildSingleton->GetMessageLoop()->PostTask( + FROM_HERE, + NewRunnableFunction(&RemoveTextureSync, aTexture, &barrier, &done)); + + // should stop the thread until the ImageClient has been created on + // the other thread + while (!done) { + barrier.Wait(); + } +} + } // layers } // mozilla diff --git a/gfx/layers/ipc/ImageBridgeChild.h b/gfx/layers/ipc/ImageBridgeChild.h index 31a8ea9a16a8..88f3d4409faf 100644 --- a/gfx/layers/ipc/ImageBridgeChild.h +++ b/gfx/layers/ipc/ImageBridgeChild.h @@ -195,6 +195,12 @@ public: virtual bool DeallocPGrallocBufferChild(PGrallocBufferChild* actor) MOZ_OVERRIDE; + virtual PTextureChild* + AllocPTextureChild() MOZ_OVERRIDE; + + virtual bool + DeallocPTextureChild(PTextureChild* actor) MOZ_OVERRIDE; + /** * Allocate a gralloc SurfaceDescriptor remotely. */ @@ -254,19 +260,6 @@ public: virtual void Connect(CompositableClient* aCompositable) MOZ_OVERRIDE; - /** - * See CompositableForwarder::AddTexture - */ - virtual bool AddTexture(CompositableClient* aCompositable, - TextureClient* aClient) MOZ_OVERRIDE; - - /** - * See CompositableForwarder::RemoveTexture - */ - virtual void RemoveTexture(CompositableClient* aCompositable, - uint64_t aTextureID, - TextureFlags aFlags) MOZ_OVERRIDE; - /** * See CompositableForwarder::UpdatedTexture */ @@ -280,6 +273,8 @@ public: virtual void UseTexture(CompositableClient* aCompositable, TextureClient* aClient) MOZ_OVERRIDE; + virtual void RemoveTexture(TextureClient* aTexture) MOZ_OVERRIDE; + virtual void PaintedTiledLayerBuffer(CompositableClient* aCompositable, const SurfaceDescriptorTiles& aTileLayerDescriptor) MOZ_OVERRIDE { @@ -378,6 +373,8 @@ public: */ virtual void DeallocShmem(mozilla::ipc::Shmem& aShmem); + virtual PTextureChild* CreateEmptyTextureChild() MOZ_OVERRIDE; + protected: ImageBridgeChild(); bool DispatchAllocShmemInternal(size_t aSize, diff --git a/gfx/layers/ipc/ImageBridgeParent.cpp b/gfx/layers/ipc/ImageBridgeParent.cpp index f230b3dfd423..0e121ae5e891 100644 --- a/gfx/layers/ipc/ImageBridgeParent.cpp +++ b/gfx/layers/ipc/ImageBridgeParent.cpp @@ -30,6 +30,7 @@ #include "nsTArray.h" // for nsTArray, nsTArray_Impl #include "nsTArrayForwardDeclare.h" // for InfallibleTArray #include "nsXULAppAPI.h" // for XRE_GetIOMessageLoop +#include "mozilla/layers/TextureHost.h" using namespace base; using namespace mozilla::ipc; @@ -127,6 +128,16 @@ ImageBridgeParent::Create(Transport* aTransport, ProcessId aOtherProcess) bool ImageBridgeParent::RecvStop() { + // If there is any texture still alive we have to force it to deallocate the + // device data (GL textures, etc.) now because shortly after SenStop() returns + // on the child side the widget will be destroyed along with it's associated + // GL context. + InfallibleTArray textures; + ManagedPTextureParent(textures); + for (unsigned int i = 0; i < textures.Length(); ++i) { + RefPtr tex = TextureHost::AsTextureHost(textures[i]); + tex->DeallocateDeviceData(); + } return true; } @@ -178,6 +189,18 @@ bool ImageBridgeParent::DeallocPCompositableParent(PCompositableParent* aActor) return true; } +PTextureParent* +ImageBridgeParent::AllocPTextureParent() +{ + return TextureHost::CreateIPDLActor(this); +} + +bool +ImageBridgeParent::DeallocPTextureParent(PTextureParent* actor) +{ + return TextureHost::DestroyIPDLActor(actor); +} + MessageLoop * ImageBridgeParent::GetMessageLoop() { return mMessageLoop; } diff --git a/gfx/layers/ipc/ImageBridgeParent.h b/gfx/layers/ipc/ImageBridgeParent.h index 1065f7b46081..da56535412d4 100644 --- a/gfx/layers/ipc/ImageBridgeParent.h +++ b/gfx/layers/ipc/ImageBridgeParent.h @@ -65,6 +65,9 @@ public: uint64_t*) MOZ_OVERRIDE; bool DeallocPCompositableParent(PCompositableParent* aActor) MOZ_OVERRIDE; + virtual PTextureParent* AllocPTextureParent() MOZ_OVERRIDE; + virtual bool DeallocPTextureParent(PTextureParent* actor) MOZ_OVERRIDE; + bool RecvStop() MOZ_OVERRIDE; MessageLoop * GetMessageLoop(); diff --git a/gfx/layers/ipc/LayerTransactionChild.cpp b/gfx/layers/ipc/LayerTransactionChild.cpp index 312a8549992a..5d7e19e65bf0 100644 --- a/gfx/layers/ipc/LayerTransactionChild.cpp +++ b/gfx/layers/ipc/LayerTransactionChild.cpp @@ -13,6 +13,7 @@ #include "mozilla/mozalloc.h" // for operator delete, etc #include "nsDebug.h" // for NS_RUNTIMEABORT, etc #include "nsTArray.h" // for nsTArray +#include "mozilla/layers/TextureClient.h" namespace mozilla { namespace layers { @@ -96,5 +97,17 @@ LayerTransactionChild::ActorDestroy(ActorDestroyReason why) #endif } +PTextureChild* +LayerTransactionChild::AllocPTextureChild() +{ + return TextureClient::CreateIPDLActor(); +} + +bool +LayerTransactionChild::DeallocPTextureChild(PTextureChild* actor) +{ + return TextureClient::DestroyIPDLActor(actor); +} + } // namespace layers } // namespace mozilla diff --git a/gfx/layers/ipc/LayerTransactionChild.h b/gfx/layers/ipc/LayerTransactionChild.h index 10b53f382ff4..d4bc07e62ae9 100644 --- a/gfx/layers/ipc/LayerTransactionChild.h +++ b/gfx/layers/ipc/LayerTransactionChild.h @@ -58,6 +58,10 @@ protected: virtual PCompositableChild* AllocPCompositableChild(const TextureInfo& aInfo) MOZ_OVERRIDE; virtual bool DeallocPCompositableChild(PCompositableChild* actor) MOZ_OVERRIDE; + + virtual PTextureChild* AllocPTextureChild() MOZ_OVERRIDE; + virtual bool DeallocPTextureChild(PTextureChild* actor) MOZ_OVERRIDE; + virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE; void AddIPDLReference() { diff --git a/gfx/layers/ipc/LayerTransactionParent.cpp b/gfx/layers/ipc/LayerTransactionParent.cpp index d07fdca9fc7c..efc338941755 100644 --- a/gfx/layers/ipc/LayerTransactionParent.cpp +++ b/gfx/layers/ipc/LayerTransactionParent.cpp @@ -39,6 +39,7 @@ #include "nsTArray.h" // for nsTArray, nsTArray_Impl, etc #include "nsTraceRefcnt.h" // for MOZ_COUNT_CTOR, etc #include "GeckoProfiler.h" +#include "mozilla/layers/TextureHost.h" typedef std::vector EditReplyVector; @@ -600,5 +601,17 @@ LayerTransactionParent::DeallocPCompositableParent(PCompositableParent* actor) return true; } +PTextureParent* +LayerTransactionParent::AllocPTextureParent() +{ + return TextureHost::CreateIPDLActor(this); +} + +bool +LayerTransactionParent::DeallocPTextureParent(PTextureParent* actor) +{ + return TextureHost::DestroyIPDLActor(actor); +} + } // namespace layers } // namespace mozilla diff --git a/gfx/layers/ipc/LayerTransactionParent.h b/gfx/layers/ipc/LayerTransactionParent.h index 4a1a80268b7e..f49a5b550ae4 100644 --- a/gfx/layers/ipc/LayerTransactionParent.h +++ b/gfx/layers/ipc/LayerTransactionParent.h @@ -106,6 +106,9 @@ protected: virtual PCompositableParent* AllocPCompositableParent(const TextureInfo& aInfo) MOZ_OVERRIDE; virtual bool DeallocPCompositableParent(PCompositableParent* actor) MOZ_OVERRIDE; + virtual PTextureParent* AllocPTextureParent() MOZ_OVERRIDE; + virtual bool DeallocPTextureParent(PTextureParent* actor) MOZ_OVERRIDE; + void Attach(ShadowLayerParent* aLayerParent, CompositableParent* aCompositable, bool aIsAsyncVideo); diff --git a/gfx/layers/ipc/LayersMessages.ipdlh b/gfx/layers/ipc/LayersMessages.ipdlh index 83049ab4df8d..61b3a32806ce 100644 --- a/gfx/layers/ipc/LayersMessages.ipdlh +++ b/gfx/layers/ipc/LayersMessages.ipdlh @@ -11,6 +11,7 @@ include protocol PCompositor; include protocol PGrallocBuffer; include protocol PLayer; include protocol PRenderFrame; +include protocol PTexture; include "gfxipc/ShadowLayerUtils.h"; include "mozilla/GfxMessageUtils.h"; @@ -300,34 +301,13 @@ struct OpUpdatePictureRect { nsIntRect picture; }; -/** - * Provides the compositor side with enough information to create a - * TextureHost. - */ -struct OpAddTexture { - PCompositable compositable; - uint64_t textureID; - SurfaceDescriptor data; - uint32_t textureFlags; -}; - -/** - * Tells the compositor-side to remove the corresponding TextureHost and - * deallocate its data. - */ -struct OpRemoveTexture { - PCompositable compositable; - uint64_t textureID; - uint32_t flags; -}; - /** * Tells the compositor-side which texture to use (for example, as front buffer * if there is several textures for double buffering) */ struct OpUseTexture { PCompositable compositable; - uint64_t textureID; + PTexture texture; }; union MaybeRegion { @@ -337,7 +317,7 @@ union MaybeRegion { struct OpUpdateTexture { PCompositable compositable; - uint64_t textureID; + PTexture texture; MaybeRegion region; }; @@ -354,8 +334,6 @@ union CompositableOperation { OpPaintTiledLayerBuffer; - OpAddTexture; - OpRemoveTexture; OpUpdateTexture; OpUseTexture; }; @@ -399,18 +377,11 @@ struct OpTextureSwap { SurfaceDescriptor image; }; -struct ReplyTextureRemoved { - PCompositable compositable; - uint64_t textureId; -}; - // Unit of a "changeset reply". This is a weird abstraction, probably // only to be used for buffer swapping. union EditReply { OpContentBufferSwap; OpTextureSwap; - - ReplyTextureRemoved; }; } // namespace diff --git a/gfx/layers/ipc/PImageBridge.ipdl b/gfx/layers/ipc/PImageBridge.ipdl index ad18a74c75c1..11f542c27396 100644 --- a/gfx/layers/ipc/PImageBridge.ipdl +++ b/gfx/layers/ipc/PImageBridge.ipdl @@ -7,6 +7,7 @@ include LayersSurfaces; include LayersMessages; include protocol PGrallocBuffer; include protocol PCompositable; +include protocol PTexture; include ProtocolTypes; include "mozilla/GfxMessageUtils.h"; @@ -25,6 +26,7 @@ intr protocol PImageBridge { manages PCompositable; manages PGrallocBuffer; + manages PTexture; parent: @@ -49,6 +51,7 @@ parent: sync Stop(); sync PCompositable(TextureInfo aInfo) returns (uint64_t id); + async PTexture(); }; diff --git a/gfx/layers/ipc/PLayerTransaction.ipdl b/gfx/layers/ipc/PLayerTransaction.ipdl index f86e6d19de20..3a6a40ced491 100644 --- a/gfx/layers/ipc/PLayerTransaction.ipdl +++ b/gfx/layers/ipc/PLayerTransaction.ipdl @@ -12,6 +12,7 @@ include protocol PCompositor; include protocol PGrallocBuffer; include protocol PLayer; include protocol PRenderFrame; +include protocol PTexture; include "mozilla/GfxMessageUtils.h"; @@ -33,6 +34,7 @@ sync protocol PLayerTransaction { manages PLayer; manages PCompositable; manages PGrallocBuffer; + manages PTexture; parent: /** @@ -66,6 +68,7 @@ parent: returns (MaybeMagicGrallocBufferHandle handle); async PLayer(); async PCompositable(TextureInfo aTextureInfo); + async PTexture(); // The isFirstPaint flag can be used to indicate that this is the first update // for a particular document. diff --git a/gfx/layers/ipc/PTexture.ipdl b/gfx/layers/ipc/PTexture.ipdl new file mode 100644 index 000000000000..ba46319a821e --- /dev/null +++ b/gfx/layers/ipc/PTexture.ipdl @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* 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 LayersSurfaces; +include protocol PLayerTransaction; +include protocol PImageBridge; +include protocol PGrallocBuffer; +include "mozilla/GfxMessageUtils.h"; + +using struct mozilla::layers::FrameMetrics from "FrameMetrics.h"; + +namespace mozilla { +namespace layers { + +/** + * PTexture is the IPDL glue between a TextureClient and a TextureHost. + */ +sync protocol PTexture { + manager PImageBridge or PLayerTransaction; + +child: + async __delete__(); + +parent: + /** + * Set the shared data and create the TextureHost on the parent side. + */ + async Init(SurfaceDescriptor aSharedData, uint32_t aTextureFlags); + + /** + * Asynchronously tell the Compositor side to remove the texture. + */ + async RemoveTexture(); + + /** + * Synchronously tell the compositor side to remove the texture. + */ + sync RemoveTextureSync(); +}; + +} // layers +} // mozilla diff --git a/gfx/layers/ipc/ShadowLayerUtilsGralloc.cpp b/gfx/layers/ipc/ShadowLayerUtilsGralloc.cpp index 35104dff7912..bfc85c187026 100644 --- a/gfx/layers/ipc/ShadowLayerUtilsGralloc.cpp +++ b/gfx/layers/ipc/ShadowLayerUtilsGralloc.cpp @@ -13,6 +13,7 @@ #include "mozilla/layers/ShadowLayers.h" #include "mozilla/layers/LayerManagerComposite.h" #include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/TextureHost.h" #include "mozilla/unused.h" #include "nsXULAppAPI.h" diff --git a/gfx/layers/ipc/ShadowLayerUtilsGralloc.h b/gfx/layers/ipc/ShadowLayerUtilsGralloc.h index eb111d914314..50cf8c92c743 100644 --- a/gfx/layers/ipc/ShadowLayerUtilsGralloc.h +++ b/gfx/layers/ipc/ShadowLayerUtilsGralloc.h @@ -15,10 +15,6 @@ #include "mozilla/layers/PGrallocBufferChild.h" #include "mozilla/layers/PGrallocBufferParent.h" -// used only for hacky fix in gecko 23 for bug 862324 -// see bug 865908 about fixing this. -#include "TextureHost.h" - #define MOZ_HAVE_SURFACEDESCRIPTORGRALLOC #define MOZ_HAVE_PLATFORM_SPECIFIC_LAYER_BUFFERS @@ -29,6 +25,8 @@ namespace layers { class MaybeMagicGrallocBufferHandle; class SurfaceDescriptorGralloc; +class TextureHost; +class DeprecatedTextureHost; /** * This class exists to share the underlying GraphicBuffer resources diff --git a/gfx/layers/ipc/ShadowLayers.cpp b/gfx/layers/ipc/ShadowLayers.cpp index 87fe40c5d85d..56e02775af34 100644 --- a/gfx/layers/ipc/ShadowLayers.cpp +++ b/gfx/layers/ipc/ShadowLayers.cpp @@ -407,39 +407,6 @@ ShadowLayerForwarder::UpdatePictureRect(CompositableClient* aCompositable, mTxn->AddNoSwapPaint(OpUpdatePictureRect(nullptr, aCompositable->GetIPDLActor(), aRect)); } -bool -ShadowLayerForwarder::AddTexture(CompositableClient* aCompositable, - TextureClient* aTexture) -{ - SurfaceDescriptor descriptor; - if (!aTexture->ToSurfaceDescriptor(descriptor)) { - NS_WARNING("Failed to serialize a TextureClient"); - return false; - } - CheckSurfaceDescriptor(&descriptor); - MOZ_ASSERT(aCompositable); - MOZ_ASSERT(aCompositable->GetIPDLActor()); - MOZ_ASSERT(aTexture->GetFlags() != 0); - mTxn->AddEdit(OpAddTexture(nullptr, aCompositable->GetIPDLActor(), - aTexture->GetID(), - descriptor, - aTexture->GetFlags())); - return true; -} - -void -ShadowLayerForwarder::RemoveTexture(CompositableClient* aCompositable, - uint64_t aTexture, - TextureFlags aFlags) -{ - mTxn->AddEdit(OpRemoveTexture(nullptr, aCompositable->GetIPDLActor(), - aTexture, - aFlags)); - if (aFlags & TEXTURE_DEALLOCATE_CLIENT) { - mTxn->MarkSyncTransaction(); - } -} - void ShadowLayerForwarder::UpdatedTexture(CompositableClient* aCompositable, TextureClient* aTexture, @@ -449,11 +416,11 @@ ShadowLayerForwarder::UpdatedTexture(CompositableClient* aCompositable, : MaybeRegion(null_t()); if (aTexture->GetFlags() & TEXTURE_IMMEDIATE_UPLOAD) { mTxn->AddPaint(OpUpdateTexture(nullptr, aCompositable->GetIPDLActor(), - aTexture->GetID(), + nullptr, aTexture->GetIPDLActor(), region)); } else { mTxn->AddNoSwapPaint(OpUpdateTexture(nullptr, aCompositable->GetIPDLActor(), - aTexture->GetID(), + nullptr, aTexture->GetIPDLActor(), region)); } } @@ -463,7 +430,13 @@ ShadowLayerForwarder::UseTexture(CompositableClient* aCompositable, TextureClient* aTexture) { mTxn->AddEdit(OpUseTexture(nullptr, aCompositable->GetIPDLActor(), - aTexture->GetID())); + nullptr, aTexture->GetIPDLActor())); +} + +void +ShadowLayerForwarder::RemoveTexture(TextureClient* aTexture) +{ + aTexture->ForceRemove(); } bool @@ -989,6 +962,13 @@ void ShadowLayerForwarder::AttachAsyncCompositable(uint64_t aCompositableID, aCompositableID)); } +PTextureChild* +ShadowLayerForwarder::CreateEmptyTextureChild() +{ + return mShadowManager->SendPTextureConstructor(); +} + + void ShadowLayerForwarder::SetShadowManager(PLayerTransactionChild* aShadowManager) { mShadowManager = static_cast(aShadowManager); diff --git a/gfx/layers/ipc/ShadowLayers.h b/gfx/layers/ipc/ShadowLayers.h index ff93acc9cc0e..74ea7ce3e4c5 100644 --- a/gfx/layers/ipc/ShadowLayers.h +++ b/gfx/layers/ipc/ShadowLayers.h @@ -149,6 +149,8 @@ public: */ void Connect(CompositableClient* aCompositable); + virtual PTextureChild* CreateEmptyTextureChild() MOZ_OVERRIDE; + virtual void CreatedSingleBuffer(CompositableClient* aCompositable, const SurfaceDescriptor& aDescriptor, const TextureInfo& aTextureInfo, @@ -277,6 +279,8 @@ public: TextureIdentifier aTextureId, SurfaceDescriptor* aDescriptor) MOZ_OVERRIDE; + virtual void RemoveTexture(TextureClient* aTexture) MOZ_OVERRIDE; + /** * Same as above, but performs an asynchronous layer transaction */ @@ -305,19 +309,6 @@ public: void UpdatePictureRect(CompositableClient* aCompositable, const nsIntRect& aRect); - /** - * See CompositableForwarder::AddTexture - */ - virtual bool AddTexture(CompositableClient* aCompositable, - TextureClient* aClient) MOZ_OVERRIDE; - - /** - * See CompositableForwarder::RemoveTexture - */ - virtual void RemoveTexture(CompositableClient* aCompositable, - uint64_t aTextureID, - TextureFlags aFlags) MOZ_OVERRIDE; - /** * See CompositableForwarder::UpdatedTexture */ diff --git a/gfx/layers/moz.build b/gfx/layers/moz.build index 9af45accae0f..862019c1ff88 100644 --- a/gfx/layers/moz.build +++ b/gfx/layers/moz.build @@ -98,6 +98,7 @@ EXPORTS.gfxipc += [ ] EXPORTS.mozilla.layers += [ + 'AtomicRefCountedWithFinalize.h', 'basic/BasicCompositor.h', 'basic/MacIOSurfaceTextureHostBasic.h', 'basic/TextureHostBasic.h', @@ -186,6 +187,7 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk': ] UNIFIED_SOURCES += [ 'GrallocImages.cpp', + 'opengl/EGLImageHelpers.cpp', 'opengl/GrallocTextureClient.cpp', 'opengl/GrallocTextureHost.cpp', ] @@ -252,11 +254,9 @@ UNIFIED_SOURCES += [ 'ipc/SharedPlanarYCbCrImage.cpp', 'ipc/SharedRGBImage.cpp', 'ipc/TaskThrottler.cpp', - 'Layers.cpp', 'LayerScope.cpp', 'LayersLogging.cpp', 'LayerSorter.cpp', - 'LayerTreeInvalidation.cpp', 'opengl/CompositingRenderTargetOGL.cpp', 'opengl/CompositorOGL.cpp', 'opengl/OGLShaderProgram.cpp', @@ -272,8 +272,18 @@ UNIFIED_SOURCES += [ SOURCES += [ 'basic/BasicImageLayer.cpp', 'ImageContainer.cpp', + 'Layers.cpp', + 'LayerTreeInvalidation.cpp', ] +# Workaround compiler bug (Bug 795594) +if CONFIG['_MSC_VER'] and CONFIG['CPU_ARCH'] == 'x86_64': + for src in [ + 'Layers.cpp', + 'LayerTreeInvalidation.cpp', + ]: + SOURCES[src].no_pgo = True + if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': SOURCES += [ 'basic/MacIOSurfaceTextureHostBasic.cpp', @@ -290,6 +300,7 @@ IPDL_SOURCES = [ 'ipc/PImageBridge.ipdl', 'ipc/PLayer.ipdl', 'ipc/PLayerTransaction.ipdl', + 'ipc/PTexture.ipdl', ] MSVC_ENABLE_PGO = True diff --git a/gfx/layers/opengl/CompositorOGL.cpp b/gfx/layers/opengl/CompositorOGL.cpp index 31889957e7c0..8e2ab2757824 100644 --- a/gfx/layers/opengl/CompositorOGL.cpp +++ b/gfx/layers/opengl/CompositorOGL.cpp @@ -41,6 +41,7 @@ #include "nsServiceManagerUtils.h" // for do_GetService #include "nsString.h" // for nsString, nsAutoCString, etc #include "DecomposeIntoNoRepeatTriangles.h" +#include "ScopedGLHelpers.h" #if MOZ_ANDROID_OMTC #include "TexturePoolOGL.h" @@ -65,6 +66,16 @@ static inline IntSize ns2gfxSize(const nsIntSize& s) { return IntSize(s.width, s.height); } +static void +BindMaskForProgram(ShaderProgramOGL* aProgram, TextureSourceOGL* aSourceMask, + GLenum aTexUnit, const gfx::Matrix4x4& aTransform) +{ + MOZ_ASSERT(LOCAL_GL_TEXTURE0 <= aTexUnit && aTexUnit <= LOCAL_GL_TEXTURE31); + aSourceMask->BindTexture(aTexUnit); + aProgram->SetMaskTextureUnit(aTexUnit - LOCAL_GL_TEXTURE0); + aProgram->SetMaskLayerTransform(aTransform); +} + // Draw the given quads with the already selected shader. Texture coordinates // are supplied if the shader requires them. static void @@ -978,28 +989,76 @@ CompositorOGL::GetProgramTypeForEffect(Effect *aEffect) const } struct MOZ_STACK_CLASS AutoBindTexture + : public ScopedGLWrapper { - AutoBindTexture() : mTexture(nullptr) {} - AutoBindTexture(TextureSourceOGL* aTexture, GLenum aTextureUnit) - : mTexture(nullptr) { Bind(aTexture, aTextureUnit); } - ~AutoBindTexture() + friend struct ScopedGLWrapper; + +protected: + GLenum mTexUnit; + GLuint mOldTexId; + +public: + explicit AutoBindTexture(GLContext* aGL) + : ScopedGLWrapper(aGL) + , mTexUnit(0) + , mOldTexId(GLuint(-1)) + { } + + AutoBindTexture(GLContext* aGL, TextureSourceOGL* aTexture, + GLenum aTexUnit = LOCAL_GL_TEXTURE0) + : ScopedGLWrapper(aGL) + , mTexUnit(0) + , mOldTexId(GLuint(-1)) { - if (mTexture) { - mTexture->UnbindTexture(); - } + MOZ_ASSERT(aTexture); + MOZ_ASSERT(mOldTexId == GLuint(-1)); + mTexUnit = aTexUnit; + + ScopedBindTextureUnit autoBindTexUnit(mGL, aTexUnit); + + mGL->GetUIntegerv(LOCAL_GL_TEXTURE_BINDING_2D, &mOldTexId); + aTexture->BindTexture(mTexUnit); } - void Bind(TextureSourceOGL* aTexture, GLenum aTextureUnit) +protected: + void UnwrapImpl() { - MOZ_ASSERT(!mTexture); - mTexture = aTexture; - mTexture->BindTexture(aTextureUnit); - } + if (mOldTexId == GLuint(-1)) + return; -private: - TextureSourceOGL* mTexture; + ScopedBindTextureUnit autoBindTexUnit(mGL, mTexUnit); + mGL->fBindTexture(LOCAL_GL_TEXTURE_2D, mOldTexId); + } }; +struct MOZ_STACK_CLASS AutoSaveTexture + : public ScopedGLWrapper +{ + friend struct ScopedGLWrapper; + +protected: + GLenum mTexUnit; + GLuint mOldTexId; + +public: + AutoSaveTexture(GLContext* aGL, GLenum aTexUnit = LOCAL_GL_TEXTURE0) + : ScopedGLWrapper(aGL) + , mTexUnit(aTexUnit) + , mOldTexId(GLuint(-1)) + { + ScopedBindTextureUnit savedTexUnit(mGL, mTexUnit); + mGL->GetUIntegerv(LOCAL_GL_TEXTURE_BINDING_2D, &mOldTexId); + } + +protected: + void UnwrapImpl() + { + ScopedBindTextureUnit savedTexUnit(mGL, mTexUnit); + mGL->fBindTexture(LOCAL_GL_TEXTURE_2D, mOldTexId); + } +}; + + void CompositorOGL::DrawLines(const std::vector& aLines, const gfx::Rect& aClipRect, const gfx::Color& aColor, @@ -1019,6 +1078,20 @@ CompositorOGL::DrawLines(const std::vector& aLines, const gfx::Rect& } } +/** + * Applies aFilter to the texture currently bound to aTarget. + */ +void ApplyFilterToBoundTexture(GLContext* aGL, + GraphicsFilter aFilter, + GLuint aTarget = LOCAL_GL_TEXTURE_2D) +{ + GLenum filter = + (aFilter == GraphicsFilter::FILTER_NEAREST ? LOCAL_GL_NEAREST : LOCAL_GL_LINEAR); + + aGL->fTexParameteri(aTarget, LOCAL_GL_TEXTURE_MIN_FILTER, filter); + aGL->fTexParameteri(aTarget, LOCAL_GL_TEXTURE_MAG_FILTER, filter); +} + void CompositorOGL::DrawQuadInternal(const Rect& aRect, const Rect& aClipRect, @@ -1112,11 +1185,9 @@ CompositorOGL::DrawQuadInternal(const Rect& aRect, program->SetRenderColor(color); - AutoBindTexture bindMask; + AutoSaveTexture bindMask(mGLContext, LOCAL_GL_TEXTURE0); if (maskType != MaskNone) { - bindMask.Bind(sourceMask, LOCAL_GL_TEXTURE0); - program->SetMaskTextureUnit(0); - program->SetMaskLayerTransform(maskQuadTransform); + BindMaskForProgram(program, sourceMask, LOCAL_GL_TEXTURE0, maskQuadTransform); } BindAndDrawQuad(program, false, aDrawMode); @@ -1137,7 +1208,7 @@ CompositorOGL::DrawQuadInternal(const Rect& aRect, LOCAL_GL_ONE, LOCAL_GL_ONE); } - AutoBindTexture bindSource(source->AsSourceOGL(), LOCAL_GL_TEXTURE0); + AutoBindTexture bindSource(mGLContext, source->AsSourceOGL(), LOCAL_GL_TEXTURE0); gfx3DMatrix textureTransform = source->AsSourceOGL()->GetTextureTransform(); program->SetTextureTransform(textureTransform); @@ -1155,18 +1226,15 @@ CompositorOGL::DrawQuadInternal(const Rect& aRect, filter = GraphicsFilter::FILTER_NEAREST; } #endif - mGLContext->ApplyFilterToBoundTexture(source->AsSourceOGL()->GetTextureTarget(), - filter); + ApplyFilterToBoundTexture(mGLContext, filter, + source->AsSourceOGL()->GetTextureTarget()); program->SetTextureUnit(0); program->SetLayerOpacity(aOpacity); - AutoBindTexture bindMask; + AutoSaveTexture bindMask(mGLContext, LOCAL_GL_TEXTURE1); if (maskType != MaskNone) { - mGLContext->fActiveTexture(LOCAL_GL_TEXTURE1); - bindMask.Bind(sourceMask, LOCAL_GL_TEXTURE1); - program->SetMaskTextureUnit(1); - program->SetMaskLayerTransform(maskQuadTransform); + BindMaskForProgram(program, sourceMask, LOCAL_GL_TEXTURE1, maskQuadTransform); } BindAndDrawQuadWithTextureRect(program, texturedEffect->mTextureCoords, source); @@ -1193,22 +1261,20 @@ CompositorOGL::DrawQuadInternal(const Rect& aRect, GraphicsFilter filter = ThebesFilter(effectYCbCr->mFilter); - AutoBindTexture bindY(sourceY, LOCAL_GL_TEXTURE0); - mGLContext->ApplyFilterToBoundTexture(filter); - AutoBindTexture bindCb(sourceCb, LOCAL_GL_TEXTURE1); - mGLContext->ApplyFilterToBoundTexture(filter); - AutoBindTexture bindCr(sourceCr, LOCAL_GL_TEXTURE2); - mGLContext->ApplyFilterToBoundTexture(filter); + AutoBindTexture bindY(mGLContext, sourceY, LOCAL_GL_TEXTURE0); + ApplyFilterToBoundTexture(mGLContext, filter); + AutoBindTexture bindCb(mGLContext, sourceCb, LOCAL_GL_TEXTURE1); + ApplyFilterToBoundTexture(mGLContext, filter); + AutoBindTexture bindCr(mGLContext, sourceCr, LOCAL_GL_TEXTURE2); + ApplyFilterToBoundTexture(mGLContext, filter); program->SetYCbCrTextureUnits(Y, Cb, Cr); program->SetLayerOpacity(aOpacity); program->SetTextureTransform(gfx3DMatrix()); - AutoBindTexture bindMask; + AutoSaveTexture bindMask(mGLContext, LOCAL_GL_TEXTURE3); if (maskType != MaskNone) { - bindMask.Bind(sourceMask, LOCAL_GL_TEXTURE3); - program->SetMaskTextureUnit(3); - program->SetMaskLayerTransform(maskQuadTransform); + BindMaskForProgram(program, sourceMask, LOCAL_GL_TEXTURE3, maskQuadTransform); } BindAndDrawQuadWithTextureRect(program, effectYCbCr->mTextureCoords, sourceYCbCr->GetSubSource(Y)); } @@ -1228,9 +1294,9 @@ CompositorOGL::DrawQuadInternal(const Rect& aRect, program->SetLayerOpacity(aOpacity); program->SetTextureTransform(gfx3DMatrix()); - AutoBindTexture bindMask; + AutoSaveTexture bindMask(mGLContext, LOCAL_GL_TEXTURE1); if (maskType != MaskNone) { - bindMask.Bind(sourceMask, LOCAL_GL_TEXTURE1); + sourceMask->BindTexture(LOCAL_GL_TEXTURE1); program->SetMaskTextureUnit(1); program->SetMaskLayerTransform(maskQuadTransform); } @@ -1277,8 +1343,8 @@ CompositorOGL::DrawQuadInternal(const Rect& aRect, LOCAL_GL_ONE, LOCAL_GL_ONE); } - AutoBindTexture bindSourceOnBlack(sourceOnBlack, LOCAL_GL_TEXTURE0); - AutoBindTexture bindSourceOnWhite(sourceOnWhite, LOCAL_GL_TEXTURE1); + AutoBindTexture bindSourceOnBlack(mGLContext, sourceOnBlack, LOCAL_GL_TEXTURE0); + AutoBindTexture bindSourceOnWhite(mGLContext, sourceOnWhite, LOCAL_GL_TEXTURE1); program->Activate(); program->SetBlackTextureUnit(0); @@ -1288,11 +1354,9 @@ CompositorOGL::DrawQuadInternal(const Rect& aRect, program->SetTextureTransform(gfx3DMatrix()); program->SetRenderOffset(offset.x, offset.y); program->SetLayerQuadRect(aRect); - AutoBindTexture bindMask; + AutoSaveTexture bindMask(mGLContext, LOCAL_GL_TEXTURE2); if (maskType != MaskNone) { - bindMask.Bind(sourceMask, LOCAL_GL_TEXTURE2); - program->SetMaskTextureUnit(2); - program->SetMaskLayerTransform(maskQuadTransform); + BindMaskForProgram(program, sourceMask, LOCAL_GL_TEXTURE2, maskQuadTransform); } BindAndDrawQuadWithTextureRect(program, effectComponentAlpha->mTextureCoords, effectComponentAlpha->mOnBlack); diff --git a/gfx/layers/opengl/EGLImageHelpers.cpp b/gfx/layers/opengl/EGLImageHelpers.cpp new file mode 100644 index 000000000000..6339730b145e --- /dev/null +++ b/gfx/layers/opengl/EGLImageHelpers.cpp @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 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 "EGLImageHelpers.h" +#include "GLContext.h" +#include "GLLibraryEGL.h" + +namespace mozilla +{ +namespace layers { + +using namespace gl; + +EGLImage +EGLImageCreateFromNativeBuffer(GLContext* aGL, void* aBuffer) +{ + EGLint attrs[] = { + LOCAL_EGL_IMAGE_PRESERVED, LOCAL_EGL_TRUE, + LOCAL_EGL_NONE, LOCAL_EGL_NONE + }; + + GLLibraryEGL* egl = aGL->GetLibraryEGL(); + if (!egl) { + NS_WARNING("Failed to obtain pointer to EGL. Returning EGL_NO_IMAGE."); + return EGL_NO_IMAGE; + } + + return egl->fCreateImage(egl->Display(), + EGL_NO_CONTEXT, + LOCAL_EGL_NATIVE_BUFFER_ANDROID, + aBuffer, attrs); +} + +void +EGLImageDestroy(GLContext* aGL, EGLImage aImage) +{ + GLLibraryEGL* egl = aGL->GetLibraryEGL(); + if (!egl) { + NS_WARNING("Failed to obtain pointer to EGL. Image not destroyed."); + return; + } + + egl->fDestroyImage(egl->Display(), aImage); +} + +} // namespace layers +} // namespace mozilla diff --git a/gfx/layers/opengl/EGLImageHelpers.h b/gfx/layers/opengl/EGLImageHelpers.h new file mode 100644 index 000000000000..ba38dfdd1451 --- /dev/null +++ b/gfx/layers/opengl/EGLImageHelpers.h @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 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 EGLIMAGEHELPERS_H_ +#define EGLIMAGEHELPERS_H_ + +typedef void* EGLImage; + +namespace mozilla { +namespace gl { + class GLContext; +} + +namespace layers { + +EGLImage EGLImageCreateFromNativeBuffer(gl::GLContext* aGL, void* aBuffer); +void EGLImageDestroy(gl::GLContext* aGL, EGLImage aImage); + +} // namespace layers +} // namespace mozilla + +#endif // EGLIMAGEHELPERS_H_ diff --git a/gfx/layers/opengl/GrallocTextureHost.cpp b/gfx/layers/opengl/GrallocTextureHost.cpp index 0e0d8cafb03c..7c5e539cb1df 100644 --- a/gfx/layers/opengl/GrallocTextureHost.cpp +++ b/gfx/layers/opengl/GrallocTextureHost.cpp @@ -10,6 +10,7 @@ #include "GrallocImages.h" // for GrallocImage #include "mozilla/layers/GrallocTextureHost.h" #include "mozilla/layers/CompositorOGL.h" +#include "EGLImageHelpers.h" #include "GLContextUtils.h" namespace mozilla { @@ -95,6 +96,7 @@ GrallocTextureSourceOGL::GrallocTextureSourceOGL(CompositorOGL* aCompositor, , mGraphicBuffer(aGraphicBuffer) , mEGLImage(0) , mFormat(aFormat) + , mNeedsReset(true) { MOZ_ASSERT(mGraphicBuffer.get()); } @@ -112,7 +114,7 @@ void GrallocTextureSourceOGL::BindTexture(GLenum aTextureUnit) * android::GraphicBuffer, so that texturing will source the GraphicBuffer. * * To this effect we create an EGLImage wrapping this GraphicBuffer, - * using CreateEGLImageForNativeBuffer, and then we tie this EGLImage to our + * using EGLImageCreateFromNativeBuffer, and then we tie this EGLImage to our * texture using fEGLImageTargetTexture2D. */ MOZ_ASSERT(gl()); @@ -169,6 +171,14 @@ GrallocTextureSourceOGL::GetFormat() const { void GrallocTextureSourceOGL::SetCompositableBackendSpecificData(CompositableBackendSpecificData* aBackendData) { + if (mCompositableBackendData != aBackendData) { + mNeedsReset = true; + } + + if (!mNeedsReset) { + return; + } + mCompositableBackendData = aBackendData; if (!mCompositor) { @@ -185,8 +195,9 @@ GrallocTextureSourceOGL::SetCompositableBackendSpecificData(CompositableBackendS gl()->fActiveTexture(LOCAL_GL_TEXTURE0); gl()->fBindTexture(textureTarget, tex); // create new EGLImage - mEGLImage = gl()->CreateEGLImageForNativeBuffer(mGraphicBuffer->getNativeBuffer()); + mEGLImage = EGLImageCreateFromNativeBuffer(gl(), mGraphicBuffer->getNativeBuffer()); gl()->fEGLImageTargetTexture2D(textureTarget, mEGLImage); + mNeedsReset = false; } gfx::IntSize @@ -205,15 +216,14 @@ GrallocTextureSourceOGL::DeallocateDeviceData() if (mEGLImage) { MOZ_ASSERT(gl()); gl()->MakeCurrent(); - gl()->DestroyEGLImage(mEGLImage); - mEGLImage = 0; + EGLImageDestroy(gl(), mEGLImage); + mEGLImage = EGL_NO_IMAGE; } } -GrallocTextureHostOGL::GrallocTextureHostOGL(uint64_t aID, - TextureFlags aFlags, +GrallocTextureHostOGL::GrallocTextureHostOGL(TextureFlags aFlags, const NewSurfaceDescriptorGralloc& aDescriptor) - : TextureHost(aID, aFlags) + : TextureHost(aFlags) { mGrallocActor = static_cast(aDescriptor.bufferParent()); @@ -273,6 +283,14 @@ GrallocTextureHostOGL::DeallocateSharedData() PGrallocBufferParent::Send__delete__(mGrallocActor); } +void +GrallocTextureHostOGL::ForgetSharedData() +{ + if (mTextureSource) { + mTextureSource->ForgetBuffer(); + } +} + void GrallocTextureHostOGL::DeallocateDeviceData() { @@ -313,7 +331,7 @@ GrallocTextureSourceOGL::GetAsSurface() { gl()->fActiveTexture(LOCAL_GL_TEXTURE0); gl()->fBindTexture(GetTextureTarget(), tex); if (!mEGLImage) { - mEGLImage = gl()->CreateEGLImageForNativeBuffer(mGraphicBuffer->getNativeBuffer()); + mEGLImage = EGLImageCreateFromNativeBuffer(gl(), mGraphicBuffer->getNativeBuffer()); } gl()->fEGLImageTargetTexture2D(GetTextureTarget(), mEGLImage); diff --git a/gfx/layers/opengl/GrallocTextureHost.h b/gfx/layers/opengl/GrallocTextureHost.h index 0d1fbd9516a7..09de91e57548 100644 --- a/gfx/layers/opengl/GrallocTextureHost.h +++ b/gfx/layers/opengl/GrallocTextureHost.h @@ -32,8 +32,6 @@ public: virtual void BindTexture(GLenum aTextureUnit) MOZ_OVERRIDE; - virtual void UnbindTexture() MOZ_OVERRIDE {} - virtual gfx::IntSize GetSize() const MOZ_OVERRIDE; virtual TextureSourceOGL* AsSourceOGL() MOZ_OVERRIDE { return this; } @@ -69,14 +67,14 @@ protected: android::sp mGraphicBuffer; EGLImage mEGLImage; gfx::SurfaceFormat mFormat; + bool mNeedsReset; }; class GrallocTextureHostOGL : public TextureHost { friend class GrallocBufferActor; public: - GrallocTextureHostOGL(uint64_t aID, - TextureFlags aFlags, + GrallocTextureHostOGL(TextureFlags aFlags, const NewSurfaceDescriptorGralloc& aDescriptor); virtual ~GrallocTextureHostOGL(); @@ -91,6 +89,8 @@ public: virtual void DeallocateSharedData() MOZ_OVERRIDE; + virtual void ForgetSharedData() MOZ_OVERRIDE; + virtual void DeallocateDeviceData() MOZ_OVERRIDE; virtual gfx::SurfaceFormat GetFormat() const; diff --git a/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp b/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp index fba0fb266d04..27756c10a604 100644 --- a/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp +++ b/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.cpp @@ -34,10 +34,9 @@ MacIOSurfaceTextureSourceOGL::GetFormat() const return mSurface->HasAlpha() ? gfx::FORMAT_R8G8B8A8 : gfx::FORMAT_B8G8R8X8; } -MacIOSurfaceTextureHostOGL::MacIOSurfaceTextureHostOGL(uint64_t aID, - TextureFlags aFlags, +MacIOSurfaceTextureHostOGL::MacIOSurfaceTextureHostOGL(TextureFlags aFlags, const SurfaceDescriptorMacIOSurface& aDescriptor) - : TextureHost(aID, aFlags) + : TextureHost(aFlags) { mSurface = MacIOSurface::LookupSurface(aDescriptor.surface(), aDescriptor.scaleFactor(), diff --git a/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.h b/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.h index f33c20587ffa..397310fac739 100644 --- a/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.h +++ b/gfx/layers/opengl/MacIOSurfaceTextureHostOGL.h @@ -41,8 +41,6 @@ public: virtual GLenum GetWrapMode() const MOZ_OVERRIDE { return LOCAL_GL_CLAMP_TO_EDGE; } - virtual void UnbindTexture() MOZ_OVERRIDE {} - // MacIOSurfaceTextureSourceOGL doesn't own any gl texture virtual void DeallocateDeviceData() {} @@ -65,8 +63,7 @@ protected: class MacIOSurfaceTextureHostOGL : public TextureHost { public: - MacIOSurfaceTextureHostOGL(uint64_t aID, - TextureFlags aFlags, + MacIOSurfaceTextureHostOGL(TextureFlags aFlags, const SurfaceDescriptorMacIOSurface& aDescriptor); // SharedTextureHostOGL doesn't own any GL texture @@ -105,4 +102,4 @@ protected: } } -#endif // MOZILLA_GFX_MACIOSURFACETEXTUREHOSTOGL_H \ No newline at end of file +#endif // MOZILLA_GFX_MACIOSURFACETEXTUREHOSTOGL_H diff --git a/gfx/layers/opengl/TextureHostOGL.cpp b/gfx/layers/opengl/TextureHostOGL.cpp index 0dc196d335e1..4b0b12391bb4 100644 --- a/gfx/layers/opengl/TextureHostOGL.cpp +++ b/gfx/layers/opengl/TextureHostOGL.cpp @@ -24,6 +24,7 @@ #include "mozilla/layers/CompositorOGL.h" // for CompositorOGL #ifdef MOZ_WIDGET_GONK # include "GrallocImages.h" // for GrallocImage +# include "EGLImageHelpers.h" #endif #include "mozilla/layers/ISurfaceAllocator.h" #include "mozilla/layers/YCbCrImageDataSerializer.h" @@ -44,7 +45,7 @@ using namespace mozilla::gfx; namespace mozilla { namespace layers { -class Compositor; +class Compositor; TemporaryRef CreateCompositableBackendSpecificDataOGL() @@ -87,8 +88,7 @@ CreateDeprecatedTextureHostOGL(SurfaceDescriptorType aDescriptorType, TemporaryRef -CreateTextureHostOGL(uint64_t aID, - const SurfaceDescriptor& aDesc, +CreateTextureHostOGL(const SurfaceDescriptor& aDesc, ISurfaceAllocator* aDeallocator, TextureFlags aFlags) { @@ -96,13 +96,13 @@ CreateTextureHostOGL(uint64_t aID, switch (aDesc.type()) { case SurfaceDescriptor::TSurfaceDescriptorShmem: case SurfaceDescriptor::TSurfaceDescriptorMemory: { - result = CreateBackendIndependentTextureHost(aID, aDesc, + result = CreateBackendIndependentTextureHost(aDesc, aDeallocator, aFlags); break; } case SurfaceDescriptor::TSharedTextureDescriptor: { const SharedTextureDescriptor& desc = aDesc.get_SharedTextureDescriptor(); - result = new SharedTextureHostOGL(aID, aFlags, + result = new SharedTextureHostOGL(aFlags, desc.shareType(), desc.handle(), gfx::ToIntSize(desc.size()), @@ -113,7 +113,7 @@ CreateTextureHostOGL(uint64_t aID, case SurfaceDescriptor::TSurfaceDescriptorMacIOSurface: { const SurfaceDescriptorMacIOSurface& desc = aDesc.get_SurfaceDescriptorMacIOSurface(); - result = new MacIOSurfaceTextureHostOGL(aID, aFlags, desc); + result = new MacIOSurfaceTextureHostOGL(aFlags, desc); break; } #endif @@ -121,7 +121,7 @@ CreateTextureHostOGL(uint64_t aID, case SurfaceDescriptor::TNewSurfaceDescriptorGralloc: { const NewSurfaceDescriptorGralloc& desc = aDesc.get_NewSurfaceDescriptorGralloc(); - result = new GrallocTextureHostOGL(aID, aFlags, desc); + result = new GrallocTextureHostOGL(aFlags, desc); break; } #endif @@ -376,13 +376,12 @@ SharedTextureSourceOGL::GetTextureTransform() return handleDetails.mTextureTransform; } -SharedTextureHostOGL::SharedTextureHostOGL(uint64_t aID, - TextureFlags aFlags, +SharedTextureHostOGL::SharedTextureHostOGL(TextureFlags aFlags, gl::SharedTextureShareType aShareType, gl::SharedTextureHandle aSharedHandle, gfx::IntSize aSize, bool inverted) - : TextureHost(aID, aFlags) + : TextureHost(aFlags) , mSize(aSize) , mCompositor(nullptr) , mSharedHandle(aSharedHandle) @@ -1156,9 +1155,9 @@ GrallocDeprecatedTextureHostOGL::DeleteTextures() { if (mEGLImage) { if (gl()->MakeCurrent()) { - gl()->DestroyEGLImage(mEGLImage); + EGLImageDestroy(gl(), mEGLImage); } - mEGLImage = 0; + mEGLImage = EGL_NO_IMAGE; } } @@ -1214,7 +1213,7 @@ GrallocDeprecatedTextureHostOGL::SwapTexturesImpl(const SurfaceDescriptor& aImag // create new EGLImage // create EGLImage during buffer swap could reduce the graphic driver's task // during rendering. - mEGLImage = gl()->CreateEGLImageForNativeBuffer(mGraphicBuffer->getNativeBuffer()); + mEGLImage = EGLImageCreateFromNativeBuffer(gl(), mGraphicBuffer->getNativeBuffer()); gl()->fEGLImageTargetTexture2D(mTextureTarget, mEGLImage); } @@ -1387,7 +1386,7 @@ GrallocDeprecatedTextureHostOGL::GetAsSurface() { gl()->fActiveTexture(LOCAL_GL_TEXTURE0); gl()->fBindTexture(mTextureTarget, tex); if (!mEGLImage) { - mEGLImage = gl()->CreateEGLImageForNativeBuffer(mGraphicBuffer->getNativeBuffer()); + mEGLImage = EGLImageCreateFromNativeBuffer(gl(), mGraphicBuffer->getNativeBuffer()); } gl()->fEGLImageTargetTexture2D(mTextureTarget, mEGLImage); diff --git a/gfx/layers/opengl/TextureHostOGL.h b/gfx/layers/opengl/TextureHostOGL.h index 52d782eae458..23743453edb2 100644 --- a/gfx/layers/opengl/TextureHostOGL.h +++ b/gfx/layers/opengl/TextureHostOGL.h @@ -127,8 +127,6 @@ public: virtual void BindTexture(GLenum aTextureUnit) = 0; - virtual void UnbindTexture() = 0; - virtual gfx::IntSize GetSize() const = 0; virtual GLenum GetTextureTarget() const { return LOCAL_GL_TEXTURE_2D; } @@ -187,11 +185,6 @@ public: virtual bool IsValid() const MOZ_OVERRIDE { return !!mTexImage; } - virtual void UnbindTexture() MOZ_OVERRIDE - { - mTexImage->ReleaseTexture(); - } - virtual GLenum GetWrapMode() const MOZ_OVERRIDE { return mTexImage->GetWrapMode(); @@ -269,8 +262,6 @@ public: virtual GLenum GetWrapMode() const MOZ_OVERRIDE { return mWrapMode; } - virtual void UnbindTexture() MOZ_OVERRIDE {} - // SharedTextureSource doesn't own any gl texture virtual void DeallocateDeviceData() {} @@ -298,8 +289,7 @@ protected: class SharedTextureHostOGL : public TextureHost { public: - SharedTextureHostOGL(uint64_t aID, - TextureFlags aFlags, + SharedTextureHostOGL(TextureFlags aFlags, gl::SharedTextureShareType aShareType, gl::SharedTextureHandle aSharedhandle, gfx::IntSize aSize, @@ -407,11 +397,6 @@ public: mTexture->BindTexture(aTextureUnit); } - void UnbindTexture() MOZ_OVERRIDE - { - mTexture->ReleaseTexture(); - } - gfx::IntSize GetSize() const MOZ_OVERRIDE; GLenum GetWrapMode() const MOZ_OVERRIDE @@ -529,10 +514,6 @@ public: { mTexImage->BindTexture(aUnit); } - void UnbindTexture() MOZ_OVERRIDE - { - mTexImage->ReleaseTexture(); - } virtual bool IsValid() const MOZ_OVERRIDE { return !!mTexImage; @@ -651,7 +632,6 @@ public: // Lock already bound us! MOZ_ASSERT(activetex == LOCAL_GL_TEXTURE0); } - void UnbindTexture() MOZ_OVERRIDE {} GLuint GetTextureID() { return mTextureHandle; } ContentType GetContentType() { @@ -732,8 +712,6 @@ public: void BindTexture(GLenum activetex) MOZ_OVERRIDE; - void UnbindTexture() MOZ_OVERRIDE {} - GLuint GetTextureID() { return mTextureHandle; } ContentType GetContentType() { return (mFormat == gfx::FORMAT_B8G8R8A8) ? @@ -793,7 +771,6 @@ public: virtual bool IsValid() const MOZ_OVERRIDE { return true; } virtual GLenum GetWrapMode() const MOZ_OVERRIDE { return LOCAL_GL_CLAMP_TO_EDGE; } virtual void BindTexture(GLenum aTextureUnit); - virtual void UnbindTexture() MOZ_OVERRIDE {} virtual gfx::IntSize GetSize() const MOZ_OVERRIDE { return mSize; @@ -876,7 +853,6 @@ public: virtual const char* Name() { return "GrallocDeprecatedTextureHostOGL"; } void BindTexture(GLenum aTextureUnit) MOZ_OVERRIDE; - void UnbindTexture() MOZ_OVERRIDE {} virtual TextureSourceOGL* AsSourceOGL() MOZ_OVERRIDE { diff --git a/gfx/tests/gtest/TestTextures.cpp b/gfx/tests/gtest/TestTextures.cpp index f95ae7e7fd20..895f36e4d80c 100644 --- a/gfx/tests/gtest/TestTextures.cpp +++ b/gfx/tests/gtest/TestTextures.cpp @@ -119,20 +119,17 @@ void TestTextureClientSurface(TextureClient* texture, gfxImageSurface* surface) texture->Unlock(); // client serialization - texture->SetID(1); SurfaceDescriptor descriptor; ASSERT_TRUE(texture->ToSurfaceDescriptor(descriptor)); ASSERT_NE(descriptor.type(), SurfaceDescriptor::Tnull_t); // host deserialization - RefPtr host = CreateBackendIndependentTextureHost(texture->GetID(), - descriptor, nullptr, + RefPtr host = CreateBackendIndependentTextureHost(descriptor, nullptr, texture->GetFlags()); ASSERT_TRUE(host.get() != nullptr); ASSERT_EQ(host->GetFlags(), texture->GetFlags()); - ASSERT_EQ(host->GetID(), texture->GetID()); // host read ASSERT_TRUE(host->Lock()); @@ -145,9 +142,6 @@ void TestTextureClientSurface(TextureClient* texture, gfxImageSurface* surface) hostDataSurface->Stride(), SurfaceFormatToImageFormat(hostDataSurface->GetFormat())); AssertSurfacesEqual(surface, hostSurface.get()); - - // host deallocation - host->DeallocateSharedData(); } // Same as above, for YCbCr surfaces @@ -168,22 +162,19 @@ void TestTextureClientYCbCr(TextureClient* client, PlanarYCbCrData& ycbcrData) { client->Unlock(); // client serialization - client->SetID(1); SurfaceDescriptor descriptor; ASSERT_TRUE(client->ToSurfaceDescriptor(descriptor)); ASSERT_NE(descriptor.type(), SurfaceDescriptor::Tnull_t); // host deserialization - RefPtr textureHost = CreateBackendIndependentTextureHost(client->GetID(), - descriptor, nullptr, + RefPtr textureHost = CreateBackendIndependentTextureHost(descriptor, nullptr, client->GetFlags()); RefPtr host = static_cast(textureHost.get()); ASSERT_TRUE(host.get() != nullptr); ASSERT_EQ(host->GetFlags(), client->GetFlags()); - ASSERT_EQ(host->GetID(), client->GetID()); // This will work iff the compositor is not BasicCompositor ASSERT_EQ(host->GetFormat(), mozilla::gfx::FORMAT_YUV); @@ -213,9 +204,6 @@ void TestTextureClientYCbCr(TextureClient* client, PlanarYCbCrData& ycbcrData) { AssertYCbCrSurfacesEqual(&ycbcrData, &data); host->Unlock(); - - // host deallocation - host->DeallocateSharedData(); } TEST(Layers, TextureSerialization) { @@ -234,7 +222,7 @@ TEST(Layers, TextureSerialization) { RefPtr client = new MemoryTextureClient(nullptr, mozilla::gfx::ImageFormatToSurfaceFormat(surface->Format()), - TEXTURE_FLAGS_DEFAULT); + TEXTURE_DEALLOCATE_CLIENT); TestTextureClientSurface(client, surface); @@ -270,7 +258,7 @@ TEST(Layers, TextureYCbCrSerialization) { RefPtr client = new MemoryTextureClient(nullptr, mozilla::gfx::FORMAT_YUV, - TEXTURE_FLAGS_DEFAULT); + TEXTURE_DEALLOCATE_CLIENT); TestTextureClientYCbCr(client, clientData); diff --git a/gfx/thebes/gfxDrawable.cpp b/gfx/thebes/gfxDrawable.cpp index e2a045420401..689170dd7ef2 100644 --- a/gfx/thebes/gfxDrawable.cpp +++ b/gfx/thebes/gfxDrawable.cpp @@ -33,6 +33,15 @@ gfxSurfaceDrawable::gfxSurfaceDrawable(DrawTarget* aDrawTarget, { } +gfxSurfaceDrawable::gfxSurfaceDrawable(SourceSurface* aSurface, + const gfxIntSize aSize, + const gfxMatrix aTransform) + : gfxDrawable(aSize) + , mSourceSurface(aSurface) + , mTransform(aTransform) +{ +} + static gfxMatrix DeviceToImageTransform(gfxContext* aContext, const gfxMatrix& aUserSpaceToImageSpace) @@ -127,6 +136,8 @@ gfxSurfaceDrawable::Draw(gfxContext* aContext, RefPtr source = mDrawTarget->Snapshot(); pattern = new gfxPattern(source, Matrix()); } + } else if (mSourceSurface) { + pattern = new gfxPattern(mSourceSurface, Matrix()); } else { pattern = new gfxPattern(mSurface); } @@ -160,7 +171,7 @@ gfxSurfaceDrawable::Draw(gfxContext* aContext, already_AddRefed gfxSurfaceDrawable::GetAsImageSurface() { - if (mDrawTarget) { + if (mDrawTarget || mSourceSurface) { // TODO: Find a way to implement this. The caller really wants a 'sub-image' of // the original, without having to do a copy. GetDataSurface() might just copy, // which isn't useful. diff --git a/gfx/thebes/gfxDrawable.h b/gfx/thebes/gfxDrawable.h index 0c38d047a47c..b3afbb558346 100644 --- a/gfx/thebes/gfxDrawable.h +++ b/gfx/thebes/gfxDrawable.h @@ -58,6 +58,8 @@ public: const gfxMatrix aTransform = gfxMatrix()); gfxSurfaceDrawable(mozilla::gfx::DrawTarget* aDT, const gfxIntSize aSize, const gfxMatrix aTransform = gfxMatrix()); + gfxSurfaceDrawable(mozilla::gfx::SourceSurface* aSurface, const gfxIntSize aSize, + const gfxMatrix aTransform = gfxMatrix()); virtual ~gfxSurfaceDrawable() {} virtual bool Draw(gfxContext* aContext, @@ -71,6 +73,7 @@ public: protected: nsRefPtr mSurface; mozilla::RefPtr mDrawTarget; + mozilla::RefPtr mSourceSurface; const gfxMatrix mTransform; }; diff --git a/gfx/thebes/gfxPattern.cpp b/gfx/thebes/gfxPattern.cpp index 6342526cacf4..72be17eb27bc 100644 --- a/gfx/thebes/gfxPattern.cpp +++ b/gfx/thebes/gfxPattern.cpp @@ -180,7 +180,7 @@ gfxPattern::GetPattern(DrawTarget *aTarget, Matrix *aPatternTransform) if (!mPattern) { mGfxPattern = new (mSurfacePattern.addr()) - SurfacePattern(mSourceSurface, ToExtendMode(mExtend), mTransform); + SurfacePattern(mSourceSurface, ToExtendMode(mExtend), mTransform, mFilter); return mGfxPattern; } diff --git a/gfx/thebes/gfxPlatform.cpp b/gfx/thebes/gfxPlatform.cpp index 6d1b03e370f9..69df30cc6475 100644 --- a/gfx/thebes/gfxPlatform.cpp +++ b/gfx/thebes/gfxPlatform.cpp @@ -719,6 +719,14 @@ gfxPlatform::GetSourceSurfaceForSurface(DrawTarget *aTarget, gfxASurface *aSurfa return nullptr; } + if (!aTarget) { + if (ScreenReferenceDrawTarget()) { + aTarget = ScreenReferenceDrawTarget(); + } else { + return nullptr; + } + } + void *userData = aSurface->GetData(&kSourceSurface); if (userData) { diff --git a/gfx/thebes/gfxPlatformFontList.cpp b/gfx/thebes/gfxPlatformFontList.cpp index f61f9803a7d6..0d301ee30aff 100644 --- a/gfx/thebes/gfxPlatformFontList.cpp +++ b/gfx/thebes/gfxPlatformFontList.cpp @@ -192,11 +192,13 @@ gfxPlatformFontList::InitOtherFamilyNames() TimeStamp end = TimeStamp::Now(); Telemetry::AccumulateTimeDelta(Telemetry::FONTLIST_INITOTHERFAMILYNAMES, start, end); +#ifdef PR_LOGGING if (LOG_FONTINIT_ENABLED()) { TimeDuration elapsed = end - start; LOG_FONTINIT(("(fontinit) InitOtherFamilyNames took %8.2f ms", elapsed.ToMilliseconds())); } +#endif } PLDHashOperator @@ -222,11 +224,13 @@ gfxPlatformFontList::InitFaceNameLists() TimeStamp end = TimeStamp::Now(); Telemetry::AccumulateTimeDelta(Telemetry::FONTLIST_INITFACENAMELISTS, start, end); +#ifdef PR_LOGGING if (LOG_FONTINIT_ENABLED()) { TimeDuration elapsed = end - start; LOG_FONTINIT(("(fontinit) InitFaceNameLists took %8.2f ms", elapsed.ToMilliseconds())); } +#endif } PLDHashOperator diff --git a/gfx/thebes/gfxUtils.cpp b/gfx/thebes/gfxUtils.cpp index aed86b409813..2f33bfd1c9a0 100644 --- a/gfx/thebes/gfxUtils.cpp +++ b/gfx/thebes/gfxUtils.cpp @@ -95,18 +95,13 @@ gfxUtils::UnpremultiplyImageSurface(gfxImageSurface *aSourceSurface, MOZ_ASSERT(aSourceSurface->Format() == aDestSurface->Format() && aSourceSurface->Width() == aDestSurface->Width() && - aSourceSurface->Height() == aDestSurface->Height() && - aSourceSurface->Stride() == aDestSurface->Stride(), + aSourceSurface->Height() == aDestSurface->Height(), "Source and destination surfaces don't have identical characteristics"); - MOZ_ASSERT(aSourceSurface->Stride() == aSourceSurface->Width() * 4, - "Source surface stride isn't tightly packed"); - // Only premultiply ARGB32 if (aSourceSurface->Format() != gfxImageFormatARGB32) { if (aDestSurface != aSourceSurface) { - memcpy(aDestSurface->Data(), aSourceSurface->Data(), - aSourceSurface->Stride() * aSourceSurface->Height()); + aDestSurface->CopyFrom(aSourceSurface); } return; } @@ -114,29 +109,33 @@ gfxUtils::UnpremultiplyImageSurface(gfxImageSurface *aSourceSurface, uint8_t *src = aSourceSurface->Data(); uint8_t *dst = aDestSurface->Data(); - uint32_t dim = aSourceSurface->Width() * aSourceSurface->Height(); - for (uint32_t i = 0; i < dim; ++i) { + for (int32_t i = 0; i < aSourceSurface->Height(); ++i) { + uint8_t *srcRow = src + (i * aSourceSurface->Stride()); + uint8_t *dstRow = dst + (i * aDestSurface->Stride()); + + for (int32_t j = 0; j < aSourceSurface->Width(); ++j) { #ifdef IS_LITTLE_ENDIAN - uint8_t b = *src++; - uint8_t g = *src++; - uint8_t r = *src++; - uint8_t a = *src++; + uint8_t b = *srcRow++; + uint8_t g = *srcRow++; + uint8_t r = *srcRow++; + uint8_t a = *srcRow++; - *dst++ = UnpremultiplyValue(a, b); - *dst++ = UnpremultiplyValue(a, g); - *dst++ = UnpremultiplyValue(a, r); - *dst++ = a; + *dstRow++ = UnpremultiplyValue(a, b); + *dstRow++ = UnpremultiplyValue(a, g); + *dstRow++ = UnpremultiplyValue(a, r); + *dstRow++ = a; #else - uint8_t a = *src++; - uint8_t r = *src++; - uint8_t g = *src++; - uint8_t b = *src++; + uint8_t a = *srcRow++; + uint8_t r = *srcRow++; + uint8_t g = *srcRow++; + uint8_t b = *srcRow++; - *dst++ = a; - *dst++ = UnpremultiplyValue(a, r); - *dst++ = UnpremultiplyValue(a, g); - *dst++ = UnpremultiplyValue(a, b); + *dstRow++ = a; + *dstRow++ = UnpremultiplyValue(a, r); + *dstRow++ = UnpremultiplyValue(a, g); + *dstRow++ = UnpremultiplyValue(a, b); #endif + } } } diff --git a/js/jsd/jsd_xpc.cpp b/js/jsd/jsd_xpc.cpp index 4d3f0142321f..13da92e6f91f 100644 --- a/js/jsd/jsd_xpc.cpp +++ b/js/jsd/jsd_xpc.cpp @@ -36,10 +36,11 @@ #include "SandboxPrivate.h" #include "nsJSPrincipals.h" #include "nsContentUtils.h" -#include "nsCxPusher.h" +#include "mozilla/dom/ScriptSettings.h" using mozilla::AutoSafeJSContext; using mozilla::AutoPushJSContext; +using mozilla::dom::AutoSystemCaller; /* * defining CAUTIOUS_SCRIPTHOOK makes jsds disable GC while calling out to the @@ -3004,8 +3005,7 @@ jsdService::EnterNestedEventLoop (jsdINestCallback *callback, uint32_t *_rval) // Nesting event queues is a thing of the past. Now, we just spin the // current event loop. nsresult rv = NS_OK; - nsCxPusher pusher; - pusher.PushNull(); + AutoSystemCaller asc; uint32_t nestLevel = ++mNestedLoopLevel; nsCOMPtr thread = do_GetCurrentThread(); diff --git a/js/public/HashTable.h b/js/public/HashTable.h index b3374d7e26df..b32207a40d9c 100644 --- a/js/public/HashTable.h +++ b/js/public/HashTable.h @@ -756,9 +756,19 @@ class HashTable : private AllocPolicy void nonNull() {} Entry *entry_; +#ifdef DEBUG + const HashTable *table_; + uint32_t generation; +#endif protected: - Ptr(Entry &entry) : entry_(&entry) {} + Ptr(Entry &entry, const HashTable &tableArg) + : entry_(&entry) +#ifdef DEBUG + , table_(&tableArg) + , generation(tableArg.generation()) +#endif + {} public: // Leaves Ptr uninitialized. @@ -768,13 +778,34 @@ class HashTable : private AllocPolicy #endif } - bool found() const { return entry_->isLive(); } - operator ConvertibleToBool() const { return found() ? &Ptr::nonNull : 0; } - bool operator==(const Ptr &rhs) const { JS_ASSERT(found() && rhs.found()); return entry_ == rhs.entry_; } - bool operator!=(const Ptr &rhs) const { return !(*this == rhs); } + bool found() const { + JS_ASSERT(generation == table_->generation()); + return entry_->isLive(); + } - T &operator*() const { return entry_->get(); } - T *operator->() const { return &entry_->get(); } + operator ConvertibleToBool() const { + return found() ? &Ptr::nonNull : 0; + } + + bool operator==(const Ptr &rhs) const { + JS_ASSERT(found() && rhs.found()); + return entry_ == rhs.entry_; + } + + bool operator!=(const Ptr &rhs) const { + JS_ASSERT(generation == table_->generation()); + return !(*this == rhs); + } + + T &operator*() const { + JS_ASSERT(generation == table_->generation()); + return entry_->get(); + } + + T *operator->() const { + JS_ASSERT(generation == table_->generation()); + return &entry_->get(); + } }; // A Ptr that can be used to add a key after a failed lookup. @@ -784,7 +815,10 @@ class HashTable : private AllocPolicy HashNumber keyHash; mozilla::DebugOnly mutationCount; - AddPtr(Entry &entry, HashNumber hn) : Ptr(entry), keyHash(hn) {} + AddPtr(Entry &entry, const HashTable &tableArg, HashNumber hn) + : Ptr(entry, tableArg), keyHash(hn), mutationCount(tableArg.mutationCount) + {} + public: // Leaves AddPtr uninitialized. AddPtr() {} @@ -799,32 +833,63 @@ class HashTable : private AllocPolicy protected: friend class HashTable; - Range(Entry *c, Entry *e) : cur(c), end(e), validEntry(true) { + Range(const HashTable &tableArg, Entry *c, Entry *e) + : cur(c) + , end(e) +#ifdef DEBUG + , table_(&tableArg) + , mutationCount(tableArg.mutationCount) + , generation(tableArg.generation()) + , validEntry(true) +#endif + { while (cur < end && !cur->isLive()) ++cur; } Entry *cur, *end; - mozilla::DebugOnly validEntry; +#ifdef DEBUG + const HashTable *table_; + uint64_t mutationCount; + uint32_t generation; + bool validEntry; +#endif public: - Range() : cur(nullptr), end(nullptr), validEntry(false) {} + Range() + : cur(nullptr) + , end(nullptr) +#ifdef DEBUG + , table_(nullptr) + , mutationCount(0) + , generation(0) + , validEntry(false) +#endif + {} bool empty() const { + JS_ASSERT(generation == table_->generation()); + JS_ASSERT(mutationCount == table_->mutationCount); return cur == end; } T &front() const { JS_ASSERT(validEntry); JS_ASSERT(!empty()); + JS_ASSERT(generation == table_->generation()); + JS_ASSERT(mutationCount == table_->mutationCount); return cur->get(); } void popFront() { JS_ASSERT(!empty()); + JS_ASSERT(generation == table_->generation()); + JS_ASSERT(mutationCount == table_->mutationCount); while (++cur < end && !cur->isLive()) continue; +#ifdef DEBUG validEntry = true; +#endif } }; @@ -837,17 +902,17 @@ class HashTable : private AllocPolicy { friend class HashTable; - HashTable &table; + HashTable &table_; bool rekeyed; bool removed; /* Not copyable. */ - Enum(const Enum &); - void operator=(const Enum &); + Enum(const Enum &) MOZ_DELETE; + void operator=(const Enum &) MOZ_DELETE; public: template explicit - Enum(Map &map) : Range(map.all()), table(map.impl), rekeyed(false), removed(false) {} + Enum(Map &map) : Range(map.all()), table_(map.impl), rekeyed(false), removed(false) {} // Removes the |front()| element from the table, leaving |front()| // invalid until the next call to |popFront()|. For example: @@ -857,18 +922,25 @@ class HashTable : private AllocPolicy // if (e.front() == 42) // e.removeFront(); void removeFront() { - table.remove(*this->cur); + table_.remove(*this->cur); removed = true; +#ifdef DEBUG this->validEntry = false; + this->mutationCount = table_.mutationCount; +#endif } // Removes the |front()| element and re-inserts it into the table with // a new key at the new Lookup position. |front()| is invalid after // this operation until the next call to |popFront()|. void rekeyFront(const Lookup &l, const Key &k) { - table.rekeyWithoutRehash(*this->cur, l, k); + Ptr p(*this->cur, table_); + table_.rekeyWithoutRehash(p, l, k); rekeyed = true; +#ifdef DEBUG this->validEntry = false; + this->mutationCount = table_.mutationCount; +#endif } void rekeyFront(const Key &k) { @@ -878,12 +950,12 @@ class HashTable : private AllocPolicy // Potentially rehashes the table. ~Enum() { if (rekeyed) { - table.gen++; - table.checkOverRemoved(); + table_.gen++; + table_.checkOverRemoved(); } if (removed) - table.compactIfUnderloaded(); + table_.compactIfUnderloaded(); } }; @@ -1392,7 +1464,7 @@ class HashTable : private AllocPolicy Range all() const { JS_ASSERT(table); - return Range(table, table + capacity()); + return Range(*this, table, table + capacity()); } bool empty() const @@ -1433,13 +1505,13 @@ class HashTable : private AllocPolicy { mozilla::ReentrancyGuard g(*this); HashNumber keyHash = prepareHash(l); - return Ptr(lookup(l, keyHash, 0)); + return Ptr(lookup(l, keyHash, 0), *this); } Ptr readonlyThreadsafeLookup(const Lookup &l) const { HashNumber keyHash = prepareHash(l); - return Ptr(lookup(l, keyHash, 0)); + return Ptr(lookup(l, keyHash, 0), *this); } AddPtr lookupForAdd(const Lookup &l) const @@ -1447,8 +1519,7 @@ class HashTable : private AllocPolicy mozilla::ReentrancyGuard g(*this); HashNumber keyHash = prepareHash(l); Entry &entry = lookup(l, keyHash, sCollisionBit); - AddPtr p(entry, keyHash); - p.mutationCount = mutationCount; + AddPtr p(entry, *this, keyHash); return p; } @@ -1456,7 +1527,6 @@ class HashTable : private AllocPolicy bool add(AddPtr &p, U &&u) { mozilla::ReentrancyGuard g(*this); - JS_ASSERT(mutationCount == p.mutationCount); JS_ASSERT(table); JS_ASSERT(!p.found()); JS_ASSERT(!(p.keyHash & sCollisionBit)); @@ -1479,6 +1549,10 @@ class HashTable : private AllocPolicy p.entry_->setLive(p.keyHash, mozilla::Forward(u)); entryCount++; mutationCount++; +#ifdef DEBUG + p.generation = generation(); + p.mutationCount = mutationCount; +#endif return true; } @@ -1520,7 +1594,10 @@ class HashTable : private AllocPolicy template bool relookupOrAdd(AddPtr& p, const Lookup &l, U &&u) { +#ifdef DEBUG + p.generation = generation(); p.mutationCount = mutationCount; +#endif { mozilla::ReentrancyGuard g(*this); JS_ASSERT(prepareHash(l) == p.keyHash); // l has not been destroyed diff --git a/js/src/Makefile.in b/js/src/Makefile.in index 14a5eade8c9e..1a7008f5c4c6 100644 --- a/js/src/Makefile.in +++ b/js/src/Makefile.in @@ -380,22 +380,6 @@ endif ifdef _MSC_VER # XXX We should add this to CXXFLAGS, too? CFLAGS += -fp:precise - -ifeq ($(CPU_ARCH),x86) -# Workaround compiler bug on PGO (Bug 721284) -NO_PROFILE_GUIDED_OPTIMIZE := \ - MonoIC.cpp \ - Compiler.cpp \ - $(NULL) -# Ditto (Bug 772303) -NO_PROFILE_GUIDED_OPTIMIZE += RegExp.cpp -endif -# Ditto (Bug 810661) -ifeq ($(CPU_ARCH),x86_64) -NO_PROFILE_GUIDED_OPTIMIZE := \ - CTypes.cpp \ - $(NULL) -endif endif # _MSC_VER ifeq ($(OS_ARCH),FreeBSD) diff --git a/js/src/build/ConfigStatus.py b/js/src/build/ConfigStatus.py index 650e24aeb9cb..b73859798619 100644 --- a/js/src/build/ConfigStatus.py +++ b/js/src/build/ConfigStatus.py @@ -64,7 +64,9 @@ def config_status(topobjdir='.', topsrcdir='.', help='display verbose output') parser.add_option('-n', dest='not_topobjdir', action='store_true', help='do not consider current directory as top object directory') - (options, args) = parser.parse_args() + parser.add_option('-d', '--diff', action='store_true', + help='print diffs of changed files.') + options, args = parser.parse_args() # Without -n, the current directory is meant to be the top object directory if not options.not_topobjdir: @@ -98,3 +100,7 @@ def config_status(topobjdir='.', topsrcdir='.', for line in summary.summaries(): print(line, file=sys.stderr) + + if options.diff: + for path, diff in sorted(summary.file_diffs.items()): + print(diff) diff --git a/js/src/config/config.mk b/js/src/config/config.mk index 8534697ec65f..2c1903172a22 100644 --- a/js/src/config/config.mk +++ b/js/src/config/config.mk @@ -35,7 +35,7 @@ endif # responsibility between Makefile.in and mozbuild files. _MOZBUILD_EXTERNAL_VARIABLES := \ ANDROID_GENERATED_RESFILES \ - ANDROID_RESFILES \ + ANDROID_RES_DIRS \ CMSRCS \ CMMSRCS \ CPP_UNIT_TESTS \ @@ -70,6 +70,7 @@ _MOZBUILD_EXTERNAL_VARIABLES := \ $(NULL) _DEPRECATED_VARIABLES := \ + ANDROID_RESFILES \ MOCHITEST_FILES_PARTS \ MOCHITEST_BROWSER_FILES_PARTS \ SHORT_LIBNAME \ diff --git a/js/src/config/makefiles/java-build.mk b/js/src/config/makefiles/java-build.mk index 179063b8fd5d..b3f34ab1d033 100644 --- a/js/src/config/makefiles/java-build.mk +++ b/js/src/config/makefiles/java-build.mk @@ -7,29 +7,6 @@ ifndef INCLUDED_JAVA_BUILD_MK #{ -ifdef ANDROID_RESFILES #{ -ifndef IGNORE_ANDROID_RESFILES #{ -res-dep := .deps-copy-java-res - -GENERATED_DIRS += res -GARBAGE += $(res-dep) - -export:: $(res-dep) - -res-dep-preqs := \ - $(addprefix $(srcdir)/,$(ANDROID_RESFILES)) \ - $(call mkdir_deps,res) \ - $(if $(IS_LANGUAGE_REPACK),FORCE) \ - $(NULL) - -# nop-build: only copy res/ files when needed -$(res-dep): $(res-dep-preqs) - $(call copy_dir,$(srcdir)/res,$(CURDIR)/res) - @$(TOUCH) $@ -endif #} IGNORE_ANDROID_RESFILES -endif #} ANDROID_RESFILES - - ifdef JAVAFILES #{ GENERATED_DIRS += classes @@ -39,7 +16,8 @@ endif #} JAVAFILES ifdef ANDROID_APK_NAME #{ -_ANDROID_RES_FLAG := -S $(or $(ANDROID_RES_DIR),res) +android_res_dirs := $(addprefix $(srcdir)/,$(or $(ANDROID_RES_DIRS),res)) +_ANDROID_RES_FLAG := $(addprefix -S ,$(android_res_dirs)) _ANDROID_ASSETS_FLAG := $(addprefix -A ,$(ANDROID_ASSETS_DIR)) GENERATED_DIRS += classes @@ -57,7 +35,11 @@ classes.dex: $(JAVAFILES) R.java: .aapt.deps $(ANDROID_APK_NAME).ap_: .aapt.deps -.aapt.deps: AndroidManifest.xml $(wildcard $(ANDROID_RES_DIR)) $(wildcard $(ANDROID_ASSETS_DIR)) +# This uses the fact that Android resource directories list all +# resource files one subdirectory below the parent resource directory. +android_res_files := $(wildcard $(addsuffix /*,$(wildcard $(addsuffix /*,$(android_res_dirs))))) + +.aapt.deps: AndroidManifest.xml $(android_res_files) $(wildcard $(ANDROID_ASSETS_DIR)) $(AAPT) package -f -M $< -I $(ANDROID_SDK)/android.jar $(_ANDROID_RES_FLAG) $(_ANDROID_ASSETS_FLAG) \ -J ${@D} \ -F $(ANDROID_APK_NAME).ap_ diff --git a/js/src/config/rules.mk b/js/src/config/rules.mk index 9f8d932c0888..4db9adab9d7e 100644 --- a/js/src/config/rules.mk +++ b/js/src/config/rules.mk @@ -642,7 +642,7 @@ tools:: endif ############################################## -ifndef NO_PROFILE_GUIDED_OPTIMIZE +ifneq (1,$(NO_PROFILE_GUIDED_OPTIMIZE)) ifdef MOZ_PROFILE_USE ifeq ($(OS_ARCH)_$(GNU_CC), WINNT_) # When building with PGO, we have to make sure to re-link diff --git a/js/src/jit/IonAnalysis.cpp b/js/src/jit/IonAnalysis.cpp index 4d39ad902c47..839c3ecb2305 100644 --- a/js/src/jit/IonAnalysis.cpp +++ b/js/src/jit/IonAnalysis.cpp @@ -2113,10 +2113,12 @@ CmpInstructions(const void *a, const void *b) } bool -jit::AnalyzeNewScriptProperties(JSContext *cx, HandleFunction fun, +jit::AnalyzeNewScriptProperties(JSContext *cx, JSFunction *fun, types::TypeObject *type, HandleObject baseobj, Vector *initializerList) { + JS_ASSERT(cx->compartment()->activeAnalysis); + // When invoking 'new' on the specified script, try to find some properties // which will definitely be added to the created object before it has a // chance to escape and be accessed elsewhere. @@ -2136,8 +2138,6 @@ jit::AnalyzeNewScriptProperties(JSContext *cx, HandleFunction fun, TempAllocator temp(&alloc); IonContext ictx(cx, &temp); - types::AutoEnterAnalysis enter(cx); - if (!cx->compartment()->ensureJitCompartmentExists(cx)) return Method_Error; diff --git a/js/src/jit/IonAnalysis.h b/js/src/jit/IonAnalysis.h index cfca70d42a4e..1f37d18f3ad2 100644 --- a/js/src/jit/IonAnalysis.h +++ b/js/src/jit/IonAnalysis.h @@ -129,7 +129,7 @@ class LinearSum }; bool -AnalyzeNewScriptProperties(JSContext *cx, HandleFunction fun, +AnalyzeNewScriptProperties(JSContext *cx, JSFunction *fun, types::TypeObject *type, HandleObject baseobj, Vector *initializerList); diff --git a/js/src/jit/RangeAnalysis.h b/js/src/jit/RangeAnalysis.h index 761b6169f397..2709f368b9f8 100644 --- a/js/src/jit/RangeAnalysis.h +++ b/js/src/jit/RangeAnalysis.h @@ -13,6 +13,10 @@ #include "jit/IonAnalysis.h" #include "jit/MIR.h" +// windows.h defines those, which messes with the definitions below. +#undef min +#undef max + namespace js { namespace jit { diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index be41c0f5c7f3..38996d6f9ae1 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -6079,12 +6079,44 @@ JS_DescribeScriptedCaller(JSContext *cx, MutableHandleScript script, unsigned *l if (i.done()) return false; + // If the caller is hidden, the embedding wants us to return null here so + // that it can check its own stack. + if (i.activation()->scriptedCallerIsHidden()) + return false; + script.set(i.script()); if (lineno) *lineno = js::PCToLineNumber(i.script(), i.pc()); return true; } +namespace JS { + +JS_PUBLIC_API(void) +HideScriptedCaller(JSContext *cx) +{ + MOZ_ASSERT(cx); + + // If there's no accessible activation on the stack, we'll return null from + // JS_DescribeScriptedCaller anyway, so there's no need to annotate + // anything. + Activation *act = cx->runtime()->mainThread.activation(); + if (!act) + return; + act->hideScriptedCaller(); +} + +JS_PUBLIC_API(void) +UnhideScriptedCaller(JSContext *cx) +{ + Activation *act = cx->runtime()->mainThread.activation(); + if (!act) + return; + act->unhideScriptedCaller(); +} + +} /* namespace JS */ + #ifdef JS_THREADSAFE static PRStatus CallOnce(void *func) diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 9c9d992eed22..d75c0963b8ec 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -4567,10 +4567,53 @@ JS_IsIdentifier(JSContext *cx, JS::HandleString str, bool *isIdentifier); /* * Return the current script and line number of the most currently running * frame. Returns true if a scripted frame was found, false otherwise. + * + * If a the embedding has hidden the scripted caller for the topmost activation + * record, this will also return false. */ extern JS_PUBLIC_API(bool) JS_DescribeScriptedCaller(JSContext *cx, JS::MutableHandleScript script, unsigned *lineno); +namespace JS { + +/* + * Informs the JS engine that the scripted caller should be hidden. This can be + * used by the embedding to maintain an override of the scripted caller in its + * calculations, by hiding the scripted caller in the JS engine and pushing data + * onto a separate stack, which it inspects when JS_DescribeScriptedCaller + * returns null. + * + * We maintain a counter on each activation record. Add() increments the counter + * of the topmost activation, and Remove() decrements it. The count may never + * drop below zero, and must always be exactly zero when the activation is + * popped from the stack. + */ +extern JS_PUBLIC_API(void) +HideScriptedCaller(JSContext *cx); + +extern JS_PUBLIC_API(void) +UnhideScriptedCaller(JSContext *cx); + +class AutoHideScriptedCaller +{ + public: + AutoHideScriptedCaller(JSContext *cx + MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : mContext(cx) + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + HideScriptedCaller(mContext); + } + ~AutoHideScriptedCaller() { + UnhideScriptedCaller(mContext); + } + + protected: + JSContext *mContext; + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + +} /* namepsace JS */ /* * Encode/Decode interpreted scripts and functions to/from memory. diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index d6bd84004a37..15a93afe1280 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -247,9 +247,9 @@ struct JSCompartment void markAllInitialShapeTableEntries(JSTracer *trc); /* Set of default 'new' or lazy types in the compartment. */ - js::types::TypeObjectSet newTypeObjects; - js::types::TypeObjectSet lazyTypeObjects; - void sweepNewTypeObjectTable(js::types::TypeObjectSet &table); + js::types::TypeObjectWithNewScriptSet newTypeObjects; + js::types::TypeObjectWithNewScriptSet lazyTypeObjects; + void sweepNewTypeObjectTable(js::types::TypeObjectWithNewScriptSet &table); /* * Hash table of all manually call site-cloned functions from within diff --git a/js/src/jsinfer.cpp b/js/src/jsinfer.cpp index 087cdb832463..0c1fe0167a3c 100644 --- a/js/src/jsinfer.cpp +++ b/js/src/jsinfer.cpp @@ -1406,7 +1406,7 @@ ObjectStateChange(ExclusiveContext *cxArg, TypeObject *object, bool markingUnkno } static void -CheckNewScriptProperties(JSContext *cx, TypeObject *type, HandleFunction fun); +CheckNewScriptProperties(JSContext *cx, TypeObject *type, JSFunction *fun); namespace { @@ -1440,8 +1440,7 @@ class ConstraintDataFreezeConfiguredProperty if (type->flags & OBJECT_FLAG_NEW_SCRIPT_REGENERATE) { type->flags &= ~OBJECT_FLAG_NEW_SCRIPT_REGENERATE; if (type->hasNewScript()) { - RootedFunction fun(cx, type->newScript()->fun); - CheckNewScriptProperties(cx, type, fun); + CheckNewScriptProperties(cx, type, type->newScript()->fun); } else { JS_ASSERT(type->flags & OBJECT_FLAG_ADDENDUM_CLEARED); type->flags &= ~OBJECT_FLAG_NEW_SCRIPT_REGENERATE; @@ -3240,7 +3239,7 @@ types::AddClearDefiniteFunctionUsesInScript(JSContext *cx, TypeObject *type, * newScript on the type after they were cleared by a GC. */ static void -CheckNewScriptProperties(JSContext *cx, TypeObject *type, HandleFunction fun) +CheckNewScriptProperties(JSContext *cx, TypeObject *type, JSFunction *fun) { JS_ASSERT(cx->compartment()->activeAnalysis); @@ -3679,29 +3678,32 @@ JSObject::makeLazyType(JSContext *cx, HandleObject obj) } /* static */ inline HashNumber -TypeObjectEntry::hash(const Lookup &lookup) +TypeObjectWithNewScriptEntry::hash(const Lookup &lookup) { return PointerHasher::hash(lookup.hashProto.raw()) ^ - PointerHasher::hash(lookup.clasp); + PointerHasher::hash(lookup.clasp) ^ + PointerHasher::hash(lookup.newFunction); } /* static */ inline bool -TypeObjectEntry::match(TypeObject *key, const Lookup &lookup) +TypeObjectWithNewScriptEntry::match(const TypeObjectWithNewScriptEntry &key, const Lookup &lookup) { - return key->proto == lookup.matchProto.raw() && key->clasp == lookup.clasp; + return key.object->proto == lookup.matchProto.raw() && + key.object->clasp == lookup.clasp && + key.newFunction == lookup.newFunction; } #ifdef DEBUG bool JSObject::hasNewType(const Class *clasp, TypeObject *type) { - TypeObjectSet &table = compartment()->newTypeObjects; + TypeObjectWithNewScriptSet &table = compartment()->newTypeObjects; if (!table.initialized()) return false; - TypeObjectSet::Ptr p = table.lookup(TypeObjectSet::Lookup(clasp, this)); - return p && *p == type; + TypeObjectWithNewScriptSet::Ptr p = table.lookup(TypeObjectWithNewScriptSet::Lookup(clasp, this, nullptr)); + return p && p->object == type; } #endif /* DEBUG */ @@ -3716,10 +3718,10 @@ JSObject::setNewTypeUnknown(JSContext *cx, const Class *clasp, HandleObject obj) * not have the SETS_MARKED_UNKNOWN bit set, so may require a type set * crawl if prototypes of the object change dynamically in the future. */ - TypeObjectSet &table = cx->compartment()->newTypeObjects; + TypeObjectWithNewScriptSet &table = cx->compartment()->newTypeObjects; if (table.initialized()) { - if (TypeObjectSet::Ptr p = table.lookup(TypeObjectSet::Lookup(clasp, obj.get()))) - MarkTypeObjectUnknownProperties(cx, *p); + if (TypeObjectWithNewScriptSet::Ptr p = table.lookup(TypeObjectWithNewScriptSet::Lookup(clasp, obj.get(), nullptr))) + MarkTypeObjectUnknownProperties(cx, p->object); } return true; @@ -3733,69 +3735,64 @@ JSObject::setNewTypeUnknown(JSContext *cx, const Class *clasp, HandleObject obj) */ class NewTypeObjectsSetRef : public BufferableRef { - TypeObjectSet *set; - TypeObject *typeObject; - JSObject *proto; + TypeObjectWithNewScriptSet *set; + const Class *clasp; + JSObject *proto; + JSFunction *newFunction; public: - NewTypeObjectsSetRef(TypeObjectSet *s, TypeObject *t, JSObject *p) - : set(s), typeObject(t), proto(p) {} + NewTypeObjectsSetRef(TypeObjectWithNewScriptSet *s, const Class *clasp, JSObject *proto, JSFunction *newFunction) + : set(s), clasp(clasp), proto(proto), newFunction(newFunction) + {} void mark(JSTracer *trc) { - const Class *clasp = typeObject->clasp; JSObject *prior = proto; JS_SET_TRACING_LOCATION(trc, (void*)&*prior); Mark(trc, &proto, "newTypeObjects set prototype"); if (prior == proto) return; - TypeObjectSet::Ptr p = set->lookup(TypeObjectSet::Lookup(clasp, prior, proto)); + TypeObjectWithNewScriptSet::Ptr p = set->lookup(TypeObjectWithNewScriptSet::Lookup(clasp, prior, proto, newFunction)); JS_ASSERT(p); // newTypeObjects set must still contain original entry. - set->rekeyAs(TypeObjectSet::Lookup(clasp, prior, proto), - TypeObjectSet::Lookup(clasp, proto), typeObject); + set->rekeyAs(TypeObjectWithNewScriptSet::Lookup(clasp, prior, proto, newFunction), + TypeObjectWithNewScriptSet::Lookup(clasp, proto, newFunction), *p); } }; #endif TypeObject * -ExclusiveContext::getNewType(const Class *clasp, TaggedProto proto_, JSFunction *fun_) +ExclusiveContext::getNewType(const Class *clasp, TaggedProto proto, JSFunction *fun) { - JS_ASSERT_IF(fun_, proto_.isObject()); - JS_ASSERT_IF(proto_.isObject(), isInsideCurrentCompartment(proto_.toObject())); + JS_ASSERT_IF(fun, proto.isObject()); + JS_ASSERT_IF(proto.isObject(), isInsideCurrentCompartment(proto.toObject())); - TypeObjectSet &newTypeObjects = compartment_->newTypeObjects; + TypeObjectWithNewScriptSet &newTypeObjects = compartment()->newTypeObjects; if (!newTypeObjects.initialized() && !newTypeObjects.init()) return nullptr; - DependentAddPtr p(this, newTypeObjects, - TypeObjectSet::Lookup(clasp, proto_)); - SkipRoot skipHash(this, &p); /* Prevent the hash from being poisoned. */ + // Canonicalize new functions to use the original one associated with its script. + if (fun) { + if (fun->hasScript()) + fun = fun->nonLazyScript()->function(); + else if (fun->isInterpretedLazy()) + fun = fun->lazyScript()->function(); + else + fun = nullptr; + } + + TypeObjectWithNewScriptSet::AddPtr p = + newTypeObjects.lookupForAdd(TypeObjectWithNewScriptSet::Lookup(clasp, proto, fun)); if (p) { - TypeObject *type = *p; + TypeObject *type = p->object; JS_ASSERT(type->clasp == clasp); - JS_ASSERT(type->proto.get() == proto_.raw()); - - /* - * If set, the type's newScript indicates the script used to create - * all objects in existence which have this type. If there are objects - * in existence which are not created by calling 'new' on newScript, - * we must clear the new script information from the type and will not - * be able to assume any definite properties for instances of the type. - * This case is rare, but can happen if, for example, two scripted - * functions have the same value for their 'prototype' property, or if - * Object.create is called with a prototype object that is also the - * 'prototype' property of some scripted function. - */ - if (type->hasNewScript() && type->newScript()->fun != fun_) - type->clearAddendum(this); - + JS_ASSERT(type->proto.get() == proto.raw()); + JS_ASSERT_IF(type->hasNewScript(), type->newScript()->fun == fun); return type; } - Rooted proto(this, proto_); - RootedFunction fun(this, fun_); + AutoEnterAnalysis enter(this); if (proto.isObject() && !proto.toObject()->setDelegate(this)) return nullptr; @@ -3805,25 +3802,24 @@ ExclusiveContext::getNewType(const Class *clasp, TaggedProto proto_, JSFunction ? proto.toObject()->lastProperty()->hasObjectFlag(BaseShape::NEW_TYPE_UNKNOWN) : true; - RootedTypeObject type(this, compartment_->types.newTypeObject(this, clasp, proto, markUnknown)); + Rooted protoRoot(this, proto); + TypeObject *type = compartment()->types.newTypeObject(this, clasp, protoRoot, markUnknown); if (!type) return nullptr; - if (!p.add(newTypeObjects, TypeObjectSet::Lookup(clasp, proto), type.get())) + if (!newTypeObjects.add(p, TypeObjectWithNewScriptEntry(type, fun))) return nullptr; #ifdef JSGC_GENERATIONAL if (proto.isObject() && hasNursery() && nursery().isInside(proto.toObject())) { asJSContext()->runtime()->gcStoreBuffer.putGeneric( - NewTypeObjectsSetRef(&newTypeObjects, type.get(), proto.toObject())); + NewTypeObjectsSetRef(&newTypeObjects, clasp, proto.toObject(), fun)); } #endif if (!typeInferenceEnabled()) return type; - AutoEnterAnalysis enter(this); - if (proto.isObject()) { RootedObject obj(this, proto.toObject()); @@ -3878,14 +3874,14 @@ ExclusiveContext::getLazyType(const Class *clasp, TaggedProto proto) AutoEnterAnalysis enter(this); - TypeObjectSet &table = compartment()->lazyTypeObjects; + TypeObjectWithNewScriptSet &table = compartment()->lazyTypeObjects; if (!table.initialized() && !table.init()) return nullptr; - DependentAddPtr p(this, table, TypeObjectSet::Lookup(clasp, proto)); + TypeObjectWithNewScriptSet::AddPtr p = table.lookupForAdd(TypeObjectWithNewScriptSet::Lookup(clasp, proto, nullptr)); if (p) { - TypeObject *type = *p; + TypeObject *type = p->object; JS_ASSERT(type->lazy()); return type; @@ -3896,7 +3892,7 @@ ExclusiveContext::getLazyType(const Class *clasp, TaggedProto proto) if (!type) return nullptr; - if (!p.add(table, TypeObjectSet::Lookup(clasp, protoRoot), type)) + if (!table.add(p, TypeObjectWithNewScriptEntry(type, nullptr))) return nullptr; type->singleton = (JSObject *) TypeObject::LAZY_SINGLETON; @@ -4172,19 +4168,25 @@ TypeCompartment::clearCompilerOutputs(FreeOp *fop) } void -JSCompartment::sweepNewTypeObjectTable(TypeObjectSet &table) +JSCompartment::sweepNewTypeObjectTable(TypeObjectWithNewScriptSet &table) { gcstats::AutoPhase ap(runtimeFromMainThread()->gcStats, gcstats::PHASE_SWEEP_TABLES_TYPE_OBJECT); JS_ASSERT(zone()->isGCSweeping()); if (table.initialized()) { - for (TypeObjectSet::Enum e(table); !e.empty(); e.popFront()) { - TypeObject *type = e.front(); - if (IsTypeObjectAboutToBeFinalized(&type)) + for (TypeObjectWithNewScriptSet::Enum e(table); !e.empty(); e.popFront()) { + TypeObjectWithNewScriptEntry entry = e.front(); + if (IsTypeObjectAboutToBeFinalized(entry.object.unsafeGet())) { e.removeFront(); - else if (type != e.front()) - e.rekeyFront(TypeObjectSet::Lookup(type->clasp, type->proto.get()), type); + } else if (entry.newFunction && IsObjectAboutToBeFinalized(&entry.newFunction)) { + e.removeFront(); + } else if (entry.object != e.front().object) { + TypeObjectWithNewScriptSet::Lookup lookup(entry.object->clasp, + entry.object->proto.get(), + entry.newFunction); + e.rekeyFront(lookup, entry); + } } } } diff --git a/js/src/jsinfer.h b/js/src/jsinfer.h index ffdc9fe1d0ee..690631dfa7ad 100644 --- a/js/src/jsinfer.h +++ b/js/src/jsinfer.h @@ -1079,33 +1079,51 @@ struct TypeObject : gc::BarrieredCell }; /* - * Entries for the per-compartment set of type objects which are the default - * 'new' or the lazy types of some prototype. + * Entries for the per-compartment set of type objects which are 'new' types to + * use for some prototype and constructed with an optional script. This also + * includes entries for the set of lazy type objects in the compartment, which + * use a null script (though there are only a few of these per compartment). */ -struct TypeObjectEntry : DefaultHasher > +struct TypeObjectWithNewScriptEntry { + ReadBarriered object; + + // Note: This pointer is only used for equality and does not need a read barrier. + JSFunction *newFunction; + + TypeObjectWithNewScriptEntry(TypeObject *object, JSFunction *newFunction) + : object(object), newFunction(newFunction) + {} + struct Lookup { const Class *clasp; TaggedProto hashProto; TaggedProto matchProto; + JSFunction *newFunction; - Lookup(const Class *clasp, TaggedProto proto) - : clasp(clasp), hashProto(proto), matchProto(proto) {} + Lookup(const Class *clasp, TaggedProto proto, JSFunction *newFunction) + : clasp(clasp), hashProto(proto), matchProto(proto), newFunction(newFunction) + {} #ifdef JSGC_GENERATIONAL /* * For use by generational post barriers only. Look up an entry whose * proto has been moved, but was hashed with the original value. */ - Lookup(const Class *clasp, TaggedProto hashProto, TaggedProto matchProto) - : clasp(clasp), hashProto(hashProto), matchProto(matchProto) {} + Lookup(const Class *clasp, TaggedProto hashProto, TaggedProto matchProto, JSFunction *newFunction) + : clasp(clasp), hashProto(hashProto), matchProto(matchProto), newFunction(newFunction) + {} #endif + }; static inline HashNumber hash(const Lookup &lookup); - static inline bool match(TypeObject *key, const Lookup &lookup); + static inline bool match(const TypeObjectWithNewScriptEntry &key, const Lookup &lookup); + static void rekey(TypeObjectWithNewScriptEntry &k, const TypeObjectWithNewScriptEntry& newKey) { k = newKey; } }; -typedef HashSet, TypeObjectEntry, SystemAllocPolicy> TypeObjectSet; +typedef HashSet TypeObjectWithNewScriptSet; /* Whether to use a new type object when calling 'new' at script/pc. */ bool diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 9b929571b92c..f909f8ece251 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -1448,7 +1448,6 @@ JSObject * js::NewObjectWithType(JSContext *cx, HandleTypeObject type, JSObject *parent, gc::AllocKind allocKind, NewObjectKind newKind) { - JS_ASSERT(type->proto->hasNewType(&JSObject::class_, type)); JS_ASSERT(parent); JS_ASSERT(allocKind <= gc::FINALIZE_OBJECT_LAST); diff --git a/js/src/moz.build b/js/src/moz.build index 37fad73e3448..31769c7439b5 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -98,7 +98,6 @@ UNIFIED_SOURCES += [ 'builtin/Object.cpp', 'builtin/ParallelArray.cpp', 'builtin/Profilers.cpp', - 'builtin/RegExp.cpp', 'builtin/TestingFunctions.cpp', 'builtin/TypedObject.cpp', 'builtin/TypeRepresentation.cpp', @@ -197,6 +196,8 @@ UNIFIED_SOURCES += [ # jsarray.cpp and jsatom.cpp cannot be built in unified mode because # xpcshell is broken during packaging when compiled with gcc-4.8.2 +# builtin/RegExp.cpp cannot be built in unified mode because it is built +# without PGO # frontend/Parser.cpp cannot be built in unified mode because of explicit # template instantiations. # jsmath.cpp cannot be built in unified mode because it needs to pull rand_s @@ -204,6 +205,7 @@ UNIFIED_SOURCES += [ # jsutil.cpp cannot be built in unified mode because it is needed for # check-vanilla-allocations. SOURCES += [ + 'builtin/RegExp.cpp', 'frontend/Parser.cpp', 'jsarray.cpp', 'jsatom.cpp', @@ -423,3 +425,9 @@ if CONFIG['JS_HAS_CTYPES']: if CONFIG['MOZ_LINKER']: DEFINES['MOZ_LINKER'] = True + +if CONFIG['_MSC_VER']: + if CONFIG['CPU_ARCH'] == 'x86': + SOURCES['builtin/RegExp.cpp'].no_pgo = True # Bug 772303 + elif CONFIG['CPU_ARCH'] == 'x86_64': + SOURCES['ctypes/CTypes.cpp'].no_pgo = True # Bug 810661 diff --git a/js/src/shell/Makefile.in b/js/src/shell/Makefile.in index d906c3d08195..a9c30fa83f5a 100644 --- a/js/src/shell/Makefile.in +++ b/js/src/shell/Makefile.in @@ -4,13 +4,6 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. -ifdef _MSC_VER -# unnecessary PGO for js shell. But gcc cannot turn off pgo because it is -# necessary to link PGO lib on gcc when a object/static lib are compiled -# for PGO. -NO_PROFILE_GUIDED_OPTIMIZE := 1 -endif - LIBS = $(NSPR_LIBS) $(EDITLINE_LIBS) $(DEPTH)/$(LIB_PREFIX)js_static.$(LIB_SUFFIX) $(MOZ_ZLIB_LIBS) ifdef MOZ_NATIVE_FFI EXTRA_LIBS += $(MOZ_FFI_LIBS) diff --git a/js/src/shell/moz.build b/js/src/shell/moz.build index 5ede1ed468a7..42b1dfa532e9 100644 --- a/js/src/shell/moz.build +++ b/js/src/shell/moz.build @@ -4,7 +4,8 @@ # 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/. -PROGRAM = CONFIG['JS_SHELL_NAME'] +if CONFIG['JS_SHELL_NAME']: + PROGRAM = CONFIG['JS_SHELL_NAME'] UNIFIED_SOURCES += [ 'js.cpp', @@ -16,3 +17,9 @@ UNIFIED_SOURCES += [ # on its behalf. for var in ('EXPORT_JS_API', 'IMPL_MFBT'): DEFINES[var] = True + +if CONFIG['_MSC_VER']: + # unnecessary PGO for js shell. But gcc cannot turn off pgo because it is + # necessary to link PGO lib on gcc when a object/static lib are compiled + # for PGO. + NO_PGO = True diff --git a/js/src/vm/Stack-inl.h b/js/src/vm/Stack-inl.h index 19cb8a1febee..914841fcb952 100644 --- a/js/src/vm/Stack-inl.h +++ b/js/src/vm/Stack-inl.h @@ -833,6 +833,7 @@ Activation::Activation(JSContext *cx, Kind kind) compartment_(cx->compartment()), prev_(cx->mainThread().activation_), savedFrameChain_(0), + hideScriptedCallerCount_(0), kind_(kind) { cx->mainThread().activation_ = this; @@ -841,6 +842,7 @@ Activation::Activation(JSContext *cx, Kind kind) Activation::~Activation() { JS_ASSERT(cx_->mainThread().activation_ == this); + JS_ASSERT(hideScriptedCallerCount_ == 0); cx_->mainThread().activation_ = prev_; } diff --git a/js/src/vm/Stack.h b/js/src/vm/Stack.h index 57a2837891a4..775736fee778 100644 --- a/js/src/vm/Stack.h +++ b/js/src/vm/Stack.h @@ -1167,6 +1167,13 @@ class Activation // set). size_t savedFrameChain_; + // Counter incremented by JS::HideScriptedCaller and decremented by + // JS::UnhideScriptedCaller. If > 0 for the top activation, + // JS_DescribeScriptedCaller will return null instead of querying that + // activation, which should prompt the caller to consult embedding-specific + // data structures instead. + size_t hideScriptedCallerCount_; + enum Kind { Interpreter, Jit, ForkJoin }; Kind kind_; @@ -1218,6 +1225,17 @@ class Activation return savedFrameChain_ > 0; } + void hideScriptedCaller() { + hideScriptedCallerCount_++; + } + void unhideScriptedCaller() { + JS_ASSERT(hideScriptedCallerCount_ > 0); + hideScriptedCallerCount_--; + } + bool scriptedCallerIsHidden() const { + return hideScriptedCallerCount_ > 0; + } + private: Activation(const Activation &other) MOZ_DELETE; void operator=(const Activation &other) MOZ_DELETE; diff --git a/js/xpconnect/idl/xpccomponents.idl b/js/xpconnect/idl/xpccomponents.idl index 36e9f0823dd0..4f8679e2b2a1 100644 --- a/js/xpconnect/idl/xpccomponents.idl +++ b/js/xpconnect/idl/xpccomponents.idl @@ -120,7 +120,7 @@ interface ScheduledGCCallback : nsISupports /** * interface of Components.utils */ -[scriptable, uuid(8dd4680f-4f06-4760-a147-292cb307662f)] +[scriptable, uuid(e14f588b-63aa-4091-be82-a459a52f8ca6)] interface nsIXPCComponents_Utils : nsISupports { @@ -508,6 +508,17 @@ interface nsIXPCComponents_Utils : nsISupports */ nsIClassInfo getDOMClassInfo(in AString aClassName); + /** + * Gets the incument global for the execution of this function. For internal + * and testing use only. + * + * If |callback| is passed, it is invoked with the incumbent global as its + * sole argument. This allows the incumbent global to be measured in callback + * environments with no scripted frames on the stack. + */ + [implicit_jscontext] + jsval getIncumbentGlobal([optional] in jsval callback); + /** * Retrieve the last time, in microseconds since epoch, that a given * watchdog-related event occured. diff --git a/js/xpconnect/src/XPCComponents.cpp b/js/xpconnect/src/XPCComponents.cpp index 9202e226724c..9f021cd3c78a 100644 --- a/js/xpconnect/src/XPCComponents.cpp +++ b/js/xpconnect/src/XPCComponents.cpp @@ -3487,6 +3487,33 @@ nsXPCComponents_Utils::GetDOMClassInfo(const nsAString& aClassName, return NS_ERROR_NOT_AVAILABLE; } +NS_IMETHODIMP +nsXPCComponents_Utils::GetIncumbentGlobal(const Value &aCallback, + JSContext *aCx, Value *aOut) +{ + nsCOMPtr global = mozilla::dom::GetIncumbentGlobal(); + RootedValue globalVal(aCx); + + if (!global) { + globalVal = NullValue(); + } else { + // Note: We rely on the wrap call for outerization. + globalVal = ObjectValue(*global->GetGlobalJSObject()); + if (!JS_WrapValue(aCx, &globalVal)) + return NS_ERROR_FAILURE; + } + + // Invoke the callback, if passed. + if (aCallback.isObject()) { + Value ignored; + if (!JS_CallFunctionValue(aCx, nullptr, aCallback, 1, globalVal.address(), &ignored)) + return NS_ERROR_FAILURE; + } + + *aOut = globalVal; + return NS_OK; +} + NS_IMETHODIMP nsXPCComponents_Utils::GetWatchdogTimestamp(const nsAString& aCategory, PRTime *aOut) { diff --git a/js/xpconnect/tests/chrome/chrome.ini b/js/xpconnect/tests/chrome/chrome.ini index fd8a6a5a8911..c8a587797eda 100644 --- a/js/xpconnect/tests/chrome/chrome.ini +++ b/js/xpconnect/tests/chrome/chrome.ini @@ -67,6 +67,7 @@ support-files = [test_paris_weakmap_keys.xul] [test_precisegc.xul] [test_sandboxImport.xul] +[test_scriptSettings.xul] [test_weakmap_keys_preserved.xul] [test_weakmap_keys_preserved2.xul] [test_weakref.xul] diff --git a/js/xpconnect/tests/chrome/test_scriptSettings.xul b/js/xpconnect/tests/chrome/test_scriptSettings.xul new file mode 100644 index 000000000000..be3bf9a28769 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_scriptSettings.xul @@ -0,0 +1,127 @@ + + + + + + + diff --git a/layout/base/ActiveLayerTracker.cpp b/layout/base/ActiveLayerTracker.cpp index 14d25339654b..5d73f1a39a63 100644 --- a/layout/base/ActiveLayerTracker.cpp +++ b/layout/base/ActiveLayerTracker.cpp @@ -10,6 +10,8 @@ #include "nsRefreshDriver.h" #include "nsPIDOMWindow.h" #include "nsIDocument.h" +#include "nsAnimationManager.h" +#include "nsTransitionManager.h" namespace mozilla { @@ -215,6 +217,18 @@ ActiveLayerTracker::IsStyleAnimated(nsIFrame* aFrame, nsCSSProperty aProperty) if (aProperty == eCSSProperty_transform && aFrame->Preserves3D()) { return IsStyleAnimated(aFrame->GetParent(), aProperty); } + nsIContent* content = aFrame->GetContent(); + if (content) { + if (mozilla::HasAnimationOrTransition( + content, nsGkAtoms::animationsProperty, aProperty)) { + return true; + } + if (mozilla::HasAnimationOrTransition( + content, nsGkAtoms::transitionsProperty, aProperty)) { + return true; + } + } + return false; } diff --git a/layout/base/nsCSSRendering.cpp b/layout/base/nsCSSRendering.cpp index 9d5547ad01e1..08037e9d08cc 100644 --- a/layout/base/nsCSSRendering.cpp +++ b/layout/base/nsCSSRendering.cpp @@ -4324,7 +4324,7 @@ nsImageRenderer::PrepareImage() if (!mPaintServerFrame) { mImageElementSurface = nsLayoutUtils::SurfaceFromElement(property->GetReferencedElement()); - if (!mImageElementSurface.mSurface) + if (!mImageElementSurface.mSourceSurface) return false; } mIsReady = true; @@ -4403,7 +4403,7 @@ nsImageRenderer::ComputeIntrinsicSize() ToAppUnits(appUnitsPerDevPixel)); } } else { - NS_ASSERTION(mImageElementSurface.mSurface, "Surface should be ready."); + NS_ASSERTION(mImageElementSurface.mSourceSurface, "Surface should be ready."); gfxIntSize surfaceSize = mImageElementSurface.mSize; result.SetSize( nsSize(nsPresContext::CSSPixelsToAppUnits(surfaceSize.width), @@ -4597,9 +4597,9 @@ nsImageRenderer::Draw(nsPresContext* aPresContext, mFlags & FLAG_SYNC_DECODE_IMAGES ? nsSVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES : 0); } else { - NS_ASSERTION(mImageElementSurface.mSurface, "Surface should be ready."); + NS_ASSERTION(mImageElementSurface.mSourceSurface, "Surface should be ready."); nsRefPtr surfaceDrawable = - new gfxSurfaceDrawable(mImageElementSurface.mSurface, + new gfxSurfaceDrawable(mImageElementSurface.mSourceSurface, mImageElementSurface.mSize); nsLayoutUtils::DrawPixelSnapped( &aRenderingContext, surfaceDrawable, graphicsFilter, diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index 66279598b38f..b5a1495957b8 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -338,12 +338,12 @@ AddAnimationsForProperty(nsIFrame* aFrame, nsCSSProperty aProperty, animSegment->startState() = InfallibleTArray(); animSegment->endState() = InfallibleTArray(); - nsCSSValueList* list = segment->mFromValue.GetCSSValueListValue(); - AddTransformFunctions(list, styleContext, presContext, bounds, scale, + nsCSSValueSharedList* list = segment->mFromValue.GetCSSValueSharedListValue(); + AddTransformFunctions(list->mHead, styleContext, presContext, bounds, scale, animSegment->startState().get_ArrayOfTransformFunction()); - list = segment->mToValue.GetCSSValueListValue(); - AddTransformFunctions(list, styleContext, presContext, bounds, scale, + list = segment->mToValue.GetCSSValueSharedListValue(); + AddTransformFunctions(list->mHead, styleContext, presContext, bounds, scale, animSegment->endState().get_ArrayOfTransformFunction()); } else if (aProperty == eCSSProperty_opacity) { animSegment->startState() = segment->mFromValue.GetFloatValue(); @@ -4059,7 +4059,7 @@ nsDisplayTransform::GetResultingTransformMatrixInternal(const FrameTransformProp frame && frame->IsSVGTransformed(&svgTransform, &transformFromSVGParent); /* Transformed frames always have a transform, or are preserving 3d (and might still have perspective!) */ if (aProperties.mTransformList) { - result = nsStyleTransformMatrix::ReadTransforms(aProperties.mTransformList, + result = nsStyleTransformMatrix::ReadTransforms(aProperties.mTransformList->mHead, frame ? frame->StyleContext() : nullptr, frame ? frame->PresContext() : nullptr, dummy, bounds, aAppUnitsPerPixel); diff --git a/layout/base/nsDisplayList.h b/layout/base/nsDisplayList.h index eee8f08ace42..2b28efd826cf 100644 --- a/layout/base/nsDisplayList.h +++ b/layout/base/nsDisplayList.h @@ -3037,7 +3037,7 @@ public: FrameTransformProperties(const nsIFrame* aFrame, float aAppUnitsPerPixel, const nsRect* aBoundsOverride); - FrameTransformProperties(const nsCSSValueList* aTransformList, + FrameTransformProperties(nsCSSValueSharedList* aTransformList, const gfxPoint3D& aToTransformOrigin, const gfxPoint3D& aToPerspectiveOrigin, nscoord aChildPerspective) @@ -3049,7 +3049,7 @@ public: {} const nsIFrame* mFrame; - const nsCSSValueList* mTransformList; + nsRefPtr mTransformList; const gfxPoint3D mToTransformOrigin; const gfxPoint3D mToPerspectiveOrigin; nscoord mChildPerspective; diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 2345b3479f8e..208dfb981906 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -35,7 +35,6 @@ #include "nsRenderingContext.h" #include "nsIInterfaceRequestorUtils.h" #include "nsCSSRendering.h" -#include "nsCxPusher.h" #include "nsThemeConstants.h" #include "nsPIDOMWindow.h" #include "nsIDocShell.h" @@ -72,6 +71,7 @@ #include "ImageContainer.h" #include "nsComputedDOMStyle.h" #include "ActiveLayerTracker.h" +#include "mozilla/gfx/2D.h" #include "mozilla/Preferences.h" @@ -89,6 +89,7 @@ using namespace mozilla::css; using namespace mozilla::dom; using namespace mozilla::layers; using namespace mozilla::layout; +using namespace mozilla::gfx; using mozilla::image::Angle; using mozilla::image::Flip; @@ -113,6 +114,7 @@ typedef FrameMetrics::ViewID ViewID; /* static */ bool nsLayoutUtils::sFontSizeInflationForceEnabled; /* static */ bool nsLayoutUtils::sFontSizeInflationDisabledInMasterProcess; /* static */ bool nsLayoutUtils::sInvalidationDebuggingIsEnabled; +/* static */ bool nsLayoutUtils::sCSSVariablesEnabled; static ViewID sScrollIdCounter = FrameMetrics::START_SCROLL_ID; @@ -228,8 +230,8 @@ nsLayoutUtils::HasAnimationsForCompositor(nsIContent* aContent, } template -static AnimationsOrTransitions* -HasAnimationOrTransition(nsIContent* aContent, +AnimationsOrTransitions* +mozilla::HasAnimationOrTransition(nsIContent* aContent, nsIAtom* aAnimationProperty, nsCSSProperty aProperty) { @@ -245,6 +247,17 @@ HasAnimationOrTransition(nsIContent* aContent, return nullptr; } +template ElementAnimations* +mozilla::HasAnimationOrTransition(nsIContent* aContent, + nsIAtom* aAnimationProperty, + nsCSSProperty aProperty); + +template ElementTransitions* +mozilla::HasAnimationOrTransition(nsIContent* aContent, + nsIAtom* aAnimationProperty, + nsCSSProperty aProperty); + + bool nsLayoutUtils::HasAnimations(nsIContent* aContent, nsCSSProperty aProperty) @@ -270,8 +283,10 @@ GetScaleForValue(const nsStyleAnimation::Value& aValue, return gfxSize(); } - nsCSSValueList* values = aValue.GetCSSValueListValue(); - if (values->mValue.GetUnit() == eCSSUnit_None) { + nsCSSValueSharedList* list = aValue.GetCSSValueSharedListValue(); + MOZ_ASSERT(list->mHead); + + if (list->mHead->mValue.GetUnit() == eCSSUnit_None) { // There is an animation, but no actual transform yet. return gfxSize(); } @@ -279,7 +294,7 @@ GetScaleForValue(const nsStyleAnimation::Value& aValue, nsRect frameBounds = aFrame->GetRect(); bool dontCare; gfx3DMatrix transform = nsStyleTransformMatrix::ReadTransforms( - aValue.GetCSSValueListValue(), + list->mHead, aFrame->StyleContext(), aFrame->PresContext(), dontCare, frameBounds, aFrame->PresContext()->AppUnitsPerDevPixel()); @@ -4757,26 +4772,17 @@ nsLayoutUtils::IsReallyFixedPos(nsIFrame* aFrame) nsLayoutUtils::SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(nsIImageLoadingContent* aElement, - uint32_t aSurfaceFlags) + uint32_t aSurfaceFlags, + DrawTarget* aTarget) { SurfaceFromElementResult result; nsresult rv; - bool forceCopy = (aSurfaceFlags & SFE_WANT_NEW_SURFACE) != 0; bool wantImageSurface = (aSurfaceFlags & SFE_WANT_IMAGE_SURFACE) != 0; - bool premultAlpha = (aSurfaceFlags & SFE_NO_PREMULTIPLY_ALPHA) == 0; - - if (!premultAlpha) { - forceCopy = true; + if (aSurfaceFlags & SFE_NO_PREMULTIPLY_ALPHA) { wantImageSurface = true; } - // Push a null JSContext on the stack so that code that runs within - // the below code doesn't think it's being called by JS. See bug - // 604262. - nsCxPusher pusher; - pusher.PushNull(); - nsCOMPtr imgRequest; rv = aElement->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST, getter_AddRefs(imgRequest)); @@ -4824,24 +4830,23 @@ nsLayoutUtils::SurfaceFromElement(nsIImageLoadingContent* aElement, if (NS_FAILED(rv) || NS_FAILED(rv2)) return result; - if (wantImageSurface && framesurf->GetType() != gfxSurfaceTypeImage) { - forceCopy = true; - } - nsRefPtr gfxsurf = framesurf; - if (forceCopy) { - if (wantImageSurface) { - gfxsurf = new gfxImageSurface (gfxIntSize(imgWidth, imgHeight), gfxImageFormatARGB32); - } else { - gfxsurf = gfxPlatform::GetPlatform()->CreateOffscreenSurface(gfxIntSize(imgWidth, imgHeight), - GFX_CONTENT_COLOR_ALPHA); - } + if (wantImageSurface) { + IntSize size(imgWidth, imgHeight); + RefPtr output = Factory::CreateDataSourceSurface(size, FORMAT_B8G8R8A8); + RefPtr dt = Factory::CreateDrawTargetForData(BACKEND_CAIRO, + output->GetData(), + size, + output->Stride(), + FORMAT_B8G8R8A8); + RefPtr source = gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(dt, gfxsurf); - nsRefPtr ctx = new gfxContext(gfxsurf); + dt->CopySurface(source, IntRect(0, 0, imgWidth, imgHeight), IntPoint()); + dt->Flush(); - ctx->SetOperator(gfxContext::OPERATOR_SOURCE); - ctx->SetSource(framesurf); - ctx->Paint(); + result.mSourceSurface = output; + } else { + result.mSourceSurface = gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(aTarget, gfxsurf); } int32_t corsmode; @@ -4849,7 +4854,6 @@ nsLayoutUtils::SurfaceFromElement(nsIImageLoadingContent* aElement, result.mCORSUsed = (corsmode != imgIRequest::CORS_NONE); } - result.mSurface = gfxsurf; result.mSize = gfxIntSize(imgWidth, imgHeight); result.mPrincipal = principal.forget(); // no images, including SVG images, can load content from another domain. @@ -4861,66 +4865,72 @@ nsLayoutUtils::SurfaceFromElement(nsIImageLoadingContent* aElement, nsLayoutUtils::SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(HTMLImageElement *aElement, - uint32_t aSurfaceFlags) + uint32_t aSurfaceFlags, + DrawTarget* aTarget) { return SurfaceFromElement(static_cast(aElement), - aSurfaceFlags); + aSurfaceFlags, aTarget); } nsLayoutUtils::SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(HTMLCanvasElement* aElement, - uint32_t aSurfaceFlags) + uint32_t aSurfaceFlags, + DrawTarget* aTarget) { SurfaceFromElementResult result; nsresult rv; - bool forceCopy = (aSurfaceFlags & SFE_WANT_NEW_SURFACE) != 0; - bool wantImageSurface = (aSurfaceFlags & SFE_WANT_IMAGE_SURFACE) != 0; bool premultAlpha = (aSurfaceFlags & SFE_NO_PREMULTIPLY_ALPHA) == 0; - if (!premultAlpha) { - forceCopy = true; - wantImageSurface = true; - } - gfxIntSize size = aElement->GetSize(); - nsRefPtr surf; - - if (!forceCopy && aElement->CountContexts() == 1) { + if (premultAlpha && aElement->CountContexts() == 1) { nsICanvasRenderingContextInternal *srcCanvas = aElement->GetContextAtIndex(0); - rv = srcCanvas->GetThebesSurface(getter_AddRefs(surf)); - - if (NS_FAILED(rv)) - surf = nullptr; + result.mSourceSurface = srcCanvas->GetSurfaceSnapshot(); } - if (surf && wantImageSurface && surf->GetType() != gfxSurfaceTypeImage) - surf = nullptr; - - if (!surf) { - if (wantImageSurface) { - surf = new gfxImageSurface(size, gfxImageFormatARGB32); + if (!result.mSourceSurface) { + nsRefPtr ctx; + RefPtr dt; + if (premultAlpha) { + if (aTarget) { + dt = aTarget->CreateSimilarDrawTarget(IntSize(size.width, size.height), FORMAT_B8G8R8A8); + } else { + dt = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(IntSize(size.width, size.height), + FORMAT_B8G8R8A8); + } + if (!dt) { + return result; + } + ctx = new gfxContext(dt); } else { - surf = gfxPlatform::GetPlatform()->CreateOffscreenSurface(size, GFX_CONTENT_COLOR_ALPHA); + // TODO: RenderContextsExternal expects to get a gfxImageFormat + // so that it can un-premultiply. + RefPtr data = Factory::CreateDataSourceSurface(IntSize(size.width, size.height), + FORMAT_B8G8R8A8); + memset(data->GetData(), 0, data->Stride() * size.height); + result.mSourceSurface = data; + nsRefPtr image = new gfxImageSurface(data->GetData(), + gfxIntSize(size.width, size.height), + data->Stride(), + gfxImageFormatARGB32); + ctx = new gfxContext(image); } - - if (!surf) - return result; - - nsRefPtr ctx = new gfxContext(surf); // XXX shouldn't use the external interface, but maybe we can layerify this uint32_t flags = premultAlpha ? HTMLCanvasElement::RenderFlagPremultAlpha : 0; rv = aElement->RenderContextsExternal(ctx, GraphicsFilter::FILTER_NEAREST, flags); if (NS_FAILED(rv)) return result; + + if (premultAlpha) { + result.mSourceSurface = dt->Snapshot(); + } } // Ensure that any future changes to the canvas trigger proper invalidation, // in case this is being used by -moz-element() aElement->MarkContextClean(); - result.mSurface = surf; result.mSize = size; result.mPrincipal = aElement->NodePrincipal(); result.mIsWriteOnly = aElement->IsWriteOnly(); @@ -4930,16 +4940,12 @@ nsLayoutUtils::SurfaceFromElement(HTMLCanvasElement* aElement, nsLayoutUtils::SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(HTMLVideoElement* aElement, - uint32_t aSurfaceFlags) + uint32_t aSurfaceFlags, + DrawTarget* aTarget) { SurfaceFromElementResult result; - bool wantImageSurface = (aSurfaceFlags & SFE_WANT_IMAGE_SURFACE) != 0; - bool premultAlpha = (aSurfaceFlags & SFE_NO_PREMULTIPLY_ALPHA) == 0; - - if (!premultAlpha) { - wantImageSurface = true; - } + NS_WARN_IF_FALSE((aSurfaceFlags & SFE_NO_PREMULTIPLY_ALPHA) == 0, "We can't support non-premultiplied alpha for video!"); uint16_t readyState; if (NS_SUCCEEDED(aElement->GetReadyState(&readyState)) && @@ -4963,18 +4969,8 @@ nsLayoutUtils::SurfaceFromElement(HTMLVideoElement* aElement, if (!surf) return result; - if (wantImageSurface && surf->GetType() != gfxSurfaceTypeImage) { - nsRefPtr imgSurf = - new gfxImageSurface(size, gfxImageFormatARGB32); - - nsRefPtr ctx = new gfxContext(imgSurf); - ctx->SetOperator(gfxContext::OPERATOR_SOURCE); - ctx->DrawSurface(surf, size); - surf = imgSurf; - } - + result.mSourceSurface = gfxPlatform::GetPlatform()->GetSourceSurfaceForSurface(aTarget, surf); result.mCORSUsed = aElement->GetCORSMode() != CORS_NONE; - result.mSurface = surf; result.mSize = size; result.mPrincipal = principal.forget(); result.mIsWriteOnly = false; @@ -4984,18 +4980,19 @@ nsLayoutUtils::SurfaceFromElement(HTMLVideoElement* aElement, nsLayoutUtils::SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(dom::Element* aElement, - uint32_t aSurfaceFlags) + uint32_t aSurfaceFlags, + DrawTarget* aTarget) { // If it's a , we may be able to just grab its internal surface if (HTMLCanvasElement* canvas = HTMLCanvasElement::FromContentOrNull(aElement)) { - return SurfaceFromElement(canvas, aSurfaceFlags); + return SurfaceFromElement(canvas, aSurfaceFlags, aTarget); } // Maybe it's