summaryrefslogtreecommitdiffstats
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--docs/_addons/bzlink.py63
-rw-r--r--docs/_search_template/searchbox.html16
-rw-r--r--docs/_static/custom_theme.css71
-rw-r--r--docs/_static/sphinx_design.css21
-rw-r--r--docs/_static/sphinx_design.js72
-rw-r--r--docs/_templates/breadcrumbs.html18
-rw-r--r--docs/bug-mgmt/guides/bug-pipeline.rst37
-rw-r--r--docs/bug-mgmt/guides/bug-types.rst29
-rw-r--r--docs/bug-mgmt/guides/other-metadata.rst28
-rw-r--r--docs/bug-mgmt/guides/priority.rst27
-rw-r--r--docs/bug-mgmt/guides/severity.rst71
-rw-r--r--docs/bug-mgmt/guides/status-flags.rst33
-rw-r--r--docs/bug-mgmt/index.rst40
-rw-r--r--docs/bug-mgmt/policies/new-feature-triage.rst55
-rw-r--r--docs/bug-mgmt/policies/regressions-github.rst151
-rw-r--r--docs/bug-mgmt/policies/triage-bugzilla.rst276
-rw-r--r--docs/bug-mgmt/processes/accessibility-review.md72
-rw-r--r--docs/bug-mgmt/processes/doc-requests.rst39
-rw-r--r--docs/bug-mgmt/processes/fixing-security-bugs.rst217
-rw-r--r--docs/bug-mgmt/processes/labels.rst155
-rw-r--r--docs/bug-mgmt/processes/regressions.rst64
-rw-r--r--docs/bug-mgmt/processes/security-approval.rst194
-rw-r--r--docs/bug-mgmt/processes/shared-bug-queues.rst34
-rw-r--r--docs/code-quality/coding-style/about-logins-rtl.pngbin0 -> 6257 bytes
-rw-r--r--docs/code-quality/coding-style/about-protections-rtl.pngbin0 -> 2310 bytes
-rw-r--r--docs/code-quality/coding-style/coding_style_cpp.rst1150
-rw-r--r--docs/code-quality/coding-style/coding_style_general.rst18
-rw-r--r--docs/code-quality/coding-style/coding_style_java.rst68
-rw-r--r--docs/code-quality/coding-style/coding_style_js.rst147
-rw-r--r--docs/code-quality/coding-style/coding_style_python.rst71
-rw-r--r--docs/code-quality/coding-style/css_guidelines.rst572
-rw-r--r--docs/code-quality/coding-style/format_cpp_code_with_clang-format.rst272
-rw-r--r--docs/code-quality/coding-style/index.rst20
-rw-r--r--docs/code-quality/coding-style/rtl_guidelines.rst356
-rw-r--r--docs/code-quality/coding-style/svg_guidelines.rst347
-rw-r--r--docs/code-quality/coding-style/using_cxx_in_firefox_code.rst1075
-rw-r--r--docs/code-quality/index.rst185
-rw-r--r--docs/code-quality/lint/create.rst368
-rw-r--r--docs/code-quality/lint/index.rst33
-rw-r--r--docs/code-quality/lint/linters/android-format.rst31
-rw-r--r--docs/code-quality/lint/linters/black.rst36
-rw-r--r--docs/code-quality/lint/linters/clang-format.rst35
-rw-r--r--docs/code-quality/lint/linters/clippy.rst36
-rw-r--r--docs/code-quality/lint/linters/codespell.rst36
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla.rst114
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/avoid-Date-timing.rst30
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/avoid-removeChild.rst20
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/balanced-listeners.rst20
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/balanced-observers.rst20
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/consistent-if-bracing.rst23
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/environment.rst76
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/import-browser-window-globals.rst8
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/import-content-task-globals.rst14
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/import-globals-from.rst18
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/import-globals.rst5
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/import-headjs-globals.rst28
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/lazy-getter-object-name.rst25
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/mark-exported-symbols-as-used.rst23
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/mark-test-function-used.rst8
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/no-aArgs.rst22
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/no-addtask-setup.rst27
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/no-arbitrary-setTimeout.rst23
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/no-compare-against-boolean-literals.rst23
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/no-cu-reportError.rst23
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/no-define-cc-etc.rst23
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/no-redeclare-with-import-autofix.rst21
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/no-throw-cr-literal.rst38
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/no-useless-parameters.rst26
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/no-useless-removeEventListener.rst20
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/no-useless-run-test.rst6
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/prefer-boolean-length-check.rst24
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/prefer-formatValues.rst23
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-addtask-only.rst6
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-chromeutils-import-params.rst22
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-eager-module-in-lazy-getter.rst35
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-global-this.rst29
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-globalThis-modification.rst19
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-import-system-module-from-non-system.rst36
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-importGlobalProperties.rst45
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-lazy-imports-into-globals.rst36
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-mixing-eager-and-lazy.rst22
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-multiple-getters-calls.rst27
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-relative-requires.rst22
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-requires-await.rst20
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-scriptableunicodeconverter.rst13
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-some-requires.rst6
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-top-level-await.rst26
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/use-cc-etc.rst26
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/use-chromeutils-generateqi.rst33
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/use-chromeutils-import.rst24
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/use-default-preference-values.rst19
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/use-includes-instead-of-indexOf.rst21
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/use-isInstance.rst42
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/use-ownerGlobal.rst20
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/use-returnValue.rst20
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/use-services.rst21
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/use-static-import.rst21
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/valid-ci-uses.rst42
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/valid-lazy.rst55
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/valid-services-property.rst30
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/valid-services.rst24
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-mozilla/var-only-at-top-level.rst21
-rw-r--r--docs/code-quality/lint/linters/eslint-plugin-spidermonkey-js.rst18
-rw-r--r--docs/code-quality/lint/linters/eslint.rst211
-rw-r--r--docs/code-quality/lint/linters/file-perm.rst42
-rw-r--r--docs/code-quality/lint/linters/file-whitespace.rst38
-rw-r--r--docs/code-quality/lint/linters/fluent-lint.rst47
-rw-r--r--docs/code-quality/lint/linters/l10n.rst45
-rw-r--r--docs/code-quality/lint/linters/license.rst39
-rw-r--r--docs/code-quality/lint/linters/lintpref.rst32
-rw-r--r--docs/code-quality/lint/linters/mingw-capitalization.rst28
-rw-r--r--docs/code-quality/lint/linters/perfdocs.rst84
-rw-r--r--docs/code-quality/lint/linters/rejected-words.rst28
-rw-r--r--docs/code-quality/lint/linters/rstlinter.rst32
-rw-r--r--docs/code-quality/lint/linters/ruff.rst44
-rw-r--r--docs/code-quality/lint/linters/rustfmt.rst33
-rw-r--r--docs/code-quality/lint/linters/stylelint.rst77
-rw-r--r--docs/code-quality/lint/linters/trojan-source.rst34
-rw-r--r--docs/code-quality/lint/linters/yamllint.rst31
-rw-r--r--docs/code-quality/lint/mozlint.rst23
-rw-r--r--docs/code-quality/lint/usage.rst133
-rw-r--r--docs/code-quality/static-analysis/existing.rst245
-rw-r--r--docs/code-quality/static-analysis/index.rst30
-rw-r--r--docs/code-quality/static-analysis/writing-new/adding-a-check.rst107
-rw-r--r--docs/code-quality/static-analysis/writing-new/advanced-check-features.rst148
-rw-r--r--docs/code-quality/static-analysis/writing-new/clang-query.rst167
-rw-r--r--docs/code-quality/static-analysis/writing-new/documentation-expanded.pngbin0 -> 41142 bytes
-rw-r--r--docs/code-quality/static-analysis/writing-new/index.rst14
-rw-r--r--docs/code-quality/static-analysis/writing-new/matcher-cookbook.rst23
-rw-r--r--docs/code-quality/static-analysis/writing-new/narrowing-matcher.pngbin0 -> 47851 bytes
-rw-r--r--docs/code-quality/static-analysis/writing-new/narrowing-matcher.xcfbin0 -> 79407 bytes
-rw-r--r--docs/code-quality/static-analysis/writing-new/writing-matchers.rst199
-rw-r--r--docs/conf.py152
-rw-r--r--docs/config.yml115
-rw-r--r--docs/contributing/Code_Review_FAQ.rst93
-rw-r--r--docs/contributing/build/artifact_builds.rst173
-rw-r--r--docs/contributing/build/building_mobile_firefox.rst34
-rw-r--r--docs/contributing/build/supported.rst1
-rw-r--r--docs/contributing/committing_rules_and_responsibilities.rst198
-rw-r--r--docs/contributing/contributing_to_mozilla.rst63
-rw-r--r--docs/contributing/contribution_quickref.rst369
-rw-r--r--docs/contributing/debugging/capturing_minidump.rst259
-rw-r--r--docs/contributing/debugging/debugging_a_hang_on_macos.rst10
-rw-r--r--docs/contributing/debugging/debugging_a_minidump.rst202
-rw-r--r--docs/contributing/debugging/debugging_firefox_with_gdb.rst501
-rw-r--r--docs/contributing/debugging/debugging_firefox_with_lldb.rst80
-rw-r--r--docs/contributing/debugging/debugging_firefox_with_rr.rst98
-rw-r--r--docs/contributing/debugging/debugging_firefox_with_valgrind.rst177
-rw-r--r--docs/contributing/debugging/debugging_on_macos.rst359
-rw-r--r--docs/contributing/debugging/debugging_on_windows.rst330
-rw-r--r--docs/contributing/debugging/img/about-processes.pngbin0 -> 48363 bytes
-rw-r--r--docs/contributing/debugging/img/about-support.pngbin0 -> 33803 bytes
-rw-r--r--docs/contributing/debugging/img/crash-gmp.pngbin0 -> 6731 bytes
-rw-r--r--docs/contributing/debugging/img/crash-gpu.pngbin0 -> 2184 bytes
-rw-r--r--docs/contributing/debugging/img/crash-rdd.pngbin0 -> 102996 bytes
-rw-r--r--docs/contributing/debugging/img/crashlist.jpgbin0 -> 77200 bytes
-rw-r--r--docs/contributing/debugging/img/crashreporter.pngbin0 -> 10065 bytes
-rw-r--r--docs/contributing/debugging/img/process-explorer.pngbin0 -> 37316 bytes
-rw-r--r--docs/contributing/debugging/img/sdk-installer.pngbin0 -> 32390 bytes
-rw-r--r--docs/contributing/debugging/img/tabcrashed.pngbin0 -> 39540 bytes
-rw-r--r--docs/contributing/debugging/img/windbg-in-startmenu.pngbin0 -> 48528 bytes
-rw-r--r--docs/contributing/debugging/local_symbols.rst64
-rw-r--r--docs/contributing/debugging/process_dump_task_manager.rst69
-rw-r--r--docs/contributing/debugging/stacktrace_report.rst153
-rw-r--r--docs/contributing/debugging/stacktrace_windbg.rst232
-rw-r--r--docs/contributing/debugging/understanding_crash_reports.rst325
-rw-r--r--docs/contributing/directory_structure.rst565
-rw-r--r--docs/contributing/editor.rst26
-rw-r--r--docs/contributing/editors/emacs.rst121
-rw-r--r--docs/contributing/editors/others.rst41
-rw-r--r--docs/contributing/editors/vim.rst75
-rw-r--r--docs/contributing/editors/vscode.rst179
-rw-r--r--docs/contributing/engineering_show_and_tell.rst83
-rw-r--r--docs/contributing/how_to_submit_a_patch.rst245
-rw-r--r--docs/contributing/img/auto_completion.gifbin0 -> 61333 bytes
-rw-r--r--docs/contributing/img/connection_done.pngbin0 -> 34017 bytes
-rw-r--r--docs/contributing/img/diagnostic_error.gifbin0 -> 201909 bytes
-rw-r--r--docs/contributing/img/example-stack.pngbin0 -> 103888 bytes
-rw-r--r--docs/contributing/img/find_references.gifbin0 -> 142707 bytes
-rw-r--r--docs/contributing/img/format_selection.gifbin0 -> 206874 bytes
-rw-r--r--docs/contributing/img/goto_definition.gifbin0 -> 249396 bytes
-rw-r--r--docs/contributing/img/remote_explorer.pngbin0 -> 53727 bytes
-rw-r--r--docs/contributing/img/remote_explorer_add.pngbin0 -> 18579 bytes
-rw-r--r--docs/contributing/img/remote_explorer_add_wind.pngbin0 -> 25862 bytes
-rw-r--r--docs/contributing/img/rename_symbol.gifbin0 -> 194963 bytes
-rw-r--r--docs/contributing/img/type_hierarchy.gifbin0 -> 138625 bytes
-rw-r--r--docs/contributing/index.rst41
-rw-r--r--docs/contributing/pocket-guide-shipping-firefox.rst523
-rw-r--r--docs/contributing/reviewer_checklist.rst181
-rw-r--r--docs/contributing/reviews.rst145
-rw-r--r--docs/contributing/stack_quickref.rst166
-rw-r--r--docs/contributing/vcs/mercurial.rst194
-rw-r--r--docs/contributing/vcs/mercurial_bundles.rst65
-rw-r--r--docs/crash-reporting/img/default-search-results.pngbin0 -> 74498 bytes
-rw-r--r--docs/crash-reporting/img/default-search-results2.pngbin0 -> 97584 bytes
-rw-r--r--docs/crash-reporting/img/facet-search-results.pngbin0 -> 19042 bytes
-rw-r--r--docs/crash-reporting/img/facet-search-results2.pngbin0 -> 58106 bytes
-rw-r--r--docs/crash-reporting/img/facet-search-results3.pngbin0 -> 25830 bytes
-rw-r--r--docs/crash-reporting/img/narrower-search-results.pngbin0 -> 53573 bytes
-rw-r--r--docs/crash-reporting/img/super-search-form.pngbin0 -> 26165 bytes
-rw-r--r--docs/crash-reporting/img/super-search-form2.pngbin0 -> 28824 bytes
-rw-r--r--docs/crash-reporting/img/super-search-form3.pngbin0 -> 40430 bytes
-rw-r--r--docs/crash-reporting/index.rst52
-rw-r--r--docs/crash-reporting/searching_crash_reports.rst257
-rw-r--r--docs/crash-reporting/uploading_symbol.rst49
-rw-r--r--docs/gtest/index.rst312
-rw-r--r--docs/index.rst71
-rw-r--r--docs/jsdoc.json5
-rw-r--r--docs/metrics/index.md6
-rw-r--r--docs/nspr/about_nspr.rst154
-rw-r--r--docs/nspr/creating_a_cookie_log.rst65
-rw-r--r--docs/nspr/index.rst66
-rw-r--r--docs/nspr/layeredpoll.rst118
-rw-r--r--docs/nspr/listing.rst10
-rw-r--r--docs/nspr/nonblocking_io_in_nspr.rst153
-rw-r--r--docs/nspr/nonblockinglayeredio.rst94
-rw-r--r--docs/nspr/nspr_build_instructions.rst136
-rw-r--r--docs/nspr/nspr_contributor_guide.rst182
-rw-r--r--docs/nspr/nspr_poll_method.rst134
-rw-r--r--docs/nspr/nspr_release_procedure.rst50
-rw-r--r--docs/nspr/nspr_s_position_on_abrupt_thread_termination.rst88
-rw-r--r--docs/nspr/optimizing_applications_for_nspr.rst45
-rw-r--r--docs/nspr/platforms.rst145
-rw-r--r--docs/nspr/process_forking_in_nspr.rst23
-rw-r--r--docs/nspr/reference/anonymous_shared_memory.rst118
-rw-r--r--docs/nspr/reference/atomic_operations.rst32
-rw-r--r--docs/nspr/reference/cached_monitors.rst37
-rw-r--r--docs/nspr/reference/condition_variables.rst50
-rw-r--r--docs/nspr/reference/date_and_time.rst83
-rw-r--r--docs/nspr/reference/dynamic_library_linking.rst114
-rw-r--r--docs/nspr/reference/floating_point_number_to_string_conversion.rst24
-rw-r--r--docs/nspr/reference/hash_tables.rst42
-rw-r--r--docs/nspr/reference/i_o_functions.rst240
-rw-r--r--docs/nspr/reference/i_o_types.rst105
-rw-r--r--docs/nspr/reference/index.rst289
-rw-r--r--docs/nspr/reference/interval_timing.rst72
-rw-r--r--docs/nspr/reference/introduction_to_nspr.rst408
-rw-r--r--docs/nspr/reference/ipc_semaphores.rst23
-rw-r--r--docs/nspr/reference/linked_lists.rst36
-rw-r--r--docs/nspr/reference/locks.rst42
-rw-r--r--docs/nspr/reference/logging.rst116
-rw-r--r--docs/nspr/reference/long_long_(64-bit)_integers.rst32
-rw-r--r--docs/nspr/reference/memory_management_operations.rst49
-rw-r--r--docs/nspr/reference/monitors.rst63
-rw-r--r--docs/nspr/reference/named_shared_memory.rst95
-rw-r--r--docs/nspr/reference/network_addresses.rst82
-rw-r--r--docs/nspr/reference/nspr_error_handling.rst206
-rw-r--r--docs/nspr/reference/nspr_log_file.rst31
-rw-r--r--docs/nspr/reference/nspr_log_modules.rst88
-rw-r--r--docs/nspr/reference/nspr_types.rst180
-rw-r--r--docs/nspr/reference/pl_comparestrings.rst27
-rw-r--r--docs/nspr/reference/pl_comparevalues.rst27
-rw-r--r--docs/nspr/reference/pl_hashstring.rst36
-rw-r--r--docs/nspr/reference/pl_hashtableadd.rst52
-rw-r--r--docs/nspr/reference/pl_hashtabledestroy.rst32
-rw-r--r--docs/nspr/reference/pl_hashtableenumerateentries.rst46
-rw-r--r--docs/nspr/reference/pl_hashtablelookup.rst45
-rw-r--r--docs/nspr/reference/pl_hashtableremove.rst46
-rw-r--r--docs/nspr/reference/pl_newhashtable.rst67
-rw-r--r--docs/nspr/reference/pl_strcpy.rst40
-rw-r--r--docs/nspr/reference/pl_strdup.rst48
-rw-r--r--docs/nspr/reference/pl_strfree.rst21
-rw-r--r--docs/nspr/reference/pl_strlen.rst28
-rw-r--r--docs/nspr/reference/plhashallocops.rst40
-rw-r--r--docs/nspr/reference/plhashcomparator.rst41
-rw-r--r--docs/nspr/reference/plhashentry.rst36
-rw-r--r--docs/nspr/reference/plhashenumerator.rst41
-rw-r--r--docs/nspr/reference/plhashfunction.rst22
-rw-r--r--docs/nspr/reference/plhashnumber.rst29
-rw-r--r--docs/nspr/reference/plhashtable.rst21
-rw-r--r--docs/nspr/reference/pr_abort.rst21
-rw-r--r--docs/nspr/reference/pr_accept.rst65
-rw-r--r--docs/nspr/reference/pr_acceptread.rst73
-rw-r--r--docs/nspr/reference/pr_access.rst41
-rw-r--r--docs/nspr/reference/pr_append_link.rst32
-rw-r--r--docs/nspr/reference/pr_assert.rst43
-rw-r--r--docs/nspr/reference/pr_atomicadd.rst37
-rw-r--r--docs/nspr/reference/pr_atomicdecrement.rst37
-rw-r--r--docs/nspr/reference/pr_atomicincrement.rst37
-rw-r--r--docs/nspr/reference/pr_atomicset.rst42
-rw-r--r--docs/nspr/reference/pr_attachsharedmemory.rst44
-rw-r--r--docs/nspr/reference/pr_attachthread.rst76
-rw-r--r--docs/nspr/reference/pr_available.rst51
-rw-r--r--docs/nspr/reference/pr_available64.rst51
-rw-r--r--docs/nspr/reference/pr_bind.rst54
-rw-r--r--docs/nspr/reference/pr_blockclockinterrupts.rst14
-rw-r--r--docs/nspr/reference/pr_callback.rst24
-rw-r--r--docs/nspr/reference/pr_calloc.rst42
-rw-r--r--docs/nspr/reference/pr_callonce.rst35
-rw-r--r--docs/nspr/reference/pr_canceljob.rst30
-rw-r--r--docs/nspr/reference/pr_centermonitor.rst57
-rw-r--r--docs/nspr/reference/pr_cexitmonitor.rst44
-rw-r--r--docs/nspr/reference/pr_cleanup.rst39
-rw-r--r--docs/nspr/reference/pr_clearinterrupt.rst29
-rw-r--r--docs/nspr/reference/pr_clist_is_empty.rst28
-rw-r--r--docs/nspr/reference/pr_close.rst40
-rw-r--r--docs/nspr/reference/pr_closedir.rst47
-rw-r--r--docs/nspr/reference/pr_closefilemap.rst40
-rw-r--r--docs/nspr/reference/pr_closesemaphore.rst30
-rw-r--r--docs/nspr/reference/pr_closesharedmemory.rst32
-rw-r--r--docs/nspr/reference/pr_cnotify.rst43
-rw-r--r--docs/nspr/reference/pr_cnotifyall.rst43
-rw-r--r--docs/nspr/reference/pr_cnvtf.rst45
-rw-r--r--docs/nspr/reference/pr_connect.rst67
-rw-r--r--docs/nspr/reference/pr_connectcontinue.rst53
-rw-r--r--docs/nspr/reference/pr_convertipv4addrtoipv6.rst30
-rw-r--r--docs/nspr/reference/pr_createfilemap.rst66
-rw-r--r--docs/nspr/reference/pr_createiolayerstub.rst46
-rw-r--r--docs/nspr/reference/pr_createpipe.rst53
-rw-r--r--docs/nspr/reference/pr_createthread.rst79
-rw-r--r--docs/nspr/reference/pr_createthreadpool.rst43
-rw-r--r--docs/nspr/reference/pr_cwait.rst63
-rw-r--r--docs/nspr/reference/pr_delete.rst38
-rw-r--r--docs/nspr/reference/pr_delete_.rst37
-rw-r--r--docs/nspr/reference/pr_deletesemaphore.rst30
-rw-r--r--docs/nspr/reference/pr_deletesharedmemory.rst32
-rw-r--r--docs/nspr/reference/pr_destroycondvar.rst30
-rw-r--r--docs/nspr/reference/pr_destroylock.rst30
-rw-r--r--docs/nspr/reference/pr_destroymonitor.rst31
-rw-r--r--docs/nspr/reference/pr_destroypollableevent.rst33
-rw-r--r--docs/nspr/reference/pr_detachsharedmemory.rst35
-rw-r--r--docs/nspr/reference/pr_detachthread.rst57
-rw-r--r--docs/nspr/reference/pr_disableclockinterrupts.rst14
-rw-r--r--docs/nspr/reference/pr_dtoa.rst93
-rw-r--r--docs/nspr/reference/pr_entermonitor.rst41
-rw-r--r--docs/nspr/reference/pr_enumerateaddrinfo.rst58
-rw-r--r--docs/nspr/reference/pr_enumeratehostent.rst61
-rw-r--r--docs/nspr/reference/pr_exitmonitor.rst44
-rw-r--r--docs/nspr/reference/pr_explodetime.rst46
-rw-r--r--docs/nspr/reference/pr_exportfilemapasstring.rst47
-rw-r--r--docs/nspr/reference/pr_extern.rst30
-rw-r--r--docs/nspr/reference/pr_familyinet.rst23
-rw-r--r--docs/nspr/reference/pr_findsymbol.rst52
-rw-r--r--docs/nspr/reference/pr_findsymbolandlibrary.rst59
-rw-r--r--docs/nspr/reference/pr_free.rst33
-rw-r--r--docs/nspr/reference/pr_freeaddrinfo.rst32
-rw-r--r--docs/nspr/reference/pr_freeif.rst34
-rw-r--r--docs/nspr/reference/pr_freelibraryname.rst39
-rw-r--r--docs/nspr/reference/pr_getaddrinfobyname.rst48
-rw-r--r--docs/nspr/reference/pr_getcanonnamefromaddrinfo.rst33
-rw-r--r--docs/nspr/reference/pr_getconnectstatus.rst48
-rw-r--r--docs/nspr/reference/pr_getcurrentthread.rst32
-rw-r--r--docs/nspr/reference/pr_getdefaultiomethods.rst31
-rw-r--r--docs/nspr/reference/pr_getdesctype.rst58
-rw-r--r--docs/nspr/reference/pr_geterror.rst23
-rw-r--r--docs/nspr/reference/pr_geterrortext.rst32
-rw-r--r--docs/nspr/reference/pr_geterrortextlength.rst22
-rw-r--r--docs/nspr/reference/pr_getfileinfo.rst55
-rw-r--r--docs/nspr/reference/pr_getfileinfo64.rst55
-rw-r--r--docs/nspr/reference/pr_gethostbyaddr.rst57
-rw-r--r--docs/nspr/reference/pr_gethostbyname.rst48
-rw-r--r--docs/nspr/reference/pr_getidentitieslayer.rst48
-rw-r--r--docs/nspr/reference/pr_getinheritedfilemap.rst44
-rw-r--r--docs/nspr/reference/pr_getlayersidentity.rst30
-rw-r--r--docs/nspr/reference/pr_getlibraryname.rst53
-rw-r--r--docs/nspr/reference/pr_getlibrarypath.rst37
-rw-r--r--docs/nspr/reference/pr_getnameforidentity.rst41
-rw-r--r--docs/nspr/reference/pr_getopenfileinfo.rst54
-rw-r--r--docs/nspr/reference/pr_getopenfileinfo64.rst56
-rw-r--r--docs/nspr/reference/pr_getoserror.rst31
-rw-r--r--docs/nspr/reference/pr_getpeername.rst35
-rw-r--r--docs/nspr/reference/pr_getprotobyname.rst47
-rw-r--r--docs/nspr/reference/pr_getprotobynumber.rst47
-rw-r--r--docs/nspr/reference/pr_getrandomnoise.rst56
-rw-r--r--docs/nspr/reference/pr_getsocketoption.rst40
-rw-r--r--docs/nspr/reference/pr_getsockname.rst35
-rw-r--r--docs/nspr/reference/pr_getspecialfd.rst56
-rw-r--r--docs/nspr/reference/pr_getthreadpriority.rst23
-rw-r--r--docs/nspr/reference/pr_getthreadprivate.rst38
-rw-r--r--docs/nspr/reference/pr_getthreadscope.rst21
-rw-r--r--docs/nspr/reference/pr_getuniqueidentity.rst49
-rw-r--r--docs/nspr/reference/pr_gmtparameters.rst47
-rw-r--r--docs/nspr/reference/pr_htonl.rst29
-rw-r--r--docs/nspr/reference/pr_htons.rst29
-rw-r--r--docs/nspr/reference/pr_implement.rst28
-rw-r--r--docs/nspr/reference/pr_implodetime.rst36
-rw-r--r--docs/nspr/reference/pr_importfilemapfromstring.rst39
-rw-r--r--docs/nspr/reference/pr_importtcpsocket.rst70
-rw-r--r--docs/nspr/reference/pr_init.rst44
-rw-r--r--docs/nspr/reference/pr_init_clist.rst27
-rw-r--r--docs/nspr/reference/pr_init_static_clist.rst32
-rw-r--r--docs/nspr/reference/pr_initialize.rst63
-rw-r--r--docs/nspr/reference/pr_initialized.rst23
-rw-r--r--docs/nspr/reference/pr_initializenetaddr.rst80
-rw-r--r--docs/nspr/reference/pr_insert_after.rst32
-rw-r--r--docs/nspr/reference/pr_insert_before.rst32
-rw-r--r--docs/nspr/reference/pr_insert_link.rst32
-rw-r--r--docs/nspr/reference/pr_interrupt.rst71
-rw-r--r--docs/nspr/reference/pr_intervalnow.rst50
-rw-r--r--docs/nspr/reference/pr_intervaltomicroseconds.rst34
-rw-r--r--docs/nspr/reference/pr_intervaltomilliseconds.rst34
-rw-r--r--docs/nspr/reference/pr_intervaltoseconds.rst33
-rw-r--r--docs/nspr/reference/pr_joinjob.rst30
-rw-r--r--docs/nspr/reference/pr_jointhread.rst57
-rw-r--r--docs/nspr/reference/pr_jointhreadpool.rst31
-rw-r--r--docs/nspr/reference/pr_list_head.rst33
-rw-r--r--docs/nspr/reference/pr_list_tail.rst33
-rw-r--r--docs/nspr/reference/pr_listen.rst48
-rw-r--r--docs/nspr/reference/pr_loadlibrary.rst46
-rw-r--r--docs/nspr/reference/pr_localtimeparameters.rst39
-rw-r--r--docs/nspr/reference/pr_lock.rst42
-rw-r--r--docs/nspr/reference/pr_malloc.rst35
-rw-r--r--docs/nspr/reference/pr_memmap.rst50
-rw-r--r--docs/nspr/reference/pr_microsecondstointerval.rst30
-rw-r--r--docs/nspr/reference/pr_millisecondstointerval.rst30
-rw-r--r--docs/nspr/reference/pr_mkdir.rst67
-rw-r--r--docs/nspr/reference/pr_msec_per_sec.rst16
-rw-r--r--docs/nspr/reference/pr_name.rst18
-rw-r--r--docs/nspr/reference/pr_netaddrtostring.rst50
-rw-r--r--docs/nspr/reference/pr_netdb_buf_size.rst20
-rw-r--r--docs/nspr/reference/pr_new.rst36
-rw-r--r--docs/nspr/reference/pr_newcondvar.rst34
-rw-r--r--docs/nspr/reference/pr_newlock.rst30
-rw-r--r--docs/nspr/reference/pr_newmonitor.rst31
-rw-r--r--docs/nspr/reference/pr_newpollableevent.rst24
-rw-r--r--docs/nspr/reference/pr_newprocessattr.rst39
-rw-r--r--docs/nspr/reference/pr_newtcpsocket.rst56
-rw-r--r--docs/nspr/reference/pr_newthreadprivateindex.rst65
-rw-r--r--docs/nspr/reference/pr_newudpsocket.rst46
-rw-r--r--docs/nspr/reference/pr_newzap.rst38
-rw-r--r--docs/nspr/reference/pr_next_link.rst35
-rw-r--r--docs/nspr/reference/pr_normalizetime.rst62
-rw-r--r--docs/nspr/reference/pr_notify.rst48
-rw-r--r--docs/nspr/reference/pr_notifyall.rst46
-rw-r--r--docs/nspr/reference/pr_notifyallcondvar.rst35
-rw-r--r--docs/nspr/reference/pr_notifycondvar.rst49
-rw-r--r--docs/nspr/reference/pr_now.rst38
-rw-r--r--docs/nspr/reference/pr_nsec_per_msec.rst16
-rw-r--r--docs/nspr/reference/pr_nsec_per_sec.rst16
-rw-r--r--docs/nspr/reference/pr_ntohl.rst29
-rw-r--r--docs/nspr/reference/pr_ntohs.rst29
-rw-r--r--docs/nspr/reference/pr_open.rst117
-rw-r--r--docs/nspr/reference/pr_openanonfilemap.rst51
-rw-r--r--docs/nspr/reference/pr_opendir.rst41
-rw-r--r--docs/nspr/reference/pr_opensemaphore.rst58
-rw-r--r--docs/nspr/reference/pr_opensharedmemory.rst68
-rw-r--r--docs/nspr/reference/pr_opentcpsocket.rst59
-rw-r--r--docs/nspr/reference/pr_openudpsocket.rst49
-rw-r--r--docs/nspr/reference/pr_poll.rst110
-rw-r--r--docs/nspr/reference/pr_popiolayer.rst53
-rw-r--r--docs/nspr/reference/pr_postsemaphore.rst30
-rw-r--r--docs/nspr/reference/pr_prev_link.rst35
-rw-r--r--docs/nspr/reference/pr_processattrsetinheritablefilemap.rst53
-rw-r--r--docs/nspr/reference/pr_processexit.rst23
-rw-r--r--docs/nspr/reference/pr_pushiolayer.rst87
-rw-r--r--docs/nspr/reference/pr_queuejob.rst43
-rw-r--r--docs/nspr/reference/pr_queuejob_connect.rst49
-rw-r--r--docs/nspr/reference/pr_queuejob_read.rst46
-rw-r--r--docs/nspr/reference/pr_queuejob_timer.rst49
-rw-r--r--docs/nspr/reference/pr_queuejob_write.rst46
-rw-r--r--docs/nspr/reference/pr_queuejobaccept.rst46
-rw-r--r--docs/nspr/reference/pr_read.rst50
-rw-r--r--docs/nspr/reference/pr_readdir.rst93
-rw-r--r--docs/nspr/reference/pr_realloc.rst43
-rw-r--r--docs/nspr/reference/pr_recv.rst56
-rw-r--r--docs/nspr/reference/pr_recvfrom.rst62
-rw-r--r--docs/nspr/reference/pr_remove_and_init_link.rst29
-rw-r--r--docs/nspr/reference/pr_remove_link.rst27
-rw-r--r--docs/nspr/reference/pr_rename.rst45
-rw-r--r--docs/nspr/reference/pr_rmdir.rst46
-rw-r--r--docs/nspr/reference/pr_secondstointerval.rst30
-rw-r--r--docs/nspr/reference/pr_seek.rst85
-rw-r--r--docs/nspr/reference/pr_seek64.rst73
-rw-r--r--docs/nspr/reference/pr_send.rst56
-rw-r--r--docs/nspr/reference/pr_sendto.rst58
-rw-r--r--docs/nspr/reference/pr_setconcurrency.rst42
-rw-r--r--docs/nspr/reference/pr_seterror.rst35
-rw-r--r--docs/nspr/reference/pr_seterrortext.rst43
-rw-r--r--docs/nspr/reference/pr_setlibrarypath.rst45
-rw-r--r--docs/nspr/reference/pr_setpollableevent.rst32
-rw-r--r--docs/nspr/reference/pr_setsocketoption.rst45
-rw-r--r--docs/nspr/reference/pr_setthreadpriority.rst37
-rw-r--r--docs/nspr/reference/pr_setthreadprivate.rst56
-rw-r--r--docs/nspr/reference/pr_shutdown.rst57
-rw-r--r--docs/nspr/reference/pr_shutdownthreadpool.rst30
-rw-r--r--docs/nspr/reference/pr_sleep.rst52
-rw-r--r--docs/nspr/reference/pr_static_assert.rst46
-rw-r--r--docs/nspr/reference/pr_stringtonetaddr.rst48
-rw-r--r--docs/nspr/reference/pr_strtod.rst50
-rw-r--r--docs/nspr/reference/pr_sync.rst40
-rw-r--r--docs/nspr/reference/pr_tickspersecond.rst36
-rw-r--r--docs/nspr/reference/pr_transmitfile.rst76
-rw-r--r--docs/nspr/reference/pr_unblockclockinterrupts.rst14
-rw-r--r--docs/nspr/reference/pr_unloadlibrary.rst41
-rw-r--r--docs/nspr/reference/pr_unlock.rst43
-rw-r--r--docs/nspr/reference/pr_unmap.rst45
-rw-r--r--docs/nspr/reference/pr_usec_per_msec.rst16
-rw-r--r--docs/nspr/reference/pr_usec_per_sec.rst16
-rw-r--r--docs/nspr/reference/pr_version.rst19
-rw-r--r--docs/nspr/reference/pr_versioncheck.rst50
-rw-r--r--docs/nspr/reference/pr_wait.rst82
-rw-r--r--docs/nspr/reference/pr_waitcondvar.rst68
-rw-r--r--docs/nspr/reference/pr_waitforpollableevent.rst33
-rw-r--r--docs/nspr/reference/pr_waitsemaphore.rst42
-rw-r--r--docs/nspr/reference/pr_write.rst50
-rw-r--r--docs/nspr/reference/pr_writev.rst79
-rw-r--r--docs/nspr/reference/praccesshow.rst17
-rw-r--r--docs/nspr/reference/prbool.rst27
-rw-r--r--docs/nspr/reference/prcalloncefn.rst22
-rw-r--r--docs/nspr/reference/prcalloncetype.rst41
-rw-r--r--docs/nspr/reference/prclist.rst27
-rw-r--r--docs/nspr/reference/prcondvar.rst20
-rw-r--r--docs/nspr/reference/prdescidentity.rst36
-rw-r--r--docs/nspr/reference/prdir.rst26
-rw-r--r--docs/nspr/reference/prerrorcode.rst30
-rw-r--r--docs/nspr/reference/prexplodedtime.rst70
-rw-r--r--docs/nspr/reference/prfiledesc.rst53
-rw-r--r--docs/nspr/reference/prfileinfo.rst49
-rw-r--r--docs/nspr/reference/prfileinfo64.rst47
-rw-r--r--docs/nspr/reference/prfilemap.rst27
-rw-r--r--docs/nspr/reference/prfileprivate.rst24
-rw-r--r--docs/nspr/reference/prfiletype.rst34
-rw-r--r--docs/nspr/reference/prfloat64.rst15
-rw-r--r--docs/nspr/reference/prhostent.rst69
-rw-r--r--docs/nspr/reference/print16.rst14
-rw-r--r--docs/nspr/reference/print32.rst22
-rw-r--r--docs/nspr/reference/print64.rst22
-rw-r--r--docs/nspr/reference/print8.rst14
-rw-r--r--docs/nspr/reference/printervaltime.rst72
-rw-r--r--docs/nspr/reference/printn.rst17
-rw-r--r--docs/nspr/reference/priomethods.rst132
-rw-r--r--docs/nspr/reference/pripv6addr.rst26
-rw-r--r--docs/nspr/reference/prjob.rst10
-rw-r--r--docs/nspr/reference/prjobfn.rst12
-rw-r--r--docs/nspr/reference/prjobiodesc.rst16
-rw-r--r--docs/nspr/reference/prlibrary.rst22
-rw-r--r--docs/nspr/reference/prlinger.rst56
-rw-r--r--docs/nspr/reference/prlock.rst22
-rw-r--r--docs/nspr/reference/prlogmoduleinfo.rst23
-rw-r--r--docs/nspr/reference/prlogmodulelevel.rst26
-rw-r--r--docs/nspr/reference/prmcastrequest.rst40
-rw-r--r--docs/nspr/reference/prmonitor.rst15
-rw-r--r--docs/nspr/reference/prnetaddr.rst85
-rw-r--r--docs/nspr/reference/process_initialization.rst63
-rw-r--r--docs/nspr/reference/process_management_and_interprocess_communication.rst64
-rw-r--r--docs/nspr/reference/prpackedbool.rst20
-rw-r--r--docs/nspr/reference/prprimordialfn.rst19
-rw-r--r--docs/nspr/reference/prprocess.rst20
-rw-r--r--docs/nspr/reference/prprocessattr.rst23
-rw-r--r--docs/nspr/reference/prprotoent.rst37
-rw-r--r--docs/nspr/reference/prptrdiff.rst14
-rw-r--r--docs/nspr/reference/prseekwhence.rst35
-rw-r--r--docs/nspr/reference/prsize.rst15
-rw-r--r--docs/nspr/reference/prsocketoptiondata.rst83
-rw-r--r--docs/nspr/reference/prsockoption.rst79
-rw-r--r--docs/nspr/reference/prstaticlinktable.rst22
-rw-r--r--docs/nspr/reference/prstatus.rst14
-rw-r--r--docs/nspr/reference/prthread.rst26
-rw-r--r--docs/nspr/reference/prthreadpool.rst10
-rw-r--r--docs/nspr/reference/prthreadpriority.rst62
-rw-r--r--docs/nspr/reference/prthreadprivatedtor.rst24
-rw-r--r--docs/nspr/reference/prthreadscope.rst56
-rw-r--r--docs/nspr/reference/prthreadstack.rst31
-rw-r--r--docs/nspr/reference/prthreadstate.rst54
-rw-r--r--docs/nspr/reference/prthreadtype.rst40
-rw-r--r--docs/nspr/reference/prtime.rst33
-rw-r--r--docs/nspr/reference/prtimeparameters.rst54
-rw-r--r--docs/nspr/reference/prtimeparamfn.rst24
-rw-r--r--docs/nspr/reference/pruint16.rst14
-rw-r--r--docs/nspr/reference/pruint32.rst22
-rw-r--r--docs/nspr/reference/pruint64.rst22
-rw-r--r--docs/nspr/reference/pruint8.rst15
-rw-r--r--docs/nspr/reference/pruintn.rst17
-rw-r--r--docs/nspr/reference/prunichar.rst18
-rw-r--r--docs/nspr/reference/pruptrdiff.rst14
-rw-r--r--docs/nspr/reference/random_number_generator.rst12
-rw-r--r--docs/nspr/reference/string_operations.rst11
-rw-r--r--docs/nspr/reference/thread_pools.rst41
-rw-r--r--docs/nspr/reference/thread_synchronization_sample.rst2
-rw-r--r--docs/nspr/reference/threads.rst129
-rw-r--r--docs/nspr/running_nspr_tests.rst95
-rw-r--r--docs/nspr/using_io_timeouts_and_interrupts_on_nt.rst131
-rw-r--r--docs/performance/Benchmarking.md98
-rw-r--r--docs/performance/GPU_performance.md42
-rw-r--r--docs/performance/activity_monitor_and_top.md165
-rw-r--r--docs/performance/automated_performance_testing_and_sheriffing.md24
-rw-r--r--docs/performance/bestpractices.md578
-rw-r--r--docs/performance/build_metrics/build_metrics.md31
-rw-r--r--docs/performance/dtrace.md49
-rw-r--r--docs/performance/img/ActMon-Energy.pngbin0 -> 148867 bytes
-rw-r--r--docs/performance/img/EJCrt4N.pngbin0 -> 331906 bytes
-rw-r--r--docs/performance/img/PerfDotHTMLRedLines.pngbin0 -> 8383 bytes
-rw-r--r--docs/performance/img/annotation.pngbin0 -> 150000 bytes
-rw-r--r--docs/performance/img/battery-status-menu.pngbin0 -> 26645 bytes
-rw-r--r--docs/performance/img/dominators-1.pngbin0 -> 28248 bytes
-rw-r--r--docs/performance/img/dominators-10.pngbin0 -> 22011 bytes
-rw-r--r--docs/performance/img/dominators-2.pngbin0 -> 29454 bytes
-rw-r--r--docs/performance/img/dominators-3.pngbin0 -> 33064 bytes
-rw-r--r--docs/performance/img/dominators-4.pngbin0 -> 40001 bytes
-rw-r--r--docs/performance/img/dominators-5.pngbin0 -> 31844 bytes
-rw-r--r--docs/performance/img/dominators-6.pngbin0 -> 56667 bytes
-rw-r--r--docs/performance/img/dominators-7.pngbin0 -> 26682 bytes
-rw-r--r--docs/performance/img/dominators-8.pngbin0 -> 26678 bytes
-rw-r--r--docs/performance/img/dominators-9.pngbin0 -> 45509 bytes
-rw-r--r--docs/performance/img/memory-1-small.pngbin0 -> 12408 bytes
-rw-r--r--docs/performance/img/memory-2-small.pngbin0 -> 20870 bytes
-rw-r--r--docs/performance/img/memory-3-small.pngbin0 -> 20962 bytes
-rw-r--r--docs/performance/img/memory-4-small.pngbin0 -> 20940 bytes
-rw-r--r--docs/performance/img/memory-5-small.pngbin0 -> 21140 bytes
-rw-r--r--docs/performance/img/memory-6-small.pngbin0 -> 20922 bytes
-rw-r--r--docs/performance/img/memory-7-small.pngbin0 -> 20555 bytes
-rw-r--r--docs/performance/img/memory-graph-dominator-multiple-references.svg4
-rw-r--r--docs/performance/img/memory-graph-dominators.svg4
-rw-r--r--docs/performance/img/memory-graph-immediate-dominator.svg4
-rw-r--r--docs/performance/img/memory-graph-unreachable.svg4
-rw-r--r--docs/performance/img/memory-graph.svg4
-rw-r--r--docs/performance/img/memory-tool-aggregate-view.pngbin0 -> 55175 bytes
-rw-r--r--docs/performance/img/memory-tool-call-stack-expanded.pngbin0 -> 69021 bytes
-rw-r--r--docs/performance/img/memory-tool-call-stack.pngbin0 -> 37960 bytes
-rw-r--r--docs/performance/img/memory-tool-in-group-icon.pngbin0 -> 6376 bytes
-rw-r--r--docs/performance/img/memory-tool-in-group-retaining-paths.pngbin0 -> 47488 bytes
-rw-r--r--docs/performance/img/memory-tool-in-group.pngbin0 -> 32277 bytes
-rw-r--r--docs/performance/img/memory-tool-inverted-call-stack.pngbin0 -> 42582 bytes
-rw-r--r--docs/performance/img/memory-tool-switch-view.pngbin0 -> 26038 bytes
-rw-r--r--docs/performance/img/monsters.svg4
-rw-r--r--docs/performance/img/pid.pngbin0 -> 43742 bytes
-rw-r--r--docs/performance/img/power-planes.jpgbin0 -> 85483 bytes
-rw-r--r--docs/performance/img/rendering.pngbin0 -> 103379 bytes
-rw-r--r--docs/performance/img/reportingperf1.pngbin0 -> 10237 bytes
-rw-r--r--docs/performance/img/reportingperf2.pngbin0 -> 27651 bytes
-rw-r--r--docs/performance/img/reportingperf3.pngbin0 -> 20182 bytes
-rw-r--r--docs/performance/img/treemap-bbc.pngbin0 -> 48965 bytes
-rw-r--r--docs/performance/img/treemap-domnodes.pngbin0 -> 10998 bytes
-rw-r--r--docs/performance/img/treemap-monsters.pngbin0 -> 20713 bytes
-rw-r--r--docs/performance/index.md53
-rw-r--r--docs/performance/intel_power_gadget.md56
-rw-r--r--docs/performance/jit_profiling_with_perf.md119
-rw-r--r--docs/performance/memory/DOM_allocation_example.md57
-rw-r--r--docs/performance/memory/about_colon_memory.md274
-rw-r--r--docs/performance/memory/aggregate_view.md198
-rw-r--r--docs/performance/memory/awsy.md22
-rw-r--r--docs/performance/memory/basic_operations.md82
-rw-r--r--docs/performance/memory/bloatview.md245
-rw-r--r--docs/performance/memory/dmd.md489
-rw-r--r--docs/performance/memory/dominators.md90
-rw-r--r--docs/performance/memory/dominators_view.md221
-rw-r--r--docs/performance/memory/gc_and_cc_logs.md109
-rw-r--r--docs/performance/memory/heap_scan_mode.md313
-rw-r--r--docs/performance/memory/leak_gauge.md45
-rw-r--r--docs/performance/memory/leak_hunting_strategies_and_tips.md219
-rw-r--r--docs/performance/memory/memory.md64
-rw-r--r--docs/performance/memory/monster_example.md79
-rw-r--r--docs/performance/memory/refcount_tracing_and_balancing.md235
-rw-r--r--docs/performance/memory/tree_map_view.md62
-rw-r--r--docs/performance/perf.md57
-rw-r--r--docs/performance/perfstats.md30
-rw-r--r--docs/performance/platform_microbenchmarks/platform_microbenchmarks.md21
-rw-r--r--docs/performance/power_profiling_overview.md326
-rw-r--r--docs/performance/powermetrics.md167
-rw-r--r--docs/performance/profiling_with_concurrency_visualizer.md5
-rw-r--r--docs/performance/profiling_with_instruments.md110
-rw-r--r--docs/performance/profiling_with_xperf.md180
-rw-r--r--docs/performance/profiling_with_zoom.md5
-rw-r--r--docs/performance/reporting_a_performance_problem.md94
-rw-r--r--docs/performance/scroll-linked_effects.md177
-rw-r--r--docs/performance/sorting_algorithms_comparison.md52
-rw-r--r--docs/performance/timerfirings_logging.md136
-rw-r--r--docs/performance/tools_power_rapl.md113
-rw-r--r--docs/performance/turbostat.md50
-rw-r--r--docs/setup/building_with_debug_symbols.rst61
-rw-r--r--docs/setup/configuring_build_options.rst404
-rw-r--r--docs/setup/contributing_code.rst181
-rw-r--r--docs/setup/index.rst25
-rw-r--r--docs/setup/linux_32bit_build_on_64bit_OS.rst37
-rw-r--r--docs/setup/linux_build.rst151
-rw-r--r--docs/setup/macos_build.rst122
-rw-r--r--docs/setup/windows_build.rst181
-rw-r--r--docs/testing-rust-code/index.md123
-rw-r--r--docs/update-infrastructure/index.md36
-rw-r--r--docs/writing-rust-code/basics.md84
-rw-r--r--docs/writing-rust-code/cpp-interop.md240
-rw-r--r--docs/writing-rust-code/index.md16
-rw-r--r--docs/writing-rust-code/uniffi.md70
-rw-r--r--docs/writing-rust-code/update-policy.md150
-rw-r--r--docs/writing-rust-code/xpcom.md120
-rw-r--r--docshell/base/BaseHistory.cpp246
-rw-r--r--docshell/base/BaseHistory.h85
-rw-r--r--docshell/base/BrowsingContext.cpp3832
-rw-r--r--docshell/base/BrowsingContext.h1458
-rw-r--r--docshell/base/BrowsingContextGroup.cpp579
-rw-r--r--docshell/base/BrowsingContextGroup.h318
-rw-r--r--docshell/base/BrowsingContextWebProgress.cpp432
-rw-r--r--docshell/base/BrowsingContextWebProgress.h98
-rw-r--r--docshell/base/CanonicalBrowsingContext.cpp3069
-rw-r--r--docshell/base/CanonicalBrowsingContext.h601
-rw-r--r--docshell/base/ChildProcessChannelListener.cpp61
-rw-r--r--docshell/base/ChildProcessChannelListener.h54
-rw-r--r--docshell/base/IHistory.h169
-rw-r--r--docshell/base/LoadContext.cpp236
-rw-r--r--docshell/base/LoadContext.h68
-rw-r--r--docshell/base/SerializedLoadContext.cpp87
-rw-r--r--docshell/base/SerializedLoadContext.h96
-rw-r--r--docshell/base/SyncedContext.h402
-rw-r--r--docshell/base/SyncedContextInlines.h358
-rw-r--r--docshell/base/URIFixup.sys.mjs1303
-rw-r--r--docshell/base/WindowContext.cpp662
-rw-r--r--docshell/base/WindowContext.h399
-rw-r--r--docshell/base/crashtests/1257730-1.html25
-rw-r--r--docshell/base/crashtests/1331295.html25
-rw-r--r--docshell/base/crashtests/1341657.html18
-rw-r--r--docshell/base/crashtests/1584467.html12
-rw-r--r--docshell/base/crashtests/1614211-1.html15
-rw-r--r--docshell/base/crashtests/1617315-1.html8
-rw-r--r--docshell/base/crashtests/1667491.html16
-rw-r--r--docshell/base/crashtests/1667491_1.html21
-rw-r--r--docshell/base/crashtests/1672873.html6
-rw-r--r--docshell/base/crashtests/1690169-1.html11
-rw-r--r--docshell/base/crashtests/1753136.html2
-rw-r--r--docshell/base/crashtests/1804803.html13
-rw-r--r--docshell/base/crashtests/1804803.sjs18
-rw-r--r--docshell/base/crashtests/369126-1.html16
-rw-r--r--docshell/base/crashtests/40929-1-inner.html14
-rw-r--r--docshell/base/crashtests/40929-1.html6
-rw-r--r--docshell/base/crashtests/430124-1.html5
-rw-r--r--docshell/base/crashtests/430628-1.html8
-rw-r--r--docshell/base/crashtests/432114-1.html8
-rw-r--r--docshell/base/crashtests/432114-2.html21
-rw-r--r--docshell/base/crashtests/436900-1-inner.html21
-rw-r--r--docshell/base/crashtests/436900-1.html8
-rw-r--r--docshell/base/crashtests/436900-2-inner.html21
-rw-r--r--docshell/base/crashtests/436900-2.html8
-rw-r--r--docshell/base/crashtests/443655.html15
-rw-r--r--docshell/base/crashtests/500328-1.html17
-rw-r--r--docshell/base/crashtests/514779-1.xhtml9
-rw-r--r--docshell/base/crashtests/614499-1.html20
-rw-r--r--docshell/base/crashtests/678872-1.html36
-rw-r--r--docshell/base/crashtests/914521.html32
-rw-r--r--docshell/base/crashtests/crashtests.list25
-rw-r--r--docshell/base/crashtests/file_432114-2.xhtml1
-rw-r--r--docshell/base/moz.build130
-rw-r--r--docshell/base/nsAboutRedirector.cpp299
-rw-r--r--docshell/base/nsAboutRedirector.h26
-rw-r--r--docshell/base/nsCTooltipTextProvider.h15
-rw-r--r--docshell/base/nsDSURIContentListener.cpp297
-rw-r--r--docshell/base/nsDSURIContentListener.h100
-rw-r--r--docshell/base/nsDocShell.cpp13877
-rw-r--r--docshell/base/nsDocShell.h1388
-rw-r--r--docshell/base/nsDocShellEditorData.cpp139
-rw-r--r--docshell/base/nsDocShellEditorData.h66
-rw-r--r--docshell/base/nsDocShellEnumerator.cpp85
-rw-r--r--docshell/base/nsDocShellEnumerator.h39
-rw-r--r--docshell/base/nsDocShellLoadState.cpp1266
-rw-r--r--docshell/base/nsDocShellLoadState.h573
-rw-r--r--docshell/base/nsDocShellLoadTypes.h205
-rw-r--r--docshell/base/nsDocShellTelemetryUtils.cpp206
-rw-r--r--docshell/base/nsDocShellTelemetryUtils.h22
-rw-r--r--docshell/base/nsDocShellTreeOwner.cpp1340
-rw-r--r--docshell/base/nsDocShellTreeOwner.h111
-rw-r--r--docshell/base/nsIContentViewer.idl318
-rw-r--r--docshell/base/nsIContentViewerEdit.idl36
-rw-r--r--docshell/base/nsIDocShell.idl796
-rw-r--r--docshell/base/nsIDocShellTreeItem.idl171
-rw-r--r--docshell/base/nsIDocShellTreeOwner.idl113
-rw-r--r--docshell/base/nsIDocumentLoaderFactory.idl39
-rw-r--r--docshell/base/nsILoadContext.idl148
-rw-r--r--docshell/base/nsILoadURIDelegate.idl35
-rw-r--r--docshell/base/nsIPrivacyTransitionObserver.idl11
-rw-r--r--docshell/base/nsIReflowObserver.idl31
-rw-r--r--docshell/base/nsIRefreshURI.idl52
-rw-r--r--docshell/base/nsIScrollObserver.h45
-rw-r--r--docshell/base/nsITooltipListener.idl44
-rw-r--r--docshell/base/nsITooltipTextProvider.idl44
-rw-r--r--docshell/base/nsIURIFixup.idl205
-rw-r--r--docshell/base/nsIWebNavigation.idl415
-rw-r--r--docshell/base/nsIWebNavigationInfo.idl55
-rw-r--r--docshell/base/nsIWebPageDescriptor.idl30
-rw-r--r--docshell/base/nsPingListener.cpp345
-rw-r--r--docshell/base/nsPingListener.h48
-rw-r--r--docshell/base/nsRefreshTimer.cpp49
-rw-r--r--docshell/base/nsRefreshTimer.h39
-rw-r--r--docshell/base/nsWebNavigationInfo.cpp64
-rw-r--r--docshell/base/nsWebNavigationInfo.h34
-rw-r--r--docshell/base/timeline/AbstractTimelineMarker.cpp72
-rw-r--r--docshell/base/timeline/AbstractTimelineMarker.h71
-rw-r--r--docshell/base/timeline/AutoGlobalTimelineMarker.cpp39
-rw-r--r--docshell/base/timeline/AutoGlobalTimelineMarker.h48
-rw-r--r--docshell/base/timeline/AutoRestyleTimelineMarker.cpp51
-rw-r--r--docshell/base/timeline/AutoRestyleTimelineMarker.h30
-rw-r--r--docshell/base/timeline/AutoTimelineMarker.cpp48
-rw-r--r--docshell/base/timeline/AutoTimelineMarker.h46
-rw-r--r--docshell/base/timeline/CompositeTimelineMarker.h31
-rw-r--r--docshell/base/timeline/ConsoleTimelineMarker.h53
-rw-r--r--docshell/base/timeline/DocLoadingTimelineMarker.h38
-rw-r--r--docshell/base/timeline/EventTimelineMarker.h40
-rw-r--r--docshell/base/timeline/JavascriptTimelineMarker.h96
-rw-r--r--docshell/base/timeline/LayerTimelineMarker.h47
-rw-r--r--docshell/base/timeline/MarkersStorage.h40
-rw-r--r--docshell/base/timeline/MessagePortTimelineMarker.h46
-rw-r--r--docshell/base/timeline/ObservedDocShell.cpp171
-rw-r--r--docshell/base/timeline/ObservedDocShell.h55
-rw-r--r--docshell/base/timeline/RestyleTimelineMarker.h37
-rw-r--r--docshell/base/timeline/TimelineConsumers.cpp202
-rw-r--r--docshell/base/timeline/TimelineConsumers.h113
-rw-r--r--docshell/base/timeline/TimelineMarker.cpp66
-rw-r--r--docshell/base/timeline/TimelineMarker.h47
-rw-r--r--docshell/base/timeline/TimelineMarkerEnums.h18
-rw-r--r--docshell/base/timeline/TimestampTimelineMarker.h36
-rw-r--r--docshell/base/timeline/WorkerTimelineMarker.h44
-rw-r--r--docshell/base/timeline/moz.build44
-rw-r--r--docshell/base/timeline/readme.md97
-rw-r--r--docshell/build/components.conf192
-rw-r--r--docshell/build/moz.build25
-rw-r--r--docshell/build/nsDocShellCID.h59
-rw-r--r--docshell/build/nsDocShellModule.cpp25
-rw-r--r--docshell/build/nsDocShellModule.h20
-rw-r--r--docshell/moz.build48
-rw-r--r--docshell/shistory/ChildSHistory.cpp294
-rw-r--r--docshell/shistory/ChildSHistory.h149
-rw-r--r--docshell/shistory/SessionHistoryEntry.cpp1828
-rw-r--r--docshell/shistory/SessionHistoryEntry.h510
-rw-r--r--docshell/shistory/moz.build42
-rw-r--r--docshell/shistory/nsIBFCacheEntry.idl16
-rw-r--r--docshell/shistory/nsISHEntry.idl476
-rw-r--r--docshell/shistory/nsISHistory.idl291
-rw-r--r--docshell/shistory/nsISHistoryListener.idl88
-rw-r--r--docshell/shistory/nsSHEntry.cpp1131
-rw-r--r--docshell/shistory/nsSHEntry.h72
-rw-r--r--docshell/shistory/nsSHEntryShared.cpp343
-rw-r--r--docshell/shistory/nsSHEntryShared.h219
-rw-r--r--docshell/shistory/nsSHistory.cpp2410
-rw-r--r--docshell/shistory/nsSHistory.h343
-rw-r--r--docshell/test/browser/Bug1622420Child.sys.mjs9
-rw-r--r--docshell/test/browser/Bug422543Child.sys.mjs98
-rw-r--r--docshell/test/browser/browser.ini237
-rw-r--r--docshell/test/browser/browser_alternate_fixup_middle_click_link.js59
-rw-r--r--docshell/test/browser/browser_backforward_restore_scroll.js54
-rw-r--r--docshell/test/browser/browser_backforward_userinteraction.js374
-rw-r--r--docshell/test/browser/browser_backforward_userinteraction_about.js67
-rw-r--r--docshell/test/browser/browser_backforward_userinteraction_systemprincipal.js112
-rw-r--r--docshell/test/browser/browser_badCertDomainFixup.js92
-rw-r--r--docshell/test/browser/browser_bfcache_copycommand.js98
-rw-r--r--docshell/test/browser/browser_browsingContext-01.js180
-rw-r--r--docshell/test/browser/browser_browsingContext-02.js235
-rw-r--r--docshell/test/browser/browser_browsingContext-embedder.js156
-rw-r--r--docshell/test/browser/browser_browsingContext-getAllBrowsingContextsInSubtree.js53
-rw-r--r--docshell/test/browser/browser_browsingContext-getWindowByName.js34
-rw-r--r--docshell/test/browser/browser_browsingContext-webProgress.js226
-rw-r--r--docshell/test/browser/browser_browsing_context_attached.js179
-rw-r--r--docshell/test/browser/browser_browsing_context_discarded.js65
-rw-r--r--docshell/test/browser/browser_bug1206879.js50
-rw-r--r--docshell/test/browser/browser_bug1309900_crossProcessHistoryNavigation.js54
-rw-r--r--docshell/test/browser/browser_bug1328501.js69
-rw-r--r--docshell/test/browser/browser_bug1347823.js91
-rw-r--r--docshell/test/browser/browser_bug134911.js57
-rw-r--r--docshell/test/browser/browser_bug1415918_beforeunload_options.js162
-rw-r--r--docshell/test/browser/browser_bug1543077-3.js46
-rw-r--r--docshell/test/browser/browser_bug1594938.js100
-rw-r--r--docshell/test/browser/browser_bug1622420.js31
-rw-r--r--docshell/test/browser/browser_bug1648464-1.js46
-rw-r--r--docshell/test/browser/browser_bug1673702.js27
-rw-r--r--docshell/test/browser/browser_bug1674464.js38
-rw-r--r--docshell/test/browser/browser_bug1688368-1.js24
-rw-r--r--docshell/test/browser/browser_bug1691153.js73
-rw-r--r--docshell/test/browser/browser_bug1705872.js74
-rw-r--r--docshell/test/browser/browser_bug1716290-1.js24
-rw-r--r--docshell/test/browser/browser_bug1716290-2.js24
-rw-r--r--docshell/test/browser/browser_bug1716290-3.js24
-rw-r--r--docshell/test/browser/browser_bug1716290-4.js24
-rw-r--r--docshell/test/browser/browser_bug1719178.js34
-rw-r--r--docshell/test/browser/browser_bug1736248-1.js34
-rw-r--r--docshell/test/browser/browser_bug1757005.js73
-rw-r--r--docshell/test/browser/browser_bug1769189.js32
-rw-r--r--docshell/test/browser/browser_bug1798780.js52
-rw-r--r--docshell/test/browser/browser_bug234628-1.js47
-rw-r--r--docshell/test/browser/browser_bug234628-10.js46
-rw-r--r--docshell/test/browser/browser_bug234628-11.js46
-rw-r--r--docshell/test/browser/browser_bug234628-2.js49
-rw-r--r--docshell/test/browser/browser_bug234628-3.js47
-rw-r--r--docshell/test/browser/browser_bug234628-4.js46
-rw-r--r--docshell/test/browser/browser_bug234628-5.js46
-rw-r--r--docshell/test/browser/browser_bug234628-6.js47
-rw-r--r--docshell/test/browser/browser_bug234628-8.js18
-rw-r--r--docshell/test/browser/browser_bug234628-9.js18
-rw-r--r--docshell/test/browser/browser_bug349769.js79
-rw-r--r--docshell/test/browser/browser_bug388121-1.js22
-rw-r--r--docshell/test/browser/browser_bug388121-2.js74
-rw-r--r--docshell/test/browser/browser_bug420605.js131
-rw-r--r--docshell/test/browser/browser_bug422543.js253
-rw-r--r--docshell/test/browser/browser_bug441169.js44
-rw-r--r--docshell/test/browser/browser_bug503832.js76
-rw-r--r--docshell/test/browser/browser_bug554155.js33
-rw-r--r--docshell/test/browser/browser_bug655270.js62
-rw-r--r--docshell/test/browser/browser_bug655273.js56
-rw-r--r--docshell/test/browser/browser_bug670318.js146
-rw-r--r--docshell/test/browser/browser_bug673087-1.js46
-rw-r--r--docshell/test/browser/browser_bug673087-2.js36
-rw-r--r--docshell/test/browser/browser_bug673467.js62
-rw-r--r--docshell/test/browser/browser_bug852909.js35
-rw-r--r--docshell/test/browser/browser_bug92473.js70
-rw-r--r--docshell/test/browser/browser_click_link_within_view_source.js78
-rw-r--r--docshell/test/browser/browser_cross_process_csp_inheritance.js125
-rw-r--r--docshell/test/browser/browser_csp_sandbox_no_script_js_uri.js55
-rw-r--r--docshell/test/browser/browser_csp_uir.js89
-rw-r--r--docshell/test/browser/browser_dataURI_unique_opaque_origin.js30
-rw-r--r--docshell/test/browser/browser_data_load_inherit_csp.js110
-rw-r--r--docshell/test/browser/browser_fall_back_to_https.js73
-rw-r--r--docshell/test/browser/browser_frameloader_swap_with_bfcache.js37
-rw-r--r--docshell/test/browser/browser_history_triggeringprincipal_viewsource.js88
-rw-r--r--docshell/test/browser/browser_isInitialDocument.js319
-rw-r--r--docshell/test/browser/browser_loadURI_postdata.js42
-rw-r--r--docshell/test/browser/browser_multiple_pushState.js25
-rw-r--r--docshell/test/browser/browser_onbeforeunload_frame.js45
-rw-r--r--docshell/test/browser/browser_onbeforeunload_navigation.js165
-rw-r--r--docshell/test/browser/browser_onbeforeunload_parent.js48
-rw-r--r--docshell/test/browser/browser_onunload_stop.js23
-rw-r--r--docshell/test/browser/browser_overlink.js27
-rw-r--r--docshell/test/browser/browser_platform_emulation.js70
-rw-r--r--docshell/test/browser/browser_search_notification.js49
-rw-r--r--docshell/test/browser/browser_tab_replace_while_loading.js83
-rw-r--r--docshell/test/browser/browser_tab_touch_events.js75
-rw-r--r--docshell/test/browser/browser_targetTopLevelLinkClicksToBlank.js285
-rw-r--r--docshell/test/browser/browser_timelineMarkers-01.js46
-rw-r--r--docshell/test/browser/browser_timelineMarkers-02.js16
-rw-r--r--docshell/test/browser/browser_timelineMarkers-frame-02.js185
-rw-r--r--docshell/test/browser/browser_title_in_session_history.js63
-rw-r--r--docshell/test/browser/browser_ua_emulation.js71
-rw-r--r--docshell/test/browser/browser_uriFixupAlternateRedirects.js66
-rw-r--r--docshell/test/browser/browser_uriFixupIntegration.js104
-rw-r--r--docshell/test/browser/browser_viewsource_chrome_to_content.js20
-rw-r--r--docshell/test/browser/browser_viewsource_multipart.js44
-rw-r--r--docshell/test/browser/dummy_iframe_page.html8
-rw-r--r--docshell/test/browser/dummy_page.html6
-rw-r--r--docshell/test/browser/favicon_bug655270.icobin0 -> 1406 bytes
-rw-r--r--docshell/test/browser/file_backforward_restore_scroll.html10
-rw-r--r--docshell/test/browser/file_backforward_restore_scroll.html^headers^1
-rw-r--r--docshell/test/browser/file_basic_multipart.sjs24
-rw-r--r--docshell/test/browser/file_bug1046022.html54
-rw-r--r--docshell/test/browser/file_bug1206879.html9
-rw-r--r--docshell/test/browser/file_bug1328501.html27
-rw-r--r--docshell/test/browser/file_bug1328501_frame.html4
-rw-r--r--docshell/test/browser/file_bug1328501_framescript.js38
-rw-r--r--docshell/test/browser/file_bug1543077-3-child.html11
-rw-r--r--docshell/test/browser/file_bug1543077-3.html16
-rw-r--r--docshell/test/browser/file_bug1622420.html1
-rw-r--r--docshell/test/browser/file_bug1648464-1-child.html13
-rw-r--r--docshell/test/browser/file_bug1648464-1.html18
-rw-r--r--docshell/test/browser/file_bug1673702.json1
-rw-r--r--docshell/test/browser/file_bug1673702.json^headers^1
-rw-r--r--docshell/test/browser/file_bug1688368-1.sjs44
-rw-r--r--docshell/test/browser/file_bug1691153.html27
-rw-r--r--docshell/test/browser/file_bug1716290-1.sjs21
-rw-r--r--docshell/test/browser/file_bug1716290-2.sjs18
-rw-r--r--docshell/test/browser/file_bug1716290-3.sjs17
-rw-r--r--docshell/test/browser/file_bug1716290-4.sjs17
-rw-r--r--docshell/test/browser/file_bug1736248-1.html4
-rw-r--r--docshell/test/browser/file_bug234628-1-child.html12
-rw-r--r--docshell/test/browser/file_bug234628-1.html17
-rw-r--r--docshell/test/browser/file_bug234628-10-child.xhtml4
-rw-r--r--docshell/test/browser/file_bug234628-10.html17
-rw-r--r--docshell/test/browser/file_bug234628-11-child.xhtml4
-rw-r--r--docshell/test/browser/file_bug234628-11-child.xhtml^headers^1
-rw-r--r--docshell/test/browser/file_bug234628-11.html17
-rw-r--r--docshell/test/browser/file_bug234628-2-child.html12
-rw-r--r--docshell/test/browser/file_bug234628-2.html17
-rw-r--r--docshell/test/browser/file_bug234628-3-child.html13
-rw-r--r--docshell/test/browser/file_bug234628-3.html18
-rw-r--r--docshell/test/browser/file_bug234628-4-child.html12
-rw-r--r--docshell/test/browser/file_bug234628-4.html18
-rw-r--r--docshell/test/browser/file_bug234628-5-child.htmlbin0 -> 498 bytes
-rw-r--r--docshell/test/browser/file_bug234628-5.html18
-rw-r--r--docshell/test/browser/file_bug234628-6-child.htmlbin0 -> 540 bytes
-rw-r--r--docshell/test/browser/file_bug234628-6-child.html^headers^1
-rw-r--r--docshell/test/browser/file_bug234628-6.html18
-rw-r--r--docshell/test/browser/file_bug234628-8-child.html12
-rw-r--r--docshell/test/browser/file_bug234628-8.html17
-rw-r--r--docshell/test/browser/file_bug234628-9-child.html12
-rw-r--r--docshell/test/browser/file_bug234628-9.htmlbin0 -> 740 bytes
-rw-r--r--docshell/test/browser/file_bug420605.html31
-rw-r--r--docshell/test/browser/file_bug503832.html35
-rw-r--r--docshell/test/browser/file_bug655270.html11
-rw-r--r--docshell/test/browser/file_bug670318.html23
-rw-r--r--docshell/test/browser/file_bug673087-1-child.html13
-rw-r--r--docshell/test/browser/file_bug673087-1.htmlbin0 -> 432 bytes
-rw-r--r--docshell/test/browser/file_bug673087-1.html^headers^1
-rw-r--r--docshell/test/browser/file_bug673087-2.html2
-rw-r--r--docshell/test/browser/file_bug852909.pdfbin0 -> 1568 bytes
-rw-r--r--docshell/test/browser/file_bug852909.pngbin0 -> 94 bytes
-rw-r--r--docshell/test/browser/file_click_link_within_view_source.html6
-rw-r--r--docshell/test/browser/file_cross_process_csp_inheritance.html11
-rw-r--r--docshell/test/browser/file_csp_sandbox_no_script_js_uri.html11
-rw-r--r--docshell/test/browser/file_csp_sandbox_no_script_js_uri.html^headers^1
-rw-r--r--docshell/test/browser/file_csp_uir.html11
-rw-r--r--docshell/test/browser/file_csp_uir_dummy.html1
-rw-r--r--docshell/test/browser/file_data_load_inherit_csp.html11
-rw-r--r--docshell/test/browser/file_multiple_pushState.html20
-rw-r--r--docshell/test/browser/file_onbeforeunload_0.html9
-rw-r--r--docshell/test/browser/file_onbeforeunload_1.html9
-rw-r--r--docshell/test/browser/file_onbeforeunload_2.html10
-rw-r--r--docshell/test/browser/file_onbeforeunload_3.html9
-rw-r--r--docshell/test/browser/file_open_about_blank.html2
-rw-r--r--docshell/test/browser/file_slow_load.sjs8
-rw-r--r--docshell/test/browser/frame-head.js114
-rw-r--r--docshell/test/browser/head.js258
-rw-r--r--docshell/test/browser/head_browser_onbeforeunload.js271
-rw-r--r--docshell/test/browser/onload_message.html25
-rw-r--r--docshell/test/browser/onpageshow_message.html41
-rw-r--r--docshell/test/browser/overlink_test.html7
-rw-r--r--docshell/test/browser/print_postdata.sjs25
-rw-r--r--docshell/test/browser/redirect_to_example.sjs5
-rw-r--r--docshell/test/browser/test-form_sjis.html24
-rw-r--r--docshell/test/chrome/112564_nocache.html10
-rw-r--r--docshell/test/chrome/112564_nocache.html^headers^1
-rw-r--r--docshell/test/chrome/215405_nocache.html14
-rw-r--r--docshell/test/chrome/215405_nocache.html^headers^1
-rw-r--r--docshell/test/chrome/215405_nostore.html14
-rw-r--r--docshell/test/chrome/215405_nostore.html^headers^1
-rw-r--r--docshell/test/chrome/582176_dummy.html1
-rw-r--r--docshell/test/chrome/582176_xml.xml2
-rw-r--r--docshell/test/chrome/582176_xslt.xsl8
-rw-r--r--docshell/test/chrome/662200a.html8
-rw-r--r--docshell/test/chrome/662200b.html8
-rw-r--r--docshell/test/chrome/662200c.html7
-rw-r--r--docshell/test/chrome/89419.html7
-rw-r--r--docshell/test/chrome/92598_nostore.html10
-rw-r--r--docshell/test/chrome/92598_nostore.html^headers^1
-rw-r--r--docshell/test/chrome/DocShellHelpers.sys.mjs74
-rw-r--r--docshell/test/chrome/allowContentRetargeting.sjs7
-rw-r--r--docshell/test/chrome/blue.pngbin0 -> 2745 bytes
-rw-r--r--docshell/test/chrome/bug112564_window.xhtml86
-rw-r--r--docshell/test/chrome/bug113934_window.xhtml165
-rw-r--r--docshell/test/chrome/bug215405_window.xhtml177
-rw-r--r--docshell/test/chrome/bug293235.html13
-rw-r--r--docshell/test/chrome/bug293235_p2.html8
-rw-r--r--docshell/test/chrome/bug293235_window.xhtml118
-rw-r--r--docshell/test/chrome/bug294258_testcase.html43
-rw-r--r--docshell/test/chrome/bug294258_window.xhtml72
-rw-r--r--docshell/test/chrome/bug298622_window.xhtml135
-rw-r--r--docshell/test/chrome/bug301397_1.html9
-rw-r--r--docshell/test/chrome/bug301397_2.html10
-rw-r--r--docshell/test/chrome/bug301397_3.html10
-rw-r--r--docshell/test/chrome/bug301397_4.html9
-rw-r--r--docshell/test/chrome/bug301397_window.xhtml218
-rw-r--r--docshell/test/chrome/bug303267.html23
-rw-r--r--docshell/test/chrome/bug303267_window.xhtml83
-rw-r--r--docshell/test/chrome/bug311007_window.xhtml204
-rw-r--r--docshell/test/chrome/bug321671_window.xhtml128
-rw-r--r--docshell/test/chrome/bug360511_case1.html15
-rw-r--r--docshell/test/chrome/bug360511_case2.html15
-rw-r--r--docshell/test/chrome/bug360511_window.xhtml127
-rw-r--r--docshell/test/chrome/bug364461_window.xhtml253
-rw-r--r--docshell/test/chrome/bug396519_window.xhtml132
-rw-r--r--docshell/test/chrome/bug396649_window.xhtml119
-rw-r--r--docshell/test/chrome/bug449778_window.xhtml107
-rw-r--r--docshell/test/chrome/bug449780_window.xhtml83
-rw-r--r--docshell/test/chrome/bug454235-subframe.xhtml7
-rw-r--r--docshell/test/chrome/bug582176_window.xhtml74
-rw-r--r--docshell/test/chrome/bug608669.xhtml14
-rw-r--r--docshell/test/chrome/bug662200_window.xhtml119
-rw-r--r--docshell/test/chrome/bug690056_window.xhtml171
-rw-r--r--docshell/test/chrome/bug846906.html10
-rw-r--r--docshell/test/chrome/bug89419.sjs12
-rw-r--r--docshell/test/chrome/bug89419_window.xhtml69
-rw-r--r--docshell/test/chrome/bug909218.html11
-rw-r--r--docshell/test/chrome/bug909218.js2
-rw-r--r--docshell/test/chrome/bug92598_window.xhtml89
-rw-r--r--docshell/test/chrome/chrome.ini107
-rw-r--r--docshell/test/chrome/docshell_helpers.js759
-rw-r--r--docshell/test/chrome/file_viewsource_forbidden_in_iframe.html11
-rw-r--r--docshell/test/chrome/gen_template.pl39
-rw-r--r--docshell/test/chrome/generic.html12
-rw-r--r--docshell/test/chrome/mozFrameType_window.xhtml49
-rw-r--r--docshell/test/chrome/red.pngbin0 -> 82 bytes
-rw-r--r--docshell/test/chrome/test.template.txt41
-rw-r--r--docshell/test/chrome/test_allowContentRetargeting.html76
-rw-r--r--docshell/test/chrome/test_bug112564.xhtml37
-rw-r--r--docshell/test/chrome/test_bug113934.xhtml29
-rw-r--r--docshell/test/chrome/test_bug215405.xhtml37
-rw-r--r--docshell/test/chrome/test_bug293235.xhtml38
-rw-r--r--docshell/test/chrome/test_bug294258.xhtml38
-rw-r--r--docshell/test/chrome/test_bug298622.xhtml38
-rw-r--r--docshell/test/chrome/test_bug301397.xhtml38
-rw-r--r--docshell/test/chrome/test_bug303267.xhtml39
-rw-r--r--docshell/test/chrome/test_bug311007.xhtml42
-rw-r--r--docshell/test/chrome/test_bug321671.xhtml38
-rw-r--r--docshell/test/chrome/test_bug360511.xhtml39
-rw-r--r--docshell/test/chrome/test_bug364461.xhtml43
-rw-r--r--docshell/test/chrome/test_bug396519.xhtml28
-rw-r--r--docshell/test/chrome/test_bug396649.xhtml41
-rw-r--r--docshell/test/chrome/test_bug428288.html37
-rw-r--r--docshell/test/chrome/test_bug449778.xhtml29
-rw-r--r--docshell/test/chrome/test_bug449780.xhtml29
-rw-r--r--docshell/test/chrome/test_bug453650.xhtml120
-rw-r--r--docshell/test/chrome/test_bug454235.xhtml40
-rw-r--r--docshell/test/chrome/test_bug456980.xhtml29
-rw-r--r--docshell/test/chrome/test_bug565388.xhtml79
-rw-r--r--docshell/test/chrome/test_bug582176.xhtml38
-rw-r--r--docshell/test/chrome/test_bug608669.xhtml80
-rw-r--r--docshell/test/chrome/test_bug662200.xhtml38
-rw-r--r--docshell/test/chrome/test_bug690056.xhtml26
-rw-r--r--docshell/test/chrome/test_bug789773.xhtml67
-rw-r--r--docshell/test/chrome/test_bug846906.xhtml94
-rw-r--r--docshell/test/chrome/test_bug89419.xhtml38
-rw-r--r--docshell/test/chrome/test_bug909218.html117
-rw-r--r--docshell/test/chrome/test_bug92598.xhtml37
-rw-r--r--docshell/test/chrome/test_docRedirect.sjs6
-rw-r--r--docshell/test/chrome/test_docRedirect.xhtml91
-rw-r--r--docshell/test/chrome/test_mozFrameType.xhtml42
-rw-r--r--docshell/test/chrome/test_open_and_immediately_close_opener.html54
-rw-r--r--docshell/test/chrome/test_viewsource_forbidden_in_iframe.xhtml159
-rw-r--r--docshell/test/chrome/window.template.txt44
-rw-r--r--docshell/test/iframesandbox/file_child_navigation_by_location.html1
-rw-r--r--docshell/test/iframesandbox/file_marquee_event_handlers.html17
-rw-r--r--docshell/test/iframesandbox/file_other_auxiliary_navigation_by_location.html15
-rw-r--r--docshell/test/iframesandbox/file_our_auxiliary_navigation_by_location.html15
-rw-r--r--docshell/test/iframesandbox/file_parent_navigation_by_location.html18
-rw-r--r--docshell/test/iframesandbox/file_sibling_navigation_by_location.html15
-rw-r--r--docshell/test/iframesandbox/file_top_navigation_by_location.html20
-rw-r--r--docshell/test/iframesandbox/file_top_navigation_by_location_exotic.html27
-rw-r--r--docshell/test/iframesandbox/file_top_navigation_by_user_activation.html27
-rw-r--r--docshell/test/iframesandbox/file_top_navigation_by_user_activation_iframe.html32
-rw-r--r--docshell/test/iframesandbox/mochitest.ini30
-rw-r--r--docshell/test/iframesandbox/test_child_navigation_by_location.html91
-rw-r--r--docshell/test/iframesandbox/test_marquee_event_handlers.html95
-rw-r--r--docshell/test/iframesandbox/test_other_auxiliary_navigation_by_location.html80
-rw-r--r--docshell/test/iframesandbox/test_our_auxiliary_navigation_by_location.html84
-rw-r--r--docshell/test/iframesandbox/test_parent_navigation_by_location.html75
-rw-r--r--docshell/test/iframesandbox/test_sibling_navigation_by_location.html78
-rw-r--r--docshell/test/iframesandbox/test_top_navigation_by_location.html167
-rw-r--r--docshell/test/iframesandbox/test_top_navigation_by_location_exotic.html204
-rw-r--r--docshell/test/iframesandbox/test_top_navigation_by_user_activation.html74
-rw-r--r--docshell/test/mochitest/bug1422334_redirect.html3
-rw-r--r--docshell/test/mochitest/bug1422334_redirect.html^headers^2
-rw-r--r--docshell/test/mochitest/bug404548-subframe.html7
-rw-r--r--docshell/test/mochitest/bug404548-subframe_window.html1
-rw-r--r--docshell/test/mochitest/bug413310-post.sjs10
-rw-r--r--docshell/test/mochitest/bug413310-subframe.html7
-rw-r--r--docshell/test/mochitest/bug529119-window.html7
-rw-r--r--docshell/test/mochitest/bug530396-noref.sjs22
-rw-r--r--docshell/test/mochitest/bug530396-subframe.html7
-rw-r--r--docshell/test/mochitest/bug570341_recordevents.html21
-rw-r--r--docshell/test/mochitest/bug668513_redirect.html1
-rw-r--r--docshell/test/mochitest/bug668513_redirect.html^headers^2
-rw-r--r--docshell/test/mochitest/bug691547_frame.html12
-rw-r--r--docshell/test/mochitest/clicker.html7
-rw-r--r--docshell/test/mochitest/double_submit.sjs79
-rw-r--r--docshell/test/mochitest/dummy_page.html6
-rw-r--r--docshell/test/mochitest/file_anchor_scroll_after_document_open.html15
-rw-r--r--docshell/test/mochitest/file_bfcache_plus_hash_1.html24
-rw-r--r--docshell/test/mochitest/file_bfcache_plus_hash_2.html17
-rw-r--r--docshell/test/mochitest/file_bug1121701_1.html29
-rw-r--r--docshell/test/mochitest/file_bug1121701_2.html23
-rw-r--r--docshell/test/mochitest/file_bug1151421.html19
-rw-r--r--docshell/test/mochitest/file_bug1186774.html1
-rw-r--r--docshell/test/mochitest/file_bug1450164.html16
-rw-r--r--docshell/test/mochitest/file_bug1729662.html8
-rw-r--r--docshell/test/mochitest/file_bug1740516_1.html29
-rw-r--r--docshell/test/mochitest/file_bug1740516_1_inner.html15
-rw-r--r--docshell/test/mochitest/file_bug1740516_2.html11
-rw-r--r--docshell/test/mochitest/file_bug1741132.html29
-rw-r--r--docshell/test/mochitest/file_bug1742865.sjs77
-rw-r--r--docshell/test/mochitest/file_bug1742865_outer.sjs25
-rw-r--r--docshell/test/mochitest/file_bug1743353.html37
-rw-r--r--docshell/test/mochitest/file_bug1747033.sjs110
-rw-r--r--docshell/test/mochitest/file_bug1773192_1.html13
-rw-r--r--docshell/test/mochitest/file_bug1773192_2.html13
-rw-r--r--docshell/test/mochitest/file_bug1773192_3.sjs3
-rw-r--r--docshell/test/mochitest/file_bug385434_1.html29
-rw-r--r--docshell/test/mochitest/file_bug385434_2.html26
-rw-r--r--docshell/test/mochitest/file_bug385434_3.html22
-rw-r--r--docshell/test/mochitest/file_bug475636.sjs97
-rw-r--r--docshell/test/mochitest/file_bug509055.html9
-rw-r--r--docshell/test/mochitest/file_bug511449.html6
-rw-r--r--docshell/test/mochitest/file_bug540462.html25
-rw-r--r--docshell/test/mochitest/file_bug580069_1.html8
-rw-r--r--docshell/test/mochitest/file_bug580069_2.sjs8
-rw-r--r--docshell/test/mochitest/file_bug590573_1.html7
-rw-r--r--docshell/test/mochitest/file_bug590573_2.html8
-rw-r--r--docshell/test/mochitest/file_bug598895_1.html1
-rw-r--r--docshell/test/mochitest/file_bug598895_2.html1
-rw-r--r--docshell/test/mochitest/file_bug634834.html5
-rw-r--r--docshell/test/mochitest/file_bug637644_1.html1
-rw-r--r--docshell/test/mochitest/file_bug637644_2.html1
-rw-r--r--docshell/test/mochitest/file_bug640387.html26
-rw-r--r--docshell/test/mochitest/file_bug653741.html13
-rw-r--r--docshell/test/mochitest/file_bug66040413
-rw-r--r--docshell/test/mochitest/file_bug660404-1.html12
-rw-r--r--docshell/test/mochitest/file_bug660404^headers^1
-rw-r--r--docshell/test/mochitest/file_bug662170.html13
-rw-r--r--docshell/test/mochitest/file_bug668513.html101
-rw-r--r--docshell/test/mochitest/file_bug669671.sjs17
-rw-r--r--docshell/test/mochitest/file_bug675587.html1
-rw-r--r--docshell/test/mochitest/file_bug680257.html16
-rw-r--r--docshell/test/mochitest/file_bug703855.html2
-rw-r--r--docshell/test/mochitest/file_bug728939.html3
-rw-r--r--docshell/test/mochitest/file_close_onpagehide1.html5
-rw-r--r--docshell/test/mochitest/file_close_onpagehide2.html5
-rw-r--r--docshell/test/mochitest/file_compressed_multipartbin0 -> 111 bytes
-rw-r--r--docshell/test/mochitest/file_compressed_multipart^headers^2
-rw-r--r--docshell/test/mochitest/file_content_javascript_loads_frame.html17
-rw-r--r--docshell/test/mochitest/file_content_javascript_loads_root.html42
-rw-r--r--docshell/test/mochitest/file_form_restoration_no_store.html38
-rw-r--r--docshell/test/mochitest/file_form_restoration_no_store.html^headers^1
-rw-r--r--docshell/test/mochitest/file_framedhistoryframes.html16
-rw-r--r--docshell/test/mochitest/file_load_during_reload.html12
-rw-r--r--docshell/test/mochitest/file_pushState_after_document_open.html11
-rw-r--r--docshell/test/mochitest/file_redirect_history.html18
-rw-r--r--docshell/test/mochitest/form_submit.sjs40
-rw-r--r--docshell/test/mochitest/form_submit_redirect.sjs15
-rw-r--r--docshell/test/mochitest/historyframes.html176
-rw-r--r--docshell/test/mochitest/mochitest.ini209
-rw-r--r--docshell/test/mochitest/ping.html6
-rw-r--r--docshell/test/mochitest/start_historyframe.html1
-rw-r--r--docshell/test/mochitest/test_anchor_scroll_after_document_open.html55
-rw-r--r--docshell/test/mochitest/test_bfcache_plus_hash.html153
-rw-r--r--docshell/test/mochitest/test_bug1045096.html29
-rw-r--r--docshell/test/mochitest/test_bug1121701.html108
-rw-r--r--docshell/test/mochitest/test_bug1151421.html61
-rw-r--r--docshell/test/mochitest/test_bug1186774.html51
-rw-r--r--docshell/test/mochitest/test_bug1422334.html40
-rw-r--r--docshell/test/mochitest/test_bug1450164.html31
-rw-r--r--docshell/test/mochitest/test_bug1507702.html57
-rw-r--r--docshell/test/mochitest/test_bug1645781.html90
-rw-r--r--docshell/test/mochitest/test_bug1729662.html76
-rw-r--r--docshell/test/mochitest/test_bug1740516.html79
-rw-r--r--docshell/test/mochitest/test_bug1741132.html79
-rw-r--r--docshell/test/mochitest/test_bug1742865.html137
-rw-r--r--docshell/test/mochitest/test_bug1743353.html57
-rw-r--r--docshell/test/mochitest/test_bug1747033.html97
-rw-r--r--docshell/test/mochitest/test_bug1773192.html61
-rw-r--r--docshell/test/mochitest/test_bug385434.html211
-rw-r--r--docshell/test/mochitest/test_bug387979.html52
-rw-r--r--docshell/test/mochitest/test_bug402210.html50
-rw-r--r--docshell/test/mochitest/test_bug404548.html39
-rw-r--r--docshell/test/mochitest/test_bug413310.html106
-rw-r--r--docshell/test/mochitest/test_bug475636.html52
-rw-r--r--docshell/test/mochitest/test_bug509055.html115
-rw-r--r--docshell/test/mochitest/test_bug511449.html56
-rw-r--r--docshell/test/mochitest/test_bug529119-1.html110
-rw-r--r--docshell/test/mochitest/test_bug529119-2.html116
-rw-r--r--docshell/test/mochitest/test_bug530396.html56
-rw-r--r--docshell/test/mochitest/test_bug540462.html44
-rw-r--r--docshell/test/mochitest/test_bug551225.html32
-rw-r--r--docshell/test/mochitest/test_bug570341.html142
-rw-r--r--docshell/test/mochitest/test_bug580069.html58
-rw-r--r--docshell/test/mochitest/test_bug590573.html198
-rw-r--r--docshell/test/mochitest/test_bug598895.html52
-rw-r--r--docshell/test/mochitest/test_bug634834.html52
-rw-r--r--docshell/test/mochitest/test_bug637644.html52
-rw-r--r--docshell/test/mochitest/test_bug640387_1.html107
-rw-r--r--docshell/test/mochitest/test_bug640387_2.html89
-rw-r--r--docshell/test/mochitest/test_bug653741.html49
-rw-r--r--docshell/test/mochitest/test_bug660404.html76
-rw-r--r--docshell/test/mochitest/test_bug662170.html51
-rw-r--r--docshell/test/mochitest/test_bug668513.html28
-rw-r--r--docshell/test/mochitest/test_bug669671.html145
-rw-r--r--docshell/test/mochitest/test_bug675587.html33
-rw-r--r--docshell/test/mochitest/test_bug680257.html76
-rw-r--r--docshell/test/mochitest/test_bug691547.html59
-rw-r--r--docshell/test/mochitest/test_bug694612.html34
-rw-r--r--docshell/test/mochitest/test_bug703855.html79
-rw-r--r--docshell/test/mochitest/test_bug728939.html37
-rw-r--r--docshell/test/mochitest/test_bug797909.html66
-rw-r--r--docshell/test/mochitest/test_close_onpagehide_by_history_back.html24
-rw-r--r--docshell/test/mochitest/test_close_onpagehide_by_window_close.html20
-rw-r--r--docshell/test/mochitest/test_compressed_multipart.html41
-rw-r--r--docshell/test/mochitest/test_content_javascript_loads.html163
-rw-r--r--docshell/test/mochitest/test_double_submit.html98
-rw-r--r--docshell/test/mochitest/test_forceinheritprincipal_overrule_owner.html57
-rw-r--r--docshell/test/mochitest/test_form_restoration.html77
-rw-r--r--docshell/test/mochitest/test_framedhistoryframes.html32
-rw-r--r--docshell/test/mochitest/test_iframe_srcdoc_to_remote.html44
-rw-r--r--docshell/test/mochitest/test_javascript_sandboxed_popup.html27
-rw-r--r--docshell/test/mochitest/test_load_during_reload.html49
-rw-r--r--docshell/test/mochitest/test_navigate_after_pagehide.html34
-rw-r--r--docshell/test/mochitest/test_pushState_after_document_open.html39
-rw-r--r--docshell/test/mochitest/test_redirect_history.html58
-rw-r--r--docshell/test/mochitest/test_triggeringprincipal_location_seturi.html105
-rw-r--r--docshell/test/mochitest/test_windowedhistoryframes.html32
-rw-r--r--docshell/test/mochitest/url1_historyframe.html1
-rw-r--r--docshell/test/mochitest/url2_historyframe.html1
-rw-r--r--docshell/test/moz.build137
-rw-r--r--docshell/test/navigation/NavigationUtils.js203
-rw-r--r--docshell/test/navigation/blank.html1
-rw-r--r--docshell/test/navigation/bluebox_bug430723.html6
-rw-r--r--docshell/test/navigation/browser.ini23
-rw-r--r--docshell/test/navigation/browser_bug1757458.js46
-rw-r--r--docshell/test/navigation/browser_bug343515.js276
-rw-r--r--docshell/test/navigation/browser_ghistorymaxsize_is_0.js82
-rw-r--r--docshell/test/navigation/browser_test-content-chromeflags.js54
-rw-r--r--docshell/test/navigation/browser_test_bfcache_eviction.js102
-rw-r--r--docshell/test/navigation/browser_test_shentry_wireframe.js128
-rw-r--r--docshell/test/navigation/browser_test_simultaneous_normal_and_history_loads.js57
-rw-r--r--docshell/test/navigation/bug343515_pg1.html5
-rw-r--r--docshell/test/navigation/bug343515_pg2.html7
-rw-r--r--docshell/test/navigation/bug343515_pg3.html7
-rw-r--r--docshell/test/navigation/bug343515_pg3_1.html6
-rw-r--r--docshell/test/navigation/bug343515_pg3_1_1.html1
-rw-r--r--docshell/test/navigation/bug343515_pg3_2.html1
-rw-r--r--docshell/test/navigation/cache_control_max_age_3600.sjs20
-rw-r--r--docshell/test/navigation/file_beforeunload_and_bfcache.html31
-rw-r--r--docshell/test/navigation/file_blockBFCache.html33
-rw-r--r--docshell/test/navigation/file_bug1300461.html61
-rw-r--r--docshell/test/navigation/file_bug1300461_back.html37
-rw-r--r--docshell/test/navigation/file_bug1300461_redirect.html10
-rw-r--r--docshell/test/navigation/file_bug1300461_redirect.html^headers^2
-rw-r--r--docshell/test/navigation/file_bug1326251.html212
-rw-r--r--docshell/test/navigation/file_bug1326251_evict_cache.html17
-rw-r--r--docshell/test/navigation/file_bug1364364-1.html33
-rw-r--r--docshell/test/navigation/file_bug1364364-2.html14
-rw-r--r--docshell/test/navigation/file_bug1375833-frame1.html8
-rw-r--r--docshell/test/navigation/file_bug1375833-frame2.html8
-rw-r--r--docshell/test/navigation/file_bug1375833.html22
-rw-r--r--docshell/test/navigation/file_bug1379762-1.html35
-rw-r--r--docshell/test/navigation/file_bug1536471.html8
-rw-r--r--docshell/test/navigation/file_bug1583110.html26
-rw-r--r--docshell/test/navigation/file_bug1609475.html51
-rw-r--r--docshell/test/navigation/file_bug1706090.html40
-rw-r--r--docshell/test/navigation/file_bug1745638.html15
-rw-r--r--docshell/test/navigation/file_bug1750973.html45
-rw-r--r--docshell/test/navigation/file_bug1758664.html32
-rw-r--r--docshell/test/navigation/file_bug386782_contenteditable.html1
-rw-r--r--docshell/test/navigation/file_bug386782_designmode.html1
-rw-r--r--docshell/test/navigation/file_bug462076_1.html55
-rw-r--r--docshell/test/navigation/file_bug462076_2.html52
-rw-r--r--docshell/test/navigation/file_bug462076_3.html52
-rw-r--r--docshell/test/navigation/file_bug508537_1.html33
-rw-r--r--docshell/test/navigation/file_bug534178.html30
-rw-r--r--docshell/test/navigation/file_contentpolicy_block_window.html5
-rw-r--r--docshell/test/navigation/file_docshell_gotoindex.html42
-rw-r--r--docshell/test/navigation/file_document_write_1.html18
-rw-r--r--docshell/test/navigation/file_evict_from_bfcache.html29
-rw-r--r--docshell/test/navigation/file_fragment_handling_during_load.html27
-rw-r--r--docshell/test/navigation/file_fragment_handling_during_load_frame1.html6
-rw-r--r--docshell/test/navigation/file_fragment_handling_during_load_frame2.sjs20
-rw-r--r--docshell/test/navigation/file_load_history_entry_page_with_one_link.html7
-rw-r--r--docshell/test/navigation/file_load_history_entry_page_with_two_links.html9
-rw-r--r--docshell/test/navigation/file_meta_refresh.html40
-rw-r--r--docshell/test/navigation/file_navigation_type.html25
-rw-r--r--docshell/test/navigation/file_nested_frames.html27
-rw-r--r--docshell/test/navigation/file_nested_frames_innerframe.html1
-rw-r--r--docshell/test/navigation/file_nested_srcdoc.html3
-rw-r--r--docshell/test/navigation/file_new_shentry_during_history_navigation_1.html5
-rw-r--r--docshell/test/navigation/file_new_shentry_during_history_navigation_1.html^headers^1
-rw-r--r--docshell/test/navigation/file_new_shentry_during_history_navigation_2.html10
-rw-r--r--docshell/test/navigation/file_new_shentry_during_history_navigation_2.html^headers^1
-rw-r--r--docshell/test/navigation/file_new_shentry_during_history_navigation_3.html22
-rw-r--r--docshell/test/navigation/file_new_shentry_during_history_navigation_3.html^headers^1
-rw-r--r--docshell/test/navigation/file_new_shentry_during_history_navigation_4.html16
-rw-r--r--docshell/test/navigation/file_online_offline_bfcache.html41
-rw-r--r--docshell/test/navigation/file_reload.html23
-rw-r--r--docshell/test/navigation/file_reload_large_postdata.sjs46
-rw-r--r--docshell/test/navigation/file_reload_nonbfcached_srcdoc.sjs27
-rw-r--r--docshell/test/navigation/file_same_url.html24
-rw-r--r--docshell/test/navigation/file_scrollRestoration_bfcache_and_nobfcache.html30
-rw-r--r--docshell/test/navigation/file_scrollRestoration_bfcache_and_nobfcache_part2.html35
-rw-r--r--docshell/test/navigation/file_scrollRestoration_bfcache_and_nobfcache_part2.html^headers^1
-rw-r--r--docshell/test/navigation/file_scrollRestoration_navigate.html17
-rw-r--r--docshell/test/navigation/file_scrollRestoration_part1_nobfcache.html63
-rw-r--r--docshell/test/navigation/file_scrollRestoration_part1_nobfcache.html^headers^1
-rw-r--r--docshell/test/navigation/file_scrollRestoration_part2_bfcache.html57
-rw-r--r--docshell/test/navigation/file_scrollRestoration_part3_nobfcache.html157
-rw-r--r--docshell/test/navigation/file_scrollRestoration_part3_nobfcache.html^headers^1
-rw-r--r--docshell/test/navigation/file_session_history_on_redirect.html16
-rw-r--r--docshell/test/navigation/file_session_history_on_redirect.html^headers^1
-rw-r--r--docshell/test/navigation/file_session_history_on_redirect_2.html16
-rw-r--r--docshell/test/navigation/file_session_history_on_redirect_2.html^headers^1
-rw-r--r--docshell/test/navigation/file_sessionhistory_iframe_removal.html37
-rw-r--r--docshell/test/navigation/file_shiftReload_and_pushState.html28
-rw-r--r--docshell/test/navigation/file_ship_beforeunload_fired.html37
-rw-r--r--docshell/test/navigation/file_static_and_dynamic_1.html31
-rw-r--r--docshell/test/navigation/file_tell_opener.html8
-rw-r--r--docshell/test/navigation/file_triggeringprincipal_frame_1.html27
-rw-r--r--docshell/test/navigation/file_triggeringprincipal_frame_2.html8
-rw-r--r--docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_a.html6
-rw-r--r--docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_a_nav.html6
-rw-r--r--docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_b.html15
-rw-r--r--docshell/test/navigation/file_triggeringprincipal_parent_iframe_window_open_base.html6
-rw-r--r--docshell/test/navigation/file_triggeringprincipal_parent_iframe_window_open_nav.html6
-rw-r--r--docshell/test/navigation/file_triggeringprincipal_subframe.html15
-rw-r--r--docshell/test/navigation/file_triggeringprincipal_subframe_nav.html21
-rw-r--r--docshell/test/navigation/file_triggeringprincipal_subframe_same_origin_nav.html20
-rw-r--r--docshell/test/navigation/file_triggeringprincipal_window_open.html6
-rw-r--r--docshell/test/navigation/frame0.html3
-rw-r--r--docshell/test/navigation/frame1.html3
-rw-r--r--docshell/test/navigation/frame2.html3
-rw-r--r--docshell/test/navigation/frame3.html3
-rw-r--r--docshell/test/navigation/frame_1_out_of_6.html6
-rw-r--r--docshell/test/navigation/frame_2_out_of_6.html6
-rw-r--r--docshell/test/navigation/frame_3_out_of_6.html6
-rw-r--r--docshell/test/navigation/frame_4_out_of_6.html6
-rw-r--r--docshell/test/navigation/frame_5_out_of_6.html6
-rw-r--r--docshell/test/navigation/frame_6_out_of_6.html6
-rw-r--r--docshell/test/navigation/frame_load_as_example_com.html6
-rw-r--r--docshell/test/navigation/frame_load_as_example_org.html6
-rw-r--r--docshell/test/navigation/frame_load_as_host1.html6
-rw-r--r--docshell/test/navigation/frame_load_as_host2.html6
-rw-r--r--docshell/test/navigation/frame_load_as_host3.html6
-rw-r--r--docshell/test/navigation/frame_recursive.html6
-rw-r--r--docshell/test/navigation/goback.html5
-rw-r--r--docshell/test/navigation/iframe.html9
-rw-r--r--docshell/test/navigation/iframe_slow_onload.html5
-rw-r--r--docshell/test/navigation/iframe_slow_onload_inner.html19
-rw-r--r--docshell/test/navigation/iframe_static.html8
-rw-r--r--docshell/test/navigation/mochitest.ini255
-rw-r--r--docshell/test/navigation/navigate.html38
-rw-r--r--docshell/test/navigation/navigation_target_popup_url.html1
-rw-r--r--docshell/test/navigation/navigation_target_url.html1
-rw-r--r--docshell/test/navigation/object_recursive_load.html6
-rw-r--r--docshell/test/navigation/open.html10
-rw-r--r--docshell/test/navigation/parent.html14
-rw-r--r--docshell/test/navigation/redbox_bug430723.html6
-rw-r--r--docshell/test/navigation/redirect_handlers.sjs29
-rw-r--r--docshell/test/navigation/redirect_to_blank.sjs6
-rw-r--r--docshell/test/navigation/slow.sjs16
-rw-r--r--docshell/test/navigation/test_aboutblank_change_process.html46
-rw-r--r--docshell/test/navigation/test_beforeunload_and_bfcache.html97
-rw-r--r--docshell/test/navigation/test_blockBFCache.html294
-rw-r--r--docshell/test/navigation/test_bug1300461.html70
-rw-r--r--docshell/test/navigation/test_bug1326251.html47
-rw-r--r--docshell/test/navigation/test_bug1364364.html65
-rw-r--r--docshell/test/navigation/test_bug1375833.html131
-rw-r--r--docshell/test/navigation/test_bug1379762.html67
-rw-r--r--docshell/test/navigation/test_bug13871.html85
-rw-r--r--docshell/test/navigation/test_bug145971.html29
-rw-r--r--docshell/test/navigation/test_bug1536471.html75
-rw-r--r--docshell/test/navigation/test_bug1583110.html36
-rw-r--r--docshell/test/navigation/test_bug1609475.html35
-rw-r--r--docshell/test/navigation/test_bug1699721.html110
-rw-r--r--docshell/test/navigation/test_bug1706090.html49
-rw-r--r--docshell/test/navigation/test_bug1745638.html40
-rw-r--r--docshell/test/navigation/test_bug1747019.html48
-rw-r--r--docshell/test/navigation/test_bug1750973.html20
-rw-r--r--docshell/test/navigation/test_bug1758664.html21
-rw-r--r--docshell/test/navigation/test_bug270414.html103
-rw-r--r--docshell/test/navigation/test_bug278916.html37
-rw-r--r--docshell/test/navigation/test_bug279495.html44
-rw-r--r--docshell/test/navigation/test_bug344861.html35
-rw-r--r--docshell/test/navigation/test_bug386782.html122
-rw-r--r--docshell/test/navigation/test_bug430624.html57
-rw-r--r--docshell/test/navigation/test_bug430723.html124
-rw-r--r--docshell/test/navigation/test_child.html47
-rw-r--r--docshell/test/navigation/test_contentpolicy_block_window.html98
-rw-r--r--docshell/test/navigation/test_docshell_gotoindex.html29
-rw-r--r--docshell/test/navigation/test_dynamic_frame_forward_back.html35
-rw-r--r--docshell/test/navigation/test_evict_from_bfcache.html63
-rw-r--r--docshell/test/navigation/test_fragment_handling_during_load.html35
-rw-r--r--docshell/test/navigation/test_grandchild.html47
-rw-r--r--docshell/test/navigation/test_load_history_entry.html196
-rw-r--r--docshell/test/navigation/test_meta_refresh.html42
-rw-r--r--docshell/test/navigation/test_navigation_type.html47
-rw-r--r--docshell/test/navigation/test_nested_frames.html35
-rw-r--r--docshell/test/navigation/test_new_shentry_during_history_navigation.html90
-rw-r--r--docshell/test/navigation/test_not-opener.html56
-rw-r--r--docshell/test/navigation/test_online_offline_bfcache.html101
-rw-r--r--docshell/test/navigation/test_open_javascript_noopener.html44
-rw-r--r--docshell/test/navigation/test_opener.html56
-rw-r--r--docshell/test/navigation/test_performance_navigation.html41
-rw-r--r--docshell/test/navigation/test_popup-navigates-children.html69
-rw-r--r--docshell/test/navigation/test_rate_limit_location_change.html100
-rw-r--r--docshell/test/navigation/test_recursive_frames.html167
-rw-r--r--docshell/test/navigation/test_reload.html42
-rw-r--r--docshell/test/navigation/test_reload_large_postdata.html61
-rw-r--r--docshell/test/navigation/test_reload_nonbfcached_srcdoc.html40
-rw-r--r--docshell/test/navigation/test_reserved.html92
-rw-r--r--docshell/test/navigation/test_same_url.html56
-rw-r--r--docshell/test/navigation/test_scrollRestoration.html214
-rw-r--r--docshell/test/navigation/test_session_history_entry_cleanup.html35
-rw-r--r--docshell/test/navigation/test_session_history_on_redirect.html92
-rw-r--r--docshell/test/navigation/test_sessionhistory.html48
-rw-r--r--docshell/test/navigation/test_sessionhistory_document_write.html34
-rw-r--r--docshell/test/navigation/test_sessionhistory_iframe_removal.html33
-rw-r--r--docshell/test/navigation/test_shiftReload_and_pushState.html35
-rw-r--r--docshell/test/navigation/test_ship_beforeunload_fired.html63
-rw-r--r--docshell/test/navigation/test_ship_beforeunload_fired_2.html65
-rw-r--r--docshell/test/navigation/test_ship_beforeunload_fired_3.html65
-rw-r--r--docshell/test/navigation/test_sibling-matching-parent.html46
-rw-r--r--docshell/test/navigation/test_sibling-off-domain.html46
-rw-r--r--docshell/test/navigation/test_state_size.html32
-rw-r--r--docshell/test/navigation/test_static_and_dynamic.html36
-rw-r--r--docshell/test/navigation/test_triggeringprincipal_frame_nav.html74
-rw-r--r--docshell/test/navigation/test_triggeringprincipal_frame_same_origin_nav.html63
-rw-r--r--docshell/test/navigation/test_triggeringprincipal_iframe_iframe_window_open.html87
-rw-r--r--docshell/test/navigation/test_triggeringprincipal_parent_iframe_window_open.html70
-rw-r--r--docshell/test/navigation/test_triggeringprincipal_window_open.html79
-rw-r--r--docshell/test/unit/AllowJavascriptChild.sys.mjs41
-rw-r--r--docshell/test/unit/AllowJavascriptParent.sys.mjs28
-rw-r--r--docshell/test/unit/data/engine.xml10
-rw-r--r--docshell/test/unit/data/enginePost.xml10
-rw-r--r--docshell/test/unit/data/enginePrivate.xml10
-rw-r--r--docshell/test/unit/head_docshell.js106
-rw-r--r--docshell/test/unit/test_URIFixup.js123
-rw-r--r--docshell/test/unit/test_URIFixup_check_host.js183
-rw-r--r--docshell/test/unit/test_URIFixup_external_protocol_fallback.js106
-rw-r--r--docshell/test/unit/test_URIFixup_forced.js159
-rw-r--r--docshell/test/unit/test_URIFixup_info.js1075
-rw-r--r--docshell/test/unit/test_URIFixup_search.js143
-rw-r--r--docshell/test/unit/test_allowJavascript.js291
-rw-r--r--docshell/test/unit/test_browsing_context_structured_clone.js70
-rw-r--r--docshell/test/unit/test_bug442584.js35
-rw-r--r--docshell/test/unit/test_pb_notification.js18
-rw-r--r--docshell/test/unit/test_privacy_transition.js21
-rw-r--r--docshell/test/unit/test_subframe_stop_after_parent_error.js146
-rw-r--r--docshell/test/unit/xpcshell.ini35
-rw-r--r--docshell/test/unit_ipc/test_pb_notification_ipc.js15
-rw-r--r--docshell/test/unit_ipc/xpcshell.ini7
1495 files changed, 125677 insertions, 0 deletions
diff --git a/docs/_addons/bzlink.py b/docs/_addons/bzlink.py
new file mode 100644
index 0000000000..6bdbdac8c5
--- /dev/null
+++ b/docs/_addons/bzlink.py
@@ -0,0 +1,63 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import re
+
+from docutils.nodes import Text, paragraph, reference
+from sphinx.transforms import SphinxTransform
+
+
+class ConvertBugsToLinks(SphinxTransform):
+ # Convert text entries in paragraphs that are in the style of "bug xxxxx"
+ # to a hyperlink that leads to the appropriate bugzilla bug link.
+
+ default_priority = 400
+ bz_url = "https://bugzilla.mozilla.org/show_bug.cgi?id={0}"
+ bz_reg = r"bug[' '][0-9]\d*"
+
+ def apply(self):
+ def check_if_paragraph(o):
+ return isinstance(o, paragraph)
+
+ def check_if_text(o):
+ return (
+ not isinstance(o.parent, reference)
+ and isinstance(o, Text)
+ and re.search(self.bz_reg, o, re.IGNORECASE)
+ )
+
+ changed = True
+ while changed:
+ changed = self.textToReferences(check_if_paragraph, check_if_text)
+ return
+
+ def textToReferences(self, check_if_paragraph, check_if_text):
+ # Analyses the document and replaces from the paragraph nodes
+ # the Text element(s) that contain bz_reg matching strings.
+ # Whevever the matching strings are more than one and
+ # a correction is made, the function returns True.
+
+ for node in self.document.traverse(check_if_paragraph):
+ for text in node.traverse(check_if_text):
+ bugs = re.findall(self.bz_reg, text, re.IGNORECASE)
+ if len(bugs) == 0:
+ continue
+ bug = bugs[0]
+ txtparts = text.split(bug, 1)
+ new_ref = reference(
+ bug,
+ bug,
+ refuri=self.bz_url.format(bug.split()[1]),
+ )
+ txt_0 = Text(txtparts[0])
+ txt_1 = Text(txtparts[1])
+ text.parent.replace(text, [txt_0, new_ref, txt_1])
+ if len(bugs) > 1:
+ return True
+ return False
+
+
+def setup(app):
+ app.add_transform(ConvertBugsToLinks)
+ return
diff --git a/docs/_search_template/searchbox.html b/docs/_search_template/searchbox.html
new file mode 100644
index 0000000000..c854452119
--- /dev/null
+++ b/docs/_search_template/searchbox.html
@@ -0,0 +1,16 @@
+<! -- This code is governed by the BSD license -->
+
+<div>
+ <h3>{{ _('Quick search') }}</h3>
+ <script>
+ (function () {
+ var cx = "dd12886298f75dbef";
+ var gcse = document.createElement("script");
+ gcse.async = true;
+ gcse.src = "https://cse.google.com/cse.js?cx=" + cx;
+ var s = document.getElementsByTagName("script")[0];
+ s.parentNode.insertBefore(gcse, s);
+ })();
+ </script>
+ <gcse:search></gcse:search>
+</div>
diff --git a/docs/_static/custom_theme.css b/docs/_static/custom_theme.css
new file mode 100644
index 0000000000..c8e34c03ca
--- /dev/null
+++ b/docs/_static/custom_theme.css
@@ -0,0 +1,71 @@
+/* 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/. */
+
+/* Increase the size of the content */
+.wy-nav-content {
+ max-width: 80% !important;
+}
+
+/* Increase the size of the tables */
+table.docutils {
+ width: 90%;
+}
+
+/* Override the default values for multiline text in a table */
+table.docutils td, table.docutils th
+{
+ font-size: 16px !important;
+}
+
+.rst-content .line-block {
+ margin-bottom: 0px !important;
+}
+
+/* Add the strikethrough feature */
+span.strikethrough {
+ text-decoration: line-through;
+}
+
+/* Better control over the table on this page */
+.matcher-cookbook td {
+ white-space: break-spaces !important;
+}
+
+.wy-table-responsive table td, .wy-table-responsive table th {
+ white-space: normal;
+}
+
+img.center {
+ display: block;
+ margin: auto;
+}
+
+img.border {
+ border: 1px solid black;
+ display: block;
+ margin: auto;
+}
+
+.center {
+ text-align: center;
+}
+
+/* Keyboard shortcuts styling */
+kbd {
+ background: linear-gradient(180deg,#f4f4f4,#d5d5d5);
+ background-color: #f4f4f4;
+ border: 1px solid #d5d5d5;
+ border-radius: 6px;
+ font-family: consolas,"Liberation Mono",courier,monospace;
+ font-size: .9rem;
+ font-weight: 700;
+ line-height: 2.3;
+ margin: 3px;
+ padding: 4px 6px 1px 6px;
+ white-space: nowrap;
+}
+
+table.docutils {
+ width: 100%;
+}
diff --git a/docs/_static/sphinx_design.css b/docs/_static/sphinx_design.css
new file mode 100644
index 0000000000..86d1ad7b08
--- /dev/null
+++ b/docs/_static/sphinx_design.css
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* For Dropdown Link in panels of sphinx-panels */
+a.dropdown-link {
+ color: #DDD;
+ margin-left: 0.5em;
+ padding: 0.25em;
+ visibility: hidden;
+}
+
+details.sd-dropdown:hover a.dropdown-link {
+ visibility: visible;
+}
+
+a.dropdown-link:hover {
+ background-color: #36557c;
+ color: #ffffff;
+ text-decoration: none;
+}
diff --git a/docs/_static/sphinx_design.js b/docs/_static/sphinx_design.js
new file mode 100644
index 0000000000..d080f2d634
--- /dev/null
+++ b/docs/_static/sphinx_design.js
@@ -0,0 +1,72 @@
+/* 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/. */
+
+(function () {
+ "use strict";
+
+ var dropdownClassName = "sd-dropdown";
+
+ function getDropdownElement() {
+ var dropdownId = window.location.hash;
+ if (!dropdownId) {
+ return false;
+ }
+
+ var dropdownElement = document.getElementById(dropdownId.substring(1));
+ if (
+ !dropdownElement ||
+ !dropdownElement.classList.contains(dropdownClassName)
+ ) {
+ return false;
+ }
+
+ return dropdownElement;
+ }
+
+ function setupDropdownLink() {
+ var dropdowns = document.getElementsByClassName(dropdownClassName);
+ for (var i = 0; i < dropdowns.length; i++) {
+ for (var j = 0; j < dropdowns[i].classList.length; j++) {
+ if (dropdowns[i].classList[j].startsWith("anchor-id-")) {
+ dropdowns[i].id = dropdowns[i].classList[j].replace("anchor-id-", "");
+ }
+ }
+
+ var aTag = document.createElement("a");
+ aTag.setAttribute("href", "#" + dropdowns[i].id);
+ aTag.classList.add("dropdown-link");
+ aTag.innerHTML = "¶";
+
+ var summaryElement =
+ dropdowns[i].getElementsByClassName("sd-summary-title")[0];
+ summaryElement.insertBefore(
+ aTag,
+ summaryElement.getElementsByClassName("docutils")[0]
+ );
+ }
+ }
+
+ function scrollToDropdown() {
+ var dropdownElement = getDropdownElement();
+ if (dropdownElement) {
+ dropdownElement.open = true;
+ dropdownElement.scrollIntoView(true);
+ }
+ }
+
+ // Initiallize dropdown link
+ window.addEventListener("DOMContentLoaded", () => {
+ if (document.getElementsByClassName(dropdownClassName).length) {
+ setupDropdownLink();
+ window.onhashchange = scrollToDropdown;
+ }
+ });
+
+ // Scroll to and open the dropdown direct links
+ window.onload = () => {
+ if (document.getElementsByClassName(dropdownClassName).length) {
+ scrollToDropdown();
+ }
+ };
+})();
diff --git a/docs/_templates/breadcrumbs.html b/docs/_templates/breadcrumbs.html
new file mode 100644
index 0000000000..392db34504
--- /dev/null
+++ b/docs/_templates/breadcrumbs.html
@@ -0,0 +1,18 @@
+<!-- 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/. -->
+
+{%- extends "sphinx_rtd_theme/breadcrumbs.html" %} {% block breadcrumbs_aside %}
+<li class="wy-breadcrumbs-aside">
+ <a
+ href="https://bugzilla.mozilla.org/enter_bug.cgi?product=Developer+Infrastructure&component=Firefox+Source+Docs%3A+Content&short_desc=Documentation+issue+on+{{ pagename }}&comment=URL+=+https://firefox-source-docs.mozilla.org/{{ pagename }}.html&bug_file_loc=https://firefox-source-docs.mozilla.org/{{ pagename }}.html"
+ rel="nofollow"
+ >Report an issue</a
+ >
+ / {%- if show_source and has_source and sourcename %}
+ <a href="{{ pathto('_sources/' + sourcename, true)|e }}" rel="nofollow">
+ {{ _('View page source') }}</a
+ >
+ {%- endif %}
+</li>
+{% endblock %}
diff --git a/docs/bug-mgmt/guides/bug-pipeline.rst b/docs/bug-mgmt/guides/bug-pipeline.rst
new file mode 100644
index 0000000000..a70adb0464
--- /dev/null
+++ b/docs/bug-mgmt/guides/bug-pipeline.rst
@@ -0,0 +1,37 @@
+Bug pipeline
+============
+
+For Firefox quality, Mozilla has different processes to report defects. In parallel, over the years, Mozilla developed many tools around bug management.
+
+.. mermaid::
+
+ graph TD
+ classDef tool fill:#f96;
+
+ Community --> B(bugzilla.mozilla.org)
+ QA --> B
+ Foxfooding --> B
+ Fuzzing --> B
+ SA[Static/Dynamic analysis] --> B
+ P[Performance monitoring] --> B
+ Y[Test automation] --> B
+ Z[Crash detection] --> B
+ B --> C{Bug update}
+ C --> D[Metadata improvements]
+ C --> E[Component triage]
+ C --> F[Test case verification]
+ F --> BM{{Bugmon}}:::tool
+ F --> MR{{Mozregression}}:::tool
+
+ D --> AN{{Autonag}}:::tool
+ E --> BB{{bugbug}}:::tool
+ D --> BB
+
+More details
+------------
+
+* :ref:`Fuzzing`
+* `Autonag <https://wiki.mozilla.org/Release_Management/autonag#Introduction>`_ - `Source <https://github.com/mozilla/relman-auto-nag/>`_
+* `Bugbug <https://github.com/mozilla/bugbug>`_ - `Blog post about triage <https://hacks.mozilla.org/2019/04/teaching-machines-to-triage-firefox-bugs/>`_ / `Blog post about CI <https://hacks.mozilla.org/2020/07/testing-firefox-more-efficiently-with-machine-learning/>`_
+* `Bugmon <https://hacks.mozilla.org/2021/01/analyzing-bugzilla-testcases-with-bugmon/>`_ - `Source <https://github.com/MozillaSecurity/bugmon>`_
+* `Mozregression <https://mozilla.github.io/mozregression/>`_ - `Source <https://github.com/mozilla/mozregression>`_
diff --git a/docs/bug-mgmt/guides/bug-types.rst b/docs/bug-mgmt/guides/bug-types.rst
new file mode 100644
index 0000000000..3626336de1
--- /dev/null
+++ b/docs/bug-mgmt/guides/bug-types.rst
@@ -0,0 +1,29 @@
+Bug Types
+=========
+
+We organize bugs by type to make it easier to make triage decisions, get
+the bug to the right person to make a decision, and understand release
+quality.
+
+- **Defect** regression, crash, hang, security vulnerability and any
+ other reported issue
+- **Enhancement** new feature, improvement in UI, performance, etc. and
+ any other request for user-facing enhancements to the product, not
+ engineering changes
+- **Task** refactoring, removal, replacement, enabling or disabling of
+ functionality and any other engineering task
+
+All bug types need triage decisions. Engineering :ref:`triages defects and
+tasks <Triage for Bugzilla>`. Product management :ref:`triages
+enhancements <New Feature Triage>`.
+
+It’s important to distinguish an enhancement from other types because
+they use different triage queues.
+
+Distinguishing between defects and tasks is important because we want to
+understand code quality and reduce the number of defects we introduce as
+we work on new features and fix existing defects.
+
+When triaging, a task can be as important as a defect. A behind the
+scenes change to how a thread is handled can affect performance as seen
+by a user.
diff --git a/docs/bug-mgmt/guides/other-metadata.rst b/docs/bug-mgmt/guides/other-metadata.rst
new file mode 100644
index 0000000000..f1b94f16d8
--- /dev/null
+++ b/docs/bug-mgmt/guides/other-metadata.rst
@@ -0,0 +1,28 @@
+Other Bug Metadata
+==================
+
+Performance
+-----------
+
+- Use the ``perf`` keyword
+- Add ``[qf:?]`` to the whiteboard if you think the Performance team
+ should look at this bug
+
+Privacy
+-------
+
+- Use the ``privacy`` keyword
+
+User Security
+-------------
+
+- Will this bug adversely affect Firefox users if left public?
+
+ - Add to security group
+
+- Otherwise move bug to one of:
+
+ - Core:: Security
+ - Firefox:: Security
+ - Toolkit:: Security
+ - Webkit:: Security
diff --git a/docs/bug-mgmt/guides/priority.rst b/docs/bug-mgmt/guides/priority.rst
new file mode 100644
index 0000000000..db0c8ee874
--- /dev/null
+++ b/docs/bug-mgmt/guides/priority.rst
@@ -0,0 +1,27 @@
+Priority Definitions
+====================
+
+We use these definitions across all components:
+
++----------------------------------------+-----------------------------+
+| Priority | Description |
++========================================+=============================+
+| \- | No decision |
++----------------------------------------+-----------------------------+
+| P1 | Fix in the current release |
+| | cycle |
++----------------------------------------+-----------------------------+
+| P2 | Fix in the next release |
+| | cycle or the following |
+| | (nightly + 1 or nightly + |
+| | 2) |
++----------------------------------------+-----------------------------+
+| P3 | Backlog |
++----------------------------------------+-----------------------------+
+| P4 | Do not use, this priority |
+| | is for web platform test |
+| | bots |
++----------------------------------------+-----------------------------+
+| P5 | Will not fix, but will |
+| | accept a patch |
++----------------------------------------+-----------------------------+
diff --git a/docs/bug-mgmt/guides/severity.rst b/docs/bug-mgmt/guides/severity.rst
new file mode 100644
index 0000000000..c75870f471
--- /dev/null
+++ b/docs/bug-mgmt/guides/severity.rst
@@ -0,0 +1,71 @@
+Defect Severity
+===============
+
+Definition
+----------
+
+We use the ``severity`` field in Bugzilla to indicate the scope of a
+bug's effect on Firefox.
+
+The field is display alongside the bug's priority.
+
+Values
+------
+
+Severity levels and their definitions are enumerated at https://wiki.mozilla.org/BMO/UserGuide/BugFields#bug_severity.
+
+By default, new bugs have a severity of ``--``.
+
+Examples of S1 bugs
+^^^^^^^^^^^^^^^^^^^
+
+- WebExtensions disabled for all users
+- Web search not working from URL bar
+- Crashes with data loss
+
+Examples of S2 bugs
+^^^^^^^^^^^^^^^^^^^
+
+Bugs that could reasonably be expected to cause a Firefox user to switch browsers,
+either because the severity is bad enough, or the frequency of occurrence is high enough.
+
+- `Bug 1640441 <https://bugzilla.mozilla.org/show_bug.cgi?id=1640441>`__ - Slack hangs
+ indefinitely in a onResize loop
+- `Bug 1645651 <https://bugzilla.mozilla.org/show_bug.cgi?id=1645651>`__ - Changes in
+ Reddit's comment section JS code makes selecting text slow on Nightly
+
+Bugs involving contractual partners (if not an S1)
+
+Bugs reported from QA
+
+- `Bug 1640913 <https://bugzilla.mozilla.org/show_bug.cgi?id=1640913>`__ - because an
+ important message is not visible with the dark theme. It's not marked as S1 since the
+ issue is reproducible only on one OS and the functionality is not affected.
+- `Bug 1641521 <https://bugzilla.mozilla.org/show_bug.cgi?id=1641521>`__ - because videos
+ are not working on several sites with ETP on (default). This is not an S1 since turning
+ ETP off fixes the issue.
+
+Fuzzblocker bugs, which prevent fuzzing from making progress
+
+Examples of S3 bugs
+^^^^^^^^^^^^^^^^^^^
+
+Bugs filed by contributors as part of daily refactoring and maintenance of the code base.
+
+`Bug 1634171 <https://bugzilla.mozilla.org/show_bug.cgi?id=1634171>`__ - Visual artifacts around circular images
+
+Bugs reported from QA
+
+- `Bug 1635105 <https://bugzilla.mozilla.org/show_bug.cgi?id=1635105>`__ because
+ the associated steps to reproduce are uncommon,
+ and the issue is no longer reproducible after refresh.
+- `Bug 1636063 <https://bugzilla.mozilla.org/show_bug.cgi?id=1636063>`__ since it's
+ reproducible only on a specific web app, and only with a particular set of configurations.
+
+
+Rules of thumb
+--------------
+
+- The severity of most bugs of type ``task`` and ``enhancement`` will be
+ ``N/A``
+- **Do not** assign bugs of type ``defect`` the severity ``N/A``
diff --git a/docs/bug-mgmt/guides/status-flags.rst b/docs/bug-mgmt/guides/status-flags.rst
new file mode 100644
index 0000000000..04d252f596
--- /dev/null
+++ b/docs/bug-mgmt/guides/status-flags.rst
@@ -0,0 +1,33 @@
+Release Status Flags
+====================
+
+The flag ``status_firefoxNN`` has many values, here’s a cheat sheet.
+
+== ========== ========== ============ =================
+— ? unaffected affected fixed
+== ========== ========== ============ =================
+? unaffected wontfix verified
+\ affected fix-optional disabled
+\ fixed verified disabled
+== ========== ========== ============ =================
+
+The headers of the table are values of the status flag. Each column are
+the states reachable from the column headings.
+
+- ``---`` we don’t know whether Firefox N is affected
+- ``?`` we don’t know whether Firefox N is affected, but we want to find
+ out.
+- ``affected`` - present in this release
+- ``unaffected`` - not present in this release
+- ``fixed`` - a contributor has landed a change set in the tree
+ to address the issue
+- ``verified`` - the fix has been verified by QA or other contributors
+- ``disabled`` - the fix or the feature has been backed out or disabled
+- ``verified disabled`` - QA or other contributors confirmed the fix or
+ the feature has been backed out or disabled
+- ``wontfix`` - we have decided not to accept/uplift a fix for this
+ release cycle (it is not the same as the bug resolution WONTFIX).
+ This can also mean that we don’t know how to fix that and will ship
+ with this bug
+- ``fix-optional`` - we would take a fix for the current release but
+ don’t consider it as important/blocking for the release
diff --git a/docs/bug-mgmt/index.rst b/docs/bug-mgmt/index.rst
new file mode 100644
index 0000000000..215b2e7317
--- /dev/null
+++ b/docs/bug-mgmt/index.rst
@@ -0,0 +1,40 @@
+Bug Handling
+============
+
+Guides
+------
+
+.. toctree::
+ :maxdepth: 1
+ :glob:
+
+ guides/*
+
+Policies
+--------
+
+.. toctree::
+ :maxdepth: 1
+ :glob:
+
+ policies/*
+
+Processes
+---------
+
+.. toctree::
+ :maxdepth: 1
+ :glob:
+
+ processes/*
+
+Related documentation
+---------------------
+
+- `bugzilla.mozilla.org documentation <https://bmo.readthedocs.org/>`__
+- `bugzilla.mozilla.org field
+ definitions <https://wiki.mozilla.org/BMO/UserGuide/BugFields>`__
+- `Lando
+ documentation <https://moz-conduit.readthedocs.io/en/latest/lando-user.html>`__
+- `Mozilla Phabricator (Code Review)
+ documentation <https://moz-conduit.readthedocs.io/en/latest/phabricator-user.html>`__
diff --git a/docs/bug-mgmt/policies/new-feature-triage.rst b/docs/bug-mgmt/policies/new-feature-triage.rst
new file mode 100644
index 0000000000..7bc8022777
--- /dev/null
+++ b/docs/bug-mgmt/policies/new-feature-triage.rst
@@ -0,0 +1,55 @@
+New Feature Triage
+==================
+
+Identifying Feature Requests
+----------------------------
+
+Bugs which request new features or enhancements should be of
+type=\ ``enhancement``.
+
+Older bugs may also be feature requests if some or all of the following
+are true:
+
+- Bugs with ``feature`` or similar in whiteboard or short description
+- ``[RFE]`` in whiteboard, short description, or description
+- Bugs not explicitly marked as a feature request, but appear to be
+ feature requests
+- Bugs marked with ``feature`` keyword
+
+Initial Triage
+--------------
+
+Staff, contractors, and other contributors looking at new bugs in
+*Firefox::Untriaged* and *::General* components should consider if a
+bug, if not marked as a feature request, should be one, and if so:
+
+- Update the bug’s type to ``enhancement``
+- Determine which product and component the bug belongs to and update
+ it **or**
+
+ - Use *needinfo* to ask a component’s triage owner or a module’s
+ owner where the request should go
+
+Product Manager Triage
+----------------------
+
+- The product manager for the component reviews bugs of type
+ ``enhancement``
+
+ - This review should be done a least weekly
+
+- Reassigns to another Product::Component if necessary **or**
+- Determines next steps
+
+ - Close bug as ``RESOLVED WONTFIX`` with comment as to why and
+ thanking submitter
+ - If bug is similar enough to work in roadmap, close bug as
+ ``RESOLVED DUPLICATE`` of the feature bug it is similar to
+
+ - If there’s not a feature bug created already, then consider
+ making this bug the feature bug
+
+ - Set type to ``enhancement``
+
+ - Set bug to ``P5`` priority with comment thanking submitter, and
+ explaining that the request will be considered for future roadmaps
diff --git a/docs/bug-mgmt/policies/regressions-github.rst b/docs/bug-mgmt/policies/regressions-github.rst
new file mode 100644
index 0000000000..1a3c6b2a4d
--- /dev/null
+++ b/docs/bug-mgmt/policies/regressions-github.rst
@@ -0,0 +1,151 @@
+Regressions from GitHub
+=======================
+
+Release Management and the weekly regression triage must be aware of the
+status of all reported regressions in order to assure we are not
+shipping known regressions in Firefox releases.
+
+If a team is using GitHub to manage their part of the Firefox project,
+there’s a risk that those groups might not see a regression.
+
+We need an agreed to standard for how we keep track of these.
+
+Policy
+------
+
+*All Firefox components, even if their bugs are tracked off of Bugzilla,
+must have a component in Bugzilla.*
+
+*If a regression bug is found in any of the release trains (Nightly,
+Beta, Release, or ESR) and the bug is in a Firefox component which uses
+an external repository, the regression must be tracked by a bug in
+Bugzilla (whether or not the component in question uses an external
+issue tracker).*
+
+*Unless approved by Release Management, any GitHub managed code with
+open regressions will not be merged to mozilla-central. Even if the
+regression is not code that has been previously merged into
+mozilla-central.*
+
+*All Firefox code managed in GitHub which uses GitHub to manage
+issues* `must use the shared
+tags <https://mozilla.github.io/bmo-harmony/labels>`__.
+
+Comments
+~~~~~~~~
+
+The bug **must** have the regression keyword.
+
+The bug **must** have release flags set.
+
+If the team works in an external bug tracker, then the Bugzilla bug
+**must** reference, using the see-also field, the URL of the bug in the
+external tracker.
+
+The bug **must not** be RESOLVED until the code from the external
+repository containing the change set for the bug has landed in
+mozilla-central. When the change set lands in mozilla-central, the
+Bugzilla tracking bug should be set to RESOLVED FIXED and release status
+flags should be updated to reflect the release trains the fix has been
+landed or uplifted into.
+
+If the change set containing the patch for the regression is reverted
+from mozilla-central, for any reason, then the tracking bug for the
+regression **must** be set to REOPENED and the release status flags
+updated accordingly.
+
+If the change set containing the patch for the bug is backed out, for
+any reason, the bug must be reopened and the status flags on the
+Bugzilla tracking bug updated.
+
+The team responsible for the component with the regression **should**
+strive to create a patch for mozilla-central which contains the fix for
+the bug alone, not a monolithic patch containing changes for several
+other bugs or features.
+
+Landings of third-party libraries `must contain a manifest
+file <https://docs.google.com/document/d/12ihxPXBo9zBBaU_pBsPrc_wNHds4Upr-PwFfiSHrbu8>`__.
+
+Best Practices
+--------------
+
+You must file a regression bug in Bugzilla
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+*If the code with the regression has landed in mozilla-central, you must
+file a regression bug.*
+
+Example
+^^^^^^^
+
+While using a release of Firefox (Nightly, Beta, Release, ESR) you run
+across a bug. Upon research using MozRegression or other tools you find
+that the bug was introduced in a change set imported from a component
+whose code and issues are managed in GitHub.
+
+Actions to take
+'''''''''''''''
+
+- Open a new bug in Bugzilla in appropriate component and add the
+ REGRESSION keyword
+- Set affected status for the releases where the bug appears
+- Open an issue in the corresponding GitHub project, put the Bugzilla
+ bug number in the title with the prefix ‘Bug’ (i.e. “Bug 99999:
+ Regression in foo”)
+- Add the REGRESSION label to the new issue
+- Add the link to the GitHub issue into the ‘See Also” field in the
+ Bugzilla bug
+
+Consequences
+''''''''''''
+
+*Until the regression is fixed or backed out of the GitHub repo, the
+project cannot merge code into mozilla-central*
+
+Example
+^^^^^^^
+
+You import a development branch of a component managed in GitHub into
+your copy of master. You find a bug and isolate it to the imported
+branch. The code is managed in their own GitHub project, but bugs are
+managed in Bugzilla.
+
+Actions to take
+'''''''''''''''
+
+- Open a new bug in Bugzilla in appropriate component and add the
+ REGRESSION keyword
+- Set affected status for the releases where the bug appears
+
+Consequences
+''''''''''''
+
+*Until the regression is fixed or backed out of the GitHub repo, the
+project cannot merge code into mozilla-central*
+
+Do not file a regression bug in Bugzilla
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+*If the code with the regression has not landed in mozilla-central, you
+do not need to file a bug.*
+
+
+Example
+^^^^^^^
+
+You import a development branch of a component managed in GitHub into
+your copy of master. You find a bug and isolate it to the imported
+branch. The code and issues are managed in their own GitHub project.
+
+
+Actions to take
+'''''''''''''''
+
+- File new issue in the GitHub repository of the imported code.
+- Label issue as REGRESSION
+
+Consequence
+'''''''''''
+
+*Issue blocks merge of GitHub project with mozilla-central until
+resolved or backed out.*
diff --git a/docs/bug-mgmt/policies/triage-bugzilla.rst b/docs/bug-mgmt/policies/triage-bugzilla.rst
new file mode 100644
index 0000000000..276979eaf1
--- /dev/null
+++ b/docs/bug-mgmt/policies/triage-bugzilla.rst
@@ -0,0 +1,276 @@
+Triage for Bugzilla
+===================
+
+Expectations
+------------
+
+All teams working on Firefox using either or both Mozilla-central and
+Bugzilla are expected to follow the following process.
+
+What is a Triaged Bug
+---------------------
+
+The new definition of Triaged will be Firefox-related bugs of type
+``defect`` where the component is not
+``UNTRIAGED``, and a severity value not equal to ``--`` or ``N/A``.
+
+Bugs of type Task or Enhancement may have a severity of ``N/A``,
+but defects must have a severity that is neither ``--`` or
+``N/A``.
+
+Why Triage
+----------
+
+We want to make sure that we looked at every defect and a severity has
+been defined. This way, we make sure that we did not miss any critical
+issues during the development and stabilization cycles.
+
+Staying on top of the bugs in your component means:
+
+- You get ahead of critical regressions and crashes which could trigger
+ a point release if uncaught
+
+ - And you don’t want to spend your holiday with the Release
+ Management team (not that they don’t like you)
+
+- Your bug queue is not overwhelming
+
+ - Members of your team do not see the bug queue and get the
+ ‘wiggins’
+
+Who Triages
+-----------
+
+Engineering managers and directors are responsible for naming the
+individuals responsible for triaging :ref:`all types of bugs <Bug Types>` in a component.
+
+We use Bugzilla to manage this. See the `list of triage
+owners <https://bugzilla.mozilla.org/page.cgi?id=triage_owners.html>`__.
+
+If you need to change who is responsible for triaging a bug in a
+component, please `file a bug against bugzilla.mozilla.org in the
+Administration
+component <https://bugzilla.mozilla.org/enter_bug.cgi?product=bugzilla.mozilla.org&component=Administration>`__.
+When a new component is created, a triage owner **must** be named.
+
+Rotating triage
+~~~~~~~~~~~~~~~
+
+Some components are monitored by a rotation of triagers. In those cases,
+the triage owner on Bugzilla will be automatically updated to reflect the
+person on the rotation. The rotations are managed as calendars.
+
+If you wish to set up a rotation for triaging one or more components,
+add a link to your rotation calendar in the `triage rotations spreadsheet <https://docs.google.com/spreadsheets/d/1EK6iCtdD8KP4UflIHscuZo6W5er2vy_TX7vsmaaBVd4>`__.
+
+Firefox::General and Toolkit::General
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Bugs in Firefox::General are fitted with Bug Bug’s model to see if
+there’s another component with a high liklihood of fit, and if a
+threshold confidence is achieved, the bug is moved to that component.
+
+Members of the community also review bugs in this component and try to
+move them.
+
+What Do You Triage
+------------------
+
+As a triage owner the queries you should be following for your component
+are:
+
+- All open bugs, in your components without a pending ``needinfo`` flag
+ which do not have a valid value of severity set
+- All bugs with active review requests in your component which have not
+ been modified in five days
+- All bugs with reviewed, but unlanded patches in your components
+- All bugs with a needinfo request unanswered for more than 10 days
+
+There’s a tool with these queries to help you find bugs
+https://mozilla.github.io/triage-center/ and the source is at
+https://github.com/mozilla/triage-center/.
+
+If a bug is an enhancement it needs a priority set and a target release
+or program milestone. These bugs are normally reviewed by product
+managers. Enhancements can lead to release notes and QA needed that we
+also need to know about
+
+If a bug is a task resulting in a changeset, release managers will need
+to known when this work will be done. A task such as refactoring fragile
+code can be risky.
+
+Weekly or More Frequently (depending on the component) find un-triaged
+bugs in the components you triage.
+
+Decide the :ref:`Severity <Defect Severity>` for each untriaged bug
+(you can override what’s already been set.)
+
+These bugs are reviewed in the weekly Regression Triage meeting
+
+- Bugs of type ``defect`` with the ``regression`` keyword without
+ ``status-firefoxNN`` decisions
+- Bugs of type ``defect`` with the ``regression`` keyword without
+ a regression range
+
+Automatic Bug Updates
+~~~~~~~~~~~~~~~~~~~~~
+
+When a bug is tracked for a release, i.e. the ``tracking_firefoxNN``
+flag is set to ``+`` or ``blocking`` triage decisions will be overridden,
+or made as follows:
+
+- If a bug is tracked for or blocking beta, release or ESR, its
+ priority will be set to ``P1``
+- If a bug is tracked for or blocking nightly, its priority will be set
+ to ``P2``
+
+Because bugs can be bumped in priority it’s essential that triage owners
+review their
+`P1 <https://bugzilla.mozilla.org/buglist.cgi?priority=P1&f1=triage_owner&o1=equals&resolution=---&v1=%25user%25>`__
+and
+`P2 <https://bugzilla.mozilla.org/buglist.cgi?priority=P2&f1=triage_owner&o1=equals&resolution=---&v1=%25user%25>`__
+bugs frequently.
+
+Assumptions
+~~~~~~~~~~~
+
+If a bug's release status in Firefox version N was ``affected`` or ``wontfix``,
+its Severity is ``S3`` or ``S4`` and its Priority is ``P3`` or lower (backlog,)
+then its release status in Firefox version N+1, if the bug is still open,
+is considered to be ``wontfix``.
+
+Questions and Edge Cases
+------------------------
+
+This bug is a feature request
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Set the bug’s type to ``enhancement``, add the ``feature`` keyword if
+relevant, and state to ``NEW``. Set the bug's Severity to ``N/A``. This
+bug will be excluded from future triage queries.
+
+This bug is a task, not a defect
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Set the bug’s type to ``task``, and state to ``NEW``. Set the bug's
+Severity to ``N/A``. This bug will be excluded from future triage queries.
+
+
+If you are not sure of a bug’s type, check :ref:`our rules for bug
+types <Bug Types>`.
+
+This bug’s state is ``UNCONFIRMED``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Are there steps to reproduce? If not, needinfo the person who filed the
+bug, requesting steps to reproduce. You are not obligated to wait
+forever for a response, and bugs for which open requests for information
+go unanswered can be ``RESOLVED`` as ``INCOMPLETE``.
+
+I need help reproducing the bug
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Set a needinfo for the QA managers, Softvision project managers, or the
+QA owner of the component of the bug.
+
+I don’t have enough information to make a decision
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you don’t have a reproduction or confirmation, or have questions
+about how to proceed, ``needinfo`` the person who filed the bug, or
+someone who can answer.
+
+The ``stalled`` keyword
+~~~~~~~~~~~~~~~~~~~~~~~
+
+The extreme case of not-enough-information is one which cannot be
+answered with a ``needinfo`` request. The reporter has shared all they
+know about the bug, we are out of strategies to take to resolve it, but
+the bug should be kept open.
+
+Mark the bug as stalled by adding the ``stalled`` keyword to it. The
+keyword will remove it from the list of bugs to be triaged.
+
+If a patch lands on a ``stalled`` bug, automation will remove the
+keyword. Otherwise, when the ``keyword`` is removed, the bug will have
+its priority reset to ``--`` and the components triage owner notified by
+automation.
+
+Bugs which remain ``stalled`` for long periods of time should be
+reviewed, and closed if necessary.
+
+Bug is in the wrong Component
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If the bug has a Severity of ``S3``, ``S4``, or ``N/A`` move the what
+you think is the correct component, or needinfo the person
+responsible for the component to ask them.
+
+If the bug has a Severity of ``S1`` or ``S2`` then notify Release Management
+and contact the triage owner of the component for which you think it belongs to.
+We cannot lose track of a high severity bug because it is in the wrong component.
+
+My project is on GitHub
+~~~~~~~~~~~~~~~~~~~~~~~
+
+We have :ref:`a guide for GitHub projects to follow <GitHub Metadata Recommendations>` when
+triaging. (Note: this guide needs updating.)
+
+Summary
+-------
+
+Multiple times weekly
+~~~~~~~~~~~~~~~~~~~~~
+
+Use queries for the components you are responsible for in
+https://mozilla.github.io/triage-center/ to find bugs in
+need of triage.
+
+For each untriaged bug:
+
+- Assign a Severity
+- **Do not** assign a ``defect`` a Severity of
+ ``N/A``
+
+You can, but are not required to set the bug's :ref:`Priority <Priority Definitions>`.
+
+Watch open needinfo flags
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Don’t let open needinfo flags linger for more than two weeks.
+
+Close minor bugs with unresponded needinfo flags.
+
+Follow up on needinfo flag requests.
+
+The `Triage Center tool <https://mozilla.github.io/triage-center/>`__ will help you find these.
+
+End of Iteration/Release Cycle
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Any open ``S1`` or ``S2`` bugs at the end of the release cycle
+will require review by engineering and release management. A
+policy on this is forthcoming.
+
+Optional
+^^^^^^^^
+
+(The guidelines on bug priority are under review.)
+
+Are there open P1s? Revisit their priority,
+and move to them to the backlog (``P3``) or ``P2``.
+
+Are there ``P2`` bugs that should move to ``P1``
+for the next cycle?
+
+Are there ``P2`` you now know are lower priority,
+move to ``P3``.
+
+Are there ``P3`` bugs you now know you won’t get to?
+Either demote to ``P5`` (will accept patch) or
+resolve as ``WONTFIX``.
+
+Getting help
+------------
+
+- Ask in #bug-handling on chat.mozilla.org
diff --git a/docs/bug-mgmt/processes/accessibility-review.md b/docs/bug-mgmt/processes/accessibility-review.md
new file mode 100644
index 0000000000..6314e9c684
--- /dev/null
+++ b/docs/bug-mgmt/processes/accessibility-review.md
@@ -0,0 +1,72 @@
+# Accessibility Review
+
+## Introduction
+At Mozilla, accessibility is a fundamental part of our mission to ensure the
+internet is "open and accessible to all," helping to empower people, regardless
+of their abilities, to contribute to the common good. Accessibility Review is a
+service provided by the Mozilla Accessibility team to review features and
+changes to ensure they are accessible to and inclusive of people with
+disabilities.
+
+## Do I Need Accessibility Review?
+You should consider requesting accessibility review if you aren't certain
+whether your change is accessible to people with disabilities. Accessibility
+review is optional, but it is strongly encouraged if you are introducing new
+user interface or are significantly redesigning existing user interface.
+Review should be requested both on the design side _and_ on the engineering side.
+
+## When Should I Request Accessibility Review?
+Generally, it's best to request accessibility review as early as possible, even
+during the product requirements or UI design stage. Particularly for more
+complex user interfaces, accessibility is much easier when incorporated into the
+design, rather than attempting to retro-fit accessibility after the
+implementation is well underway.
+
+The accessibility team has developed the [Mozilla Accessibility Release
+Guidelines](https://wiki.mozilla.org/Accessibility/Guidelines) which outline
+what is needed to make user interfaces accessible. To make accessibility review
+faster, you may wish to try to verify and implement these guidelines prior to
+requesting accessibility review.
+
+For design reviews, please allow at least a week between review request and expected-engineering-handoff. The deadline for engineering review requests is Friday of the first week of nightly builds for the release in which the feature/change is expected to ship.
+This is the same date as the PI Request deadline.
+
+## Requesting Design Review
+Design review should be requested via the #accessibility slack channel. Please post the following information to help us triage your review:
+
+```
+Timeline? (ie. when is engineering handoff? product approval? etc.)
+Tracking/bug issue:
+Product spec:
+Figma file:
+Engineering lead:
+Product manager:
+Have you completed self-review (contrast audit, focus order/role annotations, HCM mockups)?
+```
+
+In addition to posting this information, please complete the following self-review tasks **before requesting review**. Note: Some of the following links require SSO authentication.
+
+- **Perform a contrast audit**: Using a [figma plugin that audits contrast](https://www.figma.com/community/plugin/748533339900865323/Contrast), check your designs for color contrast sufficiency. Your designs should be at least "AA" rated in order to pass accessibility review. "AAA" is even better! If there are particular components that are difficult to adjust to meet "AA" standards, make a note in the figma file and the a11y team will provide specific guidance during review.
+- **Add focus order and role annotations**: Focus order annotations describe the behaviour a keyboard user should expect when navigating your design. Generally this follows the reading order. Note that we only care about focusable elements here (ie. links, buttons, inputs, etc.).
+Role annotations help screen readers and other assistive technologies identify the "kind" of component they're navigating through. These mappings expose semantic information to the user. You can find a [list of common roles here](https://www.codeinwp.com/blog/wai-aria-roles/). You may want to use a [figma plugin that annotates focus and roles](https://www.figma.com/community/plugin/731310036968334777/A11y---Focus-Order) for this process. You do not need to annotate every view in your design, pick those with the largest amount of new content.
+- **Create Windows High-Contrast Mode (HCM) mockups**: Our designs should be accessible to users [running HCM](https://docs.google.com/document/d/1El3XJiAdA5gFcG7H9iI1dNmLbht0hXfi_oKBZPWx3t0/edit). You can read more about [how HCM affects color selection](https://firefox-source-docs.mozilla.org/accessible/ColorsAndHighContrastMode.html), and [how to design for HCM](https://wiki.mozilla.org/Accessibility/Design_Guide). Using the ["Night Sky" HCM palette](https://www.figma.com/file/XQrEePCCJebjlVBQwNggQ6/Pro-Client-Accessibility-Reviews?node-id=25%3A3848), translate your designs into High Contrast. Remember, it's important we use these colors **semantically**, not based on a desire for a particular aesthetic. Colors are labelled according to their uses -- `Background` for page background, `Button Text` for button or control text, `Selected Item Background` for backgrounds of selected or active items, etc.. You do not need to mock up every view in your design, pick those with the largest amount of new content. You can find [examples of previous HCM mock ups here](https://www.figma.com/file/XQrEePCCJebjlVBQwNggQ6/Accessibility).
+Where possible, we should rely on SVG's and PNG's for image content to increase adaptability.
+
+
+## Requesting Engineering Review
+For an engineering-focused review, you submit a review request by setting the a11y-review flag to "requested"
+on a bug in Bugzilla and filling in the template that appears in the comment
+field. For features spanning several bugs, you may wish to file a new, dedicated
+bug for the accessibility review. Otherwise, particularly for smaller changes,
+you may do this on an existing bug. Note that if you file a new bug, you will
+need to submit the bug and then edit it to set the flag.
+
+## Questions?
+If you have any questions, please don't hesitate to contact the Accessibility
+team:
+
+* \#accessibility on
+ [Matrix](https://matrix.to/#/!jmuErVonajdNMbgdeY:mozilla.org?via=mozilla.org&via=matrix.org)
+ or [Slack](https://mozilla.slack.com/archives/C4E0W8B8E)
+* Email: accessibility@mozilla.com
+* Please avoid reaching out to individual team members directly -- containing review requests and questions in these channels helps us balance our workload. Thank you!
diff --git a/docs/bug-mgmt/processes/doc-requests.rst b/docs/bug-mgmt/processes/doc-requests.rst
new file mode 100644
index 0000000000..dc390a27cb
--- /dev/null
+++ b/docs/bug-mgmt/processes/doc-requests.rst
@@ -0,0 +1,39 @@
+User documentation requests
+===========================
+
+If you are working on a change (bugfix, enhancement, or feature) which
+would benefit from user-facing documentation, please use the
+``user-doc-firefox`` flag to request it.
+
+This flag can be modified by anyone with ``EDITBUGS`` privileges.
+
+The default value of the flag is ``---``.
+
+If the bug needs user-facing documentation, set the flag to
+``docs-needed``. This flag will be monitored by the support.mozilla.org
+(SUMO) team.
+
+Once the docs are ready to be published, set the flag to
+``docs-completed``.
+
+If it’s determined that documentation is not need after setting the flag
+to ``docs-needed``, update the flag to ``none-needed`` so we know that
+it’s been reviewed.
+
+Summary
+-------
+
+=========== == ==============
+From To
+=========== == ==============
+— to none-needed
+— to docs-needed
+docs-needed to none-needed
+docs-needed to docs-completed
+=========== == ==============
+
+Notes
+-----
+
+A flag is used instead of the old keywords because flags can be
+restricted to a subset of products and components.
diff --git a/docs/bug-mgmt/processes/fixing-security-bugs.rst b/docs/bug-mgmt/processes/fixing-security-bugs.rst
new file mode 100644
index 0000000000..e07853ac79
--- /dev/null
+++ b/docs/bug-mgmt/processes/fixing-security-bugs.rst
@@ -0,0 +1,217 @@
+Fixing Security Bugs
+====================
+
+A bug has been reported as security-sensitive in Bugzilla and received a
+security rating.
+
+If this bug is private - which is most likely for a reported security
+bug - **the process for patching is slightly different than the usual
+process for fixing a bug**.
+
+Here are security guidelines to follow if you’re involved in reviewing,
+testing and landing a security patch. See
+:ref:`Security Bug Approval Process`
+for more details about how to request sec-approval and land the patch.
+
+Keeping private information private
+-----------------------------------
+
+A security-sensitive bug in Bugzilla means that all information about
+the bug except its ID number are hidden. This includes the title,
+comments, reporter, assignee and CC’d people.
+
+A security-sensitive bug usually remains private until a fix is shipped
+in a new release, **and after a certain amount of time to ensure that a
+maximum number of users updated their version of Firefox**. Bugs are
+usually made public after 6 months and a couple of releases.
+
+From the moment a security bug has been privately reported to the moment
+a fix is shipped and the bug is set public, all information about that
+bug needs to be handled carefully in order to avoid an unmitigated
+vulnerability becoming known and exploited before we release a
+fix (0-day).
+
+During a normal process, information about the nature of bug can be
+accessed through:
+
+- Bug comments (Bugzilla, GitHub issue)
+- Commit message (visible on Bugzilla, tree check-ins and test servers)
+- Code comments
+- Test cases
+- Bug content can potentially be discussed on public IRC/Slack channels
+ and mailing list emails.
+
+When patching for a security bug, you’ll need to be mindful about what
+type of information you share and where.
+
+In commit messages
+~~~~~~~~~~~~~~~~~~
+
+People are watching code check-ins, so we want to avoid sharing
+information which would disclose or help finding a vulnerability too
+easily before we shipped the fix to our users. This includes:
+
+- The **nature of the vulnerability** (overflow, use-after-free, XSS,
+ CSP bypass...)
+- **Ways to trigger and exploit that vulnerability**
+ - In commit messages, code comments and test cases.
+- The fact that a bug / commit is security-related:
+
+ - **Trigger words** in the commit message or code comments such as
+ "security", "exploitable", or the nature of a security vulnerability
+ (overflow, use-after-free…)
+ - **Security approver’s name** in a commit message.
+- The Firefox versions and components affected by the vulnerability.
+- Patches with an obvious fix.
+
+In Bugzilla and other public channels
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In addition to commits, you’ll need to be mindful of not disclosing
+sensitive information about the bug in public places, such as Bugzilla:
+
+- Mention the bugs in comment of the private bug instead.
+- Do not comment sensitive information in public related bugs.
+- Also be careful about who you give bug access to: **double check
+ before CC’ing the wrong person or alias**.
+- As of recently, you may now add public bugs in the “duplicate”,
+ “depends on”, “blocks”, “regression”, “regressed by”, or “see also” section.
+ Bugzilla will only reveal those relationships to people with ``editbugs``
+ permission or access to the security bug.
+
+On IRC, Slack channels, GitHub issues, mailing lists: If you need to
+discuss about a security bug, use a private channel (protected with a
+password or with proper right access management)
+
+During Development
+------------------
+
+Testing security bugs
+~~~~~~~~~~~~~~~~~~~~~
+
+Pushing to Try servers requires Level 1 Commit access but **content
+viewing is publicly accessible**.
+
+As much as possible, **do not push to Try servers**. Testing should be
+done locally before checkin in order to prevent public disclosing of the
+bug.
+
+Because of the public visibility, pushing to Try has all the same concerns
+as committing the patch. Please heed the concerns in the
+:ref:`landing-your-patch` section before thinking about it, and check with
+the security team for an informal "sec-approval" before doing so.
+
+**Do not push the bug's own vulnerability testcase to Try.**
+
+If you need to push to Try servers, make sure your tests don’t disclose
+what the vulnerability is about or how to trigger it. Do not mention
+anywhere it is security related.
+
+Obfuscating a security patch
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If your security patch looks obvious because of the code it contains
+(e.g. a one-line fix), or if you really need to push to Try servers,
+**consider integrating your security-related patch to non-security work
+in the same area**. And/or pretend it is related to something else, like
+some performance improvement or a correctness fix. **Definitely don't
+include the bug number in the commit message.** This will help making
+the security issue less easily identifiable. (The absolute ban against
+"Security through Obscurity" is in relation to cryptographic systems. In
+other situations you still can't *rely* on obscurity but it can
+sometimes buy you a little time. In this context we need to get the
+fixes into the hands of our users faster than attackers can weaponize
+and deploy attacks and a little extra time can help.)
+
+Requesting sec-approval
+~~~~~~~~~~~~~~~~~~~~~~~
+
+See :ref:`Security Bug Approval Process`
+for more details
+
+.. _landing-your-patch:
+
+Landing your patch (with or without sec-approval)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Before asking for sec-approval or landing, ensure your patch does not disclose
+information about the security vulnerability unnecessarily. Specifically:
+
+#. The patch commit message and its contents should not mention security,
+ security bugs, or sec-approvers.
+ Note that you can alter the commit message directly in phabricator,
+ if that's the only thing you need to do - you don't need to amend your
+ local commit and re-push it.
+ While comprehensive commit messages are generally encouraged; they
+ should be omitted for security bugs and instead be posted in the bug
+ (which will eventually become public.)
+#. Separate out tests into a separate commit.
+ **Do not land tests when landing the patch. Remember we don’t want
+ to 0-day ourselves!** This includes when pushing to try.
+
+ - Tests should only be checked in later, after an official Firefox
+ release that contains the fix has been live for at least
+ four weeks. For example, if Firefox 53
+ contains a security issue that affects the world and that issue is
+ fixed in 54, tests for this fix should not be checked in
+ until four weeks after 54 goes live.
+
+ The exception to this is if there is a security issue that doesn't
+ affect any release branches, only mozilla-central and/or other
+ development branches. Since the security problem was never
+ released to the world, once the bug is fixed in all affected
+ places, tests can be checked in to the various branches.
+ - There are two main techniques for remembering to check in the
+ tests later:
+
+ a. clone the sec bug into a separate "task" bug **that is also
+ in a security-sensitive group to ensure it's not publicly visible**
+ called something like "land tests for bug xxxxx" and assign to
+ yourself. It should get a "sec-other" keyword rating.
+
+ Tip: In phabricator, you can change the bug linked to
+ a commit with tests if the tests were already separate, while keeping
+ the previously granted review, meaning you can just land the patch
+ when ready, rather than having your reviewer and you have to remember
+ what this was about a month or two down the line.
+ b. Or, set the "in-testsuite" flag to "?", and later set it to "+"
+ when the tests get checked in.
+
+
+Landing tests
+~~~~~~~~~~~~~
+
+Tests can be landed **once the release containing fixes has been live
+at least 4 weeks**.
+
+The exception is if a security issue has never been shipped in a release
+build and has been fixed in all development branches.
+
+Making a security bug public
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This is the responsibility of the security management team.
+
+Essentials
+----------
+
+- **Do not disclose any information about the vulnerability before a
+ release with a fix has gone live for enough time for users to update
+ their software**.
+
+ - This includes code comments, commit messages, tests, public
+ communication channels.
+
+- If any doubt: '''request sec-approval? '''
+- If any doubt: **needinfo security folks**.
+- **If there’s no rating, assume the worst and treat the bug as
+ sec-critical**.
+
+Documentation & Contacts
+------------------------
+
+- :ref:`Normal process for submitting a patch <How to submit a patch>`
+- `How to file a security bug <https://wiki.mozilla.org/Security/Fileabug>`__
+- `Handling Mozilla security bugs (policy) <https://www.mozilla.org/en-US/about/governance/policies/security-group/bugs/>`__
+- :ref:`Security Bug Approval Process`
+- `Contacting the Security team(s) at Mozilla: <https://wiki.mozilla.org/Security>`__
diff --git a/docs/bug-mgmt/processes/labels.rst b/docs/bug-mgmt/processes/labels.rst
new file mode 100644
index 0000000000..9541d9da61
--- /dev/null
+++ b/docs/bug-mgmt/processes/labels.rst
@@ -0,0 +1,155 @@
+GitHub Metadata Recommendations
+===============================
+
+To have better consistency with code and task tracking among Mozilla
+Central, Bugzilla, and GitHub, we request that you use a common set of
+labels in your projects. Benefits of improved consistency in our
+conventions include:
+
+- Consistency makes measurement of processes simpler across the
+ organization
+- Consistency makes it easier to write re-usable process tools
+- Consistency increases clarity for those than need to work across
+ different repositories and bug trackers
+- Consistency reduces friction around engineering mobility between
+ projects
+
+We recommend creating sets of labels in your project to do this.
+
+Bug types
+---------
+
+In Bugzilla bugs are distinguished by type: ``defect``, ``enhancement``,
+and ``tasks``. Use a label to make this distinction in your project.
+
+Statuses
+--------
+
+Bugs in GitHub issues have two states: closed and open. Bugzilla has a
+richer set of states.
+
+When you close a bug, add a label indicating `the
+resolution <https://wiki.mozilla.org/BMO/UserGuide/BugStatuses#Resolutions>`__.
+
+- ``fixed``
+
+ - A change set for the bug has been landed in Mozilla-Central
+ - A GitHub issue could be closed, but the change set has not
+ landed so it would be still considered open from the
+ Bugzilla point of view
+
+- ``invalid``
+
+ - The problem described is not a bug.
+
+- ``incomplete``
+
+ - The problem is vaguely described with no steps to reproduce, or is
+ a support request.
+
+- ``wontfix``
+
+ - The problem described is a bug which will never be fixed.
+
+- ``duplicate``
+
+ - The problem is a duplicate of an existing bug. Be sure to link the
+ bug this is a duplicate of.
+
+- ``worksforme``
+
+ - All attempts at reproducing this bug were futile, and reading the
+ code produces no clues as to why the described behavior would
+ occur.
+
+Severities (Required)
+---------------------
+
+The triage process for Firefox bugs in Bugzilla requires a non default
+value of a bug's :ref:`Severity (definitions) <Defect Severity>`.
+
+Release Status Flags
+--------------------
+
+Open Firefox bugs may also have :ref:`status flags <Release Status Flags>`
+(``status_firefoxNN``) set for Nightly, Beta, Release, or ESR.
+
+Priorities
+----------
+
+Firefox projects in Bugzilla can use the :ref:`priority field <Priority Definitions>`
+to indicate when a bug will be worked on.
+
+Keywords
+--------
+
+In GitHub issues metadata is either a label or the bug’s open/closed
+state.
+
+Some Bugzilla metadata behaves like labels, but you need to be careful
+with how you use it in order not to confuse QA.
+
+Regressions
+~~~~~~~~~~~
+
+In Bugzilla, the ``regression`` keyword indicates a regression in
+existing behavior introduced by a code change.
+
+When a bug is labeled as a regression in GitHub does it imply the
+regression is in the code module in GitHub, or the module that’s landed
+in Mozilla Central? Using the label ``regression-internal`` will signal
+QA that the regression is internal to your development cycle, and not
+one introduced into the Mozilla Central tree.
+
+If it is not clear which pull request caused the regression, add the
+``regressionwindow-wanted`` label.
+
+Other Keywords
+~~~~~~~~~~~~~~
+
+Other useful labels include ``enhancement`` to distinguish feature
+requests, and ``good first issue`` to signal to contributors (`along
+with adequate
+documentation <http://blog.humphd.org/why-good-first-bugs-often-arent/>`__.)
+
+Summary
+-------
+
+To represent Bugzilla fields, use labels following this scheme.
+
+- Bug types
+
+ - ``defect``, ``enhancement``, ``task``
+
+- Resolution statuses
+
+ - ``invalid``, ``duplicate``, ``incomplete``, ``worksforme``,
+ ``wontfix``
+
+- Regressions
+
+ - ``regression``, ``regressionwindow-wanted``,
+ ``regression-internal``
+
+
+- :ref:`Severity <Defect Severity>` (required)
+
+ - ``S1``, ``S2``, ``S3``, ``S4``, ``N/A`` (reserved for bugs
+ of type ``task`` or ``enhancement``)
+
+- :ref:`Status flags <Release Status Flags>`
+
+ - ``status_firefoxNN:<status>``
+ (example ``status_firefox77:affected``)
+
+- :ref:`Priority <Priority Definitions>`
+
+ - ``P1``, ``P2``, ``P3``, ``P5``
+
+- Other keywords
+
+ - ``good first bug``, ``perf``, &etc.
+
+
+You may already have a set of tags, so do an edit to convert them
+or use `the GitHub settings app <https://github.com/probot/settings>`__.
diff --git a/docs/bug-mgmt/processes/regressions.rst b/docs/bug-mgmt/processes/regressions.rst
new file mode 100644
index 0000000000..991771f38d
--- /dev/null
+++ b/docs/bug-mgmt/processes/regressions.rst
@@ -0,0 +1,64 @@
+How to Mark Regressions
+=======================
+
+Regressions
+-----------
+
+For regression bugs in Mozilla-Central, our policy is to tag the bug as
+a regression, identify the commits which caused the regression, then
+mark the bugs associated with those commits as causing the regression.
+
+What is a regression?
+---------------------
+
+A regression is a bug (in our scheme a ``defect``) introduced by a
+`changeset <https://en.wikipedia.org/wiki/Changeset>`__.
+
+- Bug 101 *fixes* Bug 100 with Change Set A
+- Bug 102 *reported which describes previously correct behavior now not
+ happening*
+- Bug 102 *investigated and found to be introduced by Change Set A*
+
+Marking a Regression Bug
+------------------------
+
+These things are true about regressions:
+
+- **Bug Type** is ``defect``
+- **Keywords** include ``regression``
+- **Status_FirefoxNN** is ``affected`` for each version (in current
+ nightly, beta, and release) of Firefox in which the bug was found
+- The bug’s description covers previously working behavior which is no
+ longer working [ed. I need a better phrase for this]
+
+Until the change set which caused the regression has been found through
+`mozregression <https://mozilla.github.io/mozregression/>`__ or another
+bisection tool, the bug should also have the ``regressionwindow-wanted``
+keyword.
+
+Once the change set which caused the regression has been identified,
+remove the ``regressionwindow-wanted`` keyword and set the **Regressed
+By** field to the id of the bug associated with the change set.
+
+Setting the **Regressed By** field will update the **Regresses** field
+in the other bug.
+
+Set a needinfo for the author of the regressing patch asking them to fix
+or revert the regression.
+
+Previous Method
+---------------
+
+Previously we over-loaded the **Blocks** and **Blocked By** fields to
+track the regression, setting **Blocks** to the id of the bug associated
+with the change set causing the regression, and using the
+``regression``, ``regressionwindow-wanted`` keywords and the status
+flags as described above.
+
+This made it difficult to understand what was a dependency and what was
+a regression when looking at dependency trees in Bugzilla.
+
+FAQs
+----
+
+*To be written*
diff --git a/docs/bug-mgmt/processes/security-approval.rst b/docs/bug-mgmt/processes/security-approval.rst
new file mode 100644
index 0000000000..13f0057b98
--- /dev/null
+++ b/docs/bug-mgmt/processes/security-approval.rst
@@ -0,0 +1,194 @@
+Security Bug Approval Process
+=============================
+
+How to fix a core-security bug in Firefox - developer guidelines
+----------------------------------------------------------------
+
+Follow these security guidelines if you’re involved in reviewing,
+testing and landing a security patch:
+:ref:`Fixing Security Bugs`.
+
+Purpose: don't 0-day ourselves
+------------------------------
+
+People watch our check-ins. They may be able to start exploiting our
+users before we can get an update out to them if
+
+- the patch is an obvious security fix (bounds check, kungFuDeathGrip,
+ etc.)
+- the check-in comment says "security fix" or includes trigger words
+ like "exploitable", "vulnerable", "overflow", "injection", "use after
+ free", etc.
+- comments in the code mention those types of things or how someone
+ could abuse the bug
+- the check-in contains testcases that show exactly how to trigger the
+ vulnerability
+
+Principle: assume the worst
+---------------------------
+
+- If there's no rating we assume it could be critical
+- If we don't know the regression range we assume it needs porting to
+ all supported branches
+
+Process for Security Bugs (Developer Perspective)
+-------------------------------------------------
+
+Filing / Managing Bugs
+~~~~~~~~~~~~~~~~~~~~~~
+
+- Try whenever possible to file security bugs marked as such when
+ filing, instead of filing them as open bugs and then closing later.
+ This is not always possible, but attention to this, especially when
+ filing from crash-stats, is helpful.
+- It is _ok_ to link security bugs to non-security bugs with Blocks,
+ Depends, Regressions, or See Also. Users with the editbugs permission
+ will be able to see the reference, but not view a restricted bug.
+ Users without the permission will not be able to see the link.
+
+Developing the Patch
+~~~~~~~~~~~~~~~~~~~~
+
+- Comments in the code should not mention a security issue is being
+ fixed. Don’t paint a picture or an arrow pointing to security issues
+ any more than the code changes already do.
+- Avoid linking it to non-security bugs with Blocks, Depends, or See
+ Also, especially if those bugs may give a hint to the sort of
+ security issue involved. Mention the bug in a comment on the security
+ bug instead. We can always fill in the links later after the fix has
+ shipped.
+- Do not push to Try servers if possible: this exposes the security
+ issues for these critical and high rated bugs to public viewing. In
+ an ideal case, testing of patches is done locally before final
+ check-in to mozilla-central.
+- If pushing to Try servers is necessary, **do not include the bug
+ number in the patch**. Ideally, do not include tests in the push as
+ the tests can illustrate the exact nature of the security problem
+ frequently.
+- If you must push to Try servers, with or without tests, try to
+ obfuscate what this patch is for. Try to push it with other,
+ non-security work, in the same area.
+
+Request review of the patch in the same process as normal. After the
+patch has been reviewed you will request sec-approval as needed. See
+:ref:`Fixing Security Bugs`
+for more examples/details of these points.
+
+Preparing the patch for landing
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+See :ref:`Fixing Security Bugs`
+for more details.
+
+On Requesting sec-approval
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+For security bugs with no sec- severity rating assume the worst and
+follow the rules for sec-critical. During the sec-approval process we
+will notice it has not been rated and rate it during the process.
+
+Core-security bug fixes can be landed by a developer without any
+explicit approval if:
+
+| **A)** The bug has a sec-low, sec-moderate, sec-other, or sec-want
+ rating.
+|    **or**
+| **B)** The bug is a recent regression on mozilla-central. This means
+
+- A specific regressing check-in has been identified
+- The developer can (**and has**) marked the status flags for ESR and
+ Beta as "unaffected"
+- We have not shipped this vulnerability in anything other than a
+ nightly build
+
+If it meets the above criteria, developers do not need to ask for sec-approval.
+
+In all other cases, developers should ask for sec-approval.
+Set the sec-approval flag to '?' on the patch when it is ready to be landed.
+You will find these flags in Bugzilla using the "Details" links in the
+Bugzilla attachment table (not directly on phabricator at time of writing).
+
+If developers are unsure about a bug and it has a patch ready, just
+request sec-approval anyway and move on. Don't overthink it!
+
+An automatic nomination comment will be added to bugzilla when
+sec-approval is set to '?'. The questions in this need to be filled out
+as best as possible when sec-approval is requested for the patch.
+
+It is as follows (courtesy of Dan Veditz)::
+
+ [Security approval request comment]
+ How easily can the security issue be deduced from the patch?
+ Do comments in the patch, the check-in comment, or tests included in
+ the patch paint a bulls-eye on the security problem?
+ Which older supported branches are affected by this flaw?
+ If not all supported branches, which bug introduced the flaw?
+ Do you have backports for the affected branches? If not, how
+ different, hard to create, and risky will they be?
+ How likely is this patch to cause regressions; how much testing does
+ it need?
+
+This is similar to the ESR approval nomination form and is meant to help
+us evaluate the risks around approving the patch for checkin.
+
+When the bug is approved for landing, the sec-approval flag will be set
+to '+' with a comment from the approver to land the patch. At that
+point, land it according to instructions provided..
+
+This will allow us to control when we can land security bugs without
+exposing them too early and to make sure they get landed on the various
+branches.
+
+If you have any questions or are unsure about anything in this document
+contact us on Slack in the #security channel or the current
+sec-approvers Dan Veditz and Tom Ritter.
+
+Process for Security Bugs (sec-approver Perspective)
+----------------------------------------------------
+
+The security assurance team and release management will have their own
+process for approving bugs:
+
+#. The Security assurance team goes through sec-approval ? bugs daily
+ and approves low risk fixes for central (if early in cycle).
+ Developers can also ping the Security Assurance Team (specifically
+ Tom Ritter & Dan Veditz) in #security on Slack when important.
+
+ #. If a bug lacks a security-rating one should be assigned - possibly
+ in coordination with the (other member of) the Security Assurance
+ Team
+
+#. Security team marks tracking flags to ? for all affected versions
+ when approved for central. (This allows release management to decide
+ whether to uplift to branches just like always.)
+#. Weekly security/release management triage meeting goes through
+ sec-approval + and ? bugs where beta and ESR is affected, ? bugs with
+ higher risk (sec-high and sec-critical), or ? bugs near end of cycle.
+
+Options for sec-approval including a logical combination of the
+following:
+
+- Separate out the test and comments in the code into a followup commit
+ we will commit later.
+- Remove the commit message and place it in the bug or comments in a
+ followup commit.
+- Please land it bundled in with another commit
+- Land today
+- Land today, land the tests after
+- Land closer to the release date
+- Land in Nightly to assess stability
+- Land today and request uplift to all branches
+- Request uplift to all branches and we'll land as close to shipping as
+ permitted
+- Chemspill time
+
+The decision process for which of these to choose is perceived risk on
+multiple axes:
+
+- ease of exploitation
+- reverse engineering risk
+- stability risk
+
+The most common choice is: not much stability risk, not an immediate
+reverse engineering risk, moderate to high difficulty of exploitation:
+"land whenever".
diff --git a/docs/bug-mgmt/processes/shared-bug-queues.rst b/docs/bug-mgmt/processes/shared-bug-queues.rst
new file mode 100644
index 0000000000..dc2df9bbf9
--- /dev/null
+++ b/docs/bug-mgmt/processes/shared-bug-queues.rst
@@ -0,0 +1,34 @@
+Shared Bug Queues
+=================
+
+Reviewers for change sets can be suggested at the product and component
+level, but only the person who has been asked to review code will be
+notified.
+
+Realizing that Bugzilla users can *watch* other users, `Chris
+Cooper <https://mozillians.org/en-US/u/coop/>`__ came up with the idea
+of having `a shared reviews alias for review
+requests <http://coopcoopbware.tumblr.com/post/170952242320/experiments-in-productivity-the-shared-bug-queue>`__.
+
+If you want to watch a particular part of the tree in Mozilla Central,
+then `use the Herald
+tool <https://phabricator.services.mozilla.com/book/phabricator/article/herald/>`__.
+
+Process
+-------
+
+1. Create a new bugzilla.mozilla.com account for an address which can
+ receive mail.
+ Use the ``name+extension@domain.tld`` trick such as
+ ``jmozillian+reviews@mozilla.com`` to create a unique address
+2. Respond to the email sent by Bugzilla and set a password on the
+ account
+3. `Open a bug <https://mzl.la/2Mg8Sli>`__ to convert the account to a
+ bot and make it the shared review queue for your component
+4. BMO administrator updates the email address of the new account to the
+ ``@mozilla.bugs`` address
+5. BMO administrator updates the default reviewer for the component
+ requested and sets it to the shared review account
+6. Reviewers `follow the shared review account in
+ bugzilla <https://bugzilla.mozilla.org/userprefs.cgi?tab=email>`__
+7. Reviewers get notified when shared review account is ``r?``\ ed
diff --git a/docs/code-quality/coding-style/about-logins-rtl.png b/docs/code-quality/coding-style/about-logins-rtl.png
new file mode 100644
index 0000000000..a5d0edd4c8
--- /dev/null
+++ b/docs/code-quality/coding-style/about-logins-rtl.png
Binary files differ
diff --git a/docs/code-quality/coding-style/about-protections-rtl.png b/docs/code-quality/coding-style/about-protections-rtl.png
new file mode 100644
index 0000000000..4fbbf5e889
--- /dev/null
+++ b/docs/code-quality/coding-style/about-protections-rtl.png
Binary files differ
diff --git a/docs/code-quality/coding-style/coding_style_cpp.rst b/docs/code-quality/coding-style/coding_style_cpp.rst
new file mode 100644
index 0000000000..cb4764cea5
--- /dev/null
+++ b/docs/code-quality/coding-style/coding_style_cpp.rst
@@ -0,0 +1,1150 @@
+================
+C++ Coding style
+================
+
+
+This document attempts to explain the basic styles and patterns used in
+the Mozilla codebase. New code should try to conform to these standards,
+so it is as easy to maintain as existing code. There are exceptions, but
+it's still important to know the rules!
+
+This article is particularly for those new to the Mozilla codebase, and
+in the process of getting their code reviewed. Before requesting a
+review, please read over this document, making sure that your code
+conforms to recommendations.
+
+.. container:: blockIndicator warning
+
+ The Firefox code base adopts parts of the `Google Coding style for C++
+ code <https://google.github.io/styleguide/cppguide.html>`__, but not all of its rules.
+ A few rules are followed across the code base, others are intended to be
+ followed in new or significantly revised code. We may extend this list in the
+ future, when we evaluate the Google Coding Style for C++ Code further and/or update
+ our coding practices. However, the plan is not to adopt all rules of the Google Coding
+ Style for C++ Code. Some rules are explicitly unlikely to be adopted at any time.
+
+ Followed across the code base:
+
+ - `Formatting <https://google.github.io/styleguide/cppguide.html#Formatting>`__,
+ except for subsections noted here otherwise
+ - `Implicit Conversions <https://google.github.io/styleguide/cppguide.html#Implicit_Conversions>`__,
+ which is enforced by a custom clang-plugin check, unless explicitly overridden using
+ ``MOZ_IMPLICIT``
+
+ Followed in new/significantly revised code:
+
+ - `Include guards <https://google.github.io/styleguide/cppguide.html#The__define_Guard>`__
+
+ Unlikely to be ever adopted:
+
+ - `Forward declarations <https://google.github.io/styleguide/cppguide.html#Forward_Declarations>`__
+ - `Formatting/Conditionals <https://google.github.io/styleguide/cppguide.html#Conditionals>`__
+ w.r.t. curly braces around inner statements, we require them in all cases where the
+ Google style allows to leave them out for single-line conditional statements
+
+ This list reflects the state of the Google Google Coding Style for C++ Code as of
+ 2020-07-17. It may become invalid when the Google modifies its Coding Style.
+
+
+Formatting code
+---------------
+
+Formatting is done automatically via clang-format, and controlled via in-tree
+configuration files. See :ref:`Formatting C++ Code With clang-format`
+for more information.
+
+Unix-style linebreaks (``\n``), not Windows-style (``\r\n``). You can
+convert patches, with DOS newlines to Unix via the ``dos2unix`` utility,
+or your favorite text editor.
+
+Static analysis
+---------------
+
+Several of the rules in the Google C++ coding styles and the additions mentioned below
+can be checked via clang-tidy (some rules are from the upstream clang-tidy, some are
+provided via a mozilla-specific plugin). Some of these checks also allow fixes to
+be automatically applied.
+
+``mach static-analysis`` provides a convenient way to run these checks. For example,
+for the check called ``google-readability-braces-around-statements``, you can run:
+
+.. code-block:: shell
+
+ ./mach static-analysis check --checks="-*,google-readability-braces-around-statements" --fix <file>
+
+It may be necessary to reformat the files after automatically applying fixes, see
+:ref:`Formatting C++ Code With clang-format`.
+
+Additional rules
+----------------
+
+*The norms in this section should be followed for new code. For existing code,
+use the prevailing style in a file or module, ask the owner if you are
+in another team's codebase or it's not clear what style to use.*
+
+
+
+
+Control structures
+~~~~~~~~~~~~~~~~~~
+
+Always brace controlled statements, even a single-line consequent of
+``if else else``. This is redundant, typically, but it avoids dangling
+else bugs, so it's safer at scale than fine-tuning.
+
+Examples:
+
+.. code-block:: cpp
+
+ if (...) {
+ } else if (...) {
+ } else {
+ }
+
+ while (...) {
+ }
+
+ do {
+ } while (...);
+
+ for (...; ...; ...) {
+ }
+
+ switch (...) {
+ case 1: {
+ // When you need to declare a variable in a switch, put the block in braces.
+ int var;
+ break;
+ }
+ case 2:
+ ...
+ break;
+ default:
+ break;
+ }
+
+``else`` should only ever be followed by ``{`` or ``if``; i.e., other
+control keywords are not allowed and should be placed inside braces.
+
+.. note::
+
+ For this rule, clang-tidy provides the ``google-readability-braces-around-statements``
+ check with autofixes.
+
+
+C++ namespaces
+~~~~~~~~~~~~~~
+
+Mozilla project C++ declarations should be in the ``mozilla``
+namespace. Modules should avoid adding nested namespaces under
+``mozilla``, unless they are meant to contain names which have a high
+probability of colliding with other names in the code base. For example,
+``Point``, ``Path``, etc. Such symbols can be put under
+module-specific namespaces, under ``mozilla``, with short
+all-lowercase names. Other global namespaces besides ``mozilla`` are
+not allowed.
+
+No ``using`` directives are allowed in header files, except inside class
+definitions or functions. (We don't want to pollute the global scope of
+compilation units that use the header file.)
+
+.. note::
+
+ For parts of this rule, clang-tidy provides the ``google-global-names-in-headers``
+ check. It only detects ``using namespace`` directives in the global namespace.
+
+
+``using namespace ...;`` is only allowed in ``.cpp`` files after all
+``#include``\ s. Prefer to wrap code in ``namespace ... { ... };``
+instead, if possible. ``using namespace ...;``\ should always specify
+the fully qualified namespace. That is, to use ``Foo::Bar`` do not
+write ``using namespace Foo; using namespace Bar;``, write
+``using namespace Foo::Bar;``
+
+Use nested namespaces (ex: ``namespace mozilla::widget {``
+
+.. note::
+
+ clang-tidy provides the ``modernize-concat-nested-namespaces``
+ check with autofixes.
+
+
+Anonymous namespaces
+~~~~~~~~~~~~~~~~~~~~
+
+We prefer using ``static``, instead of anonymous C++ namespaces. This may
+change once there is better debugger support (especially on Windows) for
+placing breakpoints, etc. on code in anonymous namespaces. You may still
+use anonymous namespaces for things that can't be hidden with ``static``,
+such as types, or certain objects which need to be passed to template
+functions.
+
+
+C++ classes
+~~~~~~~~~~~~
+
+.. code-block:: cpp
+
+ namespace mozilla {
+
+ class MyClass : public A
+ {
+ ...
+ };
+
+ class MyClass
+ : public X
+ , public Y
+ {
+ public:
+ MyClass(int aVar, int aVar2)
+ : mVar(aVar)
+ , mVar2(aVar2)
+ {
+ ...
+ }
+
+ // Special member functions, like constructors, that have default bodies
+ // should use '= default' annotation instead.
+ MyClass() = default;
+
+ // Unless it's a copy or move constructor or you have a specific reason to allow
+ // implicit conversions, mark all single-argument constructors explicit.
+ explicit MyClass(OtherClass aArg)
+ {
+ ...
+ }
+
+ // This constructor can also take a single argument, so it also needs to be marked
+ // explicit.
+ explicit MyClass(OtherClass aArg, AnotherClass aArg2 = AnotherClass())
+ {
+ ...
+ }
+
+ int LargerFunction()
+ {
+ ...
+ ...
+ }
+
+ private:
+ int mVar;
+ };
+
+ } // namespace mozilla
+
+Define classes using the style given above.
+
+.. note::
+
+ For the rule on ``= default``, clang-tidy provides the ``modernize-use-default``
+ check with autofixes.
+
+ For the rule on explicit constructors and conversion operators, clang-tidy
+ provides the ``mozilla-implicit-constructor`` check.
+
+Existing classes in the global namespace are named with a short prefix
+(For example, ``ns``) as a pseudo-namespace.
+
+
+Methods and functions
+~~~~~~~~~~~~~~~~~~~~~
+
+
+C/C++
+^^^^^
+
+In C/C++, method names should use ``UpperCamelCase``.
+
+Getters that never fail, and never return null, are named ``Foo()``,
+while all other getters use ``GetFoo()``. Getters can return an object
+value, via a ``Foo** aResult`` outparam (typical for an XPCOM getter),
+or as an ``already_AddRefed<Foo>`` (typical for a WebIDL getter,
+possibly with an ``ErrorResult& rv`` parameter), or occasionally as a
+``Foo*`` (typical for an internal getter for an object with a known
+lifetime). See `the bug 223255 <https://bugzilla.mozilla.org/show_bug.cgi?id=223255>`_
+for more information.
+
+XPCOM getters always return primitive values via an outparam, while
+other getters normally use a return value.
+
+Method declarations must use, at most, one of the following keywords:
+``virtual``, ``override``, or ``final``. Use ``virtual`` to declare
+virtual methods, which do not override a base class method with the same
+signature. Use ``override`` to declare virtual methods which do
+override a base class method, with the same signature, but can be
+further overridden in derived classes. Use ``final`` to declare virtual
+methods which do override a base class method, with the same signature,
+but can NOT be further overridden in the derived classes. This should
+help the person reading the code fully understand what the declaration
+is doing, without needing to further examine base classes.
+
+.. note::
+
+ For the rule on ``virtual/override/final``, clang-tidy provides the
+ ``modernize-use-override`` check with autofixes.
+
+
+Operators
+~~~~~~~~~
+
+The unary keyword operator ``sizeof``, should have its operand parenthesized
+even if it is an expression; e.g. ``int8_t arr[64]; memset(arr, 42, sizeof(arr));``.
+
+
+Literals
+~~~~~~~~
+
+Use ``\uXXXX`` unicode escapes for non-ASCII characters. The character
+set for XUL, script, and properties files is UTF-8, which is not easily
+readable.
+
+
+Prefixes
+~~~~~~~~
+
+Follow these naming prefix conventions:
+
+
+Variable prefixes
+^^^^^^^^^^^^^^^^^
+
+- k=constant (e.g. ``kNC_child``). Not all code uses this style; some
+ uses ``ALL_CAPS`` for constants.
+- g=global (e.g. ``gPrefService``)
+- a=argument (e.g. ``aCount``)
+- C++ Specific Prefixes
+
+ - s=static member (e.g. ``sPrefChecked``)
+ - m=member (e.g. ``mLength``)
+ - e=enum variants (e.g. ``enum Foo { eBar, eBaz }``). Enum classes
+ should use ``CamelCase`` instead (e.g.
+ ``enum class Foo { Bar, Baz }``).
+
+
+Global functions/macros/etc
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Macros begin with ``MOZ_``, and are all caps (e.g.
+ ``MOZ_WOW_GOODNESS``). Note that older code uses the ``NS_`` prefix;
+ while these aren't being changed, you should only use ``MOZ_`` for
+ new macros. The only exception is if you're creating a new macro,
+ which is part of a set of related macros still using the old ``NS_``
+ prefix. Then you should be consistent with the existing macros.
+
+
+Error Variables
+^^^^^^^^^^^^^^^
+
+- Local variables that are assigned ``nsresult`` result codes should be named ``rv``
+ (i.e., e.g., not ``res``, not ``result``, not ``foo``). `rv` should not be
+ used for bool or other result types.
+- Local variables that are assigned ``bool`` result codes should be named `ok`.
+
+
+C/C++ practices
+---------------
+
+- **Have you checked for compiler warnings?** Warnings often point to
+ real bugs. `Many of them <https://searchfox.org/mozilla-central/source/build/moz.configure/warnings.configure>`__
+ are enabled by default in the build system.
+- In C++ code, use ``nullptr`` for pointers. In C code, using ``NULL``
+ or ``0`` is allowed.
+
+.. note::
+
+ For the C++ rule, clang-tidy provides the ``modernize-use-nullptr`` check
+ with autofixes.
+
+- Don't use ``PRBool`` and ``PRPackedBool`` in C++, use ``bool``
+ instead.
+- For checking if a ``std`` container has no items, don't use
+ ``size()``, instead use ``empty()``.
+- When testing a pointer, use ``(!myPtr)`` or ``(myPtr)``;
+ don't use ``myPtr != nullptr`` or ``myPtr == nullptr``.
+- Do not compare ``x == true`` or ``x == false``. Use ``(x)`` or
+ ``(!x)`` instead. ``if (x == true)`` may have semantics different from
+ ``if (x)``!
+
+.. note::
+
+ clang-tidy provides the ``readability-simplify-boolean-expr`` check
+ with autofixes that checks for these and some other boolean expressions
+ that can be simplified.
+
+- In general, initialize variables with ``nsFoo aFoo = bFoo,`` and not
+ ``nsFoo aFoo(bFoo)``.
+
+ - For constructors, initialize member variables with : ``nsFoo
+ aFoo(bFoo)`` syntax.
+
+- To avoid warnings created by variables used only in debug builds, use
+ the
+ `DebugOnly<T> <https://developer.mozilla.org/docs/Mozilla/Debugging/DebugOnly%3CT%3E>`__
+ helper when declaring them.
+- You should `use the static preference
+ API <https://firefox-source-docs.mozilla.org/modules/libpref/index.html>`__ for
+ working with preferences.
+- One-argument constructors, that are not copy or move constructors,
+ should generally be marked explicit. Exceptions should be annotated
+ with ``MOZ_IMPLICIT``.
+- Use ``char32_t`` as the return type or argument type of a method that
+ returns or takes as argument a single Unicode scalar value. (Don't
+ use UTF-32 strings, though.)
+- Prefer unsigned types for semantically-non-negative integer values.
+- When operating on integers that could overflow, use ``CheckedInt``.
+- Avoid the usage of ``typedef``, instead, please use ``using`` instead.
+
+.. note::
+
+ For parts of this rule, clang-tidy provides the ``modernize-use-using``
+ check with autofixes.
+
+
+Header files
+------------
+
+Since the Firefox code base is huge and uses a monolithic build, it is
+of utmost importance for keeping build times reasonable to limit the
+number of included files in each translation unit to the required minimum.
+Exported header files need particular attention in this regard, since their
+included files propagate, and many of them are directly or indirectly
+included in a large number of translation units.
+
+- Include guards are named per the Google coding style (i.e. upper snake
+ case with a single trailing underscore). They should not include a
+ leading ``MOZ_`` or ``MOZILLA_``. For example, ``dom/media/foo.h``
+ would use the guard ``DOM_MEDIA_FOO_H_``.
+- Forward-declare classes in your header files, instead of including
+ them, whenever possible. For example, if you have an interface with a
+ ``void DoSomething(nsIContent* aContent)`` function, forward-declare
+ with ``class nsIContent;`` instead of ``#include "nsIContent.h"``.
+ If a "forwarding header" is provided for a type, include that instead of
+ putting the literal forward declaration(s) in your header file. E.g. for
+ some JavaScript types, there is ``js/TypeDecls.h``, for the string types
+ there is ``StringFwd.h``. One reason for this is that this allows
+ changing a type to a type alias by only changing the forwarding header.
+ The following uses of a type can be done with a forward declaration only:
+
+ - Parameter or return type in a function declaration
+ - Member/local variable pointer or reference type
+ - Use as a template argument (not in all cases) in a member/local variable type
+ - Defining a type alias
+
+ The following uses of a type require a full definition:
+
+ - Base class
+ - Member/local variable type
+ - Use with delete or new
+ - Use as a template argument (not in all cases)
+ - Any uses of non-scoped enum types
+ - Enum values of a scoped enum type
+
+ Use as a template argument is somewhat tricky. It depends on how the
+ template uses the type. E.g. ``mozilla::Maybe<T>`` and ``AutoTArray<T>``
+ always require a full definition of ``T`` because the size of the
+ template instance depends on the size of ``T``. ``RefPtr<T>`` and
+ ``UniquePtr<T>`` don't require a full definition (because their
+ pointer member always has the same size), but their destructor
+ requires a full definition. If you encounter a template that cannot
+ be instantiated with a forward declaration only, but it seems
+ it should be possible, please file a bug (if it doesn't exist yet).
+
+ Therefore, also consider the following guidelines to allow using forward
+ declarations as widely as possible.
+- Inline function bodies in header files often pull in a lot of additional
+ dependencies. Be mindful when adding or extending inline function bodies,
+ and consider moving the function body to the cpp file or to a separate
+ header file that is not included everywhere. Bug 1677553 intends to provide
+ a more specific guideline on this.
+- Consider the use of the `Pimpl idiom <https://en.cppreference.com/w/cpp/language/pimpl>`__,
+ i.e. hide the actual implementation in a separate ``Impl`` class that is
+ defined in the implementation file and only expose a ``class Impl;`` forward
+ declaration and ``UniquePtr<Impl>`` member in the header file.
+- Do not use non-scoped enum types. These cannot be forward-declared. Use
+ scoped enum types instead, and forward declare them when possible.
+- Avoid nested types that need to be referenced from outside the class.
+ These cannot be forward declared. Place them in a namespace instead, maybe
+ in an extra inner namespace, and forward declare them where possible.
+- Avoid mixing declarations with different sets of dependencies in a single
+ header file. This is generally advisable, but even more so when some of these
+ declarations are used by a subset of the translation units that include the
+ combined header file only. Consider such a badly mixed header file like:
+
+ .. code-block:: cpp
+
+ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+ /* vim: set ts=8 sts=2 et sw=2 tw=80: */
+ /* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ #ifndef BAD_MIXED_FILE_H_
+ #define BAD_MIXED_FILE_H_
+
+ // Only this include is needed for the function declaration below.
+ #include "nsCOMPtr.h"
+
+ // These includes are only needed for the class definition.
+ #include "nsIFile.h"
+ #include "mozilla/ComplexBaseClass.h"
+
+ namespace mozilla {
+
+ class WrappedFile : public nsIFile, ComplexBaseClass {
+ // ... class definition left out for clarity
+ };
+
+ // Assuming that most translation units that include this file only call
+ // the function, but don't need the class definition, this should be in a
+ // header file on its own in order to avoid pulling in the other
+ // dependencies everywhere.
+ nsCOMPtr<nsIFile> CreateDefaultWrappedFile(nsCOMPtr<nsIFile>&& aFileToWrap);
+
+ } // namespace mozilla
+
+ #endif // BAD_MIXED_FILE_H_
+
+
+An example header file based on these rules (with some extra comments):
+
+.. code-block:: cpp
+
+ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+ /* vim: set ts=8 sts=2 et sw=2 tw=80: */
+ /* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ #ifndef DOM_BASE_FOO_H_
+ #define DOM_BASE_FOO_H_
+
+ // Include guards should come at the very beginning and always use exactly
+ // the style above. Otherwise, compiler optimizations that avoid rescanning
+ // repeatedly included headers might not hit and cause excessive compile
+ // times.
+
+ #include <cstdint>
+ #include "nsCOMPtr.h" // This is needed because we have a nsCOMPtr<T> data member.
+
+ class nsIFile; // Used as a template argument only.
+ enum class nsresult : uint32_t; // Used as a parameter type only.
+ template <class T>
+ class RefPtr; // Used as a return type only.
+
+ namespace mozilla::dom {
+
+ class Document; // Used as a template argument only.
+
+ // Scoped enum, not as a nested type, so it can be
+ // forward-declared elsewhere.
+ enum class FooKind { Small, Big };
+
+ class Foo {
+ public:
+ // Do not put the implementation in the header file, it would
+ // require including nsIFile.h
+ Foo(nsCOMPtr<nsIFile> aFile, FooKind aFooKind);
+
+ RefPtr<Document> CreateDocument();
+
+ void SetResult(nsresult aResult);
+
+ // Even though we will default this destructor, do this in the
+ // implementation file since we would otherwise need to include
+ // nsIFile.h in the header.
+ ~Foo();
+
+ private:
+ nsCOMPtr<nsIFile> mFile;
+ };
+
+ } // namespace mozilla::dom
+
+ #endif // DOM_BASE_FOO_H_
+
+
+Corresponding implementation file:
+
+.. code-block:: cpp
+
+ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+ /* vim: set ts=8 sts=2 et sw=2 tw=80: */
+ /* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ #include "mozilla/dom/Foo.h" // corresponding header
+
+ #include "mozilla/Assertions.h" // Needed for MOZ_ASSERT.
+ #include "mozilla/dom/Document.h" // Needed because we construct a Document.
+ #include "nsError.h" // Needed because we use NS_OK aka nsresult::NS_OK.
+ #include "nsIFile.h" // This is needed because our destructor indirectly calls delete nsIFile in a template instance.
+
+ namespace mozilla::dom {
+
+ // Do not put the implementation in the header file, it would
+ // require including nsIFile.h
+ Foo::Foo(nsCOMPtr<nsIFile> aFile, FooKind aFooKind)
+ : mFile{std::move(aFile)} {
+ }
+
+ RefPtr<Document> Foo::CreateDocument() {
+ return MakeRefPtr<Document>();
+ }
+
+ void Foo::SetResult(nsresult aResult) {
+ MOZ_ASSERT(aResult != NS_OK);
+
+ // do something with aResult
+ }
+
+ // Even though we default this destructor, do this in the
+ // implementation file since we would otherwise need to include
+ // nsIFile.h in the header.
+ Foo::~Foo() = default;
+
+ } // namespace mozilla::dom
+
+
+Include directives
+------------------
+
+- Ordering:
+
+ - In an implementation file (cpp file), the very first include directive
+ should include the corresponding header file, followed by a blank line.
+ - Any conditional includes (depending on some ``#ifdef`` or similar) follow
+ after non-conditional includes. Don't mix them in.
+ - Don't place comments between non-conditional includes.
+
+ Bug 1679522 addresses automating the ordering via clang-format, which
+ is going to enforce some stricter rules. Expect the includes to be reordered.
+ If you include third-party headers that are not self-contained, and therefore
+ need to be included in a particular order, enclose those (and only those)
+ between ``// clang-format off`` and ``// clang-format on``. This should not be
+ done for Mozilla headers, which should rather be made self-contained if they
+ are not.
+
+- Brackets vs. quotes: C/C++ standard library headers are included using
+ brackets (e.g. ``#include <cstdint>``), all other include directives use
+ (double) quotes (e.g. ``#include "mozilla/dom/Document.h``).
+- Exported headers should always be included from their exported path, not
+ from their source path in the tree, even if available locally. E.g. always
+ do ``#include "mozilla/Vector.h"``, not ``#include "Vector.h"``, even
+ from within `mfbt`.
+- Generally, you should include exactly those headers that are needed, not
+ more and not less. Unfortunately this is not easy to see. Maybe C++20
+ modules will bring improvements to this, but it will take a long time
+ to be adopted.
+- The basic rule is that if you literally use a symbol in your file that
+ is declared in a header A.h, include that header. In particular in header
+ files, check if a forward declaration or including a forwarding header is
+ sufficient, see section :ref:`Header files`.
+
+ There are cases where this basic rule is not sufficient. Some cases where
+ you need to include additional headers are:
+
+ - You reference a member of a type that is not literally mentioned in your
+ code, but, e.g. is the return type of a function you are calling.
+
+ There are also cases where the basic rule leads to redundant includes. Note
+ that "redundant" here does not refer to "accidentally redundant" headers,
+ e.g. at the time of writing ``mozilla/dom/BodyUtil.h`` includes
+ ``mozilla/dom/FormData.h``, but it doesn't need to (it only needs a forward
+ declaration), so including ``mozilla/dom/FormData.h`` is "accidentally
+ redundant" when including ``mozilla/dom/BodyUtil.h``. The includes of
+ ``mozilla/dom/BodyUtil.h`` might change at any time, so if a file that
+ includes ``mozilla/dom/BodyUtil.h`` needs a full definition of
+ ``mozilla::dom::FormData``, it should includes ``mozilla/dom/FormData.h``
+ itself. In fact, these "accidentally redundant" headers MUST be included.
+ Relying on accidentally redundant includes makes any change to a header
+ file extremely hard, in particular when considering that the set of
+ accidentally redundant includes differs between platforms.
+ But some cases in fact are non-accidentally redundant, and these can and
+ typically should not be repeated:
+
+ - The includes of the header file do not need to be repeated in its
+ corresponding implementation file. Rationale: the implementation file and
+ its corresponding header file are tightly coupled per se.
+
+ Macros are a special case. Generally, the literal rule also applies here,
+ i.e. if the macro definition references a symbol, the file containing the
+ macro definition should include the header defining the symbol. E.g.
+ ``NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE`` defined in ``nsISupportsImpl.h``
+ makes use of ``MOZ_ASSERT`` defined in ``mozilla/Assertions.h``, so
+ ``nsISupportsImpl.h`` includes ``mozilla/Assertions.h``. However, this
+ requires human judgment of what is intended, since technically only the
+ invocations of the macro reference a symbol (and that's how
+ include-what-you-use handles this). It might depend on the
+ context or parameters which symbol is actually referenced, and sometimes
+ this is on purpose. In these cases, the user of the macro needs to include
+ the required header(s).
+
+
+
+COM and pointers
+----------------
+
+- Use ``nsCOMPtr<>``
+ If you don't know how to use it, start looking in the code for
+ examples. The general rule, is that the very act of typing
+ ``NS_RELEASE`` should be a signal to you to question your code:
+ "Should I be using ``nsCOMPtr`` here?". Generally the only valid use
+ of ``NS_RELEASE`` is when you are storing refcounted pointers in a
+ long-lived datastructure.
+- Declare new XPCOM interfaces using :doc:`XPIDL </xpcom/xpidl>`, so they
+ will be scriptable.
+- Use :doc:`nsCOMPtr </xpcom/refptr>` for strong references, and
+ ``nsWeakPtr`` for weak references.
+- Don't use ``QueryInterface`` directly. Use ``CallQueryInterface`` or
+ ``do_QueryInterface`` instead.
+- Use :ref:`Contract IDs <contract_ids>`,
+ instead of CIDs with ``do_CreateInstance``/``do_GetService``.
+- Use pointers, instead of references for function out parameters, even
+ for primitive types.
+
+
+IDL
+---
+
+
+Use leading-lowercase, or "interCaps"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When defining a method or attribute in IDL, the first letter should be
+lowercase, and each following word should be capitalized. For example:
+
+.. code-block:: cpp
+
+ long updateStatusBar();
+
+
+Use attributes wherever possible
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Whenever you are retrieving or setting a single value, without any
+context, you should use attributes. Don't use two methods when you could
+use an attribute. Using attributes logically connects the getting and
+setting of a value, and makes scripted code look cleaner.
+
+This example has too many methods:
+
+.. code-block:: cpp
+
+ interface nsIFoo : nsISupports
+ {
+ long getLength();
+ void setLength(in long length);
+ long getColor();
+ };
+
+The code below will generate the exact same C++ signature, but is more
+script-friendly.
+
+.. code-block:: cpp
+
+ interface nsIFoo : nsISupports
+ {
+ attribute long length;
+ readonly attribute long color;
+ };
+
+
+Use Java-style constants
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+When defining scriptable constants in IDL, the name should be all
+uppercase, with underscores between words:
+
+.. code-block:: cpp
+
+ const long ERROR_UNDEFINED_VARIABLE = 1;
+
+
+See also
+~~~~~~~~
+
+For details on interface development, as well as more detailed style
+guides, see the `Interface development
+guide <https://developer.mozilla.org/docs/Mozilla/Developer_guide/Interface_development_guide>`__.
+
+
+Error handling
+--------------
+
+
+Check for errors early and often
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Every time you make a call into an XPCOM function, you should check for
+an error condition. You need to do this even if you know that call will
+never fail. Why?
+
+- Someone may change the callee in the future to return a failure
+ condition.
+- The object in question may live on another thread, another process,
+ or possibly even another machine. The proxy could have failed to make
+ your call in the first place.
+
+Also, when you make a new function which is failable (i.e. it will
+return a ``nsresult`` or a ``bool`` that may indicate an error), you should
+explicitly mark the return value should always be checked. For example:
+
+::
+
+ // for IDL.
+ [must_use] nsISupports
+ create();
+
+ // for C++, add this in *declaration*, do not add it again in implementation.
+ [[nodiscard]] nsresult
+ DoSomething();
+
+There are some exceptions:
+
+- Predicates or getters, which return ``bool`` or ``nsresult``.
+- IPC method implementation (For example, ``bool RecvSomeMessage()``).
+- Most callers will check the output parameter, see below.
+
+.. code-block:: cpp
+
+ nsresult
+ SomeMap::GetValue(const nsString& key, nsString& value);
+
+If most callers need to check the output value first, then adding
+``[[nodiscard]]`` might be too verbose. In this case, change the return value
+to void might be a reasonable choice.
+
+There is also a static analysis attribute ``[[nodiscard]]``, which can
+be added to class declarations, to ensure that those declarations are
+always used when they are returned.
+
+
+Use the NS_WARN_IF macro when errors are unexpected.
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``NS_WARN_IF`` macro can be used to issue a console warning, in debug
+builds if the condition fails. This should only be used when the failure
+is unexpected and cannot be caused by normal web content.
+
+If you are writing code which wants to issue warnings when methods fail,
+please either use ``NS_WARNING`` directly, or use the new ``NS_WARN_IF`` macro.
+
+.. code-block:: cpp
+
+ if (NS_WARN_IF(somethingthatshouldbefalse)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+Previously, the ``NS_ENSURE_*`` macros were used for this purpose, but
+those macros hide return statements, and should not be used in new code.
+(This coding style rule isn't generally agreed, so use of ``NS_ENSURE_*``
+can be valid.)
+
+
+Return from errors immediately
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In most cases, your knee-jerk reaction should be to return from the
+current function, when an error condition occurs. Don't do this:
+
+.. code-block:: cpp
+
+ rv = foo->Call1();
+ if (NS_SUCCEEDED(rv)) {
+ rv = foo->Call2();
+ if (NS_SUCCEEDED(rv)) {
+ rv = foo->Call3();
+ }
+ }
+ return rv;
+
+Instead, do this:
+
+.. code-block:: cpp
+
+ rv = foo->Call1();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = foo->Call2();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = foo->Call3();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+Why? Error handling should not obfuscate the logic of the code. The
+author's intent, in the first example, was to make 3 calls in
+succession. Wrapping the calls in nested if() statements, instead
+obscured the most likely behavior of the code.
+
+Consider a more complicated example to hide a bug:
+
+.. code-block:: cpp
+
+ bool val;
+ rv = foo->GetBooleanValue(&val);
+ if (NS_SUCCEEDED(rv) && val) {
+ foo->Call1();
+ } else {
+ foo->Call2();
+ }
+
+The intent of the author, may have been, that ``foo->Call2()`` would only
+happen when val had a false value. In fact, ``foo->Call2()`` will also be
+called, when ``foo->GetBooleanValue(&val)`` fails. This may, or may not,
+have been the author's intent. It is not clear from this code. Here is
+an updated version:
+
+.. code-block:: cpp
+
+ bool val;
+ rv = foo->GetBooleanValue(&val);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (val) {
+ foo->Call1();
+ } else {
+ foo->Call2();
+ }
+
+In this example, the author's intent is clear, and an error condition
+avoids both calls to ``foo->Call1()`` and ``foo->Call2();``
+
+*Possible exceptions:* Sometimes it is not fatal if a call fails. For
+instance, if you are notifying a series of observers that an event has
+fired, it might be trivial that one of these notifications failed:
+
+.. code-block:: cpp
+
+ for (size_t i = 0; i < length; ++i) {
+ // we don't care if any individual observer fails
+ observers[i]->Observe(foo, bar, baz);
+ }
+
+Another possibility, is you are not sure if a component exists or is
+installed, and you wish to continue normally, if the component is not
+found.
+
+.. code-block:: cpp
+
+ nsCOMPtr<nsIMyService> service = do_CreateInstance(NS_MYSERVICE_CID, &rv);
+ // if the service is installed, then we'll use it.
+ if (NS_SUCCEEDED(rv)) {
+ // non-fatal if this fails too, ignore this error.
+ service->DoSomething();
+
+ // this is important, handle this error!
+ rv = service->DoSomethingImportant();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ // continue normally whether or not the service exists.
+
+
+Strings
+-------
+
+.. note::
+
+ This section overlaps with the more verbose advice given in
+ :doc:`String guide </xpcom/stringguide>`.
+ These should eventually be merged. For now, please refer to that guide for
+ more advice.
+
+- String arguments to functions should be declared as ``[const] nsA[C]String&``.
+- Prefer using string literals. In particular, use empty string literals,
+ i.e. ``u""_ns`` or ``""_ns``, instead of ``Empty[C]String()`` or
+ ``const nsAuto[C]String empty;``. Use ``Empty[C]String()`` only if you
+ specifically need a ``const ns[C]String&``, e.g. with the ternary operator
+ or when you need to return/bind to a reference or take the address of the
+ empty string.
+- For 16-bit literal strings, use ``u"..."_ns`` or, if necessary
+ ``NS_LITERAL_STRING_FROM_CSTRING(...)`` instead of ``nsAutoString()``
+ or other ways that would do a run-time conversion.
+ See :ref:`Avoid runtime conversion of string literals <Avoid runtime conversion of string literals>` below.
+- To compare a string with a literal, use ``.EqualsLiteral("...")``.
+- Use ``str.IsEmpty()`` instead of ``str.Length() == 0``.
+- Use ``str.Truncate()`` instead of ``str.SetLength(0)``,
+ ``str.Assign(""_ns)`` or ``str.AssignLiteral("")``.
+- Don't use functions from ``ctype.h`` (``isdigit()``, ``isalpha()``,
+ etc.) or from ``strings.h`` (``strcasecmp()``, ``strncasecmp()``).
+ These are locale-sensitive, which makes them inappropriate for
+ processing protocol text. At the same time, they are too limited to
+ work properly for processing natural-language text. Use the
+ alternatives in ``mozilla/TextUtils.h`` and in ``nsUnicharUtils.h``
+ in place of ``ctype.h``. In place of ``strings.h``, prefer the
+ ``nsStringComparator`` facilities for comparing strings or if you
+ have to work with zero-terminated strings, use ``nsCRT.h`` for
+ ASCII-case-insensitive comparison.
+
+
+Use the ``Auto`` form of strings for local values
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When declaring a local, short-lived ``nsString`` class, always use
+``nsAutoString`` or ``nsAutoCString``. These pre-allocate a 64-byte
+buffer on the stack, and avoid fragmenting the heap. Don't do this:
+
+.. code-block:: cpp
+
+ nsresult
+ foo()
+ {
+ nsCString bar;
+ ..
+ }
+
+instead:
+
+.. code-block:: cpp
+
+ nsresult
+ foo()
+ {
+ nsAutoCString bar;
+ ..
+ }
+
+
+Be wary of leaking values from non-XPCOM functions that return char\* or PRUnichar\*
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+It is an easy trap to return an allocated string, from an internal
+helper function, and then using that function inline in your code,
+without freeing the value. Consider this code:
+
+.. code-block:: cpp
+
+ static char*
+ GetStringValue()
+ {
+ ..
+ return resultString.ToNewCString();
+ }
+
+ ..
+ WarnUser(GetStringValue());
+
+In the above example, ``WarnUser`` will get the string allocated from
+``resultString.ToNewCString()`` and throw away the pointer. The
+resulting value is never freed. Instead, either use the string classes,
+to make sure your string is automatically freed when it goes out of
+scope, or make sure that your string is freed.
+
+Automatic cleanup:
+
+.. code-block:: cpp
+
+ static void
+ GetStringValue(nsAWritableCString& aResult)
+ {
+ ..
+ aResult.Assign("resulting string");
+ }
+
+ ..
+ nsAutoCString warning;
+ GetStringValue(warning);
+ WarnUser(warning.get());
+
+Free the string manually:
+
+.. code-block:: cpp
+
+ static char*
+ GetStringValue()
+ {
+ ..
+ return resultString.ToNewCString();
+ }
+
+ ..
+ char* warning = GetStringValue();
+ WarnUser(warning);
+ nsMemory::Free(warning);
+
+.. _Avoid runtime conversion of string literals:
+
+Avoid runtime conversion of string literals
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+It is very common to need to assign the value of a literal string, such
+as ``"Some String"``, into a unicode buffer. Instead of using ``nsString``'s
+``AssignLiteral`` and ``AppendLiteral``, use a user-defined literal like `u"foo"_ns`
+instead. On most platforms, this will force the compiler to compile in a
+raw unicode string, and assign it directly. In cases where the literal is defined
+via a macro that is used in both 8-bit and 16-bit ways, you can use
+`NS_LITERAL_STRING_FROM_CSTRING` to do the conversion at compile time.
+
+Incorrect:
+
+.. code-block:: cpp
+
+ nsAutoString warning;
+ warning.AssignLiteral("danger will robinson!");
+ ...
+ foo->SetStringValue(warning);
+ ...
+ bar->SetUnicodeValue(warning.get());
+
+Correct:
+
+.. code-block:: cpp
+
+ constexpr auto warning = u"danger will robinson!"_ns;
+ ...
+ // if you'll be using the 'warning' string, you can still use it as before:
+ foo->SetStringValue(warning);
+ ...
+ bar->SetUnicodeValue(warning.get());
+
+ // alternatively, use the wide string directly:
+ foo->SetStringValue(u"danger will robinson!"_ns);
+ ...
+
+ // if a macro is the source of a 8-bit literal and you cannot change it, use
+ // NS_LITERAL_STRING_FROM_CSTRING, but only if necessary.
+ #define MY_MACRO_LITERAL "danger will robinson!"
+ foo->SetStringValue(NS_LITERAL_STRING_FROM_CSTRING(MY_MACRO_LITERAL));
+
+ // If you need to pass to a raw const char16_t *, there's no benefit to
+ // go through our string classes at all, just do...
+ bar->SetUnicodeValue(u"danger will robinson!");
+
+ // .. or, again, if a macro is the source of a 8-bit literal
+ bar->SetUnicodeValue(u"" MY_MACRO_LITERAL);
+
+
+Usage of PR_(MAX|MIN|ABS|ROUNDUP) macro calls
+---------------------------------------------
+
+Use the standard-library functions (``std::max``), instead of
+``PR_(MAX|MIN|ABS|ROUNDUP)``.
+
+Use ``mozilla::Abs`` instead of ``PR_ABS``. All ``PR_ABS`` calls in C++ code have
+been replaced with ``mozilla::Abs`` calls, in `bug
+847480 <https://bugzilla.mozilla.org/show_bug.cgi?id=847480>`__. All new
+code in ``Firefox/core/toolkit`` needs to ``#include "nsAlgorithm.h"`` and
+use the ``NS_foo`` variants instead of ``PR_foo``, or
+``#include "mozilla/MathAlgorithms.h"`` for ``mozilla::Abs``.
+
+Use of SpiderMonkey rooting typedefs
+------------------------------------
+The rooting typedefs in ``js/public/TypeDecls.h``, such as ``HandleObject`` and
+``RootedObject``, are deprecated both in and outside of SpiderMonkey. They will
+eventually be removed and should not be used in new code.
diff --git a/docs/code-quality/coding-style/coding_style_general.rst b/docs/code-quality/coding-style/coding_style_general.rst
new file mode 100644
index 0000000000..950cd6ccd3
--- /dev/null
+++ b/docs/code-quality/coding-style/coding_style_general.rst
@@ -0,0 +1,18 @@
+
+Mode line
+~~~~~~~~~
+
+Files should have Emacs and vim mode line comments as the first two
+lines of the file, which should set ``indent-tabs-mode`` to ``nil``. For new
+files, use the following, specifying two-space indentation:
+
+.. code-block:: cpp
+
+ /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+ /* vim: set ts=2 et sw=2 tw=80: */
+ /* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+Be sure to use the correct ``Mode`` in the first line, don't use ``C++`` in
+JavaScript files.
diff --git a/docs/code-quality/coding-style/coding_style_java.rst b/docs/code-quality/coding-style/coding_style_java.rst
new file mode 100644
index 0000000000..f2206d8e2d
--- /dev/null
+++ b/docs/code-quality/coding-style/coding_style_java.rst
@@ -0,0 +1,68 @@
+=================
+Java Coding style
+=================
+
+- We use the `Java Coding
+ Style <https://www.oracle.com/technetwork/java/codeconvtoc-136057.html>`__.
+ Quick summary:
+
+ - FirstLetterUpperCase for class names.
+ - camelCase for method and variable names.
+ - One declaration per line:
+
+ .. code-block:: java
+
+ int x, y; // this is BAD!
+ int a; // split it over
+ int b; // two lines
+
+- Braces should be placed like so (generally, opening braces on same
+ line, closing braces on a new line):
+
+ .. code-block:: java
+
+ public void func(int arg) {
+ if (arg != 0) {
+ while (arg > 0) {
+ arg--;
+ }
+ } else {
+ arg++;
+ }
+ }
+
+- Places we differ from the Java coding style:
+
+ - Start class variable names with 'm' prefix (e.g.
+ mSomeClassVariable) and static variables with 's' prefix (e.g.
+ sSomeStaticVariable)
+ - ``import`` statements:
+
+ - Do not use wildcard imports like \`import java.util.*;\`
+ - Organize imports by blocks separated by empty line:
+ org.mozilla.*, android.*, com.*, net.*, org.*, then java.\*
+ This is basically what Android Studio does by default, except
+ that we place org.mozilla.\* at the front - please adjust
+ Settings -> Editor -> Code Style -> Java -> Imports
+ accordingly.
+ - Within each import block, alphabetize import names with
+ uppercase before lowercase. For example, ``com.example.Foo`` is
+ before ``com.example.bar``
+
+ - 4-space indents.
+ - Spaces, not tabs.
+ - Don't restrict yourself to 80-character lines. Google's Android
+ style guide suggests 100-character lines, which is also the
+ default setting in Android Studio. Java code tends to be long
+ horizontally, so use appropriate judgement when wrapping. Avoid
+ deep indents on wrapping. Note that aligning the wrapped part of a
+ line, with some previous part of the line (rather than just using
+ a fixed indent), may require shifting the code every time the line
+ changes, resulting in spurious whitespace changes.
+
+- For additional specifics on Firefox for Android, see the `Coding
+ Style guide for Firefox on
+ Android <https://wiki.mozilla.org/Mobile/Fennec/Android#Coding_Style>`__.
+- The `Android Coding
+ Style <https://source.android.com/source/code-style.html>`__ has some
+ useful guidelines too.
diff --git a/docs/code-quality/coding-style/coding_style_js.rst b/docs/code-quality/coding-style/coding_style_js.rst
new file mode 100644
index 0000000000..09d7a6fc8a
--- /dev/null
+++ b/docs/code-quality/coding-style/coding_style_js.rst
@@ -0,0 +1,147 @@
+=======================
+JavaScript Coding style
+=======================
+
+Coding style
+~~~~~~~~~~~~
+
+`prettier <https://prettier.io/>`_ is the tool used to reformat the JavaScript code.
+
+
+Methods and functions
+~~~~~~~~~~~~~~~~~~~~~
+
+In JavaScript, functions should use camelCase, but should not capitalize
+the first letter. Methods should not use the named function expression
+syntax, because our tools understand method names:
+
+.. code-block:: cpp
+
+ doSomething: function (aFoo, aBar) {
+ ...
+ }
+
+In-line functions should have spaces around braces, except before commas
+or semicolons:
+
+.. code-block:: cpp
+
+ function valueObject(aValue) { return { value: aValue }; }
+
+
+JavaScript objects
+~~~~~~~~~~~~~~~~~~
+
+.. code-block:: cpp
+
+ var foo = { prop1: "value1" };
+
+ var bar = {
+ prop1: "value1",
+ prop2: "value2"
+ };
+
+Constructors for objects should be capitalized and use Pascal Case:
+
+.. code-block:: cpp
+
+ function ObjectConstructor() {
+ this.foo = "bar";
+ }
+
+
+Operators
+~~~~~~~~~
+
+In JavaScript, overlong expressions not joined by ``&&`` and
+``||`` should break so the operator starts on the second line and
+starting in the same column as the beginning of the expression in the
+first line. This applies to ``?:``, binary arithmetic operators
+including ``+``, and member-of operators. Rationale: an operator at the
+front of the continuation line makes for faster visual scanning, as
+there is no need to read to the end of line. Also there exists a
+context-sensitive keyword hazard in JavaScript; see {{bug(442099, "bug",
+19)}}, which can be avoided by putting . at the start of a continuation
+line, in long member expression.
+
+In JavaScript, ``==`` is preferred to ``===``.
+
+Unary keyword operators, such as ``typeof``, should have their operand
+parenthesized; e.g. ``typeof("foo") == "string"``.
+
+Literals
+~~~~~~~~
+
+Double-quoted strings (e.g. ``"foo"``) are preferred to single-quoted
+strings (e.g. ``'foo'``), in JavaScript, except to avoid escaping
+embedded double quotes, or when assigning inline event handlers.
+
+
+Prefixes
+~~~~~~~~
+
+- k=constant (e.g. ``kNC_child``). Not all code uses this style; some
+ uses ``ALL_CAPS`` for constants.
+- g=global (e.g. ``gPrefService``)
+- a=argument (e.g. ``aCount``)
+
+- JavaScript Specific Prefixes
+
+ - \_=member (variable or function) (e.g. ``_length`` or
+ ``_setType(aType)``)
+ - k=enumeration value (e.g. ``const kDisplayModeNormal = 0``)
+ - on=event handler (e.g. ``function onLoad()``)
+ - Convenience constants for interface names should be prefixed with
+ ``nsI``:
+
+ .. code-block:: javascript
+
+ const nsISupports = Components.interfaces.nsISupports;
+ const nsIWBN = Components.interfaces.nsIWebBrowserNavigation;
+
+
+
+Other advices
+~~~~~~~~~~~~~
+
+- Do not compare ``x == true`` or ``x == false``. Use ``(x)`` or
+ ``(!x)`` instead. ``x == true``, is certainly different from if
+ ``(x)``! Compare objects to ``null``, numbers to ``0`` or strings to
+ ``""``, if there is chance for confusion.
+- Make sure that your code doesn't generate any strict JavaScript
+ warnings, such as:
+
+ - Duplicate variable declaration.
+ - Mixing ``return;`` with ``return value;``
+ - Undeclared variables or members. If you are unsure if an array
+ value exists, compare the index to the array's length. If you are
+ unsure if an object member exists, use ``"name"`` in ``aObject``,
+ or if you are expecting a particular type you may use
+ ``typeof(aObject.name) == "function"`` (or whichever type you are
+ expecting).
+
+- Use ``['value1, value2']`` to create a JavaScript array in preference
+ to using
+ ``new {{JSxRef("Array", "Array", "Syntax", 1)}}(value1, value2)``
+ which can be confusing, as ``new Array(length)`` will actually create
+ a physically empty array with the given logical length, while
+ ``[value]`` will always create a 1-element array. You cannot actually
+ guarantee to be able to preallocate memory for an array.
+- Use ``{ member: value, ... }`` to create a JavaScript object; a
+ useful advantage over ``new {{JSxRef("Object", "Object", "", 1)}}()``
+ is the ability to create initial properties and use extended
+ JavaScript syntax, to define getters and setters.
+- If having defined a constructor you need to assign default
+ properties, it is preferred to assign an object literal to the
+ prototype property.
+- Use regular expressions, but use wisely. For instance, to check that
+ ``aString`` is not completely whitespace use
+ ``/\S/.{{JSxRef("RegExp.test", "test(aString)", "", 1)}}``. Only use
+ {{JSxRef("String.search", "aString.search()")}} if you need to know
+ the position of the result, or {{JSxRef("String.match",
+ "aString.match()")}} if you need to collect matching substrings
+ (delimited by parentheses in the regular expression). Regular
+ expressions are less useful if the match is unknown in advance, or to
+ extract substrings in known positions in the string. For instance,
+ {{JSxRef("String.slice", "aString.slice(-1)")}} returns the last
+ letter in ``aString``, or the empty string if ``aString`` is empty.
diff --git a/docs/code-quality/coding-style/coding_style_python.rst b/docs/code-quality/coding-style/coding_style_python.rst
new file mode 100644
index 0000000000..3a818fcfd4
--- /dev/null
+++ b/docs/code-quality/coding-style/coding_style_python.rst
@@ -0,0 +1,71 @@
+===================
+Python Coding style
+===================
+
+Coding style
+~~~~~~~~~~~~
+
+ :ref:`black` is the tool used to reformat the Python code.
+
+Linting
+~~~~~~~
+
+The Python linting is done by :ref:`Flake8` and :ref:`pylint`
+They are executed by mozlint both at review phase and in the CI.
+
+Indentation
+~~~~~~~~~~~
+
+Four spaces in Python code.
+
+
+Makefile/moz.build practices
+----------------------------
+
+- Changes to makefile and moz.build variables do not require
+ build-config peer review. Any other build system changes, such as
+ adding new scripts or rules, require review from the build-config
+ team.
+- Suffix long ``if``/``endif`` conditionals with #{ & #}, so editors
+ can display matched tokens enclosing a block of statements.
+
+ ::
+
+ ifdef CHECK_TYPE #{
+ ifneq ($(flavor var_type),recursive) #{
+ $(warning var should be expandable but detected var_type=$(flavor var_type))
+ endif #}
+ endif #}
+
+- moz.build are python and follow normal Python style.
+- List assignments should be written with one element per line. Align
+ closing square brace with start of variable assignment. If ordering
+ is not important, variables should be in alphabetical order.
+
+ .. code-block:: python
+
+ var += [
+ 'foo',
+ 'bar'
+ ]
+
+- Use ``CONFIG['CPU_ARCH'] {=arm}`` to test for generic classes of
+ architecture rather than ``CONFIG['OS_TEST'] {=armv7}`` (re: bug 886689).
+
+
+Other advices
+~~~~~~~~~~~~~
+
+- Install the
+ `mozext <https://hg.mozilla.org/hgcustom/version-control-tools/file/default/hgext/mozext>`__
+ Mercurial extension, and address every issue reported on commit
+ or the output of ``hg critic``.
+- Follow `PEP 8 <https://www.python.org/dev/peps/pep-0008/>`__. Please run :ref:`black` for this.
+- Do not place statements on the same line as ``if/elif/else``
+ conditionals to form a one-liner.
+- Global vars, please avoid them at all cost.
+- Exclude outer parenthesis from conditionals.Use
+ ``if x > 5:,``\ rather than ``if (x > 5):``
+- Use string formatters, rather than var + str(val).
+ ``var = 'Type %s value is %d'% ('int', 5).``
+- Testing/Unit tests, please write them and make sure that they are executed in the CI.
diff --git a/docs/code-quality/coding-style/css_guidelines.rst b/docs/code-quality/coding-style/css_guidelines.rst
new file mode 100644
index 0000000000..e962f24d69
--- /dev/null
+++ b/docs/code-quality/coding-style/css_guidelines.rst
@@ -0,0 +1,572 @@
+CSS Guidelines
+==============
+
+This document contains guidelines defining how CSS inside the Firefox
+codebase should be written, it is notably relevant for Firefox front-end
+engineers.
+
+Basics
+------
+
+Here are some basic tips that can optimize reviews if you are changing
+CSS:
+
+- Avoid ``!important`` but if you have to use it, make sure it's
+ obvious why you're using it (ideally with a comment). The
+ `Overriding CSS`_ section contains more information about this.
+- Avoid magic numbers; prefer automatic sizing or alignment methods.
+ Some examples to avoid:
+
+ - absolutely positioned elements
+ - hardcoded values such as: ``vertical-align: -2px;`` . The reason
+ you should avoid such "hardcoded" values is that, they don't
+ necessarily work for all font-size configurations.
+
+- Avoid setting styles in JavaScript. It's generally better to set a
+ class and then specify the styles in CSS.
+- ``classList`` is generally better than ``className``. There's less
+ chance of overwriting an existing class.
+- Only use generic selectors such as ``:last-child``, when it is what
+ you mean semantically. If not, using a semantic class name is more
+ descriptive and usually better.
+
+Boilerplate
+~~~~~~~~~~~
+
+Make sure each file starts with the standard copyright header (see
+`License Boilerplate <https://www.mozilla.org/MPL/headers/>`__).
+
+Before adding more CSS
+~~~~~~~~~~~~~~~~~~~~~~
+
+It is good practice to check if the CSS that is being written is needed,
+it can be the case that a common component has been already written
+could be reused with or without changes. Most of the time, the common
+component already follows the a11y/theme standards defined in this
+guide. So, when possible, always prefer editing common components to
+writing your own.
+
+Also, it is good practice to introduce a common class when the new
+element you are styling reuses some styles from another element, this
+allows the maintenance cost and the amount of code duplication to be
+reduced.
+
+Formatting
+----------
+
+Spacing & Indentation
+~~~~~~~~~~~~~~~~~~~~~
+
+- 2 spaces indentation is preferred
+- Add a space after each comma, **except** within color functions:
+
+.. code:: css
+
+ linear-gradient(to bottom, black 1px, rgba(255,255,255,0.2) 1px)
+
+- Always add a space before ``!important``.
+
+Omit units on 0 values
+~~~~~~~~~~~~~~~~~~~~~~
+
+Do this:
+
+.. code:: css
+
+ margin: 0;
+
+Not this:
+
+.. code:: css
+
+ margin: 0px;
+
+Use expanded syntax
+~~~~~~~~~~~~~~~~~~~
+
+It is often harder to understand what the shorthand is doing and the
+shorthand can also hide some unwanted default values. It is good to
+privilege expanded syntax to make your intentions explicit.
+
+Do this:
+
+.. code:: css
+
+ border-color: red;
+
+Not this:
+
+.. code:: css
+
+ border: red;
+
+Put multiple selectors on different lines
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Do this:
+
+.. code:: css
+
+ h1,
+ h2,
+ h3 {
+ font-family: sans-serif;
+ text-align: center;
+ }
+
+Not this:
+
+.. code:: css
+
+ h1, h2, h3 {
+ font-family: sans-serif;
+ text-align: center;
+ }
+
+Naming standards for class names
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- ``lower-case-with-dashes`` is the most common.
+- But ``camelCase`` is also used sometimes. Try to follow the style of
+ existing or related code.
+
+Other tips
+~~~~~~~~~~
+
+- Assume ``="true"`` in attribute selectors.
+
+ - Example: Use ``option[checked]``, not ``option[checked="true"]``.
+
+- Avoid ID selectors unless it is really the wanted goal, since IDs
+ have higher specificity and therefore are harder to override.
+- Using descendant selectors is good practice for performance when
+ possible:
+
+ - For example:
+ ``.autocomplete-item[selected] > .autocomplete-item-title`` would
+ be more efficient than
+ ``.autocomplete-item[selected] .autocomplete-item-title``
+
+Overriding CSS
+--------------
+
+Before overriding any CSS rules, check whether overriding is really
+needed. Sometimes, when copy-pasting older code, it happens that the
+code in question contains unnecessary overrides. This could be because
+the CSS that it was overriding got removed in the meantime. In this
+case, dropping the override should work.
+
+It is also good practice to look at whether the rule you are overriding
+is still needed: maybe the UX spec for the component has changed and
+that rule can actually be updated or removed. When this is the case,
+don't be afraid to remove or update that rule.
+
+Once the two things above have been checked, check if the other rule you
+are overriding contains ``!important``, if that is case, try putting it
+in question, because it might have become obsolete.
+
+Afterwards, check the specificity of the other selector; if it is
+causing your rule to be overridden, you can try reducing its
+specificity, either by simplifying the selector or by changing where the
+rule is placed in the stylesheet. If this isn't possible, you can also
+try introducing a ``:not()`` to prevent the other rule from applying,
+this is especially relevant for different element states (``:hover``,
+``:active``, ``[checked]`` or ``[disabled]``). However, never try to
+increase the selector of the rule you are adding as it can easily become
+hard to understand.
+
+Finally, once you have checked all the things above, you can permit
+yourself to use ``!important`` along with a comment why it is needed.
+
+Using CSS variables
+-------------------
+
+Adding new variables
+~~~~~~~~~~~~~~~~~~~~
+
+Before adding new CSS variables, please consider the following
+questions:
+
+#. **Is the variable value changed at runtime?**
+ *(Either from JavaScript or overridden by another CSS file)*
+ **If the answer is no**, consider using a preprocessor variable or
+ inlining the value.
+
+#. **Is the variable value used multiple times?**
+ **If the answer is no and the value isn't changed at runtime**, then
+ you likely don't need a CSS variable.
+
+#. **Is there an alternative to using the variable like inheriting or
+ using the ``currentcolor`` keyword?**
+ Using inheriting or using ``currentcolor`` will prevent repetition of
+ the value and it is usually good practice to do so.
+
+In general, it's good to first think of how some CSS could be written
+cleanly without the CSS variable(s) and then think of how the CSS
+variable could improve that CSS.
+
+Using variables
+~~~~~~~~~~~~~~~
+
+Use the variable according to its naming
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Do this:
+
+.. code:: css
+
+ xul|tab:hover {
+ background-color: var(--in-content-box-background-hover);
+ }
+
+Not this:
+
+.. code:: css
+
+ #certificateErrorDebugInformation {
+ background-color: var(--in-content-box-background-hover);
+ }
+
+Localization
+------------
+
+Text Direction
+~~~~~~~~~~~~~~
+
+- For margins, padding and borders, use
+ ``inline-start``/``inline-end`` rather than ``left``/``right``.
+ *Example:* Use ``margin-inline-start: 3px;`` instead of
+ ``margin-left: 3px``.
+- For RTL-aware positioning (left/right), use
+ ``inset-inline-start``/``inset-inline-end``.
+- For RTL-aware float layouts, ``float: inline-start|inline-end`` can
+ be used instead of ``float: left|right``.
+- The RTL-aware equivalents of
+ ``border-{top/bottom}-{left/right}-radius`` are
+ ``border-{start/end}-{start/end}-radius``
+- When there is no special RTL-aware property available, use the pseudo
+ ``:-moz-locale-dir(ltr|rtl)`` (for XUL files) or ``:dir(ltr|rtl)``
+ (for HTML files).
+- Remember that while a tab content's scrollbar still shows on the
+ right in RTL, an overflow scrollbar will show on the left.
+- Write ``padding: 0 3px 4px;`` instead of
+ ``padding: 0 3px 4px 3px;``. This makes it more obvious that the
+ padding is symmetrical (so RTL won't be an issue).
+
+.. note::
+
+ See `CSS Logical Properties and
+ Values <https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties>`__
+ for more information.
+
+Testing
+~~~~~~~
+
+To test for RTL layouts, you can go to ``about:config`` and set
+``intl.uidirection`` to ``-1``.
+
+Writing cross-platform CSS
+--------------------------
+
+Firefox supports many different platforms and each of those platforms
+can contain many different configurations:
+
+- Windows 7, 8 and 10
+
+ - Default theme
+ - Aero basic (Windows 7, 8)
+ - Windows classic (Windows 7)
+ - High contrast (All versions)
+
+- Linux
+- macOS
+
+File structure
+~~~~~~~~~~~~~~
+
+- The ``browser/`` directory contains styles specific to Firefox
+- The ``toolkit/`` directory contains styles that are shared across all
+ toolkit applications (Thunderbird and SeaMonkey)
+
+Under each of those two directories, there is a ``themes`` directory
+containing 4 sub-directories:
+
+- ``shared``
+- ``linux``
+- ``osx``
+- ``windows``
+
+The ``shared`` directories contain styles shared across all 3 platforms,
+while the other 3 directories contain styles respective to their
+platform.
+
+For new CSS, when possible try to privilege using the ``shared``
+directory, instead of writing the same CSS for the 3 platform specific
+directories, especially for large blocks of CSS.
+
+Content CSS vs. Theme CSS
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The following directories also contain CSS:
+
+- ``browser/base/content/``
+- ``toolkit/content/``
+
+These directories contain content CSS, that applies on all platforms,
+which is styling deemed to be essential for the browser to behave
+correctly. To determine whether some CSS is theme-side or content-side,
+it is useful to know that certain CSS properties are going to lean one
+way or the other: color - 99% of the time it will be theme CSS, overflow
+- 99% content.
+
++-----------------+--------------+----------------+----------------+
+| 99% theme | 70% theme | 70% content | 99% content |
++=================+==============+================+================+
+| font-\*, color, | line-height, | cursor, width, | overflow, |
+| \*-color, | padding, | max-width, | direction, |
+| border-\*, | margin | top, | display, |
+| -moz-appearance | | bottom [2]_, | \*-align, |
+| [1]_ | | etc | align-\*, |
+| | | | \*-box-\*, |
+| | | | flex-\*, order |
++-----------------+--------------+----------------+----------------+
+
+If some CSS is layout or functionality related, then it is likely
+content CSS. If it is esthetics related, then it is likely theme CSS.
+
+When importing your stylesheets, it's best to import the content CSS
+before the theme CSS, that way the theme values get to override the
+content values (which is probably what you want), and you're going to
+want them both after the global values, so your imports will look like
+this:
+
+.. code:: html
+
+ <?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+ <?xml-stylesheet href="chrome://browser/content/path/module.css" type="text/css"?>
+ <?xml-stylesheet href="chrome://browser/skin/path/module.css" type="text/css"?>
+
+.. [1] -moz-appearance is tricky. Generally, when specifying
+ -moz-appearance: foo; you're giving hints as to how something should
+ act, however -moz-appearance: none; is probably saying 'ignore
+ browser preconceptions - I want a blank sheet', so that's more
+ visual. However -moz-appearance values aren't implemented and don't
+ behave consistently across platforms, so idealism aside
+ -moz-appearance should always be in theme CSS.
+
+.. [2] However there is probably a better way than using absolute
+ positioning.
+
+Colors
+~~~~~~
+
+For common areas of the Firefox interface (panels, toolbar buttons,
+etc.), mozilla-central often comes with some useful CSS variables that
+are adjusted with the correct values for different platform
+configurations, so using those CSS variables can definitively save some
+testing time, as you can assume they already work correctly.
+
+Using the ``currentcolor`` keyword or inheriting is also good practice,
+because sometimes the needed value is already in the color or on the
+parent element. This is especially useful in conjunction with icons
+using ``-moz-context-properties: fill;`` where the icon can adjust to
+the right platform color automatically from the text color. It is also
+possible to use ``currentcolor`` with other properties like
+``opacity`` or ``fill-opacity`` to have different
+opacities of the platform color.
+
+High contrast mode
+~~~~~~~~~~~~~~~~~~
+
+Content area
+^^^^^^^^^^^^
+
+On Windows high contrast mode, in the content area, Gecko does some
+automatic color adjustments regarding page colors. Part of those
+adjustments include making all ``box-shadow`` invisible, so this is
+something to be aware of if you create a focus ring or a border using
+the ``box-shadow`` property: consider using a ``border`` or an
+``outline`` if you want the border/focus ring to stay visible in
+high-contrast mode. An example of such bug is `bug
+1516767 <https://bugzilla.mozilla.org/show_bug.cgi?id=1516767>`__.
+
+Another adjustment to be aware of is that Gecko removes all the
+``background-image`` when high contrast mode is enabled. Consider using
+an actual ``<img>`` tag (for HTML documents) or ``list-style-image``
+(for XUL documents) if rendering the image is important.
+
+If you are not using Windows, one way to test against those adjustments
+on other platforms is:
+
+- Going to about:preferences
+- Clicking on the "Colors..." button in the "Fonts & Colors"
+ sub-section of the "Language and Appearance" section
+- Under "Override the colors specified by the page with your selections
+ above", select the "Always" option
+
+Chrome area
+^^^^^^^^^^^
+
+The automatic adjustments previously mentioned only apply to pages
+rendered in the content area. The chrome area of Firefox uses colors as
+authored, which is why using pre-defined variables, ``currentcolor`` or
+inheritance is useful to integrate with the system theme with little
+hassle.
+
+If not, as a last resort, using `system
+colors <https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#system_colors>`__
+also works for non-default Windows themes or Linux. In general, the
+following colors are used:
+
+- ``-moz-Field``: textbox or field background colors, also used as the
+ background color of listboxes or trees.
+- ``-moz-FieldText``: textbox or field text colors, also used as the
+ text color of listboxes or trees.
+- ``-moz-Dialog``: window or dialog background color.
+- ``-moz-DialogText``: window or dialog text color.
+- ``GrayText``: used on disabled items as text color. Do not use it on
+ text that is not disabled to desemphsize text, because it does not
+ guarantee a sufficient contrast ratio for non-disabled text.
+- ``ThreeDShadow``: Used as border on elements.
+- ``ThreeDLightShadow``: Used as light border on elements.
+
+Using the background/text pairs is especially important to ensure the
+contrast is respected in all situations. Never mix custom text colors
+with a system background color and vice-versa.
+
+Note that using system colors is only useful for the chrome area, since
+content area colors are overridden by Gecko anyway.
+
+Writing media queries
+~~~~~~~~~~~~~~~~~~~~~
+
+Boolean media queries
+^^^^^^^^^^^^^^^^^^^^^
+
+Do this:
+
+.. code:: css
+
+ @media (-moz-mac-yosemite-theme: 0) {
+
+Not this:
+
+.. code:: css
+
+ @media not all and (-moz-mac-yosemite-theme) {
+
+Privilege CSS for most common configuration
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+It is better to put the most common configuration (latest version of an
+OS, or default theme for example) outside of the media query. In the
+following example, ``-moz-mac-yosemite-theme`` targets macOS 10.10 and
+higher, so it should be privileged over the styling for macOS 10.9.
+
+Do this:
+
+.. code:: css
+
+ @media (-moz-mac-yosemite-theme: 0) {
+ #placesList {
+ box-shadow: inset -2px 0 0 hsla(0,0%,100%,.2);
+ }
+ }
+
+Not this:
+
+.. code:: css
+
+ #placesList {
+ box-shadow: inset -2px 0 0 hsla(0,0%,100%,.2);
+ }
+
+ @media (-moz-mac-yosemite-theme) {
+ #placesList {
+ box-shadow: none;
+ }
+ }
+
+Theme support
+-------------
+
+Firefox comes built-in with 3 themes: default, light and dark. The
+built-in light/dark themes are a bit special as they load the
+``compacttheme.css`` stylesheet. In addition to this, Firefox supports a
+variety of WebExtension themes that can be installed from AMO. For
+testing purposes, `here is an example of a WebExtension
+theme. <https://addons.mozilla.org/en-US/firefox/addon/arc-dark-theme-we/>`__
+
+Writing theme-friendly CSS
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- Some CSS variables that are pre-adjusted for different platforms are
+ also pre-adjusted for themes, so it's again a good idea to use them
+ for theme support.
+- The text color of elements often contains valuable information from
+ the theme colors, so ``currentcolor``/inheritance is again a good
+ idea for theme support.
+- Never write CSS specially for the built-in light/dark theme in
+ ``compacttheme.css`` unless that CSS isn't supposed to affect
+ WebExtension themes.
+- These selectors can be used to target dark areas:
+
+ - ``:-moz-lwtheme-brighttext``: dark window frame.
+ - ``:root[lwt-toolbar-field-brighttext]``: dark address bar and
+ searchbar.
+ - ``:root[lwt-popup-brighttext]``: dark arrow panels and
+ autocomplete panels.
+ - ``:root[lwt-sidebar-brighttext]``: dark sidebars.
+
+- If you'd like a different shade of a themed area and no CSS variable
+ is adequate, using colors with alpha transparency is usually a good
+ idea, as it will preserve the original theme author's color hue.
+
+Variables
+~~~~~~~~~
+
+For clarity, CSS variables that are only used when a theme is enabled
+have the ``--lwt-`` prefix.
+
+Layout & performance
+--------------------
+
+Layout
+~~~~~~
+
+Mixing XUL flexbox and HTML flexbox can lead to undefined behavior.
+
+CSS selectors
+~~~~~~~~~~~~~
+
+When targeting the root element of a page, using ``:root`` is the most
+performant way of doing so.
+
+Reflows and style flushes
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+See :ref:`Performance best practices for Firefox front-end engineers`
+for more information about this.
+
+Misc
+----
+
+Text aliasing
+~~~~~~~~~~~~~
+
+When convenient, avoid setting the ``opacity`` property on
+text as it will cause text to be aliased differently.
+
+HDPI support
+~~~~~~~~~~~~
+
+It's recommended to use SVG since it keeps the CSS clean when supporting
+multiple resolutions. See the :ref:`SVG Guidelines` for more information
+on SVG usage.
+
+However, if only 1x and 2x PNG assets are available, you can use this
+``@media`` query to target higher density displays (HDPI):
+
+.. code:: css
+
+ @media (min-resolution: 1.1dppx)
diff --git a/docs/code-quality/coding-style/format_cpp_code_with_clang-format.rst b/docs/code-quality/coding-style/format_cpp_code_with_clang-format.rst
new file mode 100644
index 0000000000..ca64835410
--- /dev/null
+++ b/docs/code-quality/coding-style/format_cpp_code_with_clang-format.rst
@@ -0,0 +1,272 @@
+=====================================
+Formatting C++ Code With clang-format
+=====================================
+
+Mozilla uses the Google coding style for whitespace, which is enforced
+using `clang-format <https://clang.llvm.org/docs/ClangFormat.html>`__. A
+specific version of the binary will be installed when
+``./mach clang-format`` or ``./mach bootstrap`` are run. We build our
+own binaries and update them as needed.
+
+Options are explicitly defined `in clang-format
+itself <https://github.com/llvm-mirror/clang/blob/e8a55f98df6bda77ee2eaa7f7247bd655f79ae0e/lib/Format/Format.cpp#L856>`__.
+If the options are changed in clang upstream, this might cause some
+changes in the Firefox tree. For this reason, it is best to use the
+mozilla-provided binaries.
+
+Manual formatting
+-----------------
+
+We provide a mach subcommand for running clang-format from the
+command-line. This wrapper handles ensuring the correct version of
+clang-format is installed and run.
+
+If clang-format isn’t installed, the binaries will be automatically
+downloaded from taskcluster and installed into ~/.mozbuild. We build our
+own clang-format binaries.
+
+
+Formatting local changes
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+ $ ./mach clang-format
+
+When run without arguments, it will run on a local diff. This could miss
+some reformatting (for example, when blocks are touched).
+(`searchfox <https://searchfox.org/mozilla-central/rev/501eb4718d73870892d28f31a99b46f4783efaa0/python/mozbuild/mozbuild/code-analysis/mach_commands.py#1620>`__)
+
+
+Formatting specific paths
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+ $ ./mach clang-format -p <path> # Format <path> in-place
+ $ ./mach clang-format -p <path> -s # Show changes
+
+The command also accepts a ``-p`` argument to reformat a specific
+directory or file, and a ``-s`` flag to show the changes instead of
+applying them to the working directory
+(`searchfox <https://searchfox.org/mozilla-central/rev/501eb4718d73870892d28f31a99b46f4783efaa0/python/mozbuild/mozbuild/code-analysis/mach_commands.py#1633>`__)
+
+
+Formatting specific commits / revisions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+ $ ./mach clang-format -c HEAD # Format a single git commit
+ $ ./mach clang-format -c HEAD~~..HEAD # Format a range of git commits
+ $ ./mach clang-format -c . # Format a single mercurial revision
+
+The command accepts a ``-c`` argument that takes a revision number or
+commit range, and will format the lines modified by those commits.
+(`searchfox <https://searchfox.org/mozilla-central/rev/501eb4718d73870892d28f31a99b46f4783efaa0/python/mozbuild/mozbuild/code-analysis/mach_commands.py#1635>`__)
+
+
+Scripting Clang-Format
+~~~~~~~~~~~~~~~~~~~~~~
+
+Clang format expects that the path being passed to it is the path
+on-disk. If this is not the case, for example when formatting a
+temporary file, the "real" path must be specified. This can be done with
+the ``--assume-filename <path>`` argument.
+
+
+Configuring the clang-format commit hook
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To run clang-format at commit phase, run ``mach boostrap`` or just add
+the following line in the ``hgrc`` file:
+
+.. code:: ini
+
+ [extensions]
+ clang-format = ~/.mozbuild/version-control-tools/hgext/clang-format
+
+We use a hg extension as they are more flexible than hooks.
+
+With git, the configuration is the following:
+
+::
+
+ # From the root git directory:
+ $ ln -s $(pwd)/tools/lint/hooks_clang_format.py .git/hooks/pre-commit
+
+You'll likely need to install the ``python-hglib`` package for your OS,
+or else you may get errors like ``abort: No module named hglib.client!``
+when you try to commit.
+
+
+Editor integration
+------------------
+
+It is possible to configure many editors to support running
+``clang-format`` automatically on save, or when run from within the
+editor.
+
+
+Editor plugins
+~~~~~~~~~~~~~~
+
+- `Atom <https://atom.io/packages/clang-format>`__
+- `BBEdit <http://clang.llvm.org/docs/ClangFormat.html#bbedit-integration>`__
+
+ - `clang-format-bbedit.applescript <https://raw.githubusercontent.com/llvm-mirror/clang/master/tools/clang-format/clang-format-bbedit.applescript>`__
+
+- Eclipse
+
+ - Install the
+ `CppStyle <https://marketplace.eclipse.org/content/cppstyle>`__
+ plugin
+ - In Preferences -> C/C++ -> CppStyle, set the clang-format path to
+ ~/.mozbuild/clang-tools/clang-tidy/bin/clang-format
+ - (Optional) check "Run clang-format on file save"
+
+- `Emacs <http://clang.llvm.org/docs/ClangFormat.html#emacs-integration>`__
+
+ - `clang-format.el <https://raw.githubusercontent.com/llvm-mirror/clang/master/tools/clang-format/clang-format.el>`__
+ (Or install
+ `clang-format <http://melpa.org/#/clang-format>`__ from MELPA)
+ - `google-c-style <http://melpa.org/#/google-c-style>`__ from MELPA
+
+- `Sublime Text <https://packagecontrol.io/packages/Clang%20Format>`__
+
+ - `alternative
+ tool <https://github.com/rosshemsley/SublimeClangFormat>`__
+
+- `Vim <http://clang.llvm.org/docs/ClangFormat.html#vim-integration>`__
+
+ - `clang-format.py <https://raw.githubusercontent.com/llvm-mirror/clang/master/tools/clang-format/clang-format.py>`__
+ - `vim-clang-format <https://github.com/rhysd/vim-clang-format>`__
+
+- `Visual
+ Studio <https://marketplace.visualstudio.com/items?itemName=LLVMExtensions.ClangFormat>`__
+
+ - `llvm.org plugin <http://llvm.org/builds/>`__
+ - `Integrated support in Visual Studio
+ 2017 <https://blogs.msdn.microsoft.com/vcblog/2018/03/13/clangformat-support-in-visual-studio-2017-15-7-preview-1/>`__
+
+- `Visual Studio
+ Code <https://marketplace.visualstudio.com/items?itemName=xaver.clang-format>`__
+- `XCode <https://github.com/travisjeffery/ClangFormat-Xcode>`__
+- `Script for patch
+ reformatting <http://clang.llvm.org/docs/ClangFormat.html#script-for-patch-reformatting>`__
+
+ - `clang-format-diff.py <https://raw.githubusercontent.com/llvm-mirror/clang/master/tools/clang-format/clang-format-diff.py>`__
+
+
+Configuration
+~~~~~~~~~~~~~
+
+These tools generally run clang-format themselves, and won't use
+``./mach clang-format``. The binary installed by our tooling will be
+located at ``~/.mozbuild/clang-tools/clang-tidy/bin/clang-format``.
+
+You typically shouldn't need to specify any other special configuration
+in your editor besides the clang-format binary. Most of the
+configuration that clang-format relies on for formatting is stored
+inside our source tree. More specifically, using the .clang-format file
+located in the root of the repository. Please note that this doesn't
+include the list of ignored files and directories (provided by
+.clang-format-ignore which is a feature provided by the mach command
+wrapper).
+
+Coding style configuration is done within clang-format itself. When we
+change the configuration (incorrect configuration, new feature in clang,
+etc), we use `local
+overrides <https://searchfox.org/mozilla-central/rev/501eb4718d73870892d28f31a99b46f4783efaa0/.clang-format>`__.
+
+
+Ignored files & directories
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We maintain a `list of ignored directories and
+files <https://searchfox.org/mozilla-central/rev/501eb4718d73870892d28f31a99b46f4783efaa0/.clang-format-ignore>`__,
+which is used by ``./mach clang-format``. This is generally only used
+for code broken by clang-format, and third-party code.
+
+
+Ignored code hunks
+~~~~~~~~~~~~~~~~~~
+
+Sections of code may have formatting disabled using comments. If a
+section must not be formatted, the following comments will disable the
+reformat:
+
+::
+
+ // clang-format off
+ my code which should not be reformatted
+ // clang-format on
+
+You can find an `example of code not
+formatted <https://searchfox.org/mozilla-central/rev/501eb4718d73870892d28f31a99b46f4783efaa0/xpcom/io/nsEscape.cpp#22>`__.
+
+
+Merging formatted and unformatted code
+--------------------------------------
+
+During the transition to using chromium style enforced by clang-format
+for all code in tree, it will often be necessary to rebase non-formatted
+code onto a formatted tree.
+
+
+Mercurial
+~~~~~~~~~
+
+The ``format-source`` extension, now bundled with
+``version-control-tools``, and installed by ``./mach bootstrap``, may be
+used to seamlessly handle this situation. More details may be found in
+this
+`document <https://docs.google.com/document/d/13AwAsvKMhH0mflDlfatBqn6LmZHiQih76oxM4zfrPl4/edit>`__.
+
+The parent changeset of the reformat has been tagged as
+``PRE_TREEWIDE_CLANG_FORMAT``.
+
+
+Git
+~~~
+
+To perform a rebase onto mozilla-central after the merge, a handy merge
+driver, ``clang-format-merge``, has been written:
+
+.. code:: shell
+
+ $ git clone https://github.com/emilio/clang-format-merge
+ $ /path/to/clang-format-merge/git-wrapper rebase <upstream>
+
+The wrapper should clean up after itself, and the clone may be deleted
+after the rebase is complete.
+
+
+Ignore lists
+------------
+
+To make sure that the blame/annotate features of Mercurial or git aren't
+affected. Two files are maintained to keep track of the reformatting
+commits.
+
+
+With Mercurial
+~~~~~~~~~~~~~~
+
+| The list is stored in
+ `https://searchfox.org/mozilla-central/source/.hg-annotate-ignore-revs </en-US/docs/>`__
+| Commit messages should also contain the string ``# ignore-this-changeset``
+
+The syntax in this file is generated using the following syntax:
+
+::
+
+ $ hg log --template '{node} - {author|person} - {desc|strip|firstline}\n'
+
+With git
+~~~~~~~~
+
+The list is stored in
+`https://searchfox.org/mozilla-central/source/.git-blame-ignore-revs </en-US/docs/>`__
+and contains git revisions for both gecko-dev and the git cinnabar
+repository.
diff --git a/docs/code-quality/coding-style/index.rst b/docs/code-quality/coding-style/index.rst
new file mode 100644
index 0000000000..e62ce910ca
--- /dev/null
+++ b/docs/code-quality/coding-style/index.rst
@@ -0,0 +1,20 @@
+Coding style
+============
+
+Firefox code is using different programming languages.
+For each language, we are enforcing a specific coding style.
+
+Getting Help
+------------
+
+If you need help or have questions, please don’t hesitate to contact us via Matrix
+in the "Lint and Formatting" room
+(`#lint:mozilla.org <https://chat.mozilla.org/#/room/#lint:mozilla.org>`_).
+
+
+.. toctree::
+ :caption: Coding Style User Guide
+ :maxdepth: 2
+ :glob:
+
+ *
diff --git a/docs/code-quality/coding-style/rtl_guidelines.rst b/docs/code-quality/coding-style/rtl_guidelines.rst
new file mode 100644
index 0000000000..8b2a6bcf04
--- /dev/null
+++ b/docs/code-quality/coding-style/rtl_guidelines.rst
@@ -0,0 +1,356 @@
+RTL Guidelines
+==============
+
+RTL languages such as Arabic, Hebrew, Persian and Urdu are read and
+written from right-to-left, and the user interface for these languages
+should be mirrored to ensure the content is easy to understand.
+
+When a UI is changed from LTR to RTL (or vice-versa), it’s often called
+mirroring. An RTL layout is the mirror image of an LTR layout, and it
+affects layout, text, and graphics.
+
+In RTL, anything that relates to time should be depicted as moving from
+right to left. For example, forward points to the left, and backwards
+points to the right.
+
+Mirroring layout
+~~~~~~~~~~~~~~~~
+
+When a UI is mirrored, these changes occur:
+
+- Text fields icons are displayed on the opposite side of a field
+- Navigation buttons are displayed in reverse order
+- Icons that communicate direction, like arrows, are mirrored
+- Text is usually aligned to the right
+
+In CSS, while it's possible to apply a rule for LTR and a separate one
+specifically for RTL, it's usually better to use CSS `Logical Properties <https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties>`_
+which provide the ability to control layout through logical, rather than
+physical mappings.
+
++---------------------------------------------------------+--------------------------------------------------+
+| Do | Don't do |
++---------------------------------------------------------+--------------------------------------------------+
+| ``margin-inline-start: 5px`` | ``margin-left: 5px`` |
++---------------------------------------------------------+--------------------------------------------------+
+| ``padding-inline-end: 5px`` | ``padding-right: 5px`` |
++---------------------------------------------------------+--------------------------------------------------+
+| ``float: inline-start`` | ``float: left`` |
++---------------------------------------------------------+--------------------------------------------------+
+| ``inset-inline-start: 5px`` | ``left: 5px`` |
++---------------------------------------------------------+--------------------------------------------------+
+| ``border-inline-end: 1px`` | ``border-right: 1px`` |
++---------------------------------------------------------+--------------------------------------------------+
+| ``border-{start/end}-{start/end}-radius: 2px`` | ``border-{top/bottom}-{left/right}-radius: 2px`` |
++---------------------------------------------------------+--------------------------------------------------+
+| ``padding: 1px 2px`` | ``padding: 1px 2px 1px 2px`` |
++---------------------------------------------------------+--------------------------------------------------+
+| ``margin-block: 1px 3px`` && ``margin-inline: 4px 2px`` | ``margin: 1px 2px 3px 4px`` |
++---------------------------------------------------------+--------------------------------------------------+
+| ``text-align: start`` or ``text-align: match-parent`` | ``text-align: left`` |
+| (depends on the context) | |
++---------------------------------------------------------+--------------------------------------------------+
+
+When there is no special RTL-aware property available, or when
+left/right properties must be used specifically for RTL, use the pseudo
+``:-moz-locale-dir(rtl)`` (for XUL documents) or ``:dir(rtl)`` (for HTML
+documents).
+
+For example, this rule covers LTR to display searchicon.svg 7 pixels
+from the left:
+
+.. code:: css
+
+ .search-box {
+ background-image: url(chrome://path/to/searchicon.svg);
+ background-position: 7px center;
+ }
+
+but an additional rule is necessary to cover RTL and place the search
+icon on the right:
+
+.. code:: css
+
+ .search-box:dir(rtl) {
+ background-position-x: right 7px;
+ }
+
+.. warning::
+
+ It may be inappropriate to use logical properties when embedding LTR
+ within RTL contexts. This is described further in the document.
+
+Mirroring elements
+~~~~~~~~~~~~~~~~~~
+
+RTL content also affects the direction in which some icons and images
+are displayed, particularly those depicting a sequence of events.
+
+What to mirror
+^^^^^^^^^^^^^^
+
+- Icons or animations that imply directionality or motion like
+ back/forward buttons or progress bars
+- Icons that imply text direction, like
+ `reader-mode.svg <https://searchfox.org/mozilla-central/rev/f9beb753a84aa297713d1565dcd0c5e3c66e4174/browser/themes/shared/icons/reader-mode.svg>`__
+- Icons that imply location of UI elements in the screen, like
+ `sidebars-right.svg <https://searchfox.org/mozilla-central/rev/74cc0f4dce444fe0757e2a6b8307d19e4d0e0212/browser/themes/shared/icons/sidebars-right.svg>`__,
+ `open-in-new.svg <https://searchfox.org/mozilla-central/rev/f9beb753a84aa297713d1565dcd0c5e3c66e4174/toolkit/themes/shared/icons/open-in-new.svg>`__,
+ `default theme's preview.svg <https://searchfox.org/mozilla-central/rev/f9beb753a84aa297713d1565dcd0c5e3c66e4174/toolkit/mozapps/extensions/default-theme/preview.svg>`__
+ or
+ `pane-collapse.svg <https://searchfox.org/mozilla-central/rev/74cc0f4dce444fe0757e2a6b8307d19e4d0e0212/devtools/client/debugger/images/pane-collapse.svg>`__
+- Icons representing objects that are meant to be handheld should look
+ like they're being right-handed, like the `magnifying glass
+ icon <https://searchfox.org/mozilla-central/rev/e7c61f4a68b974d5fecd216dc7407b631a24eb8f/toolkit/themes/windows/global/icons/search-textbox.svg>`__
+- Twisties in their collapsed state. Note that if the context in which
+ they appear is LTR (e.g. code in a devtools HTML view), they should
+ not be mirrored, even if the user might be using an RTL locale.
+
+What NOT to mirror
+^^^^^^^^^^^^^^^^^^
+
+- Text/numbers
+- Icons containing text/numbers
+- Icons/animations that are direction neutral
+- Icons that wouldn't look differently if they'd be mirrored, like `X
+ buttons <https://searchfox.org/mozilla-central/rev/a78233c11a6baf2c308fbed17eb16c6e57b6a2ac/devtools/client/debugger/images/close.svg>`__
+ or the `bookmark
+ star <https://searchfox.org/mozilla-central/rev/a78233c11a6baf2c308fbed17eb16c6e57b6a2ac/browser/themes/shared/icons/bookmark-hollow.svg>`__
+ icon, or any other symmetric icon
+- Icons that should look the same as LTR, like icons related to code
+ (which is always LTR) like
+ `tool-webconsole.svg <https://searchfox.org/mozilla-central/rev/74cc0f4dce444fe0757e2a6b8307d19e4d0e0212/devtools/client/themes/images/tool-webconsole.svg>`__
+- Checkmark icons
+- Video/audio player controls
+- Product logos
+- Order of size dimensions (e.g., ``1920x1080`` should not become
+ ``1080x1920``)
+- Order of size units (e.g., ``10 px`` should not become ``px 10``
+ (unless the size unit is localizable))
+
+How
+^^^
+
+The most common way to mirror images is by flipping the X axis:
+
+.. code:: css
+
+ transform: scaleX(-1);
+
+Or, if you're already using ``transform`` with a different value on the same
+element, you can also use `scale`:
+
+.. code:: css
+
+ scale: -1 1;
+
+Note that mirroring images that way doesn't work when the image is a part of
+an element with text using ``background-image``, because then the text would
+be mirrored along with the image, and the image would be positioned incorrectly.
+For such cases, try to use a different method for displaying the image,
+like having it as an element all on its own.
+If that's not possible, add a separate pre-mirrored image asset and specify
+it in a separate ``:dir(rtl)`` rule:
+
+.. code:: css
+
+ .element-with-icon {
+ background-image: url("path/to/image/image.svg");
+ }
+
+ .element-with-icon:dir(rtl) {
+ background-image: url("path/to/image/image-rtl.svg");
+ }
+
+For animations like a progress bar, when using ``@keyframes`` to change
+the ``transform: translateX()`` states, make sure to add a different
+``@keyframes`` suited for RTL, and target that in a separate ``:dir()`` rule:
+
+.. code:: css
+
+ #progressbar {
+ animation: progressbar-animation 1s linear infinite;
+ }
+
+ #progressbar:dir(rtl) {
+ animation-name: progressbar-animation-rtl;
+ }
+
+ @keyframes progressbar-animation {
+ 0% {
+ transform: translateX(-100px);
+ }
+ 100% {
+ transform: translateX(0);
+ }
+ }
+
+ @keyframes progressbar-animation-rtl {
+ 0% {
+ transform: translateX(100px);
+ }
+ 100% {
+ transform: translateX(0);
+ }
+ }
+
+Likewise, if you're using ``transform-origin``, make sure to specify the
+correct origin for RTL:
+
+.. code:: css
+
+ #progressbar {
+ transform-origin: 0 0;
+ }
+
+ #progressbar:dir(rtl) {
+ transform-origin: 100% 0;
+ }
+
+LTR text inside RTL contexts
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+By default, in RTL locales, some symbols like ``/`` and ``.`` will be moved
+around and won't be displayed in the order that they were typed in. This
+may be problematic for URLs for instance, where you don't want dots to
+change position.
+
+Here's a non-exhaustive list of elements that should be displayed like
+they would be in LTR locales:
+
+- Paths (e.g., C:\\Users\\username\\Desktop)
+- Full URLs
+- Code and code containers (like the DevTools' Inspector or the CSS
+ rules panel)
+- about:config preference names and values
+- Telephone numbers
+- Usernames & passwords (most sites on the web expect LTR
+ usernames/passwords, but there may be exceptions)
+- Other text fields where only LTR text is expected
+
+To make sure these are displayed correctly, you can use one of the
+following on the relevant element:
+
+- ``direction: ltr``
+- ``dir="ltr"`` in HTML
+
+Since the direction of such elements is forced to LTR, the text will
+also be aligned to the left, which is undesirable from an UI
+perspective, given that is inconsistent with the rest of the RTL UI
+which has text usually aligned to the right. You can fix this using
+``text-align: match-parent``. In the following screenshot, both text
+fields (username and password) and the URL have their direction set to
+LTR (to display text correctly), but the text itself is aligned to the
+right for consistency with the rest of the UI:
+
+.. image:: about-logins-rtl.png
+ :alt: about:logins textboxes in RTL layout
+
+However, since the direction in LTR, this also means that the start/end
+properties will correspond to left/right respectively, which is probably
+not what you expect. This means you have to use extra rules instead of
+using logical properties.
+
+Here's a full code example:
+
+.. code:: css
+
+ .url {
+ direction: ltr; /* Force text direction to be LTR */
+
+ /* `start` (the default value) will correspond to `left`,
+ * so we match the parent's direction in order to align the text to the right */
+ text-align: match-parent;
+ }
+
+ /* :dir(ltr/rtl) isn't meaningful on .url, since it has direction: ltr, hence
+ * why it is matched on .container. */
+ .container:dir(ltr) .url {
+ padding-left: 1em;
+ }
+
+ .container:dir(rtl) .url {
+ padding-right: 1em;
+ }
+
+.. note::
+
+ The LTR rule is separate from the global rule to avoid having the
+ left padding apply on RTL without having to reset it in the RTL rule.
+
+Auto-directionality
+^^^^^^^^^^^^^^^^^^^
+
+Sometimes, the text direction on an element should vary dynamically
+depending on the situation. This can be the case for a search input for
+instance, a user may input a query in an LTR language, but may also
+input a query in a RTL language. In this case, the search input has to
+dynamically pick the correct directionality based on the first word, in
+order to display the query text correctly. The typical way to do this is
+to use ``dir="auto"`` in HTML. It is essential that
+``text-align: match-parent`` is set, to avoid having the text alignment
+change based on the query, and logical properties also cannot be used on
+the element itself given they can change meaning depending on the query.
+
+Testing
+~~~~~~~
+
+To test for RTL layouts in Firefox, you can go to about:config and
+set ``intl.l10n.pseudo`` to ``bidi``, or select the ``Enable "bidi" locale``
+option in the 3-dots menu in the :doc:`Browser Toolbox </devtools-user/browser_toolbox/index>`.
+The Firefox UI should immediately flip, but a restart may be required
+to take effect in some Firefox features and interactions.
+
+.. note::
+
+ When testing with ``intl.l10n.pseudo`` set to ``bidi``, you may see some
+ oddities regarding text ordering due to the nature of displaying LTR
+ text in RTL layout.
+
+ .. image:: about-protections-rtl.png
+ :alt: about:protections in RTL layout- English vs. Hebrew
+
+ This shouldn't be an issue when using an actual RTL build or language pack.
+
+How to spot RTL-related issues
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Punctuation marks should appear on the left side of a
+ word/sentence/paragraph on RTL, so if a *localizable* string appears
+ in the UI with a dot, colon, ellipsis, question or exclamation mark
+ on the right side of the text, this probably means that the text
+ field is forced to be displayed as LTR.
+- If icons/images/checkmarks do not appear on the opposite side of
+ text, when compared to LTR.
+- If buttons (like the close button, "OK" and "Cancel" etc.) do not
+ appear on the opposite side of the UI and not in the opposite order,
+ when compared to LTR.
+- If paddings/margins/borders are not the same from the opposite side,
+ when compared to LTR.
+- Although Hebrew uses ``1 2 3``, all the other RTL locales we support
+ should use ``١ ٢ ٣`` as digits. So if you see ``1 2 3`` on any such
+ locale, that likely indicates a bug.
+- If navigating in the UI using the left/right arrow keys does not
+ select the correct element (i.e., pressing Left selects an item on
+ the right).
+- If navigating in the UI using the Tab key does not focus elements
+ from right to left, in an RTL context.
+- If code is displayed as RTL (e.g., ``;padding: 20px`` - the semicolon
+ should appear on the right side of the code). Code can still be
+ aligned to the right if it appears in an RTL context.
+
+See also
+~~~~~~~~
+
+- `RTL Best
+ Practices <https://docs.google.com/document/d/1Rc8rvwsLI06xArFQouTinSh3wNte9Sqn9KWi1r7xY4Y/edit#heading=h.pw54h41h12ct>`__
+- Building RTL-Aware Web Apps & Websites: `Part
+ 1 <https://hacks.mozilla.org/2015/09/building-rtl-aware-web-apps-and-websites-part-1/>`__,
+ `Part
+ 2 <https://hacks.mozilla.org/2015/10/building-rtl-aware-web-apps-websites-part-2/>`__
+
+Credits
+~~~~~~~
+
+Google's `Material Design guide for
+RTL <https://material.io/design/usability/bidirectionality.html>`__
diff --git a/docs/code-quality/coding-style/svg_guidelines.rst b/docs/code-quality/coding-style/svg_guidelines.rst
new file mode 100644
index 0000000000..f34317c91a
--- /dev/null
+++ b/docs/code-quality/coding-style/svg_guidelines.rst
@@ -0,0 +1,347 @@
+SVG Guidelines
+==============
+
+Pros and cons of SVG for images
+-------------------------------
+
+When used as a document format there is usually a compelling reason that
+makes SVG the only solution. When used as an `image
+format <https://developer.mozilla.org/en-US/docs/Web/SVG/SVG_as_an_Image>`__,
+it is sometimes less obvious whether it would be best to use SVG or a
+raster image format for any given image. The vector format SVG and
+raster formats like PNG both have their place. When choosing whether or
+not to use SVG it is best to understand the advantages and disadvantages
+of both.
+
+File size
+ Whether SVG or a raster format will produce a smaller file for a
+ given image depends very much on the image. For example, consider an
+ image of a path with a gradient fill. The size of an SVG of this
+ image will be the same regardless of the dimensions of the image. On
+ the other hand the size of a raster file of the same image will
+ likely vary tremendously depending on the dimensions of the image
+ since the larger the dimensions the more pixel data the file needs to
+ store. At very small dimensions (the extreme case being 1px x 1px)
+ the raster file will likely be much smaller than the SVG file since
+ it only needs to store one pixel of data. At large dimensions the
+ raster file may be much larger than the SVG file.
+Scalability, with caveats
+ One of the primary advantages of SVG is that as it is scaled it does
+ not pixelate. However, this is not to say that it always does away
+ with the need to have a collection of raster images for display at
+ different scales. This can be particularly true for icons. While SVG
+ may scale well enough for flat-ish icons without a lot of detail, for
+ icons that try to pack in a lot of detail graphic artists generally
+ `want to be able to pixel
+ tweak <https://www.pushing-pixels.org/2011/11/04/about-those-vector-icons.html>`__.
+Performance
+ While SVG provides a lot of flexibility in terms of scaling,
+ themability, etc. this flexibility depends on doing computations for
+ SVG images at the time they're displayed, rather than at the time the
+ author creates them. Consider an image that involves some complex
+ gradients and filters. If saved as a raster image then the work to
+ rasterize the gradients and filters takes place on the authors
+ computer before the result is stored in the raster file. This work
+ doesn't need to be redone when the image is displayed on someone
+ else's computer. On the other hand, if the image is saved as an SVG
+ image then all this work needs to be done each time the SVG is
+ displayed on someone else's computer. This isn't to say that SVG
+ images are always slower than raster equivalents. In fact it can be
+ faster to send vector information from an SVG to a user's GPU than it
+ is to extract raster data from an equivalent raster image. And even
+ when an SVG image is slower than a raster equivalent, the difference
+ is usually not noticeable. However, just don't fall into the trap of
+ thinking that SVGs are faster than equivalent raster images, or vice
+ versa. Once again, "it depends".
+
+Authoring guidelines
+--------------------
+
+A lot of SVG files (particularly those generated by SVG editors) ship
+without being cleaned up and can contain a ton of junk that bloats the
+file size and slows down rendering. In general the best way to combat
+this is to first run SVG files through a linter such as
+`svgo <https://github.com/svg/svgo>`__ (see the Tools section below).
+However, when authoring SVGs by hand here are some best practices to
+help keep them lightweight. These rules are based on some real examples
+seen in Mozilla's code.
+
+Basics
+~~~~~~
+
+- Two spaces indenting
+- No useless whitespaces or line breaks (see below for more details)
+- Adding a license header
+- Use double quotes
+
+Whitespace and line breaks
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Whitespace
+^^^^^^^^^^
+
+In addition to trailing whitespace at the end of lines, there are a few
+more cases more specific to SVGs:
+
+- Trailing whitespaces in attribute values (usually seen in path
+ definitions)
+- Excessive whitespace in path or polygon points definition
+
+Whitespace examples
+^^^^^^^^^^^^^^^^^^^
+
+This path:
+
+.. code:: html
+
+ <path d=" M5,5 L1,1z ">
+
+can be cut down to this:
+
+.. code:: html
+
+ <path d="M5,5 L1,1z">
+
+Similarly, this polygon:
+
+.. code:: html
+
+ <polygon points=" 0,0 4,4 4,0 "/>
+
+can be cut down to this:
+
+.. code:: html
+
+ <polygon points="0,0 4,4 4,0"/>
+
+Line breaks
+^^^^^^^^^^^
+
+You should only use line breaks for logical separation or if they help
+make the file readable. You should avoid line breaks between every
+single element or within attribute values. It's recommended to put the
+attributes on the same line as their tag names, if possible. You should
+also put the shortest attributes first, so they are easier to spot.
+
+Unused tags and attributes
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Editor metadata
+^^^^^^^^^^^^^^^
+
+Vector editors (Inkscape, Adobe Illustrator, Sketch) usually add a bunch
+of metadata in SVG files while saving them. Metadata can mean many
+things, including:
+
+- The typical "Created with *editor*" comments
+- Non-standard editor specific tags and attributes (``sketch:foo``,
+ ``illustrator:foo``, ``sopodi:foo``, …)
+- The `XML
+ namespace <https://developer.mozilla.org/en-US/docs/Web/SVG/Namespaces_Crash_Course>`__
+ definition that comes with the latter (``xmlns:sketch``,
+ ``xmlns:sopodi``, …)
+
+Other metadata
+^^^^^^^^^^^^^^
+
+In addition to non-standard editor metadata, standard compliant metadata
+also exists. Typical examples of this are ``<title>`` and ``<desc>``
+tags. Although this kind of data is supported by the browser, it can
+only be displayed when the SVG is opened in a new tab. Plus, in most of
+the cases, the filename is quite descriptive So it's recommended to
+remove that kind of metadata since it doesn't bring much value.
+
+You shouldn't include DOCTYPEs in your SVGs either; they are a source of
+many issues, and the SVG WG recommends not to include them. See `SVG
+Authoring
+guidelines <https://jwatt.org/svg/authoring/#doctype-declaration>`__.
+
+Avoid the use of CDATA sections
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+`CDATA
+sections <https://developer.mozilla.org/en-US/docs/Web/API/CDATASection>`__
+are used to avoid parsing some text as HTML. Most of time, CDATA isn't
+needed, for example, the content in ``<style>`` tags doesn't need to be
+wrapped in a CDATA section as the content inside the tag is already
+correctly parsed as CSS.
+
+Invisible shapes
+^^^^^^^^^^^^^^^^
+
+There are two kinds of invisible shapes: The off-screen ones and the
+uncolored ones.
+
+The offscreen shapes are hard to spot, even with an automated tool, and
+are usually context aware. Those kinds of shapes are visible but off the
+`SVG view
+box <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox>`__.
+Here's `an
+example <https://hg.mozilla.org/mozilla-central/diff/9fb143f3b36a/browser/themes/shared/heartbeat-star-lit.svg>`__
+of a file with offscreen shapes.
+
+On the other hand, the uncolored ones are easier to spot, since they
+usually come with styles making them invisible. They must meet two
+conditions: they must be devoid of any fill (or a transparent one) or
+stroke.
+
+Unused attributes on root ``<svg>`` element
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The root ``<svg>`` element can also host many useless attributes. Here's
+an
+`example <https://hg.mozilla.org/mozilla-central/diff/2d38fecce226/browser/components/loop/content/shared/img/icons-10x10.svg>`__
+taking into account the list below:
+
+- ``version``
+- ``x="0"`` and ``y="0"``
+- ``enable-background`` (unsupported by Gecko and now deprecated by the
+ Filter Effects specification)
+- ``id`` (id on root element has no effect)
+- ``xmlns:xlink`` attribute when there are no ``xlink:href`` attributes
+ used throughout the file
+- Other unused `XML
+ Namespace <https://developer.mozilla.org/en-US/docs/Web/SVG/Namespaces_Crash_Course>`__
+ definitions
+- ``xml:space`` when there is no text used in the file
+
+Other
+^^^^^
+
+- Empty tags, this may be obvious, but those are sometimes found in
+ SVGs
+- Unreferenced ids (usually on gradient stops, but also on shapes or
+ paths)
+- ``clip-rule`` attribute when the element *is not* a descendant of a
+ ``<clipPath>``
+- ``fill-rule`` attribute when the element *is* a descendant of a
+ ``<clipPath>``
+- Unreferenced/Unused clip paths, masks or defs
+ (`example <https://hg.mozilla.org/mozilla-central/diff/2d38fecce226/toolkit/themes/shared/reader/RM-Plus-24x24.svg>`__)
+
+Styling
+~~~~~~~
+
+Styling basics
+^^^^^^^^^^^^^^
+
+- Privilege short lowercase hex for colors
+- Don't use excessive precision for numeric values (usually comes from
+ illustrator)
+- Use descriptive IDs
+- Avoid inline styles and use class names or SVG attributes
+
+Styling examples
+''''''''''''''''
+
+Here are some examples for excessive number precision:
+
+- 5.000000e-02 → 0.05 (as seen
+ `here <https://hg.mozilla.org/mozilla-central/diff/2d38fecce226/browser/themes/shared/devtools/images/tool-network.svg#l1.31>`__)
+- -3.728928e-10 → 0 (as seen
+ `here <https://hg.mozilla.org/mozilla-central/diff/2d38fecce226/browser/themes/shared/aboutNetError_alert.svg#l1.12>`__)
+- translate(0.000000, -1.000000) → translate(0, -1) (as seen
+ `here <https://hg.mozilla.org/mozilla-central/diff/2d38fecce226/browser/themes/shared/heartbeat-icon.svg#l1.13>`__)
+
+As for descriptive IDs:
+
+- For gradients: SVG_ID1 → gradient1 (as seen
+ `here <https://hg.mozilla.org/mozilla-central/diff/2d38fecce226/browser/themes/shared/aboutNetError_alert.svg#l1.12>`__)
+
+Use of class names
+^^^^^^^^^^^^^^^^^^
+
+- Avoid using a class if that class is only used once in the file
+- If that class only sets a fill or a stroke, it's better to set the
+ fill/stroke directly on the actual shape, instead of introducing a
+ class just for that shape. You can also use SVG grouping to avoid
+ duplicating those attributes
+- Avoid introducing variants of the same file (color/style variants),
+ and use sprites instead (with class names)
+
+Default style values
+^^^^^^^^^^^^^^^^^^^^
+
+There's usually no need to set the default style value unless you're
+overriding a style. Here are some commonly seen examples:
+
+- ``style="display: none;"`` on ``<defs>`` elements (a ``<defs>``
+ element is hidden by default)
+- ``type="text/css"`` on ``<style>`` elements
+- ``stroke: none`` or ``stroke-width: 0``
+
+SVG grouping
+~~~~~~~~~~~~
+
+Style grouping
+^^^^^^^^^^^^^^
+
+Group similarly styled shapes under one ``<g>`` tag; this avoids having
+to set the same class/styles on many shapes.
+
+Avoid excessive grouping
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+Editors can sometimes do excessive grouping while exporting SVGs. This
+is due to the way editors work.
+
+Nested groups
+'''''''''''''
+
+Avoid multiple-level nesting of groups, these make the SVG less
+readable.
+
+Nested transforms
+'''''''''''''''''
+
+Some editors use ``<g>`` tags to do nested transforms, which is usually
+not needed. You can avoid this by doing basic algebra, for example:
+
+.. code:: xml
+
+ <g transform="translate(-62, -310)"><shape transform="translate(60, 308)"/></g>
+
+can be cut down to:
+
+.. code:: xml
+
+ <shape transform="translate(-2,-2)"/>
+
+because: -62+60 = -310+308 = -2
+
+Performance tips
+~~~~~~~~~~~~~~~~
+
+These rules are optional, but they help speeding up the SVG.
+
+- Avoid using a ``<use>`` tag when that ``<use>`` tag is being
+ referenced only once in the whole file.
+- Instead of using CSS/SVG
+ `transforms <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform>`__,
+ apply directly the transform on the path/shape definition.
+
+Tools
+~~~~~
+
+Tools can help to clean SVG files. Note, however that some of the rules
+stated above can be hard to detect with automated tools since they
+require too much context-awareness. To this date, there doesn't seem to
+be a tool that handles all of the above. However, there are some
+utilities that cover parts of this document:
+
+- Mostly complete command line tool: https://github.com/svg/svgo
+- Alternatives to SVGO:
+
+ - https://github.com/RazrFalcon/svgcleaner
+ - https://github.com/scour-project/scour
+
+- GUI for command line tool (use with "Prettify code" and "Remove
+ ``<title>``" options on): https://jakearchibald.github.io/svgomg/
+- Good alternative to SVGO/SVGOMG:
+ https://petercollingridge.appspot.com/svg-editor
+- Fixes the excessive number precision:
+ https://simon.html5.org/tools/js/svg-optimizer/
+- Converts inline styles to SVG
+ attributes: https://www.w3.org/wiki/SvgTidy
+- RaphaelJS has a couple of utilities that may be useful:
+ `raphael.js <https://dmitrybaranovskiy.github.io/raphael/>`__
diff --git a/docs/code-quality/coding-style/using_cxx_in_firefox_code.rst b/docs/code-quality/coding-style/using_cxx_in_firefox_code.rst
new file mode 100644
index 0000000000..92decbdf1e
--- /dev/null
+++ b/docs/code-quality/coding-style/using_cxx_in_firefox_code.rst
@@ -0,0 +1,1075 @@
+Using C++ in Mozilla code
+=========================
+
+C++ language features
+---------------------
+
+Mozilla code only uses a subset of C++. Runtime type information (RTTI)
+is disabled, as it tends to cause a very large increase in codesize.
+This means that ``dynamic_cast``, ``typeid()`` and ``<typeinfo>`` cannot
+be used in Mozilla code. Also disabled are exceptions; do not use
+``try``/``catch`` or throw any exceptions. Libraries that throw
+exceptions may be used if you are willing to have the throw instead be
+treated as an abort.
+
+On the side of extending C++, we compile with ``-fno-strict-aliasing``.
+This means that when reinterpreting a pointer as a differently-typed
+pointer, you don't need to adhere to the "effective type" (of the
+pointee) rule from the standard (aka. "the strict aliasing rule") when
+dereferencing the reinterpreted pointer. You still need make sure that
+you don't violate alignment requirements and need to make sure that the
+data at the memory location pointed to forms a valid value when
+interpreted according to the type of the pointer when dereferencing the
+pointer for reading. Likewise, if you write by dereferencing the
+reinterpreted pointer and the originally-typed pointer might still be
+dereferenced for reading, you need to make sure that the values you
+write are valid according to the original type. This value validity
+issue is moot for e.g. primitive integers for which all bit patterns of
+their size are valid values.
+
+- As of Mozilla 59, C++14 mode is required to build Mozilla.
+- As of Mozilla 67, MSVC can no longer be used to build Mozilla.
+- As of Mozilla 73, C++17 mode is required to build Mozilla.
+
+This means that C++17 can be used where supported on all platforms. The
+list of acceptable features is given below:
+
+.. list-table::
+ :widths: 25 25 25 25
+ :header-rows: 3
+
+ * -
+ - GCC
+ - Clang
+ -
+ * - Current minimal requirement
+ - 8.1
+ - 7.0
+ -
+ * - Feature
+ - GCC
+ - Clang
+ - Can be used in code
+ * - ``type_t &&``
+ - 4.3
+ - 2.9
+ - Yes (see notes)
+ * - ref qualifiers on methods
+ - 4.8.1
+ - 2.9
+ - Yes
+ * - default member-initializers (except for bit-fields)
+ - 4.7
+ - 3.0
+ - Yes
+ * - default member-initializers (for bit-fields)
+ - 8
+ - 6
+ - **No**
+ * - variadic templates
+ - 4.3
+ - 2.9
+ - Yes
+ * - Initializer lists
+ - 4.4
+ - 3.1
+ - Yes
+ * - ``static_assert``
+ - 4.3
+ - 2.9
+ - Yes
+ * - ``auto``
+ - 4.4
+ - 2.9
+ - Yes
+ * - lambdas
+ - 4.5
+ - 3.1
+ - Yes
+ * - ``decltype``
+ - 4.3
+ - 2.9
+ - Yes
+ * - ``Foo<Bar<T>>``
+ - 4.3
+ - 2.9
+ - Yes
+ * - ``auto func() -> int``
+ - 4.4
+ - 3.1
+ - Yes
+ * - Templated aliasing
+ - 4.7
+ - 3.0
+ - Yes
+ * - ``nullptr``
+ - 4.6
+ - 3.0
+ - Yes
+ * - ``enum foo : int16_t`` {};
+ - 4.4
+ - 2.9
+ - Yes
+ * - ``enum class foo {}``;
+ - 4.4
+ - 2.9
+ - Yes
+ * - ``enum foo;``
+ - 4.6
+ - 3.1
+ - Yes
+ * - ``[[attributes]]``
+ - 4.8
+ - 3.3
+ - **No** (see notes)
+ * - ``constexpr``
+ - 4.6
+ - 3.1
+ - Yes
+ * - ``alignas``
+ - 4.8
+ - 3.3
+ - Yes
+ * - ``alignof``
+ - 4.8
+ - 3.3
+ - Yes, but see notes ; only clang 3.6 claims as_feature(cxx_alignof)
+ * - Delegated constructors
+ - 4.7
+ - 3.0
+ - Yes
+ * - Inherited constructors
+ - 4.8
+ - 3.3
+ - Yes
+ * - ``explicit operator bool()``
+ - 4.5
+ - 3.0
+ - Yes
+ * - ``char16_t/u"string"``
+ - 4.4
+ - 3.0
+ - Yes
+ * - ``R"(string)"``
+ - 4.5
+ - 3.0
+ - Yes
+ * - ``operator""()``
+ - 4.7
+ - 3.1
+ - Yes
+ * - ``=delete``
+ - 4.4
+ - 2.9
+ - Yes
+ * - ``=default``
+ - 4.4
+ - 3.0
+ - Yes
+ * - unrestricted unions
+ - 4.6
+ - 3.1
+ - Yes
+ * - ``for (auto x : vec)`` (`be careful about the type of the iterator <https://stackoverflow.com/questions/15176104/c11-range-based-loop-get-item-by-value-or-reference-to-const>`__)
+ - 4.6
+ - 3.0
+ - Yes
+ * - ``override``/``final``
+ - 4.7
+ - 3.0
+ - Yes
+ * - ``thread_local``
+ - 4.8
+ - 3.3
+ - **No** (see notes)
+ * - function template default arguments
+ - 4.3
+ - 2.9
+ - Yes
+ * - local structs as template parameters
+ - 4.5
+ - 2.9
+ - Yes
+ * - extended friend declarations
+ - 4.7
+ - 2.9
+ - Yes
+ * - ``0b100`` (C++14)
+ - 4.9
+ - 2.9
+ - Yes
+ * - `Tweaks to some C++ contextual conversions` (C++14)
+ - 4.9
+ - 3.4
+ - Yes
+ * - Return type deduction (C++14)
+ - 4.9
+ - 3.4
+ - Yes (but only in template code when you would have used ``decltype (complex-expression)``)
+ * - Generic lambdas (C++14)
+ - 4.9
+ - 3.4
+ - Yes
+ * - Initialized lambda captures (C++14)
+ - 4.9
+ - 3.4
+ - Yes
+ * - Digit separator (C++14)
+ - 4.9
+ - 3.4
+ - Yes
+ * - Variable templates (C++14)
+ - 5.0
+ - 3.4
+ - Yes
+ * - Relaxed constexpr (C++14)
+ - 5.0
+ - 3.4
+ - Yes
+ * - Aggregate member initialization (C++14)
+ - 5.0
+ - 3.3
+ - Yes
+ * - Clarifying memory allocation (C++14)
+ - 5.0
+ - 3.4
+ - Yes
+ * - [[deprecated]] attribute (C++14)
+ - 4.9
+ - 3.4
+ - **No** (see notes)
+ * - Sized deallocation (C++14)
+ - 5.0
+ - 3.4
+ - **No** (see notes)
+ * - Concepts (Concepts TS)
+ - 6.0
+ - —
+ - **No**
+ * - Inline variables (C++17)
+ - 7.0
+ - 3.9
+ - Yes
+ * - constexpr_if (C++17)
+ - 7.0
+ - 3.9
+ - Yes
+ * - constexpr lambdas (C++17)
+ - —
+ - —
+ - **No**
+ * - Structured bindings (C++17)
+ - 7.0
+ - 4.0
+ - Yes
+ * - Separated declaration and condition in ``if``, ``switch`` (C++17)
+ - 7.0
+ - 3.9
+ - Yes
+ * - `Fold expressions <https://en.cppreference.com/w/cpp/language/fold>`__ (C++17)
+ - 6.0
+ - 3.9
+ - Yes
+ * - [[fallthrough]], [[maybe_unused]], [[nodiscard]] (C++17)
+ - 7.0
+ - 3.9
+ - Yes
+ * - Aligned allocation/deallocation (C++17)
+ - 7.0
+ - 4.0
+ - **No** (see notes)
+ * - Designated initializers (C++20)
+ - 8.0 (4.7)
+ - 10.0 (3.0)
+ - Yes [*sic*] (see notes)
+ * - #pragma once
+ - 3.4
+ - Yes
+ - **Not** until we `normalize headers <https://groups.google.com/d/msg/mozilla.dev.platform/PgDjWw3xp8k/eqCFlP4Kz1MJ>`__
+ * - `Source code information capture <https://en.cppreference.com/w/cpp/experimental/lib_extensions_2#Source_code_information_capture>`__
+ - 8.0
+ - —
+ - **No**
+
+Sources
+~~~~~~~
+
+* GCC: https://gcc.gnu.org/projects/cxx-status.html
+* Clang: https://clang.llvm.org/cxx_status.html
+
+Notes
+~~~~~
+
+rvalue references
+ Implicit move method generation cannot be used.
+
+Attributes
+ Several common attributes are defined in
+ `mozilla/Attributes.h <https://searchfox.org/mozilla-central/source/mfbt/Attributes.h>`__
+ or nscore.h.
+
+Alignment
+ Some alignment utilities are defined in `mozilla/Alignment.h
+ <https://searchfox.org/mozilla-central/source/mfbt/Alignment.h>`__.
+
+ .. caution::
+ ``MOZ_ALIGNOF`` and ``alignof`` don't have the same semantics. Be careful of what you
+ expect from them.
+
+``[[deprecated]]``
+ If we have deprecated code, we should be removing it rather than marking it as
+ such. Marking things as ``[[deprecated]]`` also means the compiler will warn
+ if you use the deprecated API, which turns into a fatal error in our
+ automation builds, which is not helpful.
+
+Sized deallocation
+ Our compilers all support this (custom flags are required for GCC and Clang),
+ but turning it on breaks some classes' ``operator new`` methods, and `some
+ work <https://bugzilla.mozilla.org/show_bug.cgi?id=1250998>`__ would need to
+ be done to make it an efficiency win with our custom memory allocator.
+
+Aligned allocation/deallocation
+ Our custom memory allocator doesn't have support for these functions.
+
+Thread locals
+ ``thread_local`` is not supported on Android.
+
+Designated initializers
+ Despite their late addition to C++ (and lack of *official* support by
+ compilers until relatively recently), `C++20's designated initializers
+ <https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf>`__ are
+ merely a subset of `a feature originally introduced in C99
+ <https://gcc.gnu.org/onlinedocs/gcc/Designated-Inits.html>`__ -- and this
+ subset has been accepted without comment in C++ code since at least GCC 4.7
+ and Clang 3.0.
+
+
+C++ and Mozilla standard libraries
+----------------------------------
+
+The Mozilla codebase contains within it several subprojects which follow
+different rules for which libraries can and can't be used it. The rules
+listed here apply to normal platform code, and assume unrestricted
+usability of MFBT or XPCOM APIs.
+
+.. warning::
+
+ The rest of this section is a draft for expository and exploratory
+ purposes. Do not trust the information listed here.
+
+What follows is a list of standard library components provided by
+Mozilla or the C++ standard. If an API is not listed here, then it is
+not permissible to use it in Mozilla code. Deprecated APIs are not
+listed here. In general, prefer Mozilla variants of data structures to
+standard C++ ones, even when permitted to use the latter, since Mozilla
+variants tend to have features not found in the standard library (e.g.,
+memory size tracking) or have more controllable performance
+characteristics.
+
+A list of approved standard library headers is maintained in
+`config/stl-headers.mozbuild <https://searchfox.org/mozilla-central/source/config/stl-headers.mozbuild>`__.
+
+
+Data structures
+~~~~~~~~~~~~~~~
+
+.. list-table::
+ :widths: 25 25 25 25
+ :header-rows: 1
+
+ * - Name
+ - Header
+ - STL equivalent
+ - Notes
+ * - ``nsAutoTArray``
+ - ``nsTArray.h``
+ -
+ - Like ``nsTArray``, but will store a small amount as stack storage
+ * - ``nsAutoTObserverArray``
+ - ``nsTObserverArray.h``
+ -
+ - Like ``nsTObserverArray``, but will store a small amount as stack storage
+ * - ``mozilla::BloomFilter``
+ - ``mozilla/BloomFilter.h``
+ -
+ - Probabilistic set membership (see `Wikipedia <https://en.wikipedia.org/wiki/Bloom_filter#Counting_filters>`__)
+ * - ``nsClassHashtable``
+ - ``nsClassHashtable.h``
+ -
+ - Adaptation of nsTHashtable, see :ref:`XPCOM Hashtable Guide`
+ * - ``nsCOMArray``
+ - ``nsCOMArray.h``
+ -
+ - Like ``nsTArray<nsCOMPtr<T>>``
+ * - ``nsDataHashtable``
+ - ``nsClassHashtable.h``
+ - ``std::unordered_map``
+ - Adaptation of ``nsTHashtable``, see :ref:`XPCOM Hashtable Guide`
+ * - ``nsDeque``
+ - ``nsDeque.h``
+ - ``std::deque<void *>``
+ -
+ * - ``mozilla::EnumSet``
+ - ``mozilla/EnumSet.h``
+ -
+ - Like ``std::set``, but for enum classes.
+ * - ``mozilla::Hash{Map,Set}``
+ - `mozilla/HashTable.h <https://searchfox.org/mozilla-central/source/mfbt/HashTable.h>`__
+ - ``std::unordered_{map,set}``
+ - A general purpose hash map and hash set.
+ * - ``nsInterfaceHashtable``
+ - ``nsInterfaceHashtable.h``
+ - ``std::unordered_map``
+ - Adaptation of ``nsTHashtable``, see :ref:`XPCOM Hashtable Guide`
+ * - ``mozilla::LinkedList``
+ - ``mozilla/LinkedList.h``
+ - ``std::list``
+ - Doubly-linked list
+ * - ``nsRef PtrHashtable``
+ - ``nsRefPtrHashtable.h``
+ - ``std::unordered_map``
+ - Adaptation of ``nsTHashtable``, see :ref:`XPCOM Hashtable Guide`
+ * - ``mozilla::SegmentedVector``
+ - ``mozilla/SegmentedVector.h``
+ - ``std::deque`` w/o O(1) pop_front
+ - Doubly-linked list of vector elements
+ * - ``mozilla::SplayTree``
+ - ``mozilla/SplayTree.h``
+ -
+ - Quick access to recently-accessed elements (see `Wikipedia <https://en.wikipedia.org/wiki/Splay_tree>`__)
+ * - ``nsTArray``
+ - ``nsTArray.h``
+ - ``std::vector``
+ -
+ * - ``nsTHashtable``
+ - ``nsTHashtable.h``
+ - ``std::unordered_{map,set}``
+ - See :ref:`XPCOM Hashtable Guide`, you probably want a subclass
+ * - ``nsTObserverArray``
+ - ``nsTObserverArray.h``
+ -
+ - Like ``nsTArray``, but iteration is stable even through mutation
+ * - ``nsTPriorityQueue``
+ - ``nsTPriorityQueue.h``
+ - ``std::priority_queue``
+ - Unlike the STL class, not a container adapter
+ * - ``mozilla::Vector``
+ - ``mozilla/Vector.h``
+ - ``std::vector``
+ -
+ * - ``mozilla::Buffer``
+ - ``mozilla/Buffer.h``
+ -
+ - Unlike ``Array``, has a run-time variable length. Unlike ``Vector``, does not have capacity and growth mechanism. Unlike ``Span``, owns its buffer.
+
+
+Safety utilities
+~~~~~~~~~~~~~~~~
+
+.. list-table::
+ :widths: 25 25 25 25
+ :header-rows: 1
+
+ * - Name
+ - Header
+ - STL equivalent
+ - Notes
+ * - ``mozilla::Array``
+ - ``mfbt/Array.h``
+ -
+ - safe array index
+ * - ``mozilla::AssertedCast``
+ - ``mfbt/Casting.h``
+ -
+ - casts
+ * - ``mozilla::CheckedInt``
+ - ``mfbt/CheckedInt.h``
+ -
+ - avoids overflow
+ * - ``nsCOMPtr``
+ - ``xpcom/base/nsCOMPtr.h``
+ - ``std::shared_ptr``
+ -
+ * - ``mozilla::EnumeratedArray``
+ - ``mfbt/EnumeratedArray.h``
+ - ``mozilla::Array``
+ -
+ * - ``mozilla::Maybe``
+ - ``mfbt/Maybe.h``
+ - ``std::optional``
+ -
+ * - ``mozilla::RangedPtr``
+ - ``mfbt/RangedPtr.h``
+ -
+ - like ``mozilla::Span`` but with two pointers instead of pointer and length
+ * - ``mozilla::RefPtr``
+ - ``mfbt/RefPtr.h``
+ - ``std::shared_ptr``
+ -
+ * - ``mozilla::Span``
+ - ``mozilla/Span.h``
+ - ``gsl::span``, ``absl::Span``, ``std::string_view``, ``std::u16string_view``
+ - Rust's slice concept for C++ (without borrow checking)
+ * - ``StaticRefPtr``
+ - ``xpcom/base/StaticPtr.h``
+ -
+ - ``nsRefPtr`` w/o static constructor
+ * - ``mozilla::UniquePtr``
+ - ``mfbt/UniquePtr.h``
+ - ``std::unique_ptr``
+ -
+ * - ``mozilla::WeakPtr``
+ - ``mfbt/WeakPtr.h``
+ - ``std::weak_ptr``
+ -
+ * - ``nsWeakPtr``
+ - ``xpcom/base/nsWeakPtr.h``
+ - ``std::weak_ptr``
+ -
+
+
+Strings
+~~~~~~~
+
+See the :doc:`Mozilla internal string guide </xpcom/stringguide>` for
+usage of ``nsAString`` (our copy-on-write replacement for
+``std::u16string``) and ``nsACString`` (our copy-on-write replacement
+for ``std::string``).
+
+Be sure not to introduce further uses of ``std::wstring``, which is not
+portable! (Some uses exist in the IPC code.)
+
+
+Algorithms
+~~~~~~~~~~
+
+.. list-table::
+ :widths: 25 25
+
+ * - ``mozilla::BinarySearch``
+ - ``mfbt/BinarySearch.h``
+ * - ``mozilla::BitwiseCast``
+ - ``mfbt/Casting.h`` (strict aliasing-safe cast)
+ * - ``mozilla/MathAlgorithms.h``
+ - (rotate, ctlz, popcount, gcd, abs, lcm)
+ * - ``mozilla::RollingMean``
+ - ``mfbt/RollingMean.h`` ()
+
+
+Concurrency
+~~~~~~~~~~~
+
+.. list-table::
+ :widths: 25 25 25 25
+ :header-rows: 1
+
+ * - Name
+ - Header
+ - STL/boost equivalent
+ - Notes
+ * - ``mozilla::Atomic``
+ - mfbt/Atomic.h
+ - ``std::atomic``
+ -
+ * - ``mozilla::CondVar``
+ - xpcom/threads/CondVar.h
+ - ``std::condition_variable``
+ -
+ * - ``mozilla::DataMutex``
+ - xpcom/threads/DataMutex.h
+ - ``boost::synchronized_value``
+ -
+ * - ``mozilla::Monitor``
+ - xpcom/threads/Monitor.h
+ -
+ -
+ * - ``mozilla::Mutex``
+ - xpcom/threads/Mutex.h
+ - ``std::mutex``
+ -
+ * - ``mozilla::ReentrantMonitor``
+ - xpcom/threads/ReentrantMonitor.h
+ -
+ -
+ * - ``mozilla::StaticMutex``
+ - xpcom/base/StaticMutex.h
+ - ``std::mutex``
+ - Mutex that can (and in fact, must) be used as a global/static variable.
+
+
+Miscellaneous
+~~~~~~~~~~~~~
+
+.. list-table::
+ :widths: 25 25 25 25
+ :header-rows: 1
+
+ * - Name
+ - Header
+ - STL/boost equivalent
+ - Notes
+ * - ``mozilla::AlignedStorage``
+ - mfbt/Alignment.h
+ - ``std::aligned_storage``
+ -
+ * - ``mozilla::MaybeOneOf``
+ - mfbt/MaybeOneOf.h
+ - ``std::optional<std::variant<T1, T2>>``
+ - ~ ``mozilla::Maybe<union {T1, T2}>``
+ * - ``mozilla::Pair``
+ - mfbt/Pair.h
+ - ``std::tuple<T1, T2>``
+ - minimal space!
+ * - ``mozilla::TimeStamp``
+ - xpcom/ds/TimeStamp.h
+ - ``std::chrono::time_point``
+ -
+ * -
+ - mozilla/PodOperations.h
+ -
+ - C++ versions of ``memset``, ``memcpy``, etc.
+ * -
+ - mozilla/ArrayUtils.h
+ -
+ -
+ * -
+ - mozilla/Compression.h
+ -
+ -
+ * -
+ - mozilla/Endian.h
+ -
+ -
+ * -
+ - mozilla/FloatingPoint.h
+ -
+ -
+ * -
+ - mozilla/HashFunctions.h
+ - ``std::hash``
+ -
+ * -
+ - mozilla/Move.h
+ - ``std::move``, ``std::swap``, ``std::forward``
+ -
+
+
+Mozilla data structures and standard C++ ranges and iterators
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Some Mozilla-defined data structures provide STL-style
+`iterators <https://en.cppreference.com/w/cpp/named_req/Iterator>`__ and
+are usable in `range-based for
+loops <https://en.cppreference.com/w/cpp/language/range-for>`__ as well
+as STL `algorithms <https://en.cppreference.com/w/cpp/algorithm>`__.
+
+Currently, these include:
+
+.. list-table::
+ :widths: 16 16 16 16 16
+ :header-rows: 1
+
+ * - Name
+ - Header
+ - Bug(s)
+ - Iterator category
+ - Notes
+ * - ``nsTArray``
+ - ``xpcom/ds/n sTArray.h``
+ - `1126552 <https://bugzilla.mozilla.org/show_bug.cgi?id=1126552>`__
+ - Random-access
+ - Also reverse-iterable. Also supports remove-erase pattern via RemoveElementsAt method. Also supports back-inserting output iterators via ``MakeBackInserter`` function.
+ * - ``nsBaseHashtable`` and subclasses: ``nsClassHashtable`` ``nsDataHashtable`` ``nsInterfaceHashtable`` ``nsJSThingHashtable`` ``nsRefPtrHashtable``
+ - ``xpcom/ds/nsBaseHashtable.h`` ``xpcom/ds/nsClassHashtable.h`` ``xpcom/ds/nsDataHashtable.h`` ``xpcom/ds/nsInterfaceHashtable.h`` ``xpcom/ds/nsJSThingHashtable.h`` ``xpcom/ds/nsRefPtrHashtable.h``
+ - `1575479 <https://bugzilla.mozilla.org/show_bug.cgi?id=1575479>`__
+ - Forward
+ -
+ * - ``nsCOMArray``
+ - ``xpcom/ds/nsCOMArray.h``
+ - `1342303 <https://bugzilla.mozilla.org/show_bug.cgi?id=1342303>`__
+ - Random-access
+ - Also reverse-iterable.
+ * - ``Array`` ``EnumerationArray`` ``RangedArray``
+ - ``mfbt/Array.h`` ``mfbt/EnumerationArray.h`` ``mfbt/RangedArray.h``
+ - `1216041 <https://bugzilla.mozilla.org/show_bug.cgi?id=1216041>`__
+ - Random-access
+ - Also reverse-iterable.
+ * - ``Buffer``
+ - ``mfbt/Buffer.h``
+ - `1512155 <https://bugzilla.mozilla.org/show_bug.cgi?id=1512155>`__
+ - Random-access
+ - Also reverse-iterable.
+ * - ``DoublyLinkedList``
+ - ``mfbt/DoublyLinkedList.h``
+ - `1277725 <https://bugzilla.mozilla.org/show_bug.cgi?id=1277725>`__
+ - Forward
+ -
+ * - ``EnumeratedRange``
+ - ``mfbt/EnumeratedRange.h``
+ - `1142999 <https://bugzilla.mozilla.org/show_bug.cgi?id=1142999>`__
+ - *Missing*
+ - Also reverse-iterable.
+ * - ``IntegerRange``
+ - ``mfbt/IntegerRange.h``
+ - `1126701 <https://bugzilla.mozilla.org/show_bug.cgi?id=1126701>`__
+ - *Missing*
+ - Also reverse-iterable.
+ * - ``SmallPointerArray``
+ - ``mfbt/SmallPointerArray.h``
+ - `1331718 <https://bugzilla.mozilla.org/show_bug.cgi?id=1331718>`__
+ - Random-access
+ -
+ * - ``Span``
+ - ``mfbt/Span.h``
+ - `1295611 <https://bugzilla.mozilla.org/show_bug.cgi?id=1295611>`__
+ - Random-access
+ - Also reverse-iterable.
+
+Note that if the iterator category is stated as "missing", the type is
+probably only usable in range-based for. This is most likely just an
+omission, which could be easily fixed.
+
+Useful in this context are also the class template ``IteratorRange``
+(which can be used to construct a range from any pair of iterators) and
+function template ``Reversed`` (which can be used to reverse any range),
+both defined in ``mfbt/ReverseIterator.h``
+
+
+Further C++ rules
+-----------------
+
+
+Don't use static constructors
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+(You probably shouldn't be using global variables to begin with. Quite
+apart from the weighty software-engineering arguments against them,
+globals affect startup time! But sometimes we have to do ugly things.)
+
+Non-portable example:
+
+.. code-block:: c++
+
+ FooBarClass static_object(87, 92);
+
+ void
+ bar()
+ {
+ if (static_object.count > 15) {
+ ...
+ }
+ }
+
+Once upon a time, there were compiler bugs that could result in
+constructors not being called for global objects. Those bugs are
+probably long gone by now, but even with the feature working correctly,
+there are so many problems with correctly ordering C++ constructors that
+it's easier to just have an init function:
+
+.. code-block:: c++
+
+ static FooBarClass* static_object;
+
+ FooBarClass*
+ getStaticObject()
+ {
+ if (!static_object)
+ static_object =
+ new FooBarClass(87, 92);
+ return static_object;
+ }
+
+ void
+ bar()
+ {
+ if (getStaticObject()->count > 15) {
+ ...
+ }
+ }
+
+
+Don't use exceptions
+~~~~~~~~~~~~~~~~~~~~
+
+See the introduction to the "C++ language features" section at the start
+of this document.
+
+
+Don't use Run-time Type Information
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+See the introduction to the "C++ language features" section at the start
+of this document.
+
+If you need runtime typing, you can achieve a similar result by adding a
+``classOf()`` virtual member function to the base class of your
+hierarchy and overriding that member function in each subclass. If
+``classOf()`` returns a unique value for each class in the hierarchy,
+you'll be able to do type comparisons at runtime.
+
+
+Don't use the C++ standard library (including iostream and locale)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+See the section "C++ and Mozilla standard libraries".
+
+
+Use C++ lambdas, but with care
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+C++ lambdas are supported across all our compilers now. Rejoice! We
+recommend explicitly listing out the variables that you capture in the
+lambda, both for documentation purposes, and to double-check that you're
+only capturing what you expect to capture.
+
+
+Use namespaces
+~~~~~~~~~~~~~~
+
+Namespaces may be used according to the style guidelines in :ref:`C++ Coding style`.
+
+
+Don't mix varargs and inlines
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+What? Why are you using varargs to begin with?! Stop that at once!
+
+
+Make header files compatible with C and C++
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Non-portable example:
+
+.. code-block:: c++
+
+ /*oldCheader.h*/
+ int existingCfunction(char*);
+ int anotherExistingCfunction(char*);
+
+ /* oldCfile.c */
+ #include "oldCheader.h"
+ ...
+
+ // new file.cpp
+ extern "C" {
+ #include "oldCheader.h"
+ };
+ ...
+
+If you make new header files with exposed C interfaces, make the header
+files work correctly when they are included by both C and C++ files.
+
+(If you need to include a C header in new C++ files, that should just
+work. If not, it's the C header maintainer's fault, so fix the header if
+you can, and if not, whatever hack you come up with will probably be
+fine.)
+
+Portable example:
+
+.. code-block:: c++
+
+ /* oldCheader.h*/
+ PR_BEGIN_EXTERN_C
+ int existingCfunction(char*);
+ int anotherExistingCfunction(char*);
+ PR_END_EXTERN_C
+
+ /* oldCfile.c */
+ #include "oldCheader.h"
+ ...
+
+ // new file.cpp
+ #include "oldCheader.h"
+ ...
+
+There are number of reasons for doing this, other than just good style.
+For one thing, you are making life easier for everyone else, doing the
+work in one common place (the header file) instead of all the C++ files
+that include it. Also, by making the C header safe for C++, you document
+that "hey, this file is now being included in C++". That's a good thing.
+You also avoid a big portability nightmare that is nasty to fix...
+
+
+Use override on subclass virtual member functions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``override`` keyword is supported in C++11 and in all our supported
+compilers, and it catches bugs.
+
+
+Always declare a copy constructor and assignment operator
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Many classes shouldn't be copied or assigned. If you're writing one of
+these, the way to enforce your policy is to declare a deleted copy
+constructor as private and not supply a definition. While you're at it,
+do the same for the assignment operator used for assignment of objects
+of the same class. Example:
+
+.. code-block:: c++
+
+ class Foo {
+ ...
+ private:
+ Foo(const Foo& x) = delete;
+ Foo& operator=(const Foo& x) = delete;
+ };
+
+Any code that implicitly calls the copy constructor will hit a
+compile-time error. That way nothing happens in the dark. When a user's
+code won't compile, they'll see that they were passing by value, when
+they meant to pass by reference (oops).
+
+
+Be careful of overloaded methods with like signatures
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+It's best to avoid overloading methods when the type signature of the
+methods differs only by one "abstract" type (e.g. ``PR_Int32`` or
+``int32``). What you will find as you move that code to different
+platforms, is suddenly on the Foo2000 compiler your overloaded methods
+will have the same type-signature.
+
+
+Type scalar constants to avoid unexpected ambiguities
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Non-portable code:
+
+.. code-block:: c++
+
+ class FooClass {
+ // having such similar signatures
+ // is a bad idea in the first place.
+ void doit(long);
+ void doit(short);
+ };
+
+ void
+ B::foo(FooClass* xyz)
+ {
+ xyz->doit(45);
+ }
+
+Be sure to type your scalar constants, e.g., ``uint32_t(10)`` or
+``10L``. Otherwise, you can produce ambiguous function calls which
+potentially could resolve to multiple methods, particularly if you
+haven't followed (2) above. Not all of the compilers will flag ambiguous
+method calls.
+
+Portable code:
+
+.. code-block:: c++
+
+ class FooClass {
+ // having such similar signatures
+ // is a bad idea in the first place.
+ void doit(long);
+ void doit(short);
+ };
+
+ void
+ B::foo(FooClass* xyz)
+ {
+ xyz->doit(45L);
+ }
+
+
+Use nsCOMPtr in XPCOM code
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+See the ``nsCOMPtr`` `User
+Manual <https://developer.mozilla.org/en-US/docs/Using_nsCOMPtr>`__ for
+usage details.
+
+
+Don't use identifiers that start with an underscore
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This rule occasionally surprises people who've been hacking C++ for
+decades. But it comes directly from the C++ standard!
+
+According to the C++ Standard, 17.4.3.1.2 Global Names
+[lib.global.names], paragraph 1:
+
+Certain sets of names and function signatures are always reserved to the
+implementation:
+
+- Each name that contains a double underscore (__) or begins with an
+ underscore followed by an uppercase letter (2.11) is reserved to the
+ implementation for any use.
+- **Each name that begins with an underscore is reserved to the
+ implementation** for use as a name in the global namespace.
+
+
+Stuff that is good to do for C or C++
+-------------------------------------
+
+
+Avoid conditional #includes when possible
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Don't write an ``#include`` inside an ``#ifdef`` if you could instead
+put it outside. Unconditional includes are better because they make the
+compilation more similar across all platforms and configurations, so
+you're less likely to cause stupid compiler errors on someone else's
+favorite platform that you never use.
+
+Bad code example:
+
+.. code-block:: c++
+
+ #ifdef MOZ_ENABLE_JPEG_FOUR_BILLION
+ #include <stdlib.h> // <--- don't do this
+ #include "jpeg4e9.h" // <--- only do this if the header really might not be there
+ #endif
+
+Of course when you're including different system files for different
+machines, you don't have much choice. That's different.
+
+
+Every .cpp source file should have a unique name
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Every object file linked into libxul needs to have a unique name. Avoid
+generic names like nsModule.cpp and instead use nsPlacesModule.cpp.
+
+
+Turn on warnings for your compiler, and then write warning free code
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+What generates a warning on one platform will generate errors on
+another. Turn warnings on. Write warning-free code. It's good for you.
+Treat warnings as errors by adding
+``ac_add_options --enable-warnings-as-errors`` to your mozconfig file.
+
+
+Use the same type for all bitfields in a ``struct`` or ``class``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Some compilers do not pack the bits when different bitfields are given
+different types. For example, the following struct might have a size of
+8 bytes, even though it would fit in 1:
+
+.. code-block:: c++
+
+ struct {
+ char ch: 1;
+ int i: 1;
+ };
+
+
+Don't use an enum type for a bitfield
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The classic example of this is using ``PRBool`` for a boolean bitfield.
+Don't do that. ``PRBool`` is a signed integer type, so the bitfield's
+value when set will be ``-1`` instead of ``+1``, which---I know,
+*crazy*, right? The things C++ hackers used to have to put up with...
+
+You shouldn't be using ``PRBool`` anyway. Use ``bool``. Bitfields of
+type ``bool`` are fine.
+
+Enums are signed on some platforms (in some configurations) and unsigned
+on others and therefore unsuitable for writing portable code when every
+bit counts, even if they happen to work on your system.
diff --git a/docs/code-quality/index.rst b/docs/code-quality/index.rst
new file mode 100644
index 0000000000..e8336b7948
--- /dev/null
+++ b/docs/code-quality/index.rst
@@ -0,0 +1,185 @@
+Code quality
+============
+
+Because Firefox is a complex piece of software, a lot of tools are
+executed to identify issues at development phase.
+In this document, we try to list these all tools.
+
+
+.. toctree::
+ :maxdepth: 1
+ :glob:
+
+ static-analysis/index.rst
+ lint/index.rst
+ coding-style/index.rst
+
+.. list-table:: C/C++
+ :header-rows: 1
+ :widths: 20 20 20 20 20
+
+ * - Tools
+ - Has autofixes
+ - Meta bug
+ - More info
+ - Upstream
+ * - Custom clang checker
+ -
+ -
+ - `Source <https://searchfox.org/mozilla-central/source/build/clang-plugin>`_
+ -
+ * - Clang-Tidy
+ - Yes
+ - `bug 712350 <https://bugzilla.mozilla.org/show_bug.cgi?id=712350>`__
+ - :ref:`Static analysis <Static Analysis>`
+ - https://clang.llvm.org/extra/clang-tidy/checks/list.html
+ * - Clang analyzer
+ -
+ - `bug 712350 <https://bugzilla.mozilla.org/show_bug.cgi?id=712350>`__
+ -
+ - https://clang-analyzer.llvm.org/
+ * - cpp virtual final
+ -
+ -
+ - :ref:`cpp virtual final`
+ -
+ * - Semmle/LGTM
+ -
+ - `bug 1458117 <https://bugzilla.mozilla.org/show_bug.cgi?id=1458117>`__
+ -
+ -
+ * - clang-format
+ - Yes
+ - `bug 1188202 <https://bugzilla.mozilla.org/show_bug.cgi?id=1188202>`__
+ - :ref:`Formatting C++ Code With clang-format`
+ - https://clang.llvm.org/docs/ClangFormat.html
+
+.. list-table:: CSS
+ :widths: 20 20 20 20 20
+ :header-rows: 1
+
+ * - Tools
+ - Has autofixes
+ - Meta bug
+ - More info
+ - Upstream
+ * - Stylelint
+ - Yes
+ - `bug 1762027 <https://bugzilla.mozilla.org/show_bug.cgi?id=1762027>`__
+ - :ref:`Stylelint`
+ - https://stylelint.io/
+
+.. list-table:: JavaScript
+ :widths: 20 20 20 20 20
+ :header-rows: 1
+
+ * - Tools
+ - Has autofixes
+ - Meta bug
+ - More info
+ - Upstream
+ * - Eslint
+ - Yes
+ - `bug 1229856 <https://bugzilla.mozilla.org/show_bug.cgi?id=1229856>`__
+ - :ref:`ESLint`
+ - https://eslint.org/
+ * - Mozilla ESLint
+ -
+ - `bug 1229856 <https://bugzilla.mozilla.org/show_bug.cgi?id=1229856>`__
+ - :ref:`Mozilla ESLint Plugin`
+ -
+ * - Prettier
+ - Yes
+ - `bug 1558517 <https://bugzilla.mozilla.org/show_bug.cgi?id=1558517>`__
+ - :ref:`JavaScript Coding style`
+ - https://prettier.io/
+
+.. list-table:: Python
+ :widths: 20 20 20 20 20
+ :header-rows: 1
+
+ * - Tools
+ - Has autofixes
+ - Meta bug
+ - More info
+ - Upstream
+ * - ruff
+ - Yes
+ - `bug 1811850 <https://bugzilla.mozilla.org/show_bug.cgi?id=1811850>`__
+ - :ref:`ruff`
+ - https://github.com/charliermarsh/ruff
+ * - black
+ - Yes
+ - `bug 1555560 <https://bugzilla.mozilla.org/show_bug.cgi?id=1555560>`__
+ - :ref:`black`
+ - https://black.readthedocs.io/en/stable
+
+.. list-table:: Rust
+ :widths: 20 20 20 20 20
+ :header-rows: 1
+
+ * - Tools
+ - Has autofixes
+ - Meta bug
+ - More info
+ - Upstream
+ * - Rustfmt
+ - Yes
+ - `bug 1454764 <https://bugzilla.mozilla.org/show_bug.cgi?id=1454764>`__
+ - :ref:`Rustfmt`
+ - https://github.com/rust-lang/rustfmt
+ * - Clippy
+ -
+ - `bug 1361342 <https://bugzilla.mozilla.org/show_bug.cgi?id=1361342>`__
+ - :ref:`clippy`
+ - https://github.com/rust-lang/rust-clippy
+
+.. list-table:: Java/Kotlin
+ :widths: 20 20 20 20 20
+ :header-rows: 1
+
+ * - Tools
+ - Has autofixes
+ - Meta bug
+ - More info
+ - Upstream
+ * - Spotless
+ - Yes
+ - `bug 1571899 <https://bugzilla.mozilla.org/show_bug.cgi?id=1571899>`__
+ - :ref:`Spotless`
+ - https://github.com/diffplug/spotless
+
+.. list-table:: Others
+ :widths: 20 20 20 20 20
+ :header-rows: 1
+
+ * - Tools
+ - Has autofixes
+ - Meta bug
+ - More info
+ - Upstream
+ * - shellcheck
+ -
+ -
+ -
+ - https://www.shellcheck.net/
+ * - rstchecker
+ -
+ -
+ - :ref:`RST Linter`
+ - https://github.com/myint/rstcheck
+ * - Typo detection
+ - Yes
+ -
+ - :ref:`Codespell`
+ - https://github.com/codespell-project/codespell
+ * - Fluent Lint
+ - No
+ -
+ - :ref:`Fluent Lint`
+ -
+ * - YAML linter
+ - No
+ -
+ - :ref:`yamllint`
+ - https://github.com/adrienverge/yamllint
diff --git a/docs/code-quality/lint/create.rst b/docs/code-quality/lint/create.rst
new file mode 100644
index 0000000000..2efdf742f5
--- /dev/null
+++ b/docs/code-quality/lint/create.rst
@@ -0,0 +1,368 @@
+Adding a New Linter to the Tree
+===============================
+
+Linter Requirements
+-------------------
+
+For a linter to be integrated into the mozilla-central tree, it needs to have:
+
+* Any required dependencies should be installed as part of ``./mach bootstrap``
+* A ``./mach lint`` interface
+* Running ``./mach lint`` command must pass (note, linters can be disabled for individual directories)
+* Taskcluster/Treeherder integration
+* In tree documentation (under ``docs/code-quality/lint``) to give a basic summary, links and any other useful information
+* Unit tests (under ``tools/lint/test``) to make sure that the linter works as expected and we don't regress.
+
+The review group in Phabricator is ``#linter-reviewers``.
+
+Linter Basics
+-------------
+
+A linter is a yaml file with a ``.yml`` extension. Depending on how the type of linter, there may
+be python code alongside the definition, pointed to by the 'payload' attribute.
+
+Here's a trivial example:
+
+no-eval.yml
+
+.. code-block:: yaml
+
+ EvalLinter:
+ description: Ensures the string eval doesn't show up.
+ extensions: ['js']
+ type: string
+ payload: eval
+
+Now ``no-eval.yml`` gets passed into :func:`LintRoller.read`.
+
+
+Linter Types
+------------
+
+There are four types of linters, though more may be added in the future.
+
+1. string - fails if substring is found
+2. regex - fails if regex matches
+3. external - fails if a python function returns a non-empty result list
+4. structured_log - fails if a mozlog logger emits any lint_error or lint_warning log messages
+
+As seen from the example above, string and regex linters are very easy to create, but they
+should be avoided if possible. It is much better to use a context aware linter for the language you
+are trying to lint. For example, use eslint to lint JavaScript files, use flake8 to lint python
+files, etc.
+
+Which brings us to the third and most interesting type of linter,
+external. External linters call an arbitrary python function which is
+responsible for not only running the linter, but ensuring the results
+are structured properly. For example, an external type could shell out
+to a 3rd party linter, collect the output and format it into a list of
+:class:`Issue` objects. The signature for this python
+function is ``lint(files, config, **kwargs)``, where ``files`` is a list of
+files to lint and ``config`` is the linter definition defined in the ``.yml``
+file.
+
+Structured log linters are much like external linters, but suitable
+for cases where the linter code is using mozlog and emits
+``lint_error`` or ``lint_warning`` logging messages when the lint
+fails. This is recommended for writing novel gecko-specific lints. In
+this case the signature for lint functions is ``lint(files, config, logger,
+**kwargs)``.
+
+
+Linter Definition
+-----------------
+
+Each ``.yml`` file must have at least one linter defined in it. Here are the supported keys:
+
+* description - A brief description of the linter's purpose (required)
+* type - One of 'string', 'regex' or 'external' (required)
+* payload - The actual linting logic, depends on the type (required)
+* include - A list of file paths that will be considered (optional)
+* exclude - A list of file paths or glob patterns that must not be matched (optional)
+* extensions - A list of file extensions to be considered (optional)
+* setup - A function that sets up external dependencies (optional)
+* support-files - A list of glob patterns matching configuration files (optional)
+* find-dotfiles - If set to ``true``, run on dot files (.*) (optional)
+* ignore-case - If set to ``true`` and ``type`` is regex, ignore the case (optional)
+
+In addition to the above, some ``.yml`` files correspond to a single lint rule. For these, the
+following additional keys may be specified:
+
+* message - A string to print on infraction (optional)
+* hint - A string with a clue on how to fix the infraction (optional)
+* rule - An id string for the lint rule (optional)
+* level - The severity of the infraction, either 'error' or 'warning' (optional)
+
+For structured_log lints the following additional keys apply:
+
+* logger - A StructuredLog object to use for logging. If not supplied
+ one will be created (optional)
+
+
+Example
+-------
+
+Here is an example of an external linter that shells out to the python flake8 linter,
+let's call the file ``flake8_lint.py`` (`in-tree version <https://searchfox.org/mozilla-central/source/tools/lint/python/flake8.py>`__):
+
+.. code-block:: python
+
+ import json
+ import os
+ import subprocess
+ from collections import defaultdict
+ from shutil import which
+
+ from mozlint import result
+
+
+ FLAKE8_NOT_FOUND = """
+ Could not find flake8! Install flake8 and try again.
+ """.strip()
+
+
+ def lint(files, config, **lintargs):
+ binary = os.environ.get('FLAKE8')
+ if not binary:
+ binary = which('flake8')
+ if not binary:
+ print(FLAKE8_NOT_FOUND)
+ return 1
+
+ # Flake8 allows passing in a custom format string. We use
+ # this to help mold the default flake8 format into what
+ # mozlint's Issue object expects.
+ cmdargs = [
+ binary,
+ '--format',
+ '{"path":"%(path)s","lineno":%(row)s,"column":%(col)s,"rule":"%(code)s","message":"%(text)s"}',
+ ] + files
+
+ proc = subprocess.Popen(cmdargs, stdout=subprocess.PIPE, env=os.environ)
+ output = proc.communicate()[0]
+
+ # all passed
+ if not output:
+ return []
+
+ results = []
+ for line in output.splitlines():
+ # res is a dict of the form specified by --format above
+ res = json.loads(line)
+
+ # parse level out of the id string
+ if 'code' in res and res['code'].startswith('W'):
+ res['level'] = 'warning'
+
+ # result.from_linter is a convenience method that
+ # creates a Issue using a LINTER definition
+ # to populate some defaults.
+ results.append(result.from_config(config, **res))
+
+ return results
+
+Now here is the linter definition that would call it:
+
+.. code-block:: yaml
+
+ flake8:
+ description: Python linter
+ include: ['.']
+ extensions: ['py']
+ type: external
+ payload: py.flake8:lint
+ support-files:
+ - '**/.flake8'
+
+Notice the payload has two parts, delimited by ':'. The first is the module
+path, which ``mozlint`` will attempt to import. The second is the object path
+within that module (e.g, the name of a function to call). It is up to consumers
+of ``mozlint`` to ensure the module is in ``sys.path``. Structured log linters
+use the same import mechanism.
+
+The ``support-files`` key is used to list configuration files or files related
+to the running of the linter itself. If using ``--outgoing`` or ``--workdir``
+and one of these files was modified, the entire tree will be linted instead of
+just the modified files.
+
+Result definition
+-----------------
+
+When generating the list of results, the following values are available.
+
+.. csv-table::
+ :header: "Name", "Description", "Optional"
+ :widths: 20, 40, 10
+
+ "linter", "Name of the linter that flagged this error", ""
+ "path", "Path to the file containing the error", ""
+ "message", "Text describing the error", ""
+ "lineno", "Line number that contains the error", ""
+ "column", "Column containing the error", ""
+ "level", "Severity of the error, either 'warning' or 'error' (default 'error')", "Yes"
+ "hint", "Suggestion for fixing the error", "Yes"
+ "source", "Source code context of the error", "Yes"
+ "rule", "Name of the rule that was violated", "Yes"
+ "lineoffset", "Denotes an error spans multiple lines, of the form (<lineno offset>, <num lines>)", "Yes"
+ "diff", "A diff describing the changes that need to be made to the code", "Yes"
+
+
+Automated testing
+-----------------
+
+Every new checker must have tests associated.
+
+They should be pretty easy to write as most of the work is managed by the Mozlint
+framework. The key declaration is the ``LINTER`` variable which must match
+the linker declaration.
+
+As an example, the `Flake8 test <https://searchfox.org/mozilla-central/source/tools/lint/test/test_flake8.py>`_ looks like the following snippet:
+
+.. code-block:: python
+
+ import mozunit
+ LINTER = 'flake8'
+
+ def test_lint_single_file(lint, paths):
+ results = lint(paths('bad.py'))
+ assert len(results) == 2
+ assert results[0].rule == 'F401'
+ assert results[1].rule == 'E501'
+ assert results[1].lineno == 5
+
+ if __name__ == '__main__':
+ mozunit.main()
+
+As always with tests, please make sure that enough positive and negative cases are covered.
+
+To run the tests:
+
+.. code-block:: shell
+
+ $ ./mach python-test --subsuite mozlint
+
+To run a specific test:
+
+.. code-block:: shell
+
+ ./mach python-test --subsuite mozlint tools/lint/test/test_black.py
+
+More tests can be `found in-tree <https://searchfox.org/mozilla-central/source/tools/lint/test>`_.
+
+Tracking fixed issues
+---------------------
+
+All the linters that provide ``fix support`` returns a dictionary instead of a list.
+
+``{"results":result,"fixed":fixed}``
+
+* results - All the linting errors it was not able to fix
+* fixed - Count of fixed errors (for ``fix=False`` this is 0)
+
+Some linters (example: `codespell <https://searchfox.org/mozilla-central/rev/0379f315c75a2875d716b4f5e1a18bf27188f1e6/tools/lint/spell/__init__.py#145-163>`_) might require two passes to count the number of fixed issues.
+Others might just need `some tuning <https://searchfox.org/mozilla-central/rev/0379f315c75a2875d716b4f5e1a18bf27188f1e6/tools/lint/file-whitespace/__init__.py#28,60,85,112>`_.
+
+For adding tests to check your fixed count, add a global variable ``fixed = 0``
+and write a function to add your test as mentioned under ``Automated testing`` section.
+
+
+Here's an example
+
+.. code-block:: python
+
+ fixed = 0
+
+
+ def test_lint_codespell_fix(lint, create_temp_file):
+ # Typo has been fixed in the contents to avoid triggering warning
+ # 'informations' ----> 'information'
+ contents = """This is a file with some typos and information.
+ But also testing false positive like optin (because this isn't always option)
+ or stuff related to our coding style like:
+ aparent (aParent).
+ but detects mistakes like mozilla
+ """.lstrip()
+
+ path = create_temp_file(contents, "ignore.rst")
+ lint([path], fix=True)
+
+ assert fixed == 2
+
+
+Bootstrapping Dependencies
+--------------------------
+
+Many linters, especially 3rd party ones, will require a set of dependencies. It
+could be as simple as installing a binary from a package manager, or as
+complicated as pulling a whole graph of tools, plugins and their dependencies.
+
+Either way, to reduce the burden on users, linters should strive to provide
+automated bootstrapping of all their dependencies. To help with this,
+``mozlint`` allows linters to define a ``setup`` config, which has the same
+path object format as an external payload. For example (`in-tree version <https://searchfox.org/mozilla-central/source/tools/lint/flake8.yml>`__):
+
+.. code-block:: yaml
+
+ flake8:
+ description: Python linter
+ include: ['.']
+ extensions: ['py']
+ type: external
+ payload: py.flake8:lint
+ setup: py.flake8:setup
+
+The setup function takes a single argument, the root of the repository being
+linted. In the case of ``flake8``, it might look like:
+
+.. code-block:: python
+
+ import subprocess
+ from distutils.spawn import find_executable
+
+ def setup(root, **lintargs):
+ # This is a simple example. Please look at the actual source for better examples.
+ if not find_executable('flake8'):
+ subprocess.call(['pip', 'install', 'flake8'])
+
+The setup function will be called implicitly before running the linter. This
+means it should return fast and not produce any output if there is no setup to
+be performed.
+
+The setup functions can also be called explicitly by running ``mach lint
+--setup``. This will only perform setup and not perform any linting. It is
+mainly useful for other tools like ``mach bootstrap`` to call into.
+
+
+Adding the linter to the CI
+---------------------------
+
+First, the job will have to be declared in Taskcluster.
+
+This should be done in the `mozlint Taskcluster configuration <https://searchfox.org/mozilla-central/source/taskcluster/ci/source-test/mozlint.yml>`_.
+You will need to define a symbol, how it is executed and on what kind of change.
+
+For example, for flake8, the configuration is the following:
+
+.. code-block:: yaml
+
+ py-flake8:
+ description: flake8 run over the gecko codebase
+ treeherder:
+ symbol: py(f8)
+ run:
+ mach: lint -l flake8 -f treeherder -f json:/builds/worker/mozlint.json
+ when:
+ files-changed:
+ - '**/*.py'
+ - '**/.flake8'
+ # moz.configure files are also Python files.
+ - '**/*.configure'
+
+If the linter requires an external program, you will have to install it in the `setup script <https://searchfox.org/mozilla-central/source/taskcluster/docker/lint/system-setup.sh>`_
+and maybe install the necessary files in the `Docker configuration <https://searchfox.org/mozilla-central/source/taskcluster/docker/lint/Dockerfile>`_.
+
+.. note::
+
+ If the defect found by the linter is minor, make sure that it is run as `tier 2 <https://wiki.mozilla.org/Sheriffing/Job_Visibility_Policy#Overview_of_the_Job_Visibility_Tiers>`_.
+ This prevents the tree from closing because of a tiny issue.
+ For example, the typo detection is run as tier-2.
diff --git a/docs/code-quality/lint/index.rst b/docs/code-quality/lint/index.rst
new file mode 100644
index 0000000000..2b2cfdefbd
--- /dev/null
+++ b/docs/code-quality/lint/index.rst
@@ -0,0 +1,33 @@
+Linting
+=======
+
+Linters are used in mozilla-central to help enforce coding style and avoid bad practices.
+They cover a wide variety of languages and checks.
+
+Getting Help
+------------
+
+If you need help or have questions, please don’t hesitate to contact us via Matrix
+in the "Lint and Formatting" room
+(`#lint:mozilla.org <https://chat.mozilla.org/#/room/#lint:mozilla.org>`_).
+
+
+.. toctree::
+ :caption: Getting Started
+ :maxdepth: 2
+
+ usage
+
+.. toctree::
+ :caption: Linter Implementations
+ :maxdepth: 1
+ :glob:
+
+ linters/*
+
+.. toctree::
+ :caption: Linter Specifics
+ :maxdepth: 1
+
+ mozlint
+ create
diff --git a/docs/code-quality/lint/linters/android-format.rst b/docs/code-quality/lint/linters/android-format.rst
new file mode 100644
index 0000000000..ed23ac64de
--- /dev/null
+++ b/docs/code-quality/lint/linters/android-format.rst
@@ -0,0 +1,31 @@
+Spotless
+========
+
+`Spotless <https://github.com/diffplug/spotless>`__ is a pluggable formatter
+for Gradle and Android.
+
+In our current configuration, Spotless includes the
+`Google Java Format plug-in <https://github.com/google/google-java-format>`__
+which formats all our Java code using the Google Java coding style guidelines,
+and `ktlint <https://ktlint.github.io/>`__ which formats all
+our Kotlin code using the official Kotlin coding convention and Android Kotlin
+Style Guide.
+
+
+Run Locally
+-----------
+
+The mozlint integration of spotless can be run using mach:
+
+.. parsed-literal::
+
+ $ mach lint --linter android-format
+
+Alternatively, omit the ``--linter android-format`` and run all configured linters, which will include
+spotless.
+
+
+Autofix
+-------
+
+The spotless linter provides a ``--fix`` option.
diff --git a/docs/code-quality/lint/linters/black.rst b/docs/code-quality/lint/linters/black.rst
new file mode 100644
index 0000000000..60a06ce95b
--- /dev/null
+++ b/docs/code-quality/lint/linters/black.rst
@@ -0,0 +1,36 @@
+Black
+=====
+
+`Black <https://black.readthedocs.io/en/stable/>`__ is a opinionated python code formatter.
+
+
+Run Locally
+-----------
+
+The mozlint integration of black can be run using mach:
+
+.. parsed-literal::
+
+ $ mach lint --linter black <file paths>
+
+Alternatively, omit the ``--linter black`` and run all configured linters, which will include
+black.
+
+
+Configuration
+-------------
+
+To enable black on new directory, add the path to the include
+section in the :searchfox:`black.yml <tools/lint/black.yml>` file.
+
+Autofix
+-------
+
+The black linter provides a ``--fix`` option.
+
+
+Sources
+-------
+
+* :searchfox:`Configuration (YAML) <tools/lint/black.yml>`
+* :searchfox:`Source <tools/lint/python/black.py>`
diff --git a/docs/code-quality/lint/linters/clang-format.rst b/docs/code-quality/lint/linters/clang-format.rst
new file mode 100644
index 0000000000..a528af4358
--- /dev/null
+++ b/docs/code-quality/lint/linters/clang-format.rst
@@ -0,0 +1,35 @@
+clang-format
+============
+
+`clang-format <https://clang.llvm.org/docs/ClangFormat.html>`__ is a tool to reformat C/C++ to the right coding style.
+
+Run Locally
+-----------
+
+The mozlint integration of clang-format can be run using mach:
+
+.. parsed-literal::
+
+ $ mach lint --linter clang-format <file paths>
+
+
+Configuration
+-------------
+
+To enable clang-format on new directory, add the path to the include
+section in the :searchfox:`clang-format.yml <tools/lint/clang-format.yml>` file.
+
+While excludes: will work, this linter will read the ignore list from :searchfox:`.clang-format-ignore file <.clang-format-ignore>`
+at the root directory. This because it is also used by the ./mach clang-format -p command.
+
+Autofix
+-------
+
+clang-format can reformat the code with the option `--fix` (based on the upstream option `-i`).
+To highlight the results, we are using the ``--dry-run`` option (from clang-format 10).
+
+Sources
+-------
+
+* :searchfox:`Configuration (YAML) <tools/lint/clang-format.yml>`
+* :searchfox:`Source <tools/lint/clang-format/__init__.py>`
diff --git a/docs/code-quality/lint/linters/clippy.rst b/docs/code-quality/lint/linters/clippy.rst
new file mode 100644
index 0000000000..40db532b88
--- /dev/null
+++ b/docs/code-quality/lint/linters/clippy.rst
@@ -0,0 +1,36 @@
+clippy
+======
+
+`clippy`_ is the tool for Rust static analysis.
+
+Run Locally
+-----------
+
+The mozlint integration of clippy can be run using mach:
+
+.. parsed-literal::
+
+ $ mach lint --linter clippy <file paths>
+
+.. note::
+
+ clippy expects a path or a .rs file. It doesn't accept Cargo.toml
+ as it would break the mozlint workflow.
+
+Configuration
+-------------
+
+To enable clippy on new directory, add the path to the include
+section in the `clippy.yml <https://searchfox.org/mozilla-central/source/tools/lint/clippy.yml>`_ file.
+
+Autofix
+-------
+
+This linter provides a ``--fix`` option.
+Please note that this option does not fix all detected issues.
+
+Sources
+-------
+
+* `Configuration (YAML) <https://searchfox.org/mozilla-central/source/tools/lint/clippy.yml>`_
+* `Source <https://searchfox.org/mozilla-central/source/tools/lint/clippy/__init__.py>`_
diff --git a/docs/code-quality/lint/linters/codespell.rst b/docs/code-quality/lint/linters/codespell.rst
new file mode 100644
index 0000000000..9299a81b6e
--- /dev/null
+++ b/docs/code-quality/lint/linters/codespell.rst
@@ -0,0 +1,36 @@
+Codespell
+=========
+
+`codespell <https://github.com/codespell-project/codespell/>`__ is a popular tool to look for typical typos in the source code.
+
+It is enabled mostly for the documentation and English locale files.
+
+Run Locally
+-----------
+
+The mozlint integration of codespell can be run using mach:
+
+.. parsed-literal::
+
+ $ mach lint --linter codespell <file paths>
+
+
+Configuration
+-------------
+
+To enable codespell on new directory, add the path to the include
+section in the :searchfox:`codespell.yml <tools/lint/codespell.yml>` file.
+
+This job is configured as `tier 2 <https://wiki.mozilla.org/Sheriffing/Job_Visibility_Policy#Overview_of_the_Job_Visibility_Tiers>`_.
+
+Autofix
+-------
+
+Codespell provides a ``--fix`` option. It is based on the ``-w`` option provided by upstream.
+
+
+Sources
+-------
+
+* :searchfox:`Configuration (YAML) <tools/lint/codespell.yml>`
+* :searchfox:`Source <tools/lint/spell/__init__.py>`
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla.rst
new file mode 100644
index 0000000000..d1d60c963c
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla.rst
@@ -0,0 +1,114 @@
+=====================
+Mozilla ESLint Plugin
+=====================
+
+This is the documentation of Mozilla ESLint PLugin.
+
+Environments
+============
+
+The plugin implements the following environments:
+
+
+.. toctree::
+ :maxdepth: 2
+
+ eslint-plugin-mozilla/environment
+
+Rules
+=====
+
+The plugin implements the following rules:
+
+.. toctree::
+ :maxdepth: 1
+
+ eslint-plugin-mozilla/avoid-Date-timing
+ eslint-plugin-mozilla/avoid-removeChild
+ eslint-plugin-mozilla/balanced-listeners
+ eslint-plugin-mozilla/balanced-observers
+ eslint-plugin-mozilla/consistent-if-bracing
+ eslint-plugin-mozilla/import-browser-window-globals
+ eslint-plugin-mozilla/import-content-task-globals
+ eslint-plugin-mozilla/import-globals
+ eslint-plugin-mozilla/import-globals-from
+ eslint-plugin-mozilla/import-headjs-globals
+ eslint-plugin-mozilla/lazy-getter-object-name
+ eslint-plugin-mozilla/mark-exported-symbols-as-used
+ eslint-plugin-mozilla/mark-test-function-used
+ eslint-plugin-mozilla/no-aArgs
+ eslint-plugin-mozilla/no-addtask-setup
+ eslint-plugin-mozilla/no-arbitrary-setTimeout
+ eslint-plugin-mozilla/no-compare-against-boolean-literals
+ eslint-plugin-mozilla/no-cu-reportError
+ eslint-plugin-mozilla/no-define-cc-etc
+ eslint-plugin-mozilla/no-redeclare-with-import-autofix
+ eslint-plugin-mozilla/no-throw-cr-literal
+ eslint-plugin-mozilla/no-useless-parameters
+ eslint-plugin-mozilla/no-useless-removeEventListener
+ eslint-plugin-mozilla/no-useless-run-test
+ eslint-plugin-mozilla/prefer-boolean-length-check
+ eslint-plugin-mozilla/prefer-formatValues
+ eslint-plugin-mozilla/reject-addtask-only
+ eslint-plugin-mozilla/reject-chromeutils-import-params
+ eslint-plugin-mozilla/reject-eager-module-in-lazy-getter
+ eslint-plugin-mozilla/reject-global-this
+ eslint-plugin-mozilla/reject-globalThis-modification
+ eslint-plugin-mozilla/reject-importGlobalProperties
+ eslint-plugin-mozilla/reject-lazy-imports-into-globals
+ eslint-plugin-mozilla/reject-mixing-eager-and-lazy
+ eslint-plugin-mozilla/reject-multiple-getters-calls
+ eslint-plugin-mozilla/reject-relative-requires
+ eslint-plugin-mozilla/reject-requires-await
+ eslint-plugin-mozilla/reject-scriptableunicodeconverter
+ eslint-plugin-mozilla/reject-some-requires
+ eslint-plugin-mozilla/reject-top-level-await
+ eslint-plugin-mozilla/reject-import-system-module-from-non-system
+ eslint-plugin-mozilla/use-cc-etc
+ eslint-plugin-mozilla/use-chromeutils-generateqi
+ eslint-plugin-mozilla/use-chromeutils-import
+ eslint-plugin-mozilla/use-default-preference-values
+ eslint-plugin-mozilla/use-includes-instead-of-indexOf
+ eslint-plugin-mozilla/use-isInstance
+ eslint-plugin-mozilla/use-ownerGlobal
+ eslint-plugin-mozilla/use-returnValue
+ eslint-plugin-mozilla/use-services
+ eslint-plugin-mozilla/use-static-import
+ eslint-plugin-mozilla/valid-ci-uses
+ eslint-plugin-mozilla/valid-lazy
+ eslint-plugin-mozilla/valid-services
+ eslint-plugin-mozilla/valid-services-property
+ eslint-plugin-mozilla/var-only-at-top-level
+
+Tests
+=====
+
+The tests for eslint-plugin-mozilla are run via `mochajs`_ on top of node. Most
+of the tests use the `ESLint Rule Unit Test framework`_.
+
+.. _mochajs: https://mochajs.org/
+.. _ESLint Rule Unit Test Framework: http://eslint.org/docs/developer-guide/working-with-rules#rule-unit-tests
+
+Running Tests
+-------------
+
+The tests for eslint-plugin-mozilla are run via `mochajs`_ on top of node. Most
+of the tests use the `ESLint Rule Unit Test framework`_.
+
+The rules have some self tests, these can be run via:
+
+.. code-block:: shell
+
+ $ cd tools/lint/eslint/eslint-plugin-mozilla
+ $ npm install
+ $ npm run test
+
+Disabling tests
+---------------
+
+In the unlikely event of needing to disable a test, currently the only way is
+by commenting-out. Please file a bug if you have to do this. Bugs should be filed
+in the *Testing* product under *Lint*.
+
+.. _mochajs: https://mochajs.org/
+.. _ESLint Rule Unit Test Framework: http://eslint.org/docs/developer-guide/working-with-rules#rule-unit-tests
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/avoid-Date-timing.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/avoid-Date-timing.rst
new file mode 100644
index 0000000000..b01b568a28
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/avoid-Date-timing.rst
@@ -0,0 +1,30 @@
+avoid-Date-timing
+=================
+
+Rejects grabbing the current time via Date.now() or new Date() for timing
+purposes when the less problematic performance.now() can be used instead.
+
+The performance.now() function returns milliseconds since page load. To
+convert that to milliseconds since the epoch, use:
+
+.. code-block:: js
+
+ performance.timing.navigationStart + performance.now()
+
+Often timing relative to the page load is adequate and that conversion may not
+be necessary.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ Date.now()
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ new Date('2017-07-11');
+ performance.now()
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/avoid-removeChild.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/avoid-removeChild.rst
new file mode 100644
index 0000000000..15ece94d0d
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/avoid-removeChild.rst
@@ -0,0 +1,20 @@
+avoid-removeChild
+=================
+
+Rejects using ``element.parentNode.removeChild(element)`` when ``element.remove()``
+can be used instead.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ elt.parentNode.removeChild(elt);
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ elt.remove();
+ elt.parentNode.removeChild(elt2);
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/balanced-listeners.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/balanced-listeners.rst
new file mode 100644
index 0000000000..f53c11e7aa
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/balanced-listeners.rst
@@ -0,0 +1,20 @@
+balanced-listeners
+==================
+
+Checks that for every occurrence of 'addEventListener' or 'on' there is an
+occurrence of 'removeEventListener' or 'off' with the same event name.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ elt.addEventListener('click', handler, false);
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ elt.addEventListener('event', handler);
+ elt.removeEventListener('event', handler);
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/balanced-observers.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/balanced-observers.rst
new file mode 100644
index 0000000000..b169a520a3
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/balanced-observers.rst
@@ -0,0 +1,20 @@
+balanced-observers
+==================
+
+Checks that for every occurrence of ``addObserver`` there is an
+occurrence of ``removeObserver`` with the same topic.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ Services.obs.addObserver(observer, 'observable');
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ Services.obs.addObserver(observer, 'observable');
+ Services.obs.removeObserver(observer, 'observable');
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/consistent-if-bracing.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/consistent-if-bracing.rst
new file mode 100644
index 0000000000..7bf6b796ef
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/consistent-if-bracing.rst
@@ -0,0 +1,23 @@
+consistent-if-bracing
+=====================
+
+Checks that if/elseif/else bodies are braced consistently, so either all bodies
+are braced or unbraced. Doesn't enforce either of those styles though.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ if (true) {1} else 0
+ if (true) 1; else {0}
+ if (true) {1} else if (true) 2; else {0}
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ if (true) {1} else {0}
+ if (false) 1; else 0
+ if (true) {1} else if (true) {2} else {0}
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/environment.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/environment.rst
new file mode 100644
index 0000000000..2c779410d6
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/environment.rst
@@ -0,0 +1,76 @@
+Environment
+===========
+
+These environments are available by specifying a comment at the top of the file,
+e.g.
+
+.. code-block:: js
+
+ /* eslint-env mozilla/chrome-worker */
+
+There are also built-in ESLint environments available as well. Find them here: http://eslint.org/docs/user-guide/configuring#specifying-environments
+
+browser-window
+--------------
+
+Defines the environment for scripts that are in the main browser.xhtml scope.
+
+chrome-script
+-------------
+
+Defines the environment for scripts loaded by
+``SpecialPowers.loadChromeScript``.
+
+chrome-worker
+-------------
+
+Defines the environment for chrome workers. This differs from normal workers by
+the fact that `ctypes` can be accessed as well.
+
+frame-script
+------------
+
+Defines the environment for scripts loaded by ``Services.mm.loadFrameScript``.
+
+jsm
+---
+
+Defines the environment for jsm files (javascript modules).
+
+privileged
+----------
+
+Defines the environment for privileged JS files.
+
+process-script
+--------------
+
+Defines the environment for scripts loaded by
+``Services.ppmm.loadProcessScript``.
+
+remote-page
+-----------
+
+Defines the environment for scripts loaded with ``<script src="...">`` in
+``about:`` pages.
+
+simpletest
+----------
+
+Defines the environment for scripts that use the SimpleTest mochitest harness.
+
+sjs
+---
+
+Defines the environment for sjs files.
+
+special-powers-sandbox
+----------------------
+
+Defines the environment for scripts evaluated inside ``SpecialPowers`` sandbox
+with the default options.
+
+xpcshell
+--------
+
+Defines the environment for xpcshell test files.
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/import-browser-window-globals.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/import-browser-window-globals.rst
new file mode 100644
index 0000000000..35c09cc8fd
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/import-browser-window-globals.rst
@@ -0,0 +1,8 @@
+import-browser-window-globals
+=============================
+
+For scripts included in browser-window, this will automatically inject the
+browser-window global scopes into the file.
+
+This is a rule rather than an environment, as it allowed us to automatically
+select the files to include.
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/import-content-task-globals.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/import-content-task-globals.rst
new file mode 100644
index 0000000000..f2550a1412
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/import-content-task-globals.rst
@@ -0,0 +1,14 @@
+import-content-task-globals
+===========================
+
+For files containing ContentTask.spawn calls, this will automatically declare
+the frame script variables in the global scope. ContentTask is only available
+to test files, so by default the configs only specify it for the mochitest based
+configurations.
+
+This saves setting the file as a mozilla/frame-script environment.
+
+Note: due to the way ESLint works, it appears it is only easy to declare these
+variables on a file global scope, rather than function global. This may mean that
+they are incorrectly allowed, but given they are test files, this should be
+detected during testing.
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/import-globals-from.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/import-globals-from.rst
new file mode 100644
index 0000000000..c2956ba05a
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/import-globals-from.rst
@@ -0,0 +1,18 @@
+import-globals-from
+===================
+
+Parses a file for globals defined in various unique Mozilla ways.
+
+When a ``/* import-globals-from <path> */`` comment is found in a file, then all
+globals from the file at <path> will be imported in the current scope. This will
+also operate recursively.
+
+This is useful for scripts that are loaded as <script> tag in a window and rely
+on each other's globals.
+
+If <path> is a relative path, then it must be relative to the file being
+checked by the rule.
+
+Note: ``import-globals-from`` does not support loading globals from ES modules.
+These should be imported as variable definitions directly, or the file where
+they are imported should be referenced.
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/import-globals.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/import-globals.rst
new file mode 100644
index 0000000000..2c47a5210f
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/import-globals.rst
@@ -0,0 +1,5 @@
+import-globals
+==============
+
+Checks ``XPCOMUtils.defineLazyGetter`` etc and adds the name to the global
+scope.
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/import-headjs-globals.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/import-headjs-globals.rst
new file mode 100644
index 0000000000..a754bd7985
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/import-headjs-globals.rst
@@ -0,0 +1,28 @@
+import-headjs-globals
+=====================
+
+Import globals from head.js and from any files that were imported by
+head.js (as far as we can correctly resolve the path).
+
+This rule is included in the test configurations.
+
+The following file import patterns are supported:
+
+- ``Services.scriptloader.loadSubScript(path)``
+- ``loader.loadSubScript(path)``
+- ``loadSubScript(path)``
+- ``loadHelperScript(path)``
+- ``import-globals-from path``
+
+If path does not exist because it is generated e.g.
+``testdir + "/somefile.js"`` we do our best to resolve it.
+
+The following patterns are supported:
+
+- ``Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");``
+- ``loader.lazyRequireGetter(this, "name2"``
+- ``loader.lazyServiceGetter(this, "name3"``
+- ``XPCOMUtils.defineLazyModuleGetter(this, "setNamedTimeout", ...)``
+- ``loader.lazyGetter(this, "toolboxStrings"``
+- ``XPCOMUtils.defineLazyGetter(this, "clipboardHelper"``
+- ``ChromeUtils.defineLazyGetter(this, "clipboardHelper"``
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/lazy-getter-object-name.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/lazy-getter-object-name.rst
new file mode 100644
index 0000000000..090f445b69
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/lazy-getter-object-name.rst
@@ -0,0 +1,25 @@
+lazy-getter-object-name
+=============================
+
+Enforce the standard object variable name ``lazy`` for
+``ChromeUtils.defineESModuleGetters``
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ const obj = {};
+ ChromeUtils.defineESModuleGetters(obj, {
+ AppConstants: “resource://gre/modules/AppConstants.sys.mjs”,
+ });
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ const lazy = {};
+ ChromeUtils.defineESModuleGetters(lazy, {
+ AppConstants: “resource://gre/modules/AppConstants.sys.mjs”,
+ });
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/mark-exported-symbols-as-used.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/mark-exported-symbols-as-used.rst
new file mode 100644
index 0000000000..92e315a249
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/mark-exported-symbols-as-used.rst
@@ -0,0 +1,23 @@
+mark-exported-symbols-as-used
+=============================
+
+Marks variables listed in ``EXPORTED_SYMBOLS`` as used so that ``no-unused-vars``
+does not complain about them.
+
+This rule also checks that ``EXPORTED_SYMBOLS`` is not defined using ``let`` as
+``let`` isn't allowed as the lexical scope may die after the script executes.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ let EXPORTED_SYMBOLS = ["foo"];
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ var EXPORTED_SYMBOLS = ["foo"];
+ const EXPORTED_SYMBOLS = ["foo"];
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/mark-test-function-used.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/mark-test-function-used.rst
new file mode 100644
index 0000000000..a518d7415b
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/mark-test-function-used.rst
@@ -0,0 +1,8 @@
+mark-test-function-used
+=======================
+
+Simply marks ``test`` (the test method) or ``run_test`` as used when in mochitests
+or xpcshell tests respectively. This avoids ESLint telling us that the function
+is never called.
+
+This rule is included in the test configurations.
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-aArgs.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-aArgs.rst
new file mode 100644
index 0000000000..7e398bcbbe
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-aArgs.rst
@@ -0,0 +1,22 @@
+no-aArgs
+========
+
+Checks that function argument names don't start with lowercase 'a' followed by
+a capital letter. This is to prevent the use of Hungarian notation whereby the
+first letter is a prefix that indicates the type or intended use of a variable.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ function(aFoo, aBar) {}
+ (aFoo, aBar) => {}
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ function(foo, bar) {}
+ (foo, bar) => {})
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-addtask-setup.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-addtask-setup.rst
new file mode 100644
index 0000000000..f26a869371
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-addtask-setup.rst
@@ -0,0 +1,27 @@
+no-addtask-setup
+================
+
+Reject using ``add_task(async function setup() { ... })`` in favour of
+``add_setup(async function() { ... })``.
+
+Using semantically separate setup functions makes ``.only`` work correctly
+and will allow for future improvements to setup/cleanup abstractions.
+
+This option can be autofixed (``--fix``).
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ add_task(async function setup() { ... });
+ add_task(function setup() { ... });
+ add_task(function init() { ... });
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ add_setup(async function() { ... });
+ add_setup(function() { ... });
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-arbitrary-setTimeout.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-arbitrary-setTimeout.rst
new file mode 100644
index 0000000000..a7d62e74ba
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-arbitrary-setTimeout.rst
@@ -0,0 +1,23 @@
+no-arbitrary-setTimeout
+=======================
+
+Disallows setTimeout with non-zero values in tests. Using arbitrary times for
+setTimeout may cause intermittent failures in tests. A value of zero is allowed
+as this is letting the event stack unwind, however also consider the use
+of ``TestUtils.waitForTick``.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ function(aFoo, aBar) {}
+ (aFoo, aBar) => {}
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ function(foo, bar) {}
+ (foo, bar) => {})
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-compare-against-boolean-literals.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-compare-against-boolean-literals.rst
new file mode 100644
index 0000000000..b7785f2fc2
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-compare-against-boolean-literals.rst
@@ -0,0 +1,23 @@
+no-compare-against-boolean-literals
+===================================
+
+Checks that boolean expressions do not compare against literal values
+of ``true`` or ``false``. This is to prevent overly verbose code such as
+``if (isEnabled == true)`` when ``if (isEnabled)`` would suffice.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ if (foo == true) {}
+ if (foo != false) {}
+ if (false == foo) {}
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ if (!foo) {}
+ if (!!foo) {}
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-cu-reportError.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-cu-reportError.rst
new file mode 100644
index 0000000000..9f5a0def27
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-cu-reportError.rst
@@ -0,0 +1,23 @@
+no-cu-reportError
+=================
+
+Disallows Cu.reportError. This has been deprecated and should be replaced by
+console.error.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ Cu.reportError("message");
+ Cu.reportError("message", stack);
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ console.error("message");
+ let error = new Error("message");
+ error.stack = stack;
+ console.error(error);
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-define-cc-etc.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-define-cc-etc.rst
new file mode 100644
index 0000000000..4421f4dd54
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-define-cc-etc.rst
@@ -0,0 +1,23 @@
+no-define-cc-etc
+================
+
+Disallows old-style definitions for ``Cc``/``Ci``/``Cu``/``Cr``. These are now
+defined globally for all chrome contexts.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+ var {Ci: interfaces, Cc: classes, Cu: utils} = Components;
+ var Cr = Components.results;
+
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ const CC = Components.Constructor;
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-redeclare-with-import-autofix.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-redeclare-with-import-autofix.rst
new file mode 100644
index 0000000000..d7d4edab50
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-redeclare-with-import-autofix.rst
@@ -0,0 +1,21 @@
+no-redeclare-with-import-autofix
+================================
+
+This is the
+`builtin eslint rule no-redeclare <https://eslint.org/docs/latest/rules/no-redeclare>`_,
+but with an additional fixer that can automatically remove superfluous
+(duplicate) imports.
+
+For redeclarations that are not imports, there is no automatic fix, as
+the author will likely have to rename variables so that they do not
+redeclare existing variables or conflict with the name of a builtin
+property or global variable.
+
+Typical duplicate imports happen when a `head.js` file, imports
+a module and a test then subsequently also attempts to import the same
+module.
+
+In browser mochitests, an additional typical scenario is importing
+modules that are already imported in the main browser window. Because
+these tests run in a scope that inherits from the main browser window
+one, there is no need to re-import such modules.
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-throw-cr-literal.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-throw-cr-literal.rst
new file mode 100644
index 0000000000..0f9222de30
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-throw-cr-literal.rst
@@ -0,0 +1,38 @@
+no-throw-cr-literal
+===================
+
+This is similar to the ESLint built-in rule no-throw-literal. It disallows
+throwing Components.results code directly.
+
+Throwing bare literals is inferior to throwing Exception objects, which provide
+stack information. Cr.ERRORs should be be passed as the second argument to
+``Components.Exception()`` to create an Exception object with stack info, and
+the correct result property corresponding to the NS_ERROR that other code
+expects.
+Using a regular ``new Error()`` to wrap just turns it into a string and doesn't
+set the result property, so the errors can't be recognised.
+
+This option can be autofixed (``--fix``).
+
+.. code-block:: js
+
+ performance.timing.navigationStart + performance.now()
+
+Often timing relative to the page load is adequate and that conversion may not
+be necessary.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ throw Cr.NS_ERROR_UNEXPECTED;
+ throw Components.results.NS_ERROR_ABORT;
+ throw new Error(Cr.NS_ERROR_NO_INTERFACE);
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ throw Components.Exception("Not implemented", Cr.NS_ERROR_NOT_IMPLEMENTED);
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-useless-parameters.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-useless-parameters.rst
new file mode 100644
index 0000000000..485caf6522
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-useless-parameters.rst
@@ -0,0 +1,26 @@
+no-useless-parameters
+=====================
+
+Reject common XPCOM methods called with useless optional parameters (eg.
+``Services.io.newURI(url, null, null)``, or non-existent parameters (eg.
+``Services.obs.removeObserver(name, observer, false)``).
+
+This option can be autofixed (``--fix``).
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ elt.addEventListener('click', handler, false);
+ Services.io.newURI('http://example.com', null, null);
+ Services.obs.notifyObservers(obj, 'topic', null);
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ elt.addEventListener('click', handler);
+ Services.io.newURI('http://example.com');
+ Services.obs.notifyObservers(obj, 'topic');
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-useless-removeEventListener.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-useless-removeEventListener.rst
new file mode 100644
index 0000000000..ce314ab58d
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-useless-removeEventListener.rst
@@ -0,0 +1,20 @@
+no-useless-removeEventListener
+==============================
+
+Reject calls to removeEventListener where ``{once: true}`` could be used instead.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ elt.addEventListener('click', function listener() {
+ elt.removeEventListener('click', listener);
+ });
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ elt.addEventListener('click', handler, {once: true});
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-useless-run-test.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-useless-run-test.rst
new file mode 100644
index 0000000000..a079405696
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/no-useless-run-test.rst
@@ -0,0 +1,6 @@
+no-useless-run-test
+===================
+
+Designed for xpcshell-tests. Rejects definitions of ``run_test()`` where the
+function only contains a single call to ``run_next_test()``. xpcshell's head.js
+already defines a utility function so there is no need for duplication.
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/prefer-boolean-length-check.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/prefer-boolean-length-check.rst
new file mode 100644
index 0000000000..cd6ee4e544
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/prefer-boolean-length-check.rst
@@ -0,0 +1,24 @@
+prefer-boolean-length-check
+===========================
+
+Prefers using a boolean length check rather than comparing against zero.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ if (foo.length == 0) {}
+ if (foo.length > 0) {}
+ if (foo && foo.length == 0) {}
+ function bar() { return foo.length > 0 }
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ if (foo.length && foo.length) {}
+ if (!foo.length) {}
+ var a = foo.length > 0
+ function bar() { return !!foo.length }
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/prefer-formatValues.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/prefer-formatValues.rst
new file mode 100644
index 0000000000..88eedee792
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/prefer-formatValues.rst
@@ -0,0 +1,23 @@
+prefer-formatValues
+===================
+
+Rejects multiple calls to document.l10n.formatValue in the same code block, to
+reduce localization overheads.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ {
+ document.l10n.formatValue('foobar');
+ document.l10n.formatValue('foobaz');
+ }
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ document.l10n.formatValue('foobar');
+ document.l10n.formatValues(['foobar', 'foobaz']);
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-addtask-only.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-addtask-only.rst
new file mode 100644
index 0000000000..e540b24416
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-addtask-only.rst
@@ -0,0 +1,6 @@
+reject-addtask-only
+===================
+
+Designed for JavaScript tests using the add_task pattern. Rejects chaining
+.only() to an add_task() call, which is useful for local testing to run a
+single task in isolation but is easy to land into the tree by accident.
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-chromeutils-import-params.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-chromeutils-import-params.rst
new file mode 100644
index 0000000000..4710878f8d
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-chromeutils-import-params.rst
@@ -0,0 +1,22 @@
+reject-chromeutils-import-params
+================================
+
+``ChromeUtils.import`` used to be able to be called with two arguments, however
+the second argument is no longer supported. Exports from modules should now be
+explicit, and the imported symbols being accessed from the returned object.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ ChromeUtils.import("resource://gre/modules/AppConstants.jsm", this);
+ ChromeUtils.import("resource://gre/modules/AppConstants.jsm", null);
+ ChromeUtils.import("resource://gre/modules/AppConstants.jsm", {});
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ const { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-eager-module-in-lazy-getter.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-eager-module-in-lazy-getter.rst
new file mode 100644
index 0000000000..fd81793690
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-eager-module-in-lazy-getter.rst
@@ -0,0 +1,35 @@
+reject-eager-module-in-lazy-getter
+==================================
+
+Rejects defining a lazy getter for module that's known to be loaded early in the
+startup process and it is not necessary to lazy load it.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ ChromeUtils.defineESModuleGetters(lazy, {
+ AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
+ });
+ XPCOMUtils.defineLazyModuleGetters(lazy, {
+ XPCOMUtils: "resource://gre/modules/XPCOMUtils.jsm",
+ });
+ XPCOMUtils.defineLazyModuleGetter(
+ lazy,
+ "AppConstants",
+ "resource://gre/modules/AppConstants.jsm",
+ });
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
+ const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+ );
+ const { AppConstants } = ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+ );
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-global-this.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-global-this.rst
new file mode 100644
index 0000000000..b3d94321f5
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-global-this.rst
@@ -0,0 +1,29 @@
+reject-global-this
+======================
+
+Rejects global ``this`` usage in JSM files. The global ``this`` is not
+available in ESM, and this is a preparation for the migration.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ this.EXPORTED_SYMBOLS = ["foo"];
+
+ XPCOMUtils.defineLazyModuleGetters(this, {
+ AddonManager: "resource://gre/modules/AddonManager.jsm",
+ });
+
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ const EXPORTED_SYMBOLS = ["foo"];
+
+ const lazy = {};
+ XPCOMUtils.defineLazyModuleGetters(lazy, {
+ AddonManager: "resource://gre/modules/AddonManager.jsm",
+ });
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-globalThis-modification.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-globalThis-modification.rst
new file mode 100644
index 0000000000..dd4fc4b2af
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-globalThis-modification.rst
@@ -0,0 +1,19 @@
+reject-globalThis-modification
+==============================
+
+Reject any modification to ``globalThis`` inside the system modules.
+
+``globalThis`` is the shared global inside the system modules, and modification
+on it is visible from all modules, and it shouldn't be done unless it's really
+necessary.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ globalThis.foo = 10;
+ Object.defineProperty(globalThis, "bar", { value: 20});
+ ChromeUtils.defineESModuleGetters(globalThis, {
+ AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
+ });
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-import-system-module-from-non-system.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-import-system-module-from-non-system.rst
new file mode 100644
index 0000000000..d168676745
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-import-system-module-from-non-system.rst
@@ -0,0 +1,36 @@
+reject-import-system-module-from-non-system
+===========================================
+
+Rejects static import declaration for system modules (``.sys.mjs``) from non-system
+modules.
+
+Using static import for a system module into a non-system module would create a separate instance of the imported object(s) that is not shared with the other system modules and would break the per-process singleton expectation.
+
+The reason for this is that inside system modules, a static import will load the module into the shared global. Inside non-system modules, the static import will load into a different global (e.g. window). This will cause the module to be loaded into different scopes, and hence create separate instances. The fix is to use ``ChromeUtils.importESModule`` which will import the object via the system module shared global scope.
+
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+Inside a non-system module:
+
+.. code-block:: js
+
+ import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
+
+Examples of correct code for this rule:
+---------------------------------------
+
+Inside a non-system module:
+
+.. code-block:: js
+
+ const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+ );
+
+Inside a system module:
+
+.. code-block:: js
+
+ import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-importGlobalProperties.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-importGlobalProperties.rst
new file mode 100644
index 0000000000..68b2e46928
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-importGlobalProperties.rst
@@ -0,0 +1,45 @@
+reject-importGlobalProperties
+=============================
+
+Rejects calls to ``Cu.importGlobalProperties`` or
+``XPCOMUtils.defineLazyGlobalGetters``.
+
+In system modules all the required properties should already be available. In
+non-module code or non-system modules, webidl defined interfaces should already
+be available and hence do not need importing.
+
+Options
+-------
+
+* "everything": Disallows using the import/getters completely.
+* "allownonwebidl": Disallows using the import functions for webidl symbols. Allows
+ other symbols.
+
+everything
+----------
+
+Incorrect code for this option:
+
+.. code-block:: js
+
+ Cu.importGlobalProperties(['TextEncoder']);
+ XPCOMUtils.defineLazyGlobalGetters(this, ['TextEncoder']);
+
+allownonwebidl
+--------------
+
+Incorrect code for this option:
+
+.. code-block:: js
+
+ // AnimationEffect is a webidl property.
+ Cu.importGlobalProperties(['AnimationEffect']);
+ XPCOMUtils.defineLazyGlobalGetters(this, ['AnimationEffect']);
+
+Correct code for this option:
+
+.. code-block:: js
+
+ // TextEncoder is not defined by webidl.
+ Cu.importGlobalProperties(['TextEncoder']);
+ XPCOMUtils.defineLazyGlobalGetters(this, ['TextEncoder']);
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-lazy-imports-into-globals.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-lazy-imports-into-globals.rst
new file mode 100644
index 0000000000..64230ab6f1
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-lazy-imports-into-globals.rst
@@ -0,0 +1,36 @@
+reject-lazy-imports-into-globals
+================================
+
+Rejects importing lazy items into ``window`` or ``globalThis`` when in a
+non-system module scope.
+
+Importing into the ``window`` scope (or ``globalThis``) will share the imported
+global with everything else in the same window. In modules, this is generally
+unnecessary and undesirable because each module imports what it requires.
+Additionally, sharing items via the global scope makes it more difficult for
+linters to determine the available globals.
+
+Instead, the globals should either be imported directly, or into a lazy object.
+If there is a good reason for sharing the globals via the ``window`` scope, then
+this rule may be disabled as long as a comment is added explaining the reasons.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ XPCOMUtils.defineLazyModuleGetter(globalThis, "foo", "foo.jsm");
+ XPCOMUtils.defineLazyModuleGetter(window, "foo", "foo.jsm");
+ XPCOMUtils.defineLazyGetter(globalThis, "foo", () => {});
+ XPCOMUtils.defineLazyGetter(window, "foo", () => {});
+ ChromeUtils.defineLazyGetter(globalThis, "foo", () => {});
+ ChromeUtils.defineLazyGetter(window, "foo", () => {});
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ const lazy = {};
+ XPCOMUtils.defineLazyGetter(lazy, "foo", () => {});
+ ChromeUtils.defineLazyGetter(lazy, "bar", () => {});
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-mixing-eager-and-lazy.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-mixing-eager-and-lazy.rst
new file mode 100644
index 0000000000..1bf5100901
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-mixing-eager-and-lazy.rst
@@ -0,0 +1,22 @@
+reject-mixing-eager-and-lazy
+==================================
+
+Rejects defining a lazy getter for a module that's eagerly imported at
+top-level script unconditionally.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ const { SomeProp } = ChromeUtils.import("resource://gre/modules/Foo.jsm");
+ XPCOMUtils.defineLazyModuleGetter(lazy, {
+ OtherProp: "resource://gre/modules/Foo.jsm",
+ });
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ const { SomeProp, OtherProp } = ChromeUtils.import("resource://gre/modules/Foo.jsm");
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-multiple-getters-calls.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-multiple-getters-calls.rst
new file mode 100644
index 0000000000..7ea048402b
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-multiple-getters-calls.rst
@@ -0,0 +1,27 @@
+reject-multiple-getters-calls
+=============================
+
+Rejects multiple calls on ``ChromeUtils.defineESModuleGetters`` for the same
+target in the same context.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ ChromeUtils.defineESModuleGetters(lazy, {
+ AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
+ });
+ ChromeUtils.defineESModuleGetters(lazy, {
+ PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
+ });
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ ChromeUtils.defineESModuleGetters(lazy, {
+ AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
+ PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
+ });
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-relative-requires.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-relative-requires.rst
new file mode 100644
index 0000000000..4387041b26
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-relative-requires.rst
@@ -0,0 +1,22 @@
+reject-relative-requires
+========================
+
+Rejects calls to require which use relative directories.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ require("./relative/path")
+ require("../parent/folder/path")
+ loader.lazyRequireGetter(this, "path", "../parent/folder/path", true)
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ require("devtools/absolute/path")
+ require("resource://gre/modules/SomeModule.jsm")
+ loader.lazyRequireGetter(this, "path", "devtools/absolute/path", true)
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-requires-await.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-requires-await.rst
new file mode 100644
index 0000000000..2a8618939f
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-requires-await.rst
@@ -0,0 +1,20 @@
+reject-requires-await
+=====================
+
+`Assert.rejects` must be preceded by an `await`, otherwise the assertion
+may not be completed before the test finishes, might not be caught
+and might cause intermittent issues in other tests.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ Assert.rejects(myfunc(), /startup failed/, "Should reject");
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ await Assert.rejects(myfunc(), /startup failed/, "Should reject");
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-scriptableunicodeconverter.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-scriptableunicodeconverter.rst
new file mode 100644
index 0000000000..8f6ae39060
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-scriptableunicodeconverter.rst
@@ -0,0 +1,13 @@
+reject-scriptableunicodeconverter
+================================================
+
+Rejects calls into ``Ci.nsIScriptableUnicodeConverter``. This is configured as a warning.
+You should use |TextEncoder|_ or |TextDecoder|_ for new code.
+If modifying old code, please consider swapping it in if possible; if this is tricky please ensure
+a bug is on file.
+
+.. |TextEncoder| replace:: ``TextEncoder``
+.. _TextEncoder: https://searchfox.org/mozilla-central/source/dom/webidl/TextEncoder.webidl
+
+.. |TextDecoder| replace:: ``TextDecoder``
+.. _TextDecoder: https://searchfox.org/mozilla-central/source/dom/webidl/TextDecoder.webidl
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-some-requires.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-some-requires.rst
new file mode 100644
index 0000000000..476dcbcb94
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-some-requires.rst
@@ -0,0 +1,6 @@
+reject-some-requires
+====================
+
+This takes an option, a regular expression. Invocations of
+``require`` with a string literal argument are matched against this
+regexp; and if it matches, the ``require`` use is flagged.
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-top-level-await.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-top-level-await.rst
new file mode 100644
index 0000000000..38be0b2d22
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/reject-top-level-await.rst
@@ -0,0 +1,26 @@
+reject-top-level-await
+======================
+
+Rejects ``await`` at the top-level of code in modules. Top-level ``await`` is
+not currently support in Gecko's component modules, so this is rejected.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ await foo;
+
+ if (expr) {
+ await foo;
+ }
+
+ for await (let x of [1, 2, 3]) { }
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ async function() { await foo; }
+ async function() { for await (let x of [1, 2, 3]) { } }
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-cc-etc.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-cc-etc.rst
new file mode 100644
index 0000000000..902b4a630c
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-cc-etc.rst
@@ -0,0 +1,26 @@
+use-cc-etc
+======================
+
+This requires using ``Cc`` rather than ``Components.classes``, and the same for
+``Components.interfaces``, ``Components.results`` and ``Components.utils``.
+This has a slight performance advantage by avoiding the use of the dot.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ let foo = Components.classes['bar'];
+ let bar = Components.interfaces.bar;
+ Components.results.NS_ERROR_ILLEGAL_INPUT;
+ Components.utils.reportError('fake');
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ let foo = Cc['bar'];
+ let bar = Ci.bar;
+ Cr.NS_ERROR_ILLEGAL_INPUT;
+ Cu.reportError('fake');
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-chromeutils-generateqi.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-chromeutils-generateqi.rst
new file mode 100644
index 0000000000..3da22e139a
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-chromeutils-generateqi.rst
@@ -0,0 +1,33 @@
+use-chromeutils-generateqi
+==========================
+
+Reject use of ``XPCOMUtils.generateQI`` and JS-implemented QueryInterface
+methods in favor of ``ChromeUtils``.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ X.prototype.QueryInterface = XPCOMUtils.generateQI(["nsIMeh"]);
+ X.prototype = { QueryInterface: XPCOMUtils.generateQI(["nsIMeh"]) };
+ X.prototype = { QueryInterface: function QueryInterface(iid) {
+ if (
+ iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIMeh) ||
+ iid.equals(nsIFlug) ||
+ iid.equals(Ci.amIFoo)
+ ) {
+ return this;
+ }
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+ } };
+
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ X.prototype.QueryInterface = ChromeUtils.generateQI(["nsIMeh"]);
+ X.prototype = { QueryInterface: ChromeUtils.generateQI(["nsIMeh"]) }
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-chromeutils-import.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-chromeutils-import.rst
new file mode 100644
index 0000000000..c38304193a
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-chromeutils-import.rst
@@ -0,0 +1,24 @@
+use-chromeutils-import
+======================
+
+Require use of ``ChromeUtils.import`` and ``ChromeUtils.defineModuleGetter``
+rather than ``Components.utils.import`` and
+``XPCOMUtils.defineLazyModuleGetter``.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ Components.utils.import("resource://gre/modules/AppConstants.jsm", this);
+ XPCOMUtils.defineLazyModuleGetter(this, "AppConstants", "resource://gre/modules/AppConstants.jsm");
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ ChromeUtils.import("resource://gre/modules/AppConstants.jsm", this);
+ ChromeUtils.defineModuleGetter(this, "AppConstants", "resource://gre/modules/AppConstants.jsm");
+ // 4 argument version of defineLazyModuleGetter is allowed.
+ XPCOMUtils.defineLazyModuleGetter(this, "AppConstants","resource://gre/modules/AppConstants.jsm","Foo");
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-default-preference-values.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-default-preference-values.rst
new file mode 100644
index 0000000000..2392709e89
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-default-preference-values.rst
@@ -0,0 +1,19 @@
+use-default-preference-values
+=============================
+
+Require providing a second parameter to ``get*Pref`` methods instead of
+using a try/catch block.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ try { blah = branch.getCharPref('blah'); } catch(e) {}
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ blah = branch.getCharPref('blah', true);
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-includes-instead-of-indexOf.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-includes-instead-of-indexOf.rst
new file mode 100644
index 0000000000..bb65ebea2c
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-includes-instead-of-indexOf.rst
@@ -0,0 +1,21 @@
+use-includes-instead-of-indexOf
+===============================
+
+Use ``.includes`` instead of ``.indexOf`` to check if something is in an array
+or string.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ let a = foo.indexOf(bar) >= 0;
+ let a = foo.indexOf(bar) == -1;
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ let a = foo.includes(bar);
+ let a = foo.indexOf(bar) > 0;
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-isInstance.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-isInstance.rst
new file mode 100644
index 0000000000..dca1e51c82
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-isInstance.rst
@@ -0,0 +1,42 @@
+use-isInstance
+==============
+
+Prefer ``.isInstance()`` in chrome scripts over the standard ``instanceof``
+operator for DOM interfaces, since the latter will return false when the object
+is created from a different context.
+
+These files are covered:
+
+- ``*.sys.mjs``
+- ``*.jsm``
+- ``*.jsm.js``
+- ``*.xhtml`` with ``there.is.only.xul``
+- ``*.js`` with a heuristic
+
+Since there is no straightforward way to detect chrome scripts, currently the
+linter assumes that any script including the following words are chrome
+privileged. This of course may not be sufficient and is open for change:
+
+- ``ChromeUtils``, but not ``SpecialPowers.ChromeUtils``
+- ``BrowserTestUtils``, ``PlacesUtils``
+- ``document.createXULElement``
+- ``loader.lazyRequireGetter``
+- ``Services.foo``, but not ``SpecialPowers.Services.foo``
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ node instanceof Node
+ text instanceof win.Text
+ target instanceof this.contentWindow.HTMLAudioElement
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ Node.isInstance(node)
+ win.Text.isInstance(text)
+ this.contentWindow.HTMLAudioElement.isInstance(target)
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-ownerGlobal.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-ownerGlobal.rst
new file mode 100644
index 0000000000..5d9905fc9f
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-ownerGlobal.rst
@@ -0,0 +1,20 @@
+use-ownerGlobal
+===============
+
+Require ``.ownerGlobal`` instead of ``.ownerDocument.defaultView``.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ aEvent.target.ownerDocument.defaultView;
+ this.DOMPointNode.ownerDocument.defaultView.getSelection();
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ aEvent.target.ownerGlobal;
+ this.DOMPointNode.ownerGlobal.getSelection();
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-returnValue.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-returnValue.rst
new file mode 100644
index 0000000000..1280703747
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-returnValue.rst
@@ -0,0 +1,20 @@
+use-returnValue
+===============
+
+Warn when idempotent methods are called and their return value is unused.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ foo.concat(bar)
+ baz.concat()
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ a = foo.concat(bar)
+ b = baz.concat()
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-services.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-services.rst
new file mode 100644
index 0000000000..1a57e3da10
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-services.rst
@@ -0,0 +1,21 @@
+use-services
+============
+
+Requires the use of ``Services`` rather than ``Cc[].getService()`` where a
+service is already defined in ``Services``.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
+ Components.classes["@mozilla.org/toolkit/app-startup;1"].getService(Components.interfaces.nsIAppStartup);
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ Services.wm.addListener()
+ Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator)
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-static-import.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-static-import.rst
new file mode 100644
index 0000000000..9090dd80b7
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/use-static-import.rst
@@ -0,0 +1,21 @@
+use-static-import
+=================
+
+Requires the use of static imports in system ES module files (``.sys.mjs``)
+where possible.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ const { XPCOMUtils } = ChromeUtils.importESModule("resource://gre/modules/XPCOMUtils.sys.mjs");
+ const { XPCOMUtils: foo } = ChromeUtils.importESModule("resource://gre/modules/XPCOMUtils.sys.mjs");
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+ import { XPCOMUtils as foo } from "resource://gre/modules/XPCOMUtils.sys.mjs";
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/valid-ci-uses.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/valid-ci-uses.rst
new file mode 100644
index 0000000000..440d730e05
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/valid-ci-uses.rst
@@ -0,0 +1,42 @@
+valid-ci-uses
+=============
+
+Ensures that interface accesses on ``Ci`` are valid, and property accesses on
+``Ci.<interface>`` are also valid.
+
+This rule requires a full build to run, and is not turned on by default. To run
+this rule manually, use:
+
+.. code-block:: console
+
+ MOZ_OBJDIR=objdir-ff-opt ./mach eslint --rule="mozilla/valid-ci-uses: error" *
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+``nsIFoo`` does not exist.
+
+.. code-block:: js
+
+ Ci.nsIFoo
+
+``UNKNOWN_CONSTANT`` does not exist on nsIURIFixup.
+
+.. code-block:: js
+
+ Ci.nsIURIFixup.UNKNOWN_CONSTANT
+
+Examples of correct code for this rule:
+---------------------------------------
+
+``nsIFile`` does exist.
+
+.. code-block:: js
+
+ Ci.nsIFile
+
+``FIXUP_FLAG_NONE`` does exist on nsIURIFixup.
+
+.. code-block:: js
+
+ Ci.nsIURIFixup.FIXUP_FLAG_NONE
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/valid-lazy.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/valid-lazy.rst
new file mode 100644
index 0000000000..fcbe5d064e
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/valid-lazy.rst
@@ -0,0 +1,55 @@
+valid-lazy
+==========
+
+Ensures that definitions and uses of properties on the ``lazy`` object are valid.
+This rule checks for using unknown properties, duplicated symbols, unused
+symbols, and also lazy getter used at top-level unconditionally.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ const lazy = {};
+ if (x) {
+ // Unknown lazy member property {{name}}
+ lazy.bar.foo();
+ }
+
+.. code-block:: js
+
+ const lazy = {};
+ XPCOMUtils.defineLazyGetter(lazy, "foo", "foo.jsm");
+
+ // Duplicate symbol foo being added to lazy.
+ XPCOMUtils.defineLazyGetter(lazy, "foo", "foo1.jsm");
+ if (x) {
+ lazy.foo3.bar();
+ }
+
+.. code-block:: js
+
+ const lazy = {};
+ // Unused lazy property foo
+ XPCOMUtils.defineLazyGetter(lazy, "foo", "foo.jsm");
+
+.. code-block:: js
+
+ const lazy = {};
+ XPCOMUtils.defineLazyGetter(lazy, "foo", "foo.jsm");
+ // Used at top-level unconditionally.
+ lazy.foo.bar();
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ const lazy = {};
+ XPCOMUtils.defineLazyGetter(lazy, "foo1", () => {});
+ XPCOMUtils.defineLazyModuleGetters(lazy, { foo2: "foo2.jsm" });
+
+ if (x) {
+ lazy.foo1.bar();
+ lazy.foo2.bar();
+ }
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/valid-services-property.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/valid-services-property.rst
new file mode 100644
index 0000000000..c6c61abac2
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/valid-services-property.rst
@@ -0,0 +1,30 @@
+valid-services-property
+=======================
+
+Ensures that accesses of properties of items accessed via the ``Services``
+object are valid.
+
+This rule requires a full build to run, and is not turned on by default. To run
+this rule manually, use:
+
+.. code-block:: console
+
+ MOZ_OBJDIR=objdir-ff-opt ./mach eslint --rule="mozilla/valid-services-property: error" *
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+Assuming ``foo`` is not defined within ``Ci.nsISearchService``.
+
+.. code-block:: js
+
+ Services.search.foo();
+
+Examples of correct code for this rule:
+---------------------------------------
+
+Assuming ``bar`` is defined within ``Ci.nsISearchService``.
+
+.. code-block:: js
+
+ Services.search.bar();
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/valid-services.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/valid-services.rst
new file mode 100644
index 0000000000..bd76cb52ac
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/valid-services.rst
@@ -0,0 +1,24 @@
+valid-services
+==============
+
+Ensures that accesses of the ``Services`` object are valid.
+``Services`` are defined in ``tools/lint/eslint/eslint-plugin-mozilla/lib/services.json`` and can be added by copying from
+``<objdir>/xpcom/components/services.json`` after a build.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+Assuming ``foo`` is not defined within Services.
+
+.. code-block:: js
+
+ Services.foo.fn();
+
+Examples of correct code for this rule:
+---------------------------------------
+
+Assuming ``bar`` is defined within Services.
+
+.. code-block:: js
+
+ Services.bar.fn();
diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/var-only-at-top-level.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/var-only-at-top-level.rst
new file mode 100644
index 0000000000..d21fc1b299
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/var-only-at-top-level.rst
@@ -0,0 +1,21 @@
+var-only-at-top-level
+=====================
+
+Marks all var declarations that are not at the top level invalid.
+
+Examples of incorrect code for this rule:
+-----------------------------------------
+
+.. code-block:: js
+
+ { var foo; }
+ function() { var bar; }
+
+Examples of correct code for this rule:
+---------------------------------------
+
+.. code-block:: js
+
+ var foo;
+ { let foo; }
+ function () { let bar; }
diff --git a/docs/code-quality/lint/linters/eslint-plugin-spidermonkey-js.rst b/docs/code-quality/lint/linters/eslint-plugin-spidermonkey-js.rst
new file mode 100644
index 0000000000..e20c8562b6
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint-plugin-spidermonkey-js.rst
@@ -0,0 +1,18 @@
+==============================
+Mozilla ESLint SpiderMonkey JS
+==============================
+
+This plugin adds a processor and an environment for the SpiderMonkey JS code.
+
+Processors
+==========
+
+The processor is used to pre-process all `*.js` files and deals with the macros
+that SpiderMonkey uses.
+
+Environments
+============
+
+The plugin provides a custom environment for SpiderMonkey's self-hosted code. It
+adds all self-hosting functions, error message numbers, and other self-hosting
+definitions as global, read-only identifiers.
diff --git a/docs/code-quality/lint/linters/eslint.rst b/docs/code-quality/lint/linters/eslint.rst
new file mode 100644
index 0000000000..3f3e33467b
--- /dev/null
+++ b/docs/code-quality/lint/linters/eslint.rst
@@ -0,0 +1,211 @@
+ESLint
+======
+
+`ESLint`__ is a popular linter for JavaScript. The ESLint integration also uses
+`Prettier`_ to enforce code formatting.
+
+Run Locally
+-----------
+
+The mozlint integration of ESLint can be run using mach:
+
+.. parsed-literal::
+
+ $ mach lint --linter eslint <file paths>
+
+Alternatively, omit the ``--linter eslint`` and run all configured linters, which will include
+ESLint.
+
+ESLint also supports the ``--fix`` option to autofix most errors raised from most of the rules.
+
+See the `Usage guide`_ for more options.
+
+Understanding Rules and Errors
+------------------------------
+
+* Only some files are linted, see the :searchfox:`configuration <tools/lint/eslint.yml>` for details.
+
+ * By design we do not lint/format reftests not crashtests as these are specially crafted tests.
+
+* If you don't understand a rule, you can look it in `eslint.org's rule list`_ for more
+ information about it.
+* For Mozilla specific rules (with the mozilla/ prefix), see these for more details:
+
+ * `eslint-plugin-mozilla`_
+ * `eslint-plugin-spidermonkey-js`_
+
+Common Issues and How To Solve Them
+-----------------------------------
+
+My editor says that ``mozilla/whatever`` is unknown
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+* Run ``./mach eslint --setup``, and restart your editor.
+
+My editor doesn't understand a new global I've just added (e.g. to a content file or head.js file)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+* This is a limitation which is a mixture of our ESLint setup and how we share globals across files.
+* Restarting your editor should pick up the new globals.
+* You can always double check via ``./mach lint --linter eslint <file path>`` on the command line.
+
+I am getting a linter error "Unknown Services member property"
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Make sure to add any new Services to ``tools/lint/eslint/eslint-plugin-mozilla/lib/services.json``. For example by copying from
+``<objdir>/xpcom/components/services.json`` after a build.
+
+.. _adding-tests:
+
+I'm adding tests, how do I set up the right configuration?
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Please note we prefer the tests to be in named directories as this makes it
+easier to identify the types of tests developers are working with. Additionally,
+it is not possible to scope ESLint rules to individual files based on .ini
+files without a build step that would break editors, or an expensive loading
+cycle.
+
+* If the directory path of the tests is one of the `known ones`_, then ESLint will
+ do the right thing for that test type. This is the preferred option.
+
+ * For example placing xpcshell-tests in ``browser/components/foo/test/unit/``
+ will set up ESLint correctly.
+
+* If you really can't match the directory name, e.g. like the
+ ``browser/base/content/tests/*``, then you'll need to add a new entry in
+ :searchfox:`.eslintrc-test-paths.js <.eslintrc-test-paths.js>`.
+
+Please do not add new cases of multiple types of tests within a single directory,
+this is `difficult for ESLint to handle`_. Currently this may cause:
+
+* Rules to be incorrectly applied to the wrong types of test file.
+* Extra definitions for globals in tests which means that the no undefined variables
+ rule does not get triggered in some cases.
+
+I'm using an ES module
+^^^^^^^^^^^^^^^^^^^^^^
+
+* Use a ``.mjs`` extension for the file. ESLint will pick this up and automatically
+ treat it as a module.
+* If it is a system module (e.g. component definition or other non-frontend code),
+ use a ``.sys.mjs`` extension.
+
+This code should neither be linted nor formatted
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+* If it is a third-party piece of code, please add it to :searchfox:`ThirdPartyPaths.txt <tools/rewriting/ThirdPartyPaths.txt>`.
+* If it is a generated file, please add it to :searchfox:`Generated.txt <tools/rewriting/Generated.txt>`.
+* If intentionally invalid, please add it to :searchfox:`.eslintignore <.eslintignore>`.
+
+This code shouldn't be formatted
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The vast majority of code should be formatted, however we allow some limited
+cases where it makes sense, for example:
+
+* A table in an array where laying it out in a table fashion makes it more readable.
+* Other structures or function calls where layout is more readable in a particular format.
+
+To disable prettier for code like this, ``// prettier-ignore`` may be used on
+the line previous to where you want it disabled.
+See the `prettier ignore docs`_ for more information.
+
+I have valid code that is failing the ``no-undef`` rule or can't be parsed
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+* Please do not add this to :searchfox:`.eslintignore <.eslintignore>`. Generally
+ this can be fixed, if the following tips don't help, please `seek help`_.
+* If you are adding a new test directory, see the :ref:`section above <adding-tests>`
+
+* If you are writing a script loaded into special environment (e.g. frame script) you may need to tell ESLint to use the `environment definitions`_ for each case:
+
+ * ``/* eslint-env mozilla/frame-script */``
+
+* If you are writing a worker, then you may need to use the worker or chrome-worker environment:
+
+ * ``/* eslint-env worker */``
+ * ``/* eslint-env mozilla/chrome-worker */``
+
+* I use ``Services.scriptloader.loadSubScript``:
+
+ * ``/* import-globals-from relative/path/to/file.js``
+
+Configuration
+-------------
+
+The global configuration file lives in ``topsrcdir/.eslintrc``. This global configuration can be
+overridden by including an ``.eslintrc`` in the appropriate subdirectory. For an overview of the
+supported configuration, see `ESLint's documentation`_.
+
+Please keep differences in rules across the tree to a minimum. We want to be consistent to
+make it easier for developers.
+
+Sources
+-------
+
+* :searchfox:`Configuration (YAML) <tools/lint/eslint.yml>`
+* :searchfox:`Source <tools/lint/eslint/__init__.py>`
+
+Builders
+--------
+
+`Mark Banner (standard8) <https://people.mozilla.org/s?query=standard8>`__ owns
+the builders. Questions can also be asked on #lint:mozilla.org on Matrix.
+
+ESLint (ES)
+^^^^^^^^^^^
+
+This is a tier-1 task. For test failures the patch causing the
+issue should be backed out or the issue fixed.
+
+Some failures can be fixed with ``./mach eslint --fix path/to/file``.
+
+For test harness issues, file bugs in Developer Infrastructure :: Lint and Formatting.
+
+ESLint-build (ES-B)
+^^^^^^^^^^^^^^^^^^^
+
+This is a tier-2 task that is run once a day at midnight UTC via a cron job.
+
+It currently runs the ESLint rules plus two additional rules:
+
+* `valid-ci-uses <eslint-plugin-mozilla/valid-ci-uses.html>`__
+* `valid-services-property <eslint-plugin-mozilla/valid-services-property.html>`__
+
+These are two rules that both require build artifacts.
+
+To run them manually, you can run:
+
+``MOZ_OBJDIR=objdir-ff-opt ./mach eslint --rule "mozilla/valid-ci-uses: error" --rule "mozilla/valid-services-property: error" *``
+
+For test failures, the regression causing bug may be able to be found by:
+
+ * Determining if the file where the error is reported has been changed recently.
+ * Seeing if an associated ``.idl`` file has been changed.
+
+If no regressing bug can easily be found, file a bug in the relevant
+product/component for the file where the failure is and cc :standard8.
+
+For test harness issues, file bugs in Developer Infrastructure :: Lint and Formatting.
+
+.. toctree::
+ :hidden:
+
+ eslint-plugin-mozilla
+ eslint-plugin-spidermonkey-js
+
+.. __: https://eslint.org/
+.. _Prettier: https://prettier.io/
+.. _Usage guide: ../usage.html
+.. _ESLint's documentation: https://eslint.org/docs/user-guide/configuring
+.. _eslint.org's rule list: https://eslint.org/docs/rules/
+.. _eslint-plugin-mozilla: eslint-plugin-mozilla.html
+.. _eslint-plugin-spidermonkey-js: eslint-plugin-spidermonkey-js.html
+.. _informed that it is a module: https://searchfox.org/mozilla-central/rev/9399e5832979755cd340383f4ca4069dd5fc7774/browser/base/content/.eslintrc.js
+.. _seek help: ../index.html#getting-help
+.. _patterns in .eslintrc.js: https://searchfox.org/mozilla-central/rev/9399e5832979755cd340383f4ca4069dd5fc7774/.eslintrc.js#24-38
+.. _environment definitions: ./eslint-plugin-mozilla/environment.html
+.. _known ones: https://searchfox.org/mozilla-central/rev/287583a4a605eee8cd2d41381ffaea7a93d7b987/.eslintrc.js#24-40
+.. _difficult for ESLint to handle: https://bugzilla.mozilla.org/show_bug.cgi?id=1379669
+.. _prettier ignore docs: https://prettier.io/docs/en/ignore.html
diff --git a/docs/code-quality/lint/linters/file-perm.rst b/docs/code-quality/lint/linters/file-perm.rst
new file mode 100644
index 0000000000..5c3a02fa1b
--- /dev/null
+++ b/docs/code-quality/lint/linters/file-perm.rst
@@ -0,0 +1,42 @@
+File permission
+===============
+
+This linter verifies if a file has unnecessary permissions.
+If a file has execution permissions (+x), file-perm will
+generate a warning.
+
+It will ignore files starting with ``#!`` for types of files
+that typically have shebang lines (such as python, node or
+shell scripts).
+
+This linter does not have any affect on Windows.
+
+
+Run Locally
+-----------
+
+This mozlint linter can be run using mach:
+
+.. parsed-literal::
+
+ $ mach lint --linter file-perm <file paths>
+
+
+Configuration
+-------------
+
+This linter is enabled on the whole code base.
+
+This job is configured as `tier 2 <https://wiki.mozilla.org/Sheriffing/Job_Visibility_Policy#Overview_of_the_Job_Visibility_Tiers>`_.
+
+Autofix
+-------
+
+This linter provides a ``--fix`` option. The python script is doing the change itself.
+
+
+Sources
+-------
+
+* `Configuration (YAML) <https://searchfox.org/mozilla-central/source/tools/lint/file-perm.yml>`_
+* `Source <https://searchfox.org/mozilla-central/source/tools/lint/file-perm/__init__.py>`_
diff --git a/docs/code-quality/lint/linters/file-whitespace.rst b/docs/code-quality/lint/linters/file-whitespace.rst
new file mode 100644
index 0000000000..201f10d123
--- /dev/null
+++ b/docs/code-quality/lint/linters/file-whitespace.rst
@@ -0,0 +1,38 @@
+Trailing whitespaces
+====================
+
+This linter verifies if a file has:
+
+* unnecessary trailing whitespaces,
+* Windows carriage return,
+* empty lines at the end of file,
+* if file ends with a newline or not
+
+
+Run Locally
+-----------
+
+This mozlint linter can be run using mach:
+
+.. parsed-literal::
+
+ $ mach lint --linter file-whitespace <file paths>
+
+
+Configuration
+-------------
+
+This linter is enabled on most of the code base.
+
+This job is configured as `tier 2 <https://wiki.mozilla.org/Sheriffing/Job_Visibility_Policy#Overview_of_the_Job_Visibility_Tiers>`_.
+
+Autofix
+-------
+
+This linter provides a ``--fix`` option. The python script is doing the change itself.
+
+Sources
+-------
+
+* :searchfox:`Configuration (YAML) <tools/lint/file-whitespace.yml>`
+* :searchfox:`Source <tools/lint/file-whitespace/__init__.py>`
diff --git a/docs/code-quality/lint/linters/fluent-lint.rst b/docs/code-quality/lint/linters/fluent-lint.rst
new file mode 100644
index 0000000000..809adb4a5e
--- /dev/null
+++ b/docs/code-quality/lint/linters/fluent-lint.rst
@@ -0,0 +1,47 @@
+Fluent Lint
+===========
+
+Fluent lint is a linter for Fluent files (.ftl). Currently, it includes:
+
+* Checks for invalid typography in messages (e.g. straight single or double quotes).
+* Checks for comments layout.
+* Checks for identifiers (minimum length, allowed characters).
+* Hard-coded brand names.
+
+
+Run Locally
+-----------
+
+The mozlint integration of fluent-lint can be run using mach:
+
+.. parsed-literal::
+
+ $ mach lint --linter fluent-lint <file paths>
+
+Alternatively, omit the ``--linter fluent-lint`` and run all configured linters, which will include
+fluent-lint.
+
+
+Run on Taskcluster
+------------------
+
+The fluent-lint job shows up as text(fluent) in the linting job. It should run automatically if
+changes are made to fluent (ftl) files.
+
+
+Configuration
+-------------
+
+The main configuration file is found in :searchfox:`tools/lint/fluent-lint/exclusions.yml`. This provides
+a way of excluding identifiers or files from checking. In general, exclusions are only to be
+used for identifiers that are generated programmatically, but unfortunately, there are other
+exclusions that are required for historical reasons. In almost all cases, it should *not* be
+necessary to add new exclusions to this file.
+
+
+Sources
+-------
+
+* :searchfox:`Configuration (YAML) <tools/lint/fluent-lint.yml>`
+* :searchfox:`Source <tools/lint/fluent-lint/__init__.py>`
+* :searchfox:`Test <tools/lint/test/test_fluent_lint.py>`
diff --git a/docs/code-quality/lint/linters/l10n.rst b/docs/code-quality/lint/linters/l10n.rst
new file mode 100644
index 0000000000..de4ce990c8
--- /dev/null
+++ b/docs/code-quality/lint/linters/l10n.rst
@@ -0,0 +1,45 @@
+L10n
+====
+
+The l10n linter checks for mistakes and problems in the localizable files.
+Most of the code lives inside the
+`compare-locales <https://pypi.org/project/compare-locales/>`_
+package, and is shipping as the ``moz-l10n-lint`` command.
+
+The linter checks for fundamental issues like parsing errors, but it also
+finds more subtle mistakes like duplicated messages. It also warns if you're
+trying to change a string without changing the ID, or to add a string that's
+still in use in a stable channel with a different value.
+
+The warnings on string ID changes get reported on phabricator, but they're
+not making the build fail. To find out when to change IDs and when not to,
+read the :ref:`Lifecycle & Workflow <Localization>` section in the
+localization documentation.
+
+Run Locally
+-----------
+
+The can be run using mach:
+
+.. parsed-literal::
+
+ $ mach lint --linter l10n <file paths>
+
+Alternatively, omit the ``--linter l10n`` and run all configured linters, which
+will include the l10n linter.
+
+
+Updating the Reference
+----------------------
+
+The linter checks out the cross-channel localization files into your
+``.mozbuild`` state directory. By default this is updated automatically after
+48 hours. There might be new strings anyway, if you want to ensure an
+updated clone, remove the marker file in
+``~/.mozbuild/gecko-strings/.hg/l10n_pull_marker``.
+
+Sources
+-------
+
+* :searchfox:`Configuration (YAML) <tools/lint/l10n.yml>`
+* :searchfox:`Source <tools/lint/python/l10n_lint.py>`
diff --git a/docs/code-quality/lint/linters/license.rst b/docs/code-quality/lint/linters/license.rst
new file mode 100644
index 0000000000..0533333218
--- /dev/null
+++ b/docs/code-quality/lint/linters/license.rst
@@ -0,0 +1,39 @@
+License
+=======
+
+This linter verifies if a file has a known license header.
+
+By default, Firefox uses MPL-2 license with the `appropriate headers <https://www.mozilla.org/en-US/MPL/headers/>`_.
+In some cases (thirdpardy code), a file might have a different header file.
+If this is the case, one of the significant line of the header should be listed in the list `of valid licenses
+<https://searchfox.org/mozilla-central/source/tools/lint/license/valid-licenses.txt>`_.
+
+Run Locally
+-----------
+
+This mozlint linter can be run using mach:
+
+.. parsed-literal::
+
+ $ mach lint --linter license <file paths>
+
+
+Configuration
+-------------
+
+This linter is enabled on most of the whole code base.
+
+Autofix
+-------
+
+This linter provides a ``--fix`` option. The python script is doing the change itself
+and will use the right header MPL-2 header depending on the language.
+It will add the license at the right place in case the file is a script (ie starting with ``!#``
+or a XML file ``<?xml>``).
+
+
+Sources
+-------
+
+* `Configuration (YAML) <https://searchfox.org/mozilla-central/source/tools/lint/license.yml>`_
+* `Source <https://searchfox.org/mozilla-central/source/tools/lint/license/__init__.py>`_
diff --git a/docs/code-quality/lint/linters/lintpref.rst b/docs/code-quality/lint/linters/lintpref.rst
new file mode 100644
index 0000000000..ca19089172
--- /dev/null
+++ b/docs/code-quality/lint/linters/lintpref.rst
@@ -0,0 +1,32 @@
+Lintpref
+========
+
+The lintpref linter is a simple linter for libpref files to check for duplicate
+entries between :searchfox:`modules/libpref/init/all.js` and
+:searchfox:`modules/libpref/init/StaticPrefList.yaml`. If a duplicate is found,
+lintpref will raise an error and emit the ``all.js`` line where you can find
+the duplicate entry.
+
+
+Running Locally
+---------------
+
+The linter can be run using mach:
+
+ .. parsed-literal::
+
+ $ mach lint --linter lintpref
+
+
+Fixing Lintpref Errors
+----------------------
+
+In most cases, duplicate entries should be avoided and the duplicate removed
+from ``all.js``. If for any reason a pref should exist in both files, the pref
+should be added to ``IGNORE_PREFS`` in :searchfox:`tools/lint/libpref/__init__.py`.
+
+Sources
+-------
+
+* :searchfox:`Configuration (YAML) <tools/lint/lintpref.yml>`
+* :searchfox:`Source <tools/lint/libpref/__init__.py>`
diff --git a/docs/code-quality/lint/linters/mingw-capitalization.rst b/docs/code-quality/lint/linters/mingw-capitalization.rst
new file mode 100644
index 0000000000..e6c51a4d14
--- /dev/null
+++ b/docs/code-quality/lint/linters/mingw-capitalization.rst
@@ -0,0 +1,28 @@
+MinGW capitalization
+====================
+
+This linter verifies that Windows include file are lowercase.
+It might break the mingw build otherwise.
+
+
+Run Locally
+-----------
+
+This mozlint linter can be run using mach:
+
+.. parsed-literal::
+
+ $ mach lint --linter mingw-capitalization <file paths>
+
+
+Configuration
+-------------
+
+This linter is enabled on the whole code base except WebRTC
+
+
+Sources
+-------
+
+* :searchfox:`Configuration (YAML) <tools/lint/mingw-capitalization.yml>`
+* :searchfox:`Source <tools/lint/cpp/mingw-capitalization.py>`
diff --git a/docs/code-quality/lint/linters/perfdocs.rst b/docs/code-quality/lint/linters/perfdocs.rst
new file mode 100644
index 0000000000..3169ee553f
--- /dev/null
+++ b/docs/code-quality/lint/linters/perfdocs.rst
@@ -0,0 +1,84 @@
+PerfDocs
+========
+
+`PerfDocs`_ is a tool that checks to make sure all performance tests are documented in tree.
+
+At the moment, it is only used for this documentation verification, but in the future it will also auto-generate documentation from these descriptions that will be displayed in the source-docs documentation page (rather than the wiki, which is where they currently reside).
+
+Run Locally
+-----------
+
+The mozlint integration of PerfDocs can be run using mach:
+
+.. parsed-literal::
+
+ $ mach lint --linter perfdocs .
+
+
+Configuration
+-------------
+
+There are no configuration options available for this linter. It scans the full source tree under ``testing``, looking for folders named ``perfdocs`` and then validates their content. This has only been implemented for Raptor so far, but Talos will be added in the future. We also hope to expand this to search outside the ``testing`` directory.
+
+The ``perfdocs`` folders, there needs to be an ``index.rst`` file and it needs to contain the string ``{documentation}`` in some location in the file which is where the test documentation will be placed. The folders must also have a ``config.yml`` file following this schema:
+
+.. code-block:: python
+
+ CONFIG_SCHEMA = {
+ "type": "object",
+ "properties": {
+ "name": {"type": "string"},
+ "manifest": {"type": "string"},
+ "suites": {
+ "type": "object",
+ "properties": {
+ "suite_name": {
+ "type": "object",
+ "properties": {
+ "tests": {
+ "type": "object",
+ "properties": {
+ "test_name": {"type": "string"},
+ }
+ },
+ "description": {"type": "string"},
+ },
+ "required": [
+ "description"
+ ]
+ }
+ }
+ }
+ },
+ "required": [
+ "name",
+ "manifest",
+ "suites"
+ ]
+ }
+
+Here is an example of a configuration file for the Raptor framework:
+
+.. parsed-literal::
+
+ name: raptor
+ manifest: testing/raptor/raptor/raptor.ini
+ suites:
+ desktop:
+ description: "Desktop tests."
+ tests:
+ raptor-tp6: "Raptor TP6 tests."
+ mobile:
+ description: "Mobile tests"
+ benchmarks:
+ description: "Benchmark tests."
+ tests:
+ wasm: "All wasm tests."
+
+Note that there needs to be a FrameworkGatherer implemented for the framework being documented since each of them may have different ways of parsing test manifests for the tests. See `RaptorGatherer <https://searchfox.org/mozilla-central/source/tools/lint/perfdocs/framework_gatherers.py>`_ for an example gatherer that was implemented for Raptor.
+
+Sources
+-------
+
+* `Configuration <https://searchfox.org/mozilla-central/source/tools/lint/perfdocs.yml>`__
+* `Source <https://searchfox.org/mozilla-central/source/tools/lint/perfdocs>`__
diff --git a/docs/code-quality/lint/linters/rejected-words.rst b/docs/code-quality/lint/linters/rejected-words.rst
new file mode 100644
index 0000000000..9afe7df27e
--- /dev/null
+++ b/docs/code-quality/lint/linters/rejected-words.rst
@@ -0,0 +1,28 @@
+Rejected words
+==============
+
+Reject some words we don't want to use in the code base for various reasons.
+
+Run Locally
+-----------
+
+The mozlint integration of codespell can be run using mach:
+
+.. parsed-literal::
+
+ $ mach lint --linter rejected-words <file paths>
+
+
+Configuration
+-------------
+
+This linter is enabled on the whole code base. Issues existing in the code base
+are listed in the exclude list in the :searchfox:`rejected-words.yml
+<tools/lint/rejected-words.yml>` file.
+
+New words can be added in the `payload` section.
+
+Sources
+-------
+
+* :searchfox:`Configuration (YAML) <tools/lint/rejected-words.yml>`
diff --git a/docs/code-quality/lint/linters/rstlinter.rst b/docs/code-quality/lint/linters/rstlinter.rst
new file mode 100644
index 0000000000..46a68d5849
--- /dev/null
+++ b/docs/code-quality/lint/linters/rstlinter.rst
@@ -0,0 +1,32 @@
+RST Linter
+==========
+
+`rstcheck`_ is a popular linter for restructuredtext.
+
+
+Run Locally
+-----------
+
+The mozlint integration of rst linter can be run using mach:
+
+.. parsed-literal::
+
+ $ mach lint --linter rst <file paths>
+
+
+Configuration
+-------------
+
+All directories will have rst linter run against them.
+If you wish to exclude a subdirectory of an included one, you can add it to the ``exclude``
+directive.
+
+
+.. _rstcheck: https://github.com/myint/rstcheck
+
+
+Sources
+-------
+
+* :searchfox:`Configuration (YAML) <tools/lint/rst.yml>`
+* :searchfox:`Source <tools/lint/rst/__init__.py>`
diff --git a/docs/code-quality/lint/linters/ruff.rst b/docs/code-quality/lint/linters/ruff.rst
new file mode 100644
index 0000000000..359e8dcbf6
--- /dev/null
+++ b/docs/code-quality/lint/linters/ruff.rst
@@ -0,0 +1,44 @@
+Ruff
+====
+
+`Ruff <https://github.com/charliermarsh/ruff>`_ is an extremely fast Python
+linter and formatter, written in Rust. It can process all of mozilla-central in
+under a second, and implements rule sets from a large array of Python linters
+and formatters, including:
+
+* flake8 (pycodestyle, pyflakes and mccabe)
+* isort
+* pylint
+* pyupgrade
+* and many many more!
+
+Run Locally
+-----------
+
+The mozlint integration of ruff can be run using mach:
+
+.. parsed-literal::
+
+ $ mach lint --linter ruff <file paths>
+
+
+Configuration
+-------------
+
+Ruff is configured in the root `pyproject.toml`_ file. Additionally, ruff will
+pick up any ``pyproject.toml`` or ``ruff.toml`` files in subdirectories. The
+settings in these files will only apply to files contained within these
+subdirs. For more details on configuration discovery, see the `configuration
+documentation`_.
+
+For a list of options, see the `settings documentation`_.
+
+Sources
+-------
+
+* `Configuration (YAML) <https://searchfox.org/mozilla-central/source/tools/lint/ruff.yml>`_
+* `Source <https://searchfox.org/mozilla-central/source/tools/lint/python/ruff.py>`_
+
+.. _pyproject.toml: https://searchfox.org/mozilla-central/source/pyproject.toml
+.. _configuration documentation: https://beta.ruff.rs/docs/configuration/
+.. _settings documentation: https://beta.ruff.rs/docs/settings/
diff --git a/docs/code-quality/lint/linters/rustfmt.rst b/docs/code-quality/lint/linters/rustfmt.rst
new file mode 100644
index 0000000000..eb7e75fa6b
--- /dev/null
+++ b/docs/code-quality/lint/linters/rustfmt.rst
@@ -0,0 +1,33 @@
+Rustfmt
+=======
+
+`rustfmt <https://github.com/rust-lang/rustfmt>`__ is the tool for Rust coding style.
+
+Run Locally
+-----------
+
+The mozlint integration of rustfmt can be run using mach:
+
+.. parsed-literal::
+
+ $ mach lint --linter rustfmt <file paths>
+
+
+Configuration
+-------------
+
+To enable rustfmt on new directory, add the path to the include
+section in the :searchfox:`rustfmt.yml <tools/lint/rustfmt.yml>` file.
+
+
+Autofix
+-------
+
+Rustfmt is reformatting the code by default. To highlight the results, we are using
+the ``--check`` option.
+
+Sources
+-------
+
+* :searchfox:`Configuration (YAML) <tools/lint/rustfmt.yml>`
+* :searchfox:`Source <tools/lint/rust/__init__.py>`
diff --git a/docs/code-quality/lint/linters/stylelint.rst b/docs/code-quality/lint/linters/stylelint.rst
new file mode 100644
index 0000000000..1e8b6c7ff9
--- /dev/null
+++ b/docs/code-quality/lint/linters/stylelint.rst
@@ -0,0 +1,77 @@
+Stylelint
+=========
+
+`Stylelint`__ is a popular linter for CSS.
+
+Run Locally
+-----------
+
+The mozlint integration of Stylelint can be run using mach:
+
+.. parsed-literal::
+
+ $ mach lint --linter stylelint <file paths>
+
+Alternatively, omit the ``--linter stylelint`` and run all configured linters, which will include
+Stylelint.
+
+Stylelint also supports the ``--fix`` option to autofix most errors raised from most of the rules.
+
+See the `Usage guide`_ for more options.
+
+Understanding Rules and Errors
+------------------------------
+
+* Only some files are linted, see the :searchfox:`configuration <tools/lint/stylelint.yml>` for details.
+
+ * By design we do not lint/format reftests not crashtests as these are specially crafted tests.
+
+* If you don't understand a rule, you can look it in `stylelint.io's rule list`_ for more
+ information about it.
+
+Common Issues and How To Solve Them
+-----------------------------------
+
+This code should neither be linted nor formatted
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+* If it is a third-party piece of code, please add it to :searchfox:`ThirdPartyPaths.txt <tools/rewriting/ThirdPartyPaths.txt>`.
+* If it is a generated file, please add it to :searchfox:`Generated.txt <tools/rewriting/Generated.txt>`.
+* If intentionally invalid, please add it to :searchfox:`.stylelintignore <.stylelintignore>`.
+
+Configuration
+-------------
+
+The global configuration file lives in ``topsrcdir/.stylelintrc.js``.
+For an overview of the supported configuration, see `Stylelint's documentation`_.
+
+Please keep differences in rules across the tree to a minimum. We want to be consistent to
+make it easier for developers.
+
+Sources
+-------
+
+* :searchfox:`Configuration (YAML) <tools/lint/stylelint.yml>`
+* :searchfox:`Source <tools/lint/stylelint/__init__.py>`
+
+Builders
+--------
+
+`Gijs Kruitbosch (gijs) <https://people.mozilla.org/s?query=gijs>`__ owns
+the builders. Questions can also be asked on #lint:mozilla.org on Matrix.
+
+Stylelint task
+^^^^^^^^^^^^^^
+
+This is a tier-1 task. For test failures the patch causing the
+issue should be backed out or the issue fixed.
+
+Some failures can be fixed with ``./mach lint -l stylelint --fix path/to/file``.
+
+For test harness issues, file bugs in Developer Infrastructure :: Lint and Formatting.
+
+
+.. __: https://stylelint.io/
+.. _Usage guide: ../usage.html
+.. _Stylelint's documentation: https://stylelint.io/user-guide/configure/
+.. _stylelint.io's rule list: https://stylelint.io/user-guide/rules/
diff --git a/docs/code-quality/lint/linters/trojan-source.rst b/docs/code-quality/lint/linters/trojan-source.rst
new file mode 100644
index 0000000000..250bdd9afe
--- /dev/null
+++ b/docs/code-quality/lint/linters/trojan-source.rst
@@ -0,0 +1,34 @@
+Trojan Source
+=============
+
+This linter verifies if a change is using some invalid unicode.
+
+The goal of this linter is to identify some potential usage of this
+technique:
+
+https://trojansource.codes/
+
+The code is inspired by the Red Hat script published:
+
+https://access.redhat.com/security/vulnerabilities/RHSB-2021-007#diagnostic-tools
+
+Run Locally
+-----------
+
+This mozlint linter can be run using mach:
+
+.. parsed-literal::
+
+ $ mach lint --linter trojan-source <file paths>
+
+
+Configuration
+-------------
+
+This linter is enabled on most of the code base on C/C++, Python and Rust.
+
+Sources
+-------
+
+* `Configuration (YAML) <https://searchfox.org/mozilla-central/source/tools/lint/trojan-source.yml>`_
+* `Source <https://searchfox.org/mozilla-central/source/tools/lint/trojan-source/__init__.py>`_
diff --git a/docs/code-quality/lint/linters/yamllint.rst b/docs/code-quality/lint/linters/yamllint.rst
new file mode 100644
index 0000000000..e148a6aace
--- /dev/null
+++ b/docs/code-quality/lint/linters/yamllint.rst
@@ -0,0 +1,31 @@
+yamllint
+========
+
+`yamllint <https://github.com/adrienverge/yamllint>`__ is a linter for YAML files.
+
+
+Run Locally
+-----------
+
+The mozlint integration of yamllint can be run using mach:
+
+.. parsed-literal::
+
+ $ mach lint --linter yaml <file paths>
+
+Alternatively, omit ``--linter yaml`` to run all configured linters, including
+yamllint.
+
+
+Configuration
+-------------
+
+To enable yamllint on a new directory, add the path to the include section in
+the :searchfox:`yaml.yml <tools/lint/yaml.yml>` file.
+
+
+Sources
+-------
+
+* :searchfox:`Configuration (YAML) <tools/lint/yaml.yml>`
+* :searchfox:`Source <tools/lint/yamllint_/__init__.py>`
diff --git a/docs/code-quality/lint/mozlint.rst b/docs/code-quality/lint/mozlint.rst
new file mode 100644
index 0000000000..48a6a78a6f
--- /dev/null
+++ b/docs/code-quality/lint/mozlint.rst
@@ -0,0 +1,23 @@
+MozLint
+=======
+
+Linters are used in mozilla-central to help enforce coding style and avoid bad practices.
+Due to the wide variety of languages in use, this is not always an easy task.
+In addition, linters should be runnable from editors, from the command line, from review tools
+and from continuous integration. It's easy to see how the complexity of running all of these
+different kinds of linters in all of these different places could quickly balloon out of control.
+
+``Mozlint`` is a library that accomplishes several goals:
+
+1. It provides a standard method for adding new linters to the tree, which can be as easy as
+ defining a config object in a ``.yml`` file. This helps keep lint related code localized, and
+ prevents different teams from coming up with their own unique lint implementations.
+2. It provides a streamlined interface for running all linters at once. Instead of running N
+ different lint commands to test your patch, a single ``mach lint`` command will automatically run
+ all applicable linters. This means there is a single API surface that other tools can use to
+ invoke linters.
+3. With a simple taskcluster configuration, Mozlint provides an easy way to execute all these jobs
+ at review phase.
+
+``Mozlint`` isn't designed to be used directly by end users. Instead, it can be consumed by things
+like mach, phabricator and taskcluster.
diff --git a/docs/code-quality/lint/usage.rst b/docs/code-quality/lint/usage.rst
new file mode 100644
index 0000000000..d0eb1d5b02
--- /dev/null
+++ b/docs/code-quality/lint/usage.rst
@@ -0,0 +1,133 @@
+Running Linters Locally
+=======================
+
+Using the Command Line
+----------------------
+
+You can run all the various linters in the tree using the ``mach lint`` command. Simply pass in the
+directory or file you wish to lint (defaults to current working directory):
+
+.. parsed-literal::
+
+ ./mach lint path/to/files
+
+Multiple paths are allowed:
+
+.. parsed-literal::
+
+ ./mach lint path/to/foo.js path/to/bar.py path/to/dir
+
+To force execution on a directory that would otherwise be excluded:
+
+.. parsed-literal::
+
+ ./mach lint -n path/in/the/exclude/list
+
+``Mozlint`` will automatically determine which types of files exist, and which linters need to be run
+against them. For example, if the directory contains both JavaScript and Python files then mozlint
+will automatically run both ESLint and Flake8 against those files respectively.
+
+To restrict which linters are invoked manually, pass in ``-l/--linter``:
+
+.. parsed-literal::
+
+ ./mach lint -l eslint path/to/files
+
+You can see a list of the available linters by running:
+
+.. parsed-literal::
+
+ ./mach lint --list
+
+Finally, ``mozlint`` can lint the files touched by outgoing revisions or the working directory using
+the ``-o/--outgoing`` and ``-w/--workdir`` arguments respectively. These work both with mercurial and
+git. In the case of ``--outgoing``, the default remote repository the changes would be pushed to is
+used as the comparison. If desired, a remote can be specified manually. In git, you may only want to
+lint staged commits from the working directory, this can be accomplished with ``--workdir=staged``.
+Examples:
+
+.. parsed-literal::
+
+ ./mach lint --workdir
+ ./mach lint --workdir=staged
+ ./mach lint --outgoing
+ ./mach lint --outgoing origin/master
+ ./mach lint -wo
+
+.. _lint-vcs-hook:
+
+Using a VCS Hook
+----------------
+
+There are also both pre-commit and pre-push version control hooks that work in
+either hg or git. To enable a pre-push hg hook, add the following to hgrc:
+
+.. parsed-literal::
+
+ [hooks]
+ pre-push.lint = python:./tools/lint/hooks.py:hg
+
+
+To enable a pre-commit hg hook, add the following to hgrc:
+
+.. parsed-literal::
+
+ [hooks]
+ pretxncommit.lint = python:./tools/lint/hooks.py:hg
+
+
+To enable a pre-push git hook, run the following command:
+
+.. parsed-literal::
+
+ $ ln -s /path/to/gecko/tools/lint/hooks.py .git/hooks/pre-push
+
+
+To enable a pre-commit git hook, run the following command:
+
+.. parsed-literal::
+
+ $ ln -s /path/to/gecko/tools/lint/hooks.py .git/hooks/pre-commit
+
+
+Fixing Lint Errors
+==================
+
+``Mozlint`` has a best-effort ability to fix lint errors:
+
+.. parsed-literal::
+
+ $ ./mach lint --fix
+
+Not all linters support fixing, and even the ones that do can not usually fix
+all types of errors. Any errors that cannot be automatically fixed, will be
+printed to stdout like normal. In that case, you can also fix errors manually:
+
+.. parsed-literal::
+
+ $ ./mach lint --edit
+
+This requires the $EDITOR environment variable be defined. For most editors,
+this will simply open each file containing errors one at a time. For vim (or
+neovim), this will populate the `quickfix list`_ with the errors.
+
+The ``--fix`` and ``--edit`` arguments can be combined, in which case any
+errors that can be fixed automatically will be, and the rest will be opened
+with your $EDITOR.
+
+Editor Integration
+==================
+
+Editor integrations are highly recommended for linters, as they let you see
+errors in real time, and can help you fix issues before you compile or run tests.
+
+Although mozilla-central does not currently have an integration available for
+`./mach lint`, there are various integrations available for some of the major
+linting tools that we use:
+
+* `ESLint`_
+* `Black (Python)`_
+
+.. _quickfix list: http://vimdoc.sourceforge.net/htmldoc/quickfix.html
+.. _ESLint: https://eslint.org/docs/user-guide/integrations#editors
+.. _Black (Python): https://black.readthedocs.io/en/stable/editor_integration.html
diff --git a/docs/code-quality/static-analysis/existing.rst b/docs/code-quality/static-analysis/existing.rst
new file mode 100644
index 0000000000..81ed79a54f
--- /dev/null
+++ b/docs/code-quality/static-analysis/existing.rst
@@ -0,0 +1,245 @@
+Existing Infrastructure and Analysis
+====================================
+
+This document is about how Static Analysis occurs at Mozilla: the Firefox-specific and general llvm clang-tidy checks that are run on submissions in Phabricator and how to run them locally. For information about how to develop your own static analysis checks, please see `Writing New Firefox-Specific Checks </code-quality/static-analysis/writing-new/>`_.
+
+For linting, please see the `linting documentation </code-quality/lint/>`_.
+
+For reviews, use the `#static-analysis-reviewers review group <https://phabricator.services.mozilla.com/project/view/120/>`__.
+Ask questions on `#static-analysis:mozilla.org <https://chat.mozilla.org/#/room/#static-analysis:mozilla.org>`__.
+
+
+Clang-Tidy static analysis
+--------------------------
+
+As explained earlier, our current static-analysis infrastructure is based on
+`clang-tidy <http://clang.llvm.org/extra/clang-tidy/>`__. The checkers that
+we use are split into 3 categories:
+
+#. :searchfox:`Firefox specific checkers <build/clang-plugin>`. They detect incorrect Gecko programming
+ patterns which could lead to bugs or security issues.
+#. `Clang-tidy checkers <https://clang.llvm.org/extra/clang-tidy/checks/list.html>`_. They aim to suggest better programming practices
+ and to improve memory efficiency and performance.
+#. `Clang-analyzer checkers <https://clang-analyzer.llvm.org/>`_. These checks are more advanced, for example
+ some of them can detect dead code or memory leaks, but as a typical
+ side effect they have false positives. Because of that, we have
+ disabled them for now, but will enable some of them in the near
+ future.
+
+In order to simplify the process of static-analysis we have focused on
+integrating this process with Phabricator and mach. A list of some
+checkers that are used during automated scan can be found
+:searchfox:`here <tools/clang-tidy/config.yaml>`.
+
+Static analysis at review phase
+-------------------------------
+
+We created a TaskCluster bot that runs clang static analysis on every
+patch submitted to Phabricator. It then quickly reports any code defects
+directly on the review platform, thus preventing bad patches from
+landing until all their defects are fixed. Currently, its feedback is
+posted in about 10 minutes after a patch series is published on the
+review platform.
+
+As part of the process, the various linting jobs are also executed
+using try. This can be also used to add new jobs, see: :ref:`attach-job-review`.
+An example of automated review can be found `on
+phabricator <https://phabricator.services.mozilla.com/D2066>`__.
+
+
+./mach static-analysis
+----------------------
+
+The ``./mach static-analysis`` command is supported on all Firefox built platforms. During the first run it
+automatically installs all of its dependencies, such as the clang-tidy
+executable, in the .mozbuild folder thus making it very easy to use. The
+resources that are used are provided by toolchain artifacts clang-tidy
+target.
+
+This is used through ``mach static-analysis`` command that has the
+following parameters:
+
+- ``check`` - Runs the checks using the installed helper tool from
+ ~/.mozbuild.
+- ``--checks, -c`` - Checks to enabled during the scan. The checks
+ enabled
+ :searchfox:`in the yaml file <tools/clang-tidy/config.yaml>`
+ are used by default.
+- ``--fix, -f`` - Try to autofix errors detected by the checkers.
+ Depending on the checker, this option might not do anything.
+ The list of checkers with autofix can be found on the `clang-tidy website <https://clang.llvm.org/extra/clang-tidy/checks/list.html>`__.
+- ``--header-filter, -h-f`` - Regular expression matching the names of
+ the headers to output diagnostic from.Diagnostic from the main file
+ of each translation unit are always displayed.
+
+As an example we run static-analysis through mach on
+``dom/presentation/Presentation.cpp`` with
+``google-readability-braces-around-statements`` check and autofix we
+would have:
+
+.. code-block:: shell
+
+ ./mach static-analysis check --checks="-*, google-readability-braces-around-statements" --fix dom/presentation/Presentation.cpp
+
+If you want to use a custom clang-tidy binary this can be done by using
+the ``install`` subcommand of ``mach static-analysis``, but please note
+that the archive that is going to be used must be compatible with the
+directory structure clang-tidy from toolchain artifacts.
+
+.. code-block:: shell
+
+ ./mach static-analysis install clang.tar.gz
+
+
+Regression Testing
+------------------
+
+In order to prevent regressions in our clang-tidy based static analysis,
+we have created a
+:searchfox:`task <taskcluster/ci/static-analysis-autotest/kind.yml>`
+on automation. This task runs on each commit and launches a test suite
+that is integrated into mach.
+
+The test suite implements the following:
+
+- Downloads the necessary clang-tidy artifacts.
+- Reads the
+ :searchfox:`configuration <tools/clang-tidy/config.yaml>`
+ file.
+- For each checker reads the test file plus the expected result. A
+ sample of test and expected result can be found
+ :searchfox:`in the test file <tools/clang-tidy/test/clang-analyzer-deadcode.DeadStores.cpp>`
+ and
+ :searchfox:`the json file <tools/clang-tidy/test/clang-analyzer-deadcode.DeadStores.json>`.
+
+This testing suit can be run locally by doing the following:
+
+.. code-block:: shell
+
+ ./mach static-analysis autotest
+
+If we want to test only a specific checker, let's say
+modernize-raw-string-literal, we can run:
+
+.. code-block:: shell
+
+ ./mach static-analysis autotest modernize-raw-string-literal
+
+If we want to add a new checker we need to generated the expected result
+file, by doing:
+
+.. code-block:: shell
+
+ ./mach static-analysis autotest modernize-raw-string-literal -d
+
+
+Build-time static-analysis
+--------------------------
+
+If you want to build with the Firefox Clang plug-in
+(located in ``/build/clang-plugin`` and associated with
+``MOZ_CLANG_PLUGIN`` and the attributes in ``/mfbt/Attributes.h``)
+just add ``--enable-clang-plugin`` to your mozconfig!
+If you want to also have our experimental checkers that will produce ``warnings`` as
+diagnostic messages also add ``--enable-clang-plugin-alpha``.
+This requires to build Firefox using Clang.
+
+Configuring the build environment
+---------------------------------
+
+Once you have your Clang build in place, you will need to set up tools
+to use it.
+A full working .mozconfig for the desktop browser is:
+
+.. code-block:: shell
+
+ . $topsrcdir/browser/config/mozconfig
+ mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-ff-dbg
+
+ ac_add_options --enable-debug
+
+Attempts to use ``ccache`` will likely result in failure to compile. It
+is also necessary to avoid optimized builds, as these will modify macros
+which will result in many false positives.
+
+At this point, your Firefox build environment should be configured to
+compile via the Clang static analyzer!
+
+
+Performing scanning builds
+--------------------------
+
+It is not enough to simply start the build like normal. Instead, you
+need to run the build through a Clang utility script which will keep
+track of all produced analysis and consolidate it automatically.
+
+Reports are published daily on
+`https://sylvestre.ledru.info/reports/fx-scan-build/ <http://sylvestre.ledru.info/reports/fx-scan-build/>`__
+Many of the defects reported as sources for Good First Bug.
+
+That script is scan-build. You can find it in
+``$clang_source/tools/scan-build/scan-build``.
+
+Try running your build through ``scan-build``:
+
+.. code-block:: shell
+
+ $ cd /path/to/mozilla/source
+
+ # Blow away your object directory because incremental builds don't make sense
+ $ rm -rf obj-dir
+
+ # To start the build:
+ scan-build --show-description ./mach build -v
+
+ # The above should execute without any errors. However, it should take longer than
+ # normal because all compilation will be executing through Clang's static analyzer,
+ # which adds overhead.
+
+If things are working properly, you should see a bunch of console spew,
+just like any build.
+
+The first time you run scan-build, CTRL+C after a few files are
+compiled. You should see output like:
+
+.. code-block:: shell
+
+ scan-build: 3 bugs found.
+ scan-build: Run 'scan-view /Users/gps/tmp/mcsb/2011-12-15-3' to examine bug reports.
+
+If you see a message like:
+
+.. code-block:: shell
+
+ scan-build: Removing directory '/var/folders/s2/zc78dpsx2rz6cpc_21r9g5hr0000gn/T/scan-build-2011-12-15-1' because it contains no reports.
+
+either no static analysis results were available yet or your environment
+is not configured properly.
+
+By default, ``scan-build`` writes results to a folder in a
+pseudo-temporary location. You can control where results go by passing
+the ``-o /path/to/output`` arguments to ``scan-build``.
+
+You may also want to run ``scan-build --help`` to see all the options
+available. For example, it is possible to selectively enable and disable
+individual analyzers.
+
+
+Analyzing the output
+--------------------
+
+Once the build has completed, ``scan-build`` will produce a report
+summarizing all the findings. This is called ``index.html`` in the
+output directory. You can run ``scan-view`` (from
+``$clang_source/tools/scan-view/scan-view``) as ``scan-build's`` output
+suggests; this merely fires up a local HTTP server. Or you should be
+able to open the ``index.html`` directly with your browser.
+
+
+False positives
+---------------
+
+By definition, there are currently false positives in the static
+analyzer. A lot of these are due to the analyzer having difficulties
+following the relatively complicated error handling in various
+preprocessor macros.
diff --git a/docs/code-quality/static-analysis/index.rst b/docs/code-quality/static-analysis/index.rst
new file mode 100644
index 0000000000..595eab363d
--- /dev/null
+++ b/docs/code-quality/static-analysis/index.rst
@@ -0,0 +1,30 @@
+Static Analysis
+===============
+
+Static Analysis is running an analysis of the source code without actually executing the code. For the most part, at Mozilla static analysis refers to the stuff we do with `clang-tidy <http://clang.llvm.org/extra/clang-tidy/>`__. It uses
+checkers in order to prevent different programming errors present in the
+code. The checkers that we use are split into 3 categories:
+
+#. :searchfox:`Firefox specific checkers <build/clang-plugin>`. They detect incorrect Gecko programming
+ patterns which could lead to bugs or security issues.
+#. `Clang-tidy checkers <https://clang.llvm.org/extra/clang-tidy/checks/list.html>`_. They aim to suggest better programming practices
+ and to improve memory efficiency and performance.
+#. `Clang-analyzer checkers <https://clang-analyzer.llvm.org/>`_. These checks are more advanced, for example
+ some of them can detect dead code or memory leaks, but as a typical
+ side effect they have false positives. Because of that, we have
+ disabled them for now, but will enable some of them in the near
+ future.
+
+In order to simplify the process of static-analysis we have focused on
+integrating this process with Phabricator and mach. A list of some
+checkers that are used during automated scan can be found
+:searchfox:`here <tools/clang-tidy/config.yaml>`.
+
+This documentation is split into two parts:
+
+.. toctree::
+ :maxdepth: 1
+ :glob:
+
+ existing.rst
+ writing-new/index.rst
diff --git a/docs/code-quality/static-analysis/writing-new/adding-a-check.rst b/docs/code-quality/static-analysis/writing-new/adding-a-check.rst
new file mode 100644
index 0000000000..ec3bc030af
--- /dev/null
+++ b/docs/code-quality/static-analysis/writing-new/adding-a-check.rst
@@ -0,0 +1,107 @@
+.. _add_a_check:
+
+Adding a check
+==============
+
+After you've completed a matcher using clang-query, it's time to take it to the next step and turn it into C++ and run it on the whole m-c codebase and see what happens.
+
+Clang plugins live in `build/clang-plugin <https://searchfox.org/mozilla-central/source/build/clang-plugin>`_ and here we'll cover what is needed to add one. To see how the most recent check was added, you can look at the log for `Checks.inc <https://hg.mozilla.org/mozilla-central/log/tip/build/clang-plugin/Checks.inc>`_ which is one of the necessary files to edit. That's also what we'll be covering next.
+
+Boilerplate Steps to Add a New Check
+------------------------------------
+
+First pick a name. Pick something that makes sense without punctuation, in no more than 8 words or so. For this example we'll call it "Missing Else In Enum Comparisons".
+
+#. Add it alphabetically in build/clang-plugin/Checks.inc, ChecksIncludes.inc, and moz.build
+#. ``cd build/clang-plugin && touch MissingElseInEnumComparisons.h MissingElseInEnumComparisons.cpp``
+#. Copy the contents of an existing, simple .h file (e.g. `build/clang-plugin/ScopeChecker.h <https://searchfox.org/mozilla-central/source/build/clang-plugin/ScopeChecker.h>`_) and edit the class name and header guards.
+#. Create the following boilerplate for your implementation:
+
+::
+
+ /* 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 "MissingElseInEnumComparisons.h"
+ #include "CustomMatchers.h"
+
+ void MissingElseInEnumComparisons::registerMatchers(MatchFinder *AstMatcher) {
+
+ }
+
+ void MissingElseInEnumComparisons::check(const MatchFinder::MatchResult &Result) {
+
+ }
+
+
+Converting your matcher to C++
+------------------------------
+With the boilerplate out of the way, now we can focus on converting the matcher over to C++. Once it's in C++ you'll also be able to take advantage of techniques that will make your matcher easier to read and understand.
+
+The gist of converting your matcher is to take the following pseudo-code and paste your entire matcher in where 'foo' is; keeping the `.bind("node")` there:
+
+::
+
+ AstMatcher->addMatcher(
+ traverse(TK_IgnoreUnlessSpelledInSource,
+ foo
+ .bind("node")),
+ this);
+
+
+It honest to god is usually that easy. Here's a working example where I pasted in straight from Compiler Explorer:
+
+::
+
+ AstMatcher->addMatcher(
+ traverse(TK_IgnoreUnlessSpelledInSource,
+ ifStmt(allOf(
+ has(
+ binaryOperator(
+ has(
+ declRefExpr(hasType(enumDecl().bind("enum")))
+ )
+ )
+ ),
+ hasElse(
+ ifStmt(allOf(
+ unless(hasElse(anything())),
+ has(
+ binaryOperator(
+ has(
+ declRefExpr(hasType(enumDecl()))
+ )
+ )
+ )
+ ))
+ )
+ ))
+ .bind("node")),
+ this);
+
+
+
+If for some reason you're not using the ``IgnoreUnlessSpelledInSource`` Traversal Mode, remove the call to traverse and the corresponding closing paren. (Also, if you're comparing this code to existing source code, know that because this traversal mode is a new clang feature, most historical clang checks do not use it.)
+
+Wiring up Warnings and Errors
+-----------------------------
+To get started with a some simple output, just take the boilerplate warning here and stick it in:
+
+::
+
+ const auto *MatchedDecl = Result.Nodes.getNodeAs<IfStmt>("node");
+ diag(MatchedDecl->getIfLoc(),
+ "Enum comparisons in an if/else if block without a trailing else.",
+ DiagnosticIDs::Warning);
+
+
+You'll need to edit two things:
+
+#. Make sure "node" matches whatever you put in `.bind()` up above.
+#. ``getNodeAs<IfStmt>`` needs to be changed to whatever type of element "node" is. Above, we bind "node" to an ifStmt so that's what we need to cast it to. Doing this step incorrectly will cause clang to crash during compilation as if there was some internal compiler error.
+
+
+Running it on Central
+----------------------
+After this, the next thing to do is to add ``ac_add_options --enable-clang-plugin`` to your .mozconfig and do a build. Your plugin will be automatically compiled and used across the entire codebase. I suggest using ``./mach build | tee output.txt`` and then ``grep "Enum comparisons" output.txt | cut -d " " -f 3- | sort | uniq``. (The ``cut`` is there to get rid of the timestamp in the line.)
diff --git a/docs/code-quality/static-analysis/writing-new/advanced-check-features.rst b/docs/code-quality/static-analysis/writing-new/advanced-check-features.rst
new file mode 100644
index 0000000000..e8adcf664f
--- /dev/null
+++ b/docs/code-quality/static-analysis/writing-new/advanced-check-features.rst
@@ -0,0 +1,148 @@
+.. _advanced_check_features:
+
+Advanced Check Features
+=======================
+
+This page covers additional ways to improve and extend the check you've added to build/clang-plugin.
+
+Adding Tests
+------------
+
+No doubt you've seen the tests for existing checks in `build/clang-plugin/tests <https://searchfox.org/mozilla-central/source/build/clang-plugin/tests>`_. Adding tests is straightforward; and your reviewer should insist you do so. Simply copying the existing format of any test and how diagnostics are marked as expected.
+
+One wrinkle - all clang plugin checks are applied to all tests. We try to write tests so that only one check applies to it. If you write a check that triggers on an existing test, try to fix the existing test slightly so the new check does not trigger on it.
+
+Using Bind To Output More Useful Information
+--------------------------------------------
+
+You've probably been wondering what the heck ``.bind()`` is for. You've been seeing it all over the place but never has it actually been explained what it's for and when to use it.
+
+``.bind()`` is used to give a name to part of the AST discovered through your matcher, so you can use it later. Let's go back to our sample matcher:
+
+::
+
+ AstMatcher->addMatcher(
+ traverse(TK_IgnoreUnlessSpelledInSource,
+ ifStmt(allOf(
+ has(
+ binaryOperator(
+ has(
+ declRefExpr(hasType(enumDecl()))
+ )
+ )
+ ),
+ hasElse(
+ ifStmt(allOf(
+ unless(hasElse(anything())),
+ has(
+ binaryOperator(
+ has(
+ declRefExpr(hasType(enumDecl()))
+ )
+ )
+ )
+ ))
+ )
+ ))
+ .bind("node")),
+ this);
+
+Now the ``.bind("node")`` makes more sense. We're naming the If statement we matched, so we can refer to it later when we call ``Result.Nodes.getNodeAs<IfStmt>("node")``.
+
+Let's say we want to provide the *type* of the enum in our warning message. There are two enums we end up seeing in our matcher - the enum in the first if statement, and the enum in the second. We're going to arbitrarily pick the first and give it the name ``enumType``:
+
+::
+
+ AstMatcher->addMatcher(
+ traverse(TK_IgnoreUnlessSpelledInSource,
+ ifStmt(allOf(
+ has(
+ binaryOperator(
+ has(
+ declRefExpr(hasType(enumDecl().bind("enumType")))
+ )
+ )
+ ),
+ hasElse(
+ ifStmt(allOf(
+ unless(hasElse(anything())),
+ has(
+ binaryOperator(
+ has(
+ declRefExpr(hasType(enumDecl()))
+ )
+ )
+ )
+ ))
+ )
+ ))
+ .bind("node")),
+ this);
+
+And in our check() function, we can use it like so:
+
+::
+
+ void MissingElseInEnumComparisons::check(
+ const MatchFinder::MatchResult &Result) {
+ const auto *MatchedDecl = Result.Nodes.getNodeAs<IfStmt>("node");
+ const auto *EnumType = Result.Nodes.getNodeAs<EnumDecl>("enumType");
+
+ diag(MatchedDecl->getIfLoc(),
+ "Enum comparisons to %0 in an if/else if block without a trailing else.",
+ DiagnosticIDs::Warning) << EnumType->getName();
+ }
+
+Repeated matcher calls
+--------------------------
+
+If you find yourself repeating the same several matchers in several spots, you can turn it into a variable to use.
+
+::
+
+ auto isTemporaryLifetimeBoundCall =
+ cxxMemberCallExpr(
+ onImplicitObjectArgument(anyOf(has(cxxTemporaryObjectExpr()),
+ has(materializeTemporaryExpr()))),
+ callee(functionDecl(isMozTemporaryLifetimeBound())));
+
+ auto hasTemporaryLifetimeBoundCall =
+ anyOf(isTemporaryLifetimeBoundCall,
+ conditionalOperator(
+ anyOf(hasFalseExpression(isTemporaryLifetimeBoundCall),
+ hasTrueExpression(isTemporaryLifetimeBoundCall))));
+
+The above example is parameter-less, but if you need to supply a parameter that changes, you can turn it into a lambda:
+
+::
+
+ auto hasConstCharPtrParam = [](const unsigned int Position) {
+ return hasParameter(
+ Position, hasType(hasCanonicalType(pointsTo(asString("const char")))));
+ };
+
+ auto hasParamOfType = [](const unsigned int Position, const char *Name) {
+ return hasParameter(Position, hasType(asString(Name)));
+ };
+
+ auto hasIntegerParam = [](const unsigned int Position) {
+ return hasParameter(Position, hasType(isInteger()));
+ };
+
+ AstMatcher->addMatcher(
+ callExpr(
+ hasName("fopen"),
+ hasConstCharPtrParam(0))
+ .bind("funcCall"),
+ this);
+
+
+Allow-listing existing callsites
+--------------------------------
+
+While it's not a great situation, you can set up an allow-list of existing callsites if you need to. A simple allow-list is demonstrated in `NoGetPrincipalURI <https://hg.mozilla.org/mozilla-central/rev/fb60b22ee6616521b386d90aec07b03b77905f4e>`_. The `NoNewThreadsChecker <https://hg.mozilla.org/mozilla-central/rev/f400f164b3947b4dd54089a36ea31cca2d72805b>`_ is an example of a more sophisticated way of setting up a larger allow-list.
+
+
+Custom Annotations
+------------------
+It's possible to create custom annotations that will be a no-op when compiled, but can be used by a static analysis check. These can be used to annotate special types of sources and sinks (for example). We have some examples of this in-tree presently (such as ``MOZ_CAN_RUN_SCRIPT``) but currently don't have a detailed walkthrough in this documentation of how to set these up and use them. (Patches welcome.)
diff --git a/docs/code-quality/static-analysis/writing-new/clang-query.rst b/docs/code-quality/static-analysis/writing-new/clang-query.rst
new file mode 100644
index 0000000000..1308a32821
--- /dev/null
+++ b/docs/code-quality/static-analysis/writing-new/clang-query.rst
@@ -0,0 +1,167 @@
+.. _using_clang_query:
+
+Using clang-query
+=================
+
+clang-query is a tool that allows you to quickly iterate and develop the difficult part of a matcher.
+Once the design of the matcher is completed, it can be transferred to a C++ clang-tidy plugin, `similar
+to the ones in mozilla-central <https://searchfox.org/mozilla-central/source/build/clang-plugin>`_.
+
+Recommended Boilerplate
+-----------------------
+
+::
+
+ set traversal IgnoreUnlessSpelledInSource
+ set bind-root true
+ # ^ true unless you use any .bind("foo") commands
+ set print-matcher true
+ enable output dump
+
+
+clang-query Options
+-------------------
+
+set traversal
+~~~~~~~~~~~~~
+
+`Traversal mode <https://clang.llvm.org/docs/LibASTMatchersReference.html#traverse-mode>`_ specifies how the AST Matcher will traverse the nodes in the Abstract Syntax Tree. There are two values:
+
+AsIs
+ This mode notes all the nodes in the AST, even if they are not explicitly spelled out in the source. This will include nodes you have never seen and probably don't immediately understand, for example ``ExprWithCleanups`` and ``MaterializeTemporaryExpr``. In this mode, it is necessary to write matchers that expliticly match or otherwise traverse these potentially unexpected nodes.
+
+IgnoreUnlessSpelledInSource
+ This mode skips over 'implicit' nodes that are created as a result of implicit casts or other usually-low-level language details. This is typically much more user-friendly. **Typically you would want to use** ``set traversal IgnoreUnlessSpelledInSource``.
+
+More examples are available `in the documentation <https://clang.llvm.org/docs/LibASTMatchersReference.html#traverse-mode>`_, but here is a simple example:
+
+::
+
+ B func1() {
+ return 42;
+ }
+
+ /*
+ AST Dump in 'Asis' mode for C++17/C++20 dialect:
+
+ FunctionDecl
+ `-CompoundStmt
+ `-ReturnStmt
+ `-ImplicitCastExpr
+ `-CXXConstructExpr
+ `-IntegerLiteral 'int' 42
+
+ AST Dump in 'IgnoreUnlessSpelledInSource' mode for all dialects:
+
+ FunctionDecl
+ `-CompoundStmt
+ `-ReturnStmt
+ `-IntegerLiteral 'int' 42
+ */
+
+
+set bind-root
+~~~~~~~~~~~~~
+
+If you are matching objects and assigning them names for later use, this option may be relevant. If you are debugging a single matcher and not using any ``.bind()``, it is irrelevant.
+
+Consider the output of ``match functionDecl().bind("x")``:
+
+::
+
+ clang-query> match functionDecl().bind("x")
+
+ Match #1:
+
+ testfile.cpp:1:1: note: "root" binds here
+ int addTwo(int num)
+ ^~~~~~~~~~~~~~~~~~~
+ testfile.cpp:1:1: note: "x" binds here
+ int addTwo(int num)
+ ^~~~~~~~~~~~~~~~~~~
+
+ Match #2:
+
+ testfile.cpp:6:1: note: "root" binds here
+ int main(int, char**)
+ ^~~~~~~~~~~~~~~~~~~~~
+ testfile.cpp:6:1: note: "x" binds here
+ int main(int, char**)
+ ^~~~~~~~~~~~~~~~~~~~~
+ 2 matches.
+
+
+clang-query automatically binds ``root`` to the match, but we also bound the name ``x`` to that match. The ``root`` is redundant. If you ``set bind-root false`` then the output is less noisy:
+
+::
+
+ clang-query> set bind-root false
+ clang-query> m functionDecl().bind("x")
+
+ Match #1:
+
+ testfile.cpp:1:1: note: "x" binds here
+ int addtwo(int num)
+ ^~~~~~~~~~~~~~~~~~~
+
+ Match #2:
+
+ testfile.cpp:6:1: note: "x" binds here
+ int main(int, char**)
+ ^~~~~~~~~~~~~~~~~~~~~
+ 2 matches.
+
+
+set print-matcher
+~~~~~~~~~~~~~~~~~
+
+``set print-matcher true`` will print a header line of the form 'Matcher: <foo>' where foo is the matcher you have written. It is helpful when debugging multiple matchers at the same time, and no inconvience otherwise.
+
+enable/disable/set output <foo>
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+These commands will control the type of output you get from clang-query. The options are:
+
+``print``
+ Shows you the C++ form of the node you are matching. This is typically not useful.
+
+``diag``
+ Shows you the individual node you are matching.
+
+``dump`` (alias: ``detailed-ast``)
+ Shows you the node you are matching and the entire subtree for the node
+
+By default, you get ``diag`` output. You can change the output by choosing ``set output``. You can *add* output by using ``enable output``. You can *disable* output using ``disable output`` but this is typically not needed.
+
+So if you want to get all three output formats you can do:
+
+::
+
+ # diag output happens automatically because you did not override with 'set'
+ enable output print
+ enable output dump
+
+
+Patches
+-------
+
+This section tracks some patches; they are currently not used, but we may want them in the future.
+
+- Functionality:
+
+ - `traverse() operator available to clang-query <https://reviews.llvm.org/D80654>`_
+ - `srclog output <https://reviews.llvm.org/D93325>`_
+ - `allow anyOf() to be empty <https://reviews.llvm.org/D94126>`_
+ - breakpoints
+ - debug
+ - profile
+
+- Matcher Changes:
+
+ - `binaryOperation() matcher <https://reviews.llvm.org/D94129>`_
+
+- Plumbing:
+
+ - `mapAnyOf() <https://reviews.llvm.org/D94127>`_ (`Example of usage <https://reviews.llvm.org/D94131>`_)
+ - `Make cxxOperatorCallExpr matchers API-compatible with n-ary operators <https://reviews.llvm.org/D94128>`_
+ - `CXXRewrittenBinaryOperator <https://reviews.llvm.org/D94130>`_
diff --git a/docs/code-quality/static-analysis/writing-new/documentation-expanded.png b/docs/code-quality/static-analysis/writing-new/documentation-expanded.png
new file mode 100644
index 0000000000..82f12a516d
--- /dev/null
+++ b/docs/code-quality/static-analysis/writing-new/documentation-expanded.png
Binary files differ
diff --git a/docs/code-quality/static-analysis/writing-new/index.rst b/docs/code-quality/static-analysis/writing-new/index.rst
new file mode 100644
index 0000000000..9107fb1b59
--- /dev/null
+++ b/docs/code-quality/static-analysis/writing-new/index.rst
@@ -0,0 +1,14 @@
+Writing New Firefox-Specific Static Analysis Checks
+===================================================
+
+This section is intended to help Mozilla engineers either casually play with writing a static analysis check
+or seriously develop one we can land and run internally. While being written for internal consumption, it's broadly applicable outside Mozilla.
+
+.. toctree::
+ :maxdepth: 2
+
+ clang-query.rst
+ writing-matchers.rst
+ matcher-cookbook.rst
+ adding-a-check.rst
+ advanced-check-features.rst
diff --git a/docs/code-quality/static-analysis/writing-new/matcher-cookbook.rst b/docs/code-quality/static-analysis/writing-new/matcher-cookbook.rst
new file mode 100644
index 0000000000..9eb0d96c43
--- /dev/null
+++ b/docs/code-quality/static-analysis/writing-new/matcher-cookbook.rst
@@ -0,0 +1,23 @@
+.. _matcher_cookbook:
+
+Matcher Cookbook
+=================
+
+This page is designed to be a selection of common ingredients to a more complicated matcher.
+
+.. list-table::
+ :widths: 35 65
+ :header-rows: 1
+ :class: matcher-cookbook
+
+ * - Desired Outcome
+ - Syntax
+ * - Ignore header files
+
+ *If you have an #include in your example code, your matcher may match things in the header files.*
+ - Add **isExpansionInMainFile()** to the matcher. e.g.
+
+ ``m functionDecl(isExpansionInMainFile())``
+
+
+*More coming*
diff --git a/docs/code-quality/static-analysis/writing-new/narrowing-matcher.png b/docs/code-quality/static-analysis/writing-new/narrowing-matcher.png
new file mode 100644
index 0000000000..52c82791d3
--- /dev/null
+++ b/docs/code-quality/static-analysis/writing-new/narrowing-matcher.png
Binary files differ
diff --git a/docs/code-quality/static-analysis/writing-new/narrowing-matcher.xcf b/docs/code-quality/static-analysis/writing-new/narrowing-matcher.xcf
new file mode 100644
index 0000000000..0102f79e76
--- /dev/null
+++ b/docs/code-quality/static-analysis/writing-new/narrowing-matcher.xcf
Binary files differ
diff --git a/docs/code-quality/static-analysis/writing-new/writing-matchers.rst b/docs/code-quality/static-analysis/writing-new/writing-matchers.rst
new file mode 100644
index 0000000000..5b693f5f27
--- /dev/null
+++ b/docs/code-quality/static-analysis/writing-new/writing-matchers.rst
@@ -0,0 +1,199 @@
+.. _writing_matchers:
+
+Writing Matchers
+================
+
+On this page we will give some information about what a matcher is, and then provide an example of developing a simple match iteratively.
+
+Types of Matchers
+-----------------
+
+There are three types of matches: Node, Narrowing, and Traversal. There isn't always a clear separation or distinction between them, so treat this explanation as illustrative rather than definitive. Here is the documentation on matchers: `https://clang.llvm.org/docs/LibASTMatchersReference.html <https://clang.llvm.org/docs/LibASTMatchersReference.html>`_
+
+On that page it is not obvious, so we want to note, **cicking on the name of a matcher expands help about that matcher.** Example:
+
+.. image:: documentation-expanded.png
+
+Node Matchers
+~~~~~~~~~~~~~
+
+Node matchers can be thought of as 'Nouns'. They specify a **type** of node you want to match, that is, a particular *thing*. A function, a binary operation, a variable, a type.
+
+A full list of `node matchers are listed in the documentation <https://clang.llvm.org/docs/LibASTMatchersReference.html#node-matchers>`_. Some common ones are ``functionDecl()``, ``binaryOperator()``, and ``stmt()``.
+
+Narrowing Matchers
+~~~~~~~~~~~~~~~~~~
+
+Narrowing matchers can be thought of as 'Adjectives'. They narrow, or describe, a node, and therefore must be applied to a Node Matcher. For instance a node matcher may be a ``functionDecl``, and the narrowing matcher applied to it may be ``parameterCountIs``.
+
+The `table in the documentation <https://clang.llvm.org/docs/LibASTMatchersReference.html#narrowing-matchers>`_ lists all the narrowing matchers, which they apply to and how to use them. Here is how to read the table:
+
+.. image:: narrowing-matcher.png
+
+And some examples:
+
+::
+
+ m functionDecl(parameterCountIs(1))
+ m functionDecl(anyOf(isDefinition(), isVariadic()))
+
+
+As you can see **only one Narrowing Matcher is allowed** and it goes inside the parens of the Node Matcher. In the first example, the matcher is ``parameterCountIs``, in the second it is ``anyOf``.
+
+In the second, we use the singular ``anyOf`` matcher to match any of multiple other Narrowing Matchers: ``isDefinition`` or ``isVariadic``. The other two common combining narrowing matchers are ``allOf()`` and ``unless()``.
+
+If you *need* to specify a narrowing matcher (because it's a required argument to some other matcher), you can use the ``anything()`` narrowing matcher to have a no-op narrowing matcher.
+
+Traversal Matchers
+~~~~~~~~~~~~~~~~~~
+
+Traversal Matchers *also* can be thought of as adjectives - at least most of them. They also describe a specific node, but the difference from a narrowing matcher is that the scope of the description is broader than the individual node. A narrowing matcher says something about the node in isolation (e.g. the number of arguments it has) while a traversal matcher says something about the node's contents or place in the program.
+
+Again, the `the documentation <https://clang.llvm.org/docs/LibASTMatchersReference.html#traversal-matchers>`_ is the best place to explore and understand these, but here is a simple example for the traversal matcher ``hasArraySize()``:
+
+::
+
+ Given:
+ class MyClass { };
+ MyClass *p1 = new MyClass[10];
+
+
+ cxxNewExpr()
+ matches the expression 'new MyClass[10]'.
+
+ cxxNewExpr(hasArraySize(integerLiteral(equals(9))))
+ does not match anything
+
+ cxxNewExpr(hasArraySize(integerLiteral(equals(10))))
+ matches the expression 'new MyClass[10]'.
+
+
+
+Example of Iterative Matcher Development
+----------------------------------------
+
+When developing matchers, it will be much easier if you do the following:
+
+1. Write out the code you want to match. Write it out in as many different ways as you can. Examples: For some value in the code use a variable, a constant and a function that returns a value. Put the code you want to match inside of a function, inside of a conditional, inside of a function call, and inside of an inline function definition.
+2. Write out the code you *don't* want to match, but looks like code you do. Write out benign function calls, benign assignments, etc.
+3. Iterate on your matcher and treat it as _code_ you're writing. Indent it, copy it somewhere in case your browser crashes, even stick it in a tiny temporary version-controlled file.
+
+As an example of the above, below is a sample iterative development process of a more complicated matcher.
+
+ **Goal**: Match function calls where one of the parameters is an assignment expression with an integer literal, but the function parameter has a default value in the function definition.
+
+::
+
+ int add1(int a, int b) { return a + b; }
+ int add2(int c, int d = 8) { return c + d; }
+
+ int main() {
+ int x, y, z;
+
+ add1(x, y); // <- No match, no assignment
+ add1(3 + 4, y); // <- No match, no assignment
+ add1(z = x, y); // <- No match, assignment, but not an integer literal
+ add1(z = 2, y); // <- No match, assignment, integer literal, but function parameter lacks default value
+ add2(3, z = 2); // <- Match
+ }
+
+
+Here is the iterative development process:
+
+::
+
+ //-------------------------------------
+ // Step 1: Find all the function calls
+ m callExpr()
+ // Matches all calls, as expected.
+
+ //-------------------------------------
+ // Step 2: Start refining based on the arguments to the call
+ m callExpr(forEachArgumentWithParam()))
+ // Error: forEachArgumentWithParam expects two parameters
+
+ //-------------------------------------
+ // Step 3: Figure out the syntax to matching all the calls with this new operator
+ m callExpr(
+ forEachArgumentWithParam(
+ anything(),
+ anything()
+ )
+ )
+ // Matches all calls, as expected
+
+ //-------------------------------------
+ // Step 4: Find the calls with a binary operator of any kind
+ m callExpr(
+ forEachArgumentWithParam(
+ binaryOperator(),
+ anything()
+ )
+ )
+ // Does not match the first call, but matches the others
+
+ //-------------------------------------
+ // Step 5: Limit the binary operator to assignments
+ m callExpr(
+ forEachArgumentWithParam(
+ binaryOperator(isAssignmentOperator()),
+ anything()
+ )
+ )
+ // Now matches the final three calls
+
+ //-------------------------------------
+ // Step 6: Starting to refine matching the right-hand of the assignment
+ m callExpr(
+ forEachArgumentWithParam(
+ binaryOperator(
+ allOf(
+ isAssignmentOperator(),
+ hasRHS()
+ )),
+ anything()
+ )
+ )
+ // Error, hasRHS expects a parameter
+
+ //-------------------------------------
+ // Step 7:
+ m callExpr(
+ forEachArgumentWithParam(
+ binaryOperator(
+ allOf(
+ isAssignmentOperator(),
+ hasRHS(anything())
+ )),
+ anything()
+ )
+ )
+ // Okay, back to matching the final three calls
+
+ //-------------------------------------
+ // Step 8: Refine to just integer literals
+ m callExpr(
+ forEachArgumentWithParam(
+ binaryOperator(
+ allOf(
+ isAssignmentOperator(),
+ hasRHS(integerLiteral())
+ )),
+ anything()
+ )
+ )
+ // Now we match the final two calls
+
+ //-------------------------------------
+ // Step 9: Apply a restriction to the parameter definition
+ m callExpr(
+ forEachArgumentWithParam(
+ binaryOperator(
+ allOf(
+ isAssignmentOperator(),
+ hasRHS(integerLiteral())
+ )),
+ hasDefaultArgument()
+ )
+ )
+ // Now we match the final call
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000000..cf2205a7a6
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,152 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import sys
+
+# Set up Python environment to load build system packages.
+OUR_DIR = os.path.dirname(__file__)
+topsrcdir = os.path.normpath(os.path.join(OUR_DIR, ".."))
+
+# Escapes $, [, ] and 3 dots in copy button
+copybutton_prompt_text = r">>> |\.\.\. |\$ |In \[\d*\]: | {2,5}\.\.\.: | {5,8}: "
+copybutton_prompt_is_regexp = True
+
+EXTRA_PATHS = (
+ "layout/tools/reftest",
+ "python/mach",
+ "python/mozbuild",
+ "python/mozversioncontrol",
+ "testing/mozbase/manifestparser",
+ "testing/mozbase/mozfile",
+ "testing/mozbase/mozprocess",
+ "testing/mozbase/moznetwork/moznetwork",
+ "third_party/python/jsmin",
+ "third_party/python/which",
+ "docs/_addons",
+ "taskcluster/gecko_taskgraph/test",
+)
+
+sys.path[:0] = [os.path.join(topsrcdir, p) for p in EXTRA_PATHS]
+
+sys.path.insert(0, OUR_DIR)
+
+extensions = [
+ "myst_parser",
+ "sphinx.ext.autodoc",
+ "sphinx.ext.autosectionlabel",
+ "sphinx.ext.doctest",
+ "sphinx.ext.graphviz",
+ "sphinx.ext.napoleon",
+ "sphinx.ext.todo",
+ "mozbuild.sphinx",
+ "sphinx_js",
+ "sphinxcontrib.mermaid",
+ "sphinx_copybutton",
+ "sphinx_markdown_tables",
+ "sphinx_design",
+ "bzlink",
+]
+
+# JSDoc must run successfully for dirs specified, so running
+# tree-wide (the default) will not work currently.
+# When adding more paths to this list, please ensure that they are not
+# excluded from valid-jsdoc in the top-level .eslintrc.js.
+js_source_path = [
+ "../browser/components/extensions",
+ "../browser/components/migration",
+ "../browser/components/migration/content",
+ "../browser/components/uitour",
+ "../browser/components/urlbar",
+ "../remote/marionette",
+ "../testing/mochitest/BrowserTestUtils",
+ "../testing/mochitest/tests/SimpleTest/SimpleTest.js",
+ "../testing/mochitest/tests/SimpleTest/EventUtils.js",
+ "../testing/modules/Assert.sys.mjs",
+ "../testing/modules/TestUtils.sys.mjs",
+ "../toolkit/actors",
+ "../toolkit/components/extensions",
+ "../toolkit/components/extensions/parent",
+ "../toolkit/components/featuregates",
+ "../toolkit/mozapps/extensions",
+ "../toolkit/components/prompts/src",
+ "../toolkit/components/pictureinpicture",
+ "../toolkit/components/pictureinpicture/content",
+ "../toolkit/components/search",
+]
+root_for_relative_js_paths = ".."
+jsdoc_config_path = "jsdoc.json"
+
+templates_path = ["_templates"]
+source_suffix = [".rst", ".md"]
+master_doc = "index"
+project = "Firefox Source Docs"
+
+# Override the search box to use Google instead of
+# sphinx search on firefox-source-docs.mozilla.org
+if (
+ os.environ.get("MOZ_SOURCE_DOCS_USE_GOOGLE") == "1"
+ and os.environ.get("MOZ_SCM_LEVEL") == "3"
+):
+ templates_path.append("_search_template")
+
+html_sidebars = {
+ "**": [
+ "searchbox.html",
+ ]
+}
+html_logo = os.path.join(
+ topsrcdir, "browser/branding/nightly/content/firefox-wordmark.svg"
+)
+html_favicon = os.path.join(topsrcdir, "browser/branding/nightly/firefox.ico")
+
+exclude_patterns = ["_build", "_staging", "_venv", "**security/nss/legacy/**"]
+pygments_style = "sphinx"
+# generate label “slugs” for header anchors so that
+# we can reference them from markdown links.
+myst_heading_anchors = 5
+
+# We need to perform some adjustment of the settings and environment
+# when running on Read The Docs.
+on_rtd = os.environ.get("READTHEDOCS", None) == "True"
+
+if on_rtd:
+ # SHELL isn't set on RTD and mach.mixin.process's import raises if a
+ # shell-related environment variable can't be found. Set the variable here
+ # to hack us into working on RTD.
+ assert "SHELL" not in os.environ
+ os.environ["SHELL"] = "/bin/bash"
+else:
+ # We only need to set the RTD theme when not on RTD because the RTD
+ # environment handles this otherwise.
+ import sphinx_rtd_theme
+
+ html_theme = "sphinx_rtd_theme"
+ html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
+
+
+html_static_path = ["_static"]
+htmlhelp_basename = "FirefoxTreeDocs"
+
+moz_project_name = "main"
+
+html_show_copyright = False
+
+# Only run autosection for the page title.
+# Otherwise, we have a huge number of duplicate links.
+# For example, the page https://firefox-source-docs.mozilla.org/code-quality/lint/
+# is called "Linting"
+# just like https://firefox-source-docs.mozilla.org/remote/CodeStyle.html
+autosectionlabel_maxdepth = 1
+
+
+def install_sphinx_design(app, pagename, templatename, context, doctree):
+ if "perfdocs" in pagename:
+ app.add_js_file("sphinx_design.js")
+ app.add_css_file("sphinx_design.css")
+
+
+def setup(app):
+ app.add_css_file("custom_theme.css")
+ app.connect("html-page-context", install_sphinx_design)
diff --git a/docs/config.yml b/docs/config.yml
new file mode 100644
index 0000000000..f2589fdc2e
--- /dev/null
+++ b/docs/config.yml
@@ -0,0 +1,115 @@
+---
+
+# The order of the main categories are defined in index.rst
+# Sub categories orders are preserved
+categories:
+ setup_doc:
+ - setup
+ contributing_doc:
+ - contributing
+ - bug-mgmt
+ user_guide:
+ - devtools-user
+ source_doc:
+ - mots
+ - browser
+ - dom
+ - editor
+ - layout
+ - gfx
+ - ipc
+ - devtools
+ - toolkit
+ - js
+ - mobile/android/geckoview
+ - dom/bindings/webidl
+ - modules/libpref
+ - networking
+ - remote
+ - services
+ - uriloader
+ - widget/cocoa
+ - widget/windows
+ - accessible
+ - code-quality
+ - writing-rust-code
+ - tools/profiler
+ - performance
+ - xpcom
+ - nspr
+ - security/nss
+ build_doc:
+ - mach
+ - tools/try
+ - build/buildsystem
+ - taskcluster
+ - tools/moztreedocs
+ testing_doc:
+ - testing/automated-testing
+ - testing/tests-for-new-config
+ - testing/intermittent
+ - testing/testing-policy
+ - testing/ci-configs
+ - testing/browser-chrome
+ - testing/chrome-tests
+ - testing/marionette
+ - testing/geckodriver
+ - testing/test-verification
+ - testing/webrender
+ - testing/mochitest-plain
+ - testing/xpcshell
+ - web-platform
+ - gtest
+ - tools/fuzzing
+ - tools/sanitizer
+ - testing/perfdocs
+ - tools/code-coverage
+ - testing-rust-code
+ release_doc:
+ - update-infrastructure
+ l10n_doc:
+ - intl
+ - l10n
+ python_doc:
+ - mozbase
+ - python
+ fennec_doc:
+ - mobile/android
+ metrics_doc:
+ - metrics
+
+redirects:
+ browser/browser: browser
+ contributing/how_to_contribute_firefox.html: contributing/contribution_quickref.html
+ contributing/artifact_builds.html: contributing/build/artifact_builds.html
+ contributing/linux_build.html: setup/linux_build.html
+ contributing/build/linux_build.html: setup/linux_build.html
+ contributing/mercurial.html: contributing/vcs/mercurial.html
+ contributing/mercurial_bundles.html: contributing/vcs/mercurial_bundles.html
+ contributing/vscode.html: contributing/editors/vscode.html
+ dom/dom: dom
+ layout/layout: layout
+ gfx/gfx: gfx
+ intl/l10n/l10n: l10n
+ ipc/ipc: ipc
+ modules/libpref/libpref: modules/libpref
+ python/mach: mach
+ python/python: python
+ setup/getting_set_up.html: contributing/contributing_to_mozilla.html
+ taskcluster/taskcluster: taskcluster
+ testing/geckodriver/geckodriver: testing/geckodriver
+ testing/marionette/marionette: testing/marionette
+ toolkit/components/telemetry/telemetry: toolkit/components/telemetry
+ tools/compare-locales/index.html: build/buildsystem/locales.html
+ tools/docs/index.html: tools/moztreedocs/index.html
+ tools/docs/contribute/how_to_contribute_firefox.html: contributing/how_to_contribute_firefox.html
+ tools/docs/contribute/directory_structure.html: contributing/directory_structure.html
+ tools/lint: code-quality/lint
+ tools/lint/coding-style: code-quality/coding-style
+ tools/static-analysis/index.html: code-quality/static-analysis.html
+ xpcom/xpcom: xpcom
+
+fatal warnings:
+ - "WARNING: '([^']*)' reference target not found:((?!.rst).)*$"
+
+max_num_warnings: 861
diff --git a/docs/contributing/Code_Review_FAQ.rst b/docs/contributing/Code_Review_FAQ.rst
new file mode 100644
index 0000000000..18fe85a6e2
--- /dev/null
+++ b/docs/contributing/Code_Review_FAQ.rst
@@ -0,0 +1,93 @@
+Code Review FAQ
+===============
+
+What is the purpose of code review?
+-----------------------------------
+
+Code review is our basic mechanism for validating the design and
+implementation of patches. It also helps us maintain a level of
+consistency in design and implementation practices across the many
+hackers and among the various modules of Mozilla.
+
+Of course, code review doesn't happen instantaneously, and so there is
+some latency built into the system. We're always looking for ways to
+reduce the wait, while simultaneously allowing reviewers to do a good
+chunk of hacking themselves. We don't have a perfect system, and we
+never will. It's still evolving, so let us know if you have suggestions.
+
+Mozilla used to have the concept of "super-review", but `a consensus was
+reached in
+2018 <https://groups.google.com/forum/#!topic/mozilla.governance/HHU0h-44NDo>`__
+to retire this process.
+
+Who must review my code?
+------------------------
+
+You must have an approval ("r={{ mediawiki.external('name') }}") from
+the module owner or designated "peer" of the module where the code will
+be checked in. If your code affects several modules, then generally you
+should have an "r={{ mediawiki.external('name') }}" from the owner or
+designated peer of each affected module. We try to be reasonable here,
+so we don't have an absolute rule on when every module owner must
+approve. For example, tree-wide changes such as a change to a string
+class or a change to text that is displayed in many modules generally
+doesn't get reviewed by every module owner.
+
+You may wish to ask others as well.
+
+
+What do reviewers look for?
+---------------------------
+
+A review is focused on a patch's design, implementation, usefulness in
+fixing a stated problem, and fit within its module. A reviewer should be
+someone with domain expertise in the problem area. A reviewer may also
+utilize other areas of his or her expertise and comment on other
+possible improvements. There are no inherent limitations on what
+comments a reviewer might make about improving the code.
+
+Reviewers will probably look at the following areas of the code:
+
+- “goal” review: is the issue being fixed actually a bug? Does the
+ patch fix the fundamental problem?
+- API/design review. Because APIs define the interactions between
+ modules, they need special care. Review is especially important to
+ keep APIs balanced and targeted, and not too specific or
+ overdesigned. There are a `WebIDL review
+ checklist <https://wiki.mozilla.org/WebAPI/WebIDL_Review_Checklist>`__.
+ There are also templates for emails that should be sent when APIs are
+ going to be exposed to the Web and general guidance around naming on
+ `this wiki
+ page <https://wiki.mozilla.org/WebAPI/ExposureGuidelines>`__.
+- Maintainability review. Code which is unreadable is impossible to
+ maintain. If the reviewer has to ask questions about the purpose of a
+ piece of code, then it is probably not documented well enough. Does
+ the code follow the :ref:`Coding style` ? Be careful when
+ reviewing code using modern C++ features like auto.
+- Security review. Does the design use security concepts such as input
+ sanitizers, wrappers, and other techniques? Does this code need
+ additional security testing such as fuzz-testing or static analysis?
+- Integration review. Does this code work properly with other modules?
+ Is it localized properly? Does it have server dependencies? Does it
+ have user documentation?
+- Testing review. Are there tests for correct function? Are there tests
+ for error conditions and incorrect inputs which could happen during
+ operation?
+- Performance review. Has this code been profiled? Are you sure it's
+ not negatively affecting performance of other code?
+- License review. Does the code follow the `code licensing
+ rules <http://www.mozilla.org/hacking/committer/committers-agreement.pdf>`__?
+
+
+How can I tell the status of reviews?
+-------------------------------------
+
+When a patch has passed review you'll see "Accepted" in green at the top
+of a Phabricator revision, under the title. In Bugzilla (which is
+deprecated in favour of Phabricator), this is indicated by "{{
+mediawiki.external('name') }}:review+" in the attachment table in the
+bug report. If it has failed review then you'll see "Needs Revision" in
+red at the top of the revision, or, in Bugzilla, "{{
+mediawiki.external('name') }}:review-". Most of the time that a reviewer
+sets a review flag, they will also add a comment to the bug explaining
+the review.
diff --git a/docs/contributing/build/artifact_builds.rst b/docs/contributing/build/artifact_builds.rst
new file mode 100644
index 0000000000..7a92a559f6
--- /dev/null
+++ b/docs/contributing/build/artifact_builds.rst
@@ -0,0 +1,173 @@
+Understanding Artifact Builds
+=============================
+
+Firefox for Desktop and Android supports a **fast build mode** called
+*artifact mode*. The resulting builds are called *artifact builds*.
+Artifact mode downloads pre-built C++ components rather than building them
+locally, trading bandwidth for time.
+
+Artifact builds will be useful to many developers who are not working
+with compiled code (see "Restrictions" below). Artifacts are typically
+fetched from `mozilla-central <https://hg.mozilla.org/mozilla-central/>`__.
+
+To automatically download and use pre-built binary artifacts, add the
+following lines into your :ref:`mozconfig <Configuring Build Options>`
+file:
+
+.. code-block:: shell
+
+ # Automatically download and use compiled C++ components:
+ ac_add_options --enable-artifact-builds
+
+ # Write build artifacts to:
+ mk_add_options MOZ_OBJDIR=./objdir-frontend
+
+To automatically download and use the debug version of the pre-built
+binary artifact (currently supported for Linux, OSX and Windows
+artifacts), add ``ac_add_options --enable-debug`` to your mozconfig file
+(with artifact builds option already enabled):
+
+.. code-block:: shell
+
+ # Enable debug versions of the pre-built binary artifacts:
+ ac_add_options --enable-debug
+
+ # Automatically download and use compiled C++ components:
+ ac_add_options --enable-artifact-builds
+
+ # Download debug info so that stack traces refers to file and columns rather than library and Hex address
+ ac_add_options --enable-artifact-build-symbols
+
+ # Write build artifacts to:
+ mk_add_options MOZ_OBJDIR=./objdir-frontend-debug-artifact
+
+
+Prerequisites
+-------------
+
+Artifact builds are supported for users of Mercurial and Git. Git
+artifact builds require a mozilla-central clone made with the help of
+`git-cinnabar <https://github.com/glandium/git-cinnabar>`__. Please
+follow the instructions on the git-cinnabar project page to install
+git-cinnabar. Further information about using git-cinnabar to interact
+with Mozilla repositories can be found on `the project
+wiki <https://github.com/glandium/git-cinnabar/wiki/Mozilla:-A-git-workflow-for-Gecko-development>`__.
+
+Building
+--------
+
+If you've added ``--enable-artifact-builds`` to your ``mozconfig``, each
+time you run ``mach build`` and ``mach build path/to/subdirectory`` the
+build system will determine what the best pre-built binary artifacts
+available are, download them, and put them in place for you. The
+computations are cached, so the additional calculations should be very
+fast after the up-to-date artifacts are downloaded -- just a second or
+two on modern hardware. Most Desktop developers should find that
+
+.. code-block:: shell
+
+ ./mach build
+ ./mach run
+
+just works.
+
+To only rebuild local changes (to avoid re-checking for pushes and/or
+unzipping the downloaded cached artifacts after local commits), you can
+use:
+
+.. code-block:: shell
+
+ ./mach build faster
+
+which only "builds" local JS, CSS and packaged (e.g. images and other
+asset) files.
+
+Most Firefox for Android developers should find that
+
+.. code-block:: shell
+
+ ./mach build
+ ./mach package
+ ./mach install
+
+just works.
+
+Pulling artifacts from a try build
+----------------------------------
+
+To only accept artifacts from a specific revision (such as a try build),
+set ``MOZ_ARTIFACT_REVISION`` in your environment to the value of the
+revision that is at the head of the desired push. Note that this will
+override the default behavior of finding a recent candidate build with
+the required artifacts, and will cause builds to fail if the specified
+revision does not contain the required artifacts.
+
+Restrictions
+------------
+
+Oh, so many. Artifact builds are rather delicate: any mismatch between
+your local source directory and the downloaded binary artifacts can
+result in difficult to diagnose incompatibilities, including unexplained
+crashes and catastrophic XPCOM initialization and registration
+failures. These are rare, but do happen.
+
+Things that are supported
+-------------------------
+
+- Modifying JavaScript, (X)HTML, and CSS resources; and string
+ properties and FTL files.
+- Modifying Android Java code, resources, and strings.
+- Running mochitests and xpcshell tests.
+- Modifying ``Scalars.yaml`` to add Scalar Telemetry (since {{
+ Bug("1425909") }}, except artifact builds on try).
+- Modifying ``Events.yaml`` to add Event Telemetry (since {{
+ Bug("1448945") }}, except artifact builds on try).
+
+Essentially everything updated by ``mach build faster`` should work with
+artifact builds.
+
+Things that are not supported
+-----------------------------
+
+- Support for products other than Firefox for Desktop and
+ Android are not supported and are unlikely to ever be supported.
+ Other projects like Thunderbird may provide
+ `their own support <https://developer.thunderbird.net/thunderbird-development/building-thunderbird/artifact-builds>`__
+ for artifact builds.
+- You cannot modify C, C++, or Rust source code anywhere in the tree.
+ If it’s compiled to machine code, it can't be changed.
+- You cannot modify ``histograms.json`` to add Telemetry histogram
+ definitions.(But see `Bug 1206117 <https://bugzilla.mozilla.org/show_bug.cgi?id=1206117>`__).
+- Modifying build system configuration and definitions does not work in
+ all situations.
+
+Things that are not **yet** supported
+-------------------------------------
+
+- Tests other than mochitests, xpcshell, and Marionette-based tests.
+ There aren’t inherent barriers here, but these are not known to work.
+- Modifying WebIDL definitions, even ones implemented in JavaScript.
+
+Troubleshooting
+---------------
+
+There are two parts to artifact mode:
+the ``--disable-compile-environment`` option, and the ``mach artifact``
+command that implements the downloading and caching. Start by running
+
+.. code-block:: shell
+
+ ./mach artifact install --verbose
+
+to see what the build system is trying to do. There is some support for
+querying and printing the cache; run ``mach artifact`` to see
+information about those commands.
+
+Downloaded artifacts are stored in
+``$MOZBUILD_STATE_PATH/package-frontend``, which is almost always
+``~/.mozbuild/package-frontend``.
+
+Discussion is best started on the `dev-builds mailing
+list <https://lists.mozilla.org/listinfo/dev-builds>`__. Questions are
+best raised in `#build <https://chat.mozilla.org/#/room/#build:mozilla.org>`__ on `Matrix <https://chat.mozilla.org/>`__. Please
+file bugs in *Firefox Build System :: General*, blocking `Bug 901840 <https://bugzilla.mozilla.org/show_bug.cgi?id=901840>`__
diff --git a/docs/contributing/build/building_mobile_firefox.rst b/docs/contributing/build/building_mobile_firefox.rst
new file mode 100644
index 0000000000..90e922af8a
--- /dev/null
+++ b/docs/contributing/build/building_mobile_firefox.rst
@@ -0,0 +1,34 @@
+Firefox for Mobile Devices
+--------------------------
+
+We have several different mobile products aimed at different tasks,
+devices, and audiences:
+
+- Building **Firefox for Android** (codename: fenix). Our general-purpose
+ mobile browser is split into several different artifact layers:
+
+ - `The fenix Android application </mobile/android/fenix.html>`_
+ - `The android-components Android library <https://github.com/mozilla-mobile/firefox-android/tree/main/android-components>`_
+ - `The GeckoView platform </mobile/android/geckoview>`_
+
+- `Firefox for iOS <https://github.com/mozilla-mobile/firefox-ios>`_,
+ our general-purpose browser for iOS with desktop sync built-in.
+- Building **Firefox Focus**, our privacy-focused browser for
+
+ - `iOS <https://github.com/mozilla-mobile/focus-ios>`_
+ - `Android <https://github.com/mozilla-mobile/firefox-android/tree/main/focus-android>`_. This browser
+ also uses the android-components library and GeckoView platform, like Firefox for Android
+
+For both Desktop and Mobile development, please bear the following in
+mind:
+
+- While you can build Firefox on older hardware it can take quite a bit
+ of time to compile on slower machines. Having at least 8GB of RAM is
+ recommended, and more is always better. The build process is both CPU
+ and I/O intensive, so building on a machine with an SSD is also
+ strongly preferred.
+- Fast broadband internet is strongly recommended as well. Both the
+ development environment and the source code repository are quite
+ large.
+- Though you can build Firefox to run on 32-bit machines, the build
+ process for almost all of our products requires a 64-bit OS.
diff --git a/docs/contributing/build/supported.rst b/docs/contributing/build/supported.rst
new file mode 100644
index 0000000000..4223f812ff
--- /dev/null
+++ b/docs/contributing/build/supported.rst
@@ -0,0 +1 @@
+.. include:: ../../build/buildsystem/supported-configurations.rst
diff --git a/docs/contributing/committing_rules_and_responsibilities.rst b/docs/contributing/committing_rules_and_responsibilities.rst
new file mode 100644
index 0000000000..c3edcba79f
--- /dev/null
+++ b/docs/contributing/committing_rules_and_responsibilities.rst
@@ -0,0 +1,198 @@
+Committing rules and responsibilities
+=====================================
+
++--------------------------------------------------------------------+
+| This page is an import from MDN and the contents might be outdated |
++--------------------------------------------------------------------+
+
+Preparation
+-----------
+
+There are things you need to be sure of before you even attempt to check
+in:
+
+- Your code must
+ :ref:`compile <Building Firefox On Linux>` and `pass all the automated tests <https://developer.mozilla.org/docs/Mozilla/QA/Automated_testing>`__
+ before you consider pushing changes. If you are at all unsure, verify
+ your changes with the
+ `mozilla-central <https://wiki.mozilla.org/Build:TryServer>`__.
+ try server, as appropriate.
+- You need :ref:`code review <Code Review FAQ>`.
+- Depending on the stage of the development process, you may need
+ `approval <https://wiki.mozilla.org/Tree_Rules>`__. Commits to trees
+ where approval is required must have "a=" in the commit message
+ followed by the name of the approver.
+- Code should be factored in such a way such that we can disable
+ features which cause regressions, either by backout or via a kill
+ switch/preference. Be especially careful when landing features which
+ depend on other new features which may be disabled. Ask
+ mozilla.dev.planning for assistance if there are any questions.
+
+Checkin comment
+---------------
+
+The checkin comment for the change you push should include the bug
+number, the names of the reviewers, and a clear explanation of the fix.
+Please say what changes are made, not what problem was fixed, e.g.:
+
+Good: "Bug 123456 - Null-check presentation shell so we don't crash when a
+button removes itself during its own onclick handler. r=paul, a=ringo."
+
+Bad: "Bug 123456 - crash clicking button on www.example.com"
+
+If you are not the author of the code, use ``hg commit -u`` to specify
+the actual author in the Mercurial changeset:
+
+::
+
+ hg commit -u "Pat Chauthor <pat@chauthor.com>"
+
+Commit message restrictions
+---------------------------
+
+The purpose of these new restrictions, implemented via a mercurial hook,
+is to prevent commit messages that do not have a bug number. We will
+still allow a small set of special commits lacking bugs numbers, like
+merges and backouts.
+
+This hook will be enabled on mozilla-central and every major branch that
+directly merges into it, such as autoland or integration
+branches, team branches, or established project branches.
+
+An example for a passing commit message would be,
+
+::
+
+ Bug 577872 - Create WebM versions of Ogg reftests. r=kinetik
+
+Note the *Bug ####*, you at least need that. You also can't commit
+bustage-fixes without a bug number anymore. This is intentional to keep
+track of the bug which caused it.
+
+Allowed are:
+
+- Commit messages containing "bug" or "b=" followed by a bug number
+- Commit messages containing "no bug" (please use this sparingly)
+- Commit message indicating backout of a given 12+ digit changeset ID,
+ starting with (back out|backing out|backed out|backout)( of)?
+ (rev|changeset|cset)s? [0-9a-f]{12}
+- Commit messages that start with "merge" or "merging" and are actually
+ for a merge changeset.
+
+Special exceptions:
+
+- Commits by the special users "ffxbld", "seabld", "tbirdbld", or
+ "cltbld".
+- When the commit is older then some date shortly after the hook has
+ been enabled, to allow merges from other branches. This exception
+ will be lifted after a short period of time (probably a few months)
+ after the hooks is enabled.
+- You can also specify "IGNORE BAD COMMIT MESSAGES" in the tip (latest)
+ commit message to override all the restrictions. This is an extreme
+ measure, so you should only do this if you have a very good reason.
+
+Explicitly disallowed:
+
+- Commit messages containing "try: " to avoid unintentional commits
+ that were meant for the try server.
+
+All tests for allowed or excluded messages are case-insensitive. The
+hook,
+`commit-message.py <https://hg.mozilla.org/hgcustom/version-control-tools/file/tip/hghooks/mozhghooks/commit-message.py>`__,
+was added in `bug 506949 <https://bugzilla.mozilla.org/show_bug.cgi?id=506949>`__.
+
+
+Check the tree
+--------------
+
+TaskCluster is a continuous build system that builds and tests every change
+checked into autoland/mozilla-central and related source trees.
+`Treeherder <https://treeherder.mozilla.org/>`__ displays the progress
+and results of all the build and test jobs for a given tree. For a
+particular job, green means all is well, orange means tests have failed,
+and red means the build itself broke. Purple means that a test was
+interrupted, possibly by a problem with the build system or the
+network. Blue means that a test was interrupted in a known way and will
+be automatically restarted. You can click on the "Help" link in the top
+right corner of Treeherder for a legend to help you decode all the other
+colors and letters.
+
+If the tree is green, it is okay to check in. If some builds are orange
+or red, you can either wait, or make sure all the failures are
+classified with annotations/comments that reference bug numbers or
+fixes.
+
+If the tree is marked as "closed", or if you have questions about any
+oranges or reds, you should contact the sheriff before checking in.
+
+
+Failures and backouts
+---------------------
+
+Patches which cause unit test failures (on :ref:`tier 1
+platforms <Supported Build Hosts and Targets>`) will be backed out.
+Regressions on tier-2 platforms and in performance are not cause for a
+direct backout, but you will be expected to help fix them if quickly.
+
+*Note: Performance regressions require future data points to ensure a
+sustained regression and can take anywhere from 3 hours to 30 hours
+depending on the volume of the tree and build frequency. All regression
+alerts do get briefly investigated and bugs are filed if necessary.*
+
+
+Dealing with test failures
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If a build or a test job fails, you can click on the red or orange or
+purple symbol for the job on Treeherder to display more information.
+The information will appear in the footer, including a summary of any
+error messages, a "+" icon to re-trigger the job (schedule it to run
+again), and links to the log files and to possibly-related bugs.
+
+Here are some steps you can follow to figure out what is causing most
+failures, `and "star" them
+appropriately <http://ehsanakhgari.org/blog/2010-04-09/assisted-starring-oranges>`__:
+
+#. Click on the failing job to see a list of suggested bugs. If the
+ failure clearly matches a known bug, **click on the star** next to
+ that bug and then click "Add a comment" and then submit the comment.
+ This is referred to as "starring the build;" you'll see this phrase
+ or ones like it in IRC a lot.
+#. If the failure might match a known bug but you are not sure, click
+ the bug number to open the Bugzilla report, and click the failing job
+ to open its log. If the log and the bug do match, add a comment as
+ in step 1 (above).
+#. If the summary does not seem to match any suggested bugs, search
+ Bugzilla for the name of the failing test or the error message. If
+ you find a matching bug, add a comment in the bug in Bugzilla, and
+ another to the job in Treeherder.
+#. If you can't figure out whether a known bug exists (for example,
+ because you can't figure out what part of the log you should search
+ for), look on Treeherder to see if there are other similar failures
+ nearby, or ask on #developers to see if anyone recognizes it as a
+ known failure. For example, many Android tests fail frequently in
+ ways that do not produce useful log messages. You can often find the
+ appropriate bug just by looking at other Android failures that are
+ already starred.
+#. If there is no matching bug, you can back out the change (if you
+ suspect the failure was caused by your changeset) or re-trigger the
+ job (if you suspect it's an unrelated intermittent failure). After
+ more test runs it should become clear whether it is a new regression
+ or just an unknown intermittent failure.
+#. If it turns out to be an unknown intermittent failure, file a new bug
+ with "intermittent-failure" in the keywords. Include the name of the
+ test file and an one-line summary of the log messages in the Summary
+ field. In the description, include an excerpt of the error messages
+ from the log, and a link to the log file itself.
+
+At any point if you are not sure or can't figure out what to do, ask for
+advice or help in `#developers <https://chat.mozilla.org>`__.
+If a large number of jobs are failing and you suspect an infrastructure problem, you can also ask
+about it in `#releng <https://chat.mozilla.org>`__.
+
+
+Dealing with performance regressions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Under some circumstances, if your patch causes a performance regression
+that is not acceptable, it will get backed out.
diff --git a/docs/contributing/contributing_to_mozilla.rst b/docs/contributing/contributing_to_mozilla.rst
new file mode 100644
index 0000000000..0e12922ece
--- /dev/null
+++ b/docs/contributing/contributing_to_mozilla.rst
@@ -0,0 +1,63 @@
+Contributing to Mozilla projects
+================================
+
+This page is here to help you get from "I want to build Firefox"
+to "I'm building my own Firefox" to "I can contribute to Firefox".
+So if you'd like to help Mozilla build the best web browsers in the
+world, you're in the right place.
+
+.. rubric:: Need help?
+ :name: Need_help
+
+The Mozilla community prides itself on being an open, accessible, and
+friendly community for new participants. If you have any difficulties
+getting involved or finding answers to your questions, please `come and
+ask your questions in our
+chatroom <https://chat.mozilla.org/#/room/#introduction:mozilla.org>`_,
+where we can help you get started.
+
+We know even before you start contributing that getting set up to work
+on Firefox and finding a bug that's a good fit for your skills can be a
+challenge, and we're always looking for ways to improve this process: making
+Mozilla more open, accessible, and easier to participate with. If you're
+having any trouble following this documentation, or hit a barrier you
+can't get around, please join us in the the Introduction room on Matrix.
+
+What skills do I need?
+----------------------
+
+Mozilla maintains small and large projects and we are thrilled to have contributors with
+very diverse skills:
+
+- If you know **C++,** **Rust,** **JavaScript,** **HTML** or **CSS**,
+ you can :ref:`contribute to the core layers <Firefox Contributors' Quick Reference>` of
+ Firefox and many other Mozilla projects.
+- If you know **Rust**, you can also contribute to the `Rust programming
+ language <https://github.com/rust-lang/rust>`_ itself, numerous crates like `grcov <https://github.com/mozilla/grcov/>`_
+ or `Servo <https://servo.org/>`_, the web browser engine designed for parallelism and safety.
+- If you know **Kotlin**, you can contribute to `Firefox
+ for Android <https://github.com/mozilla-mobile/fenix>`_ (code name:
+ "Fenix").
+- If you know **Swift**, you can contribute to `Firefox for
+ iOS <https://github.com/mozilla-mobile/firefox-ios>`_ and `Firefox
+ Focus for iOS <https://github.com/mozilla-mobile/focus-ios>`_.
+- If you know **C++**, you can contribute to our `VPN client <https://github.com/mozilla-mobile/mozilla-vpn-client>`_.
+- If you know **Python**, you can contribute to our web services,
+ including Firefox Sync and Firefox Accounts.
+- If you know **Make**, **shell**, **Perl**, or **Python**, you can
+ contribute to our build systems, release engineering, and automation.
+- If you know **Go** or **JavaScript**, you can contribute to `TaskCluster
+ <https://github.com/taskcluster/taskcluster>`_ our CI infrastructure.
+- If you know **C**, you can contribute to `NSS <https://developer.mozilla.org/docs/Mozilla/Projects/NSS>`_,
+ `Opus <https://opus-codec.org/>`_, and `Daala <https://wiki.xiph.org/Daala>`_.
+- There are even many ways to contribute to the Mozilla mission without
+ programming. If getting involved in design, support, translation,
+ testing, or other types of contributions sparks your interest please
+ see the `Volunteer Opportunities
+ wiki <https://contribute.mozilla.org>`_ or the `Mozilla
+ community <https://mozilla.community/>`_ site.
+
+Perhaps you do not know programming yet, but you want to start learning?
+There are `plenty of
+resources <https://developer.mozilla.org/learn>`_ available on
+the MDN Web Docs!
diff --git a/docs/contributing/contribution_quickref.rst b/docs/contributing/contribution_quickref.rst
new file mode 100644
index 0000000000..dcaa969c97
--- /dev/null
+++ b/docs/contributing/contribution_quickref.rst
@@ -0,0 +1,369 @@
+Firefox Contributors' Quick Reference
+=====================================
+
+Some parts of this process, including cloning and compiling, can take a long time even on modern hardware.
+If at any point you get stuck, please don't hesitate to ask at `https://chat.mozilla.org <https://chat.mozilla.org>`__
+in the `#introduction <https://chat.mozilla.org/#/room/#introduction:mozilla.org>`__ channel.
+
+Don’t hesitate to look at the :ref:`Getting Set Up To Work On The Firefox Codebase<Getting Set Up To Work On The Firefox Codebase>` for a more detailed tutorial.
+
+Before you start
+----------------
+Please register and create your account for
+
+`Bugzilla <https://bugzilla.mozilla.org/>`__ : web-based general-purpose bug tracking system.
+To register with Phabricator, make sure you enable Two-Factor Authentication (My Profile >> Edit Profile & Preferences >> Two-Factor Authentication) in Bugzilla.
+
+`Phabricator <https://phabricator.services.mozilla.com/>`__: web-based software development collaboration tools, mainly for code review.
+Please obtain an API Token (Settings >> Conduit API Tokens)
+
+Windows dependencies
+--------------------
+
+#. You need a :ref:`supported version of Windows<tier_1_hosts>`.
+#. Download and install `Visual Studio Community Edition. <https://visualstudio.microsoft.com/downloads/>`__
+#. Finally download the `MozillaBuild Package. <https://ftp.mozilla.org/pub/mozilla/libraries/win32/MozillaBuildSetup-Latest.exe>`__ Installation directory should be:
+
+ .. code-block:: shell
+
+ $ c:\mozilla-build\
+
+#. Before moving on to the next steps, make sure to fulfill the :ref:`Windows prerequisites <Building Firefox On Windows>`
+
+.. note::
+
+ All the commands of this tutorial must be run in the shell provided with the MozillaBuild Package (start-shell.bat)
+
+:ref:`More information <Building Firefox On Windows>`
+
+Bootstrap a copy of the Firefox source code
+-------------------------------------------
+
+You can download the source code and have Firefox automatically download and install the other dependencies it needs. The below command as per your Operating System, will download a lot of data (years of Firefox history!) then guide you through the interactive setup process.
+
+Downloading can take from 40 minutes to two hours (depending on your connection) and the repository should be less than 5GB (~ 20GB after the build).
+
+The default options are recommended.
+If you're not planning to write C++ or Rust code, select :ref:`Artifact Mode <Understanding Artifact Builds>`
+and follow the instructions at the end of the bootstrap for creating a mozconfig file.
+
+To Setup Firefox On Windows
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: shell
+
+ $ cd c:/
+ $ mkdir mozilla-source
+ $ cd mozilla-source
+ $ wget https://hg.mozilla.org/mozilla-central/raw-file/default/python/mozboot/bin/bootstrap.py
+ $ python3 bootstrap.py
+
+More information :ref:`for Windows <Building Firefox On Windows>`
+
+To Setup Firefox On macOS and Linux
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: shell
+
+ $ curl https://hg.mozilla.org/mozilla-central/raw-file/default/python/mozboot/bin/bootstrap.py -O
+ $ python3 bootstrap.py
+
+More information :ref:`for Linux <Building Firefox On Linux>` and :ref:`for MacOS <Building Firefox On MacOS>`
+
+To set up your editor
+---------------------
+
+.. note::
+
+ Visual Studio Code is the recommended editor for Firefox development.
+ Not because it is better than the other editors but because we decided to
+ focus our energy on a single editor.
+
+Setting up your editor is an important part of the contributing process. Having
+linting and other features integrated, saves you time and will help with reducing
+build and reviews cycles.
+
+See our :ref:`editor page for more information about how to set up your favorite editor <Editor / IDE integration>`.
+
+To build & run
+--------------
+
+Once the System is bootstrapped, run:
+
+.. code-block:: shell
+
+ $ cd mozilla-unified
+ $ ./mach build
+
+which will check for dependencies and start the build.
+This will take a while; a few minutes to a few hours depending on your hardware.
+
+.. note::
+
+ The default build is a compiled build with optimizations. Check out the
+ :ref:`mozconfig file documentation <Configuring Build Options>`
+ to see other build options. If you don't plan to change C++ or Rust code,
+ an :ref:`artifact build <Understanding Artifact Builds>` will be faster.
+
+To run it:
+
+.. code-block:: shell
+
+ $ ./mach run
+
+:ref:`More information about Linux <Building Firefox On Linux>` / :ref:`More information about MacOS <Building Firefox On MacOS>`
+
+.. _write_a_patch:
+
+To write a patch
+----------------
+
+Make the changes you need in the codebase. You can look up UI text in `Searchfox <https://searchfox.org>`__ to find the right file.
+
+Then:
+
+.. code-block:: shell
+
+ # Mercurial
+ $ hg commit
+
+ # Git
+ $ git commit
+
+.. _Commit message:
+
+The commit message should look like:
+
+.. code-block:: text
+
+ Bug xxxx - Short description of your change. r?reviewer
+
+ Optionally, a longer description of the change.
+
+**Make sure you include the bug number and at least one reviewer (or reviewer group) in this format.**
+
+To :ref:`find a reviewer or a review group <Getting reviews>`, the easiest way is to run
+``hg log <modified-file>`` (or ``git log <modified-file>``, if
+you're using git) on the relevant files, and look who usually is
+reviewing the actual changes (ie not reformat, renaming of variables, etc).
+
+
+To visualize your patch in the repository, run:
+
+.. code-block:: shell
+
+ # Mercurial
+ $ hg wip
+
+ # Git
+ $ git show
+
+:ref:`More information on how to work with stack of patches <Working with stack of patches Quick Reference>`
+
+:ref:`More information <Mercurial Overview>`
+
+To make sure the change follows the coding style
+------------------------------------------------
+
+To detect coding style violations, use mach lint:
+
+.. code-block:: shell
+
+ $ ./mach lint path/to/the/file/or/directory/you/changed
+
+ # To get the autofix, add --fix:
+ $ ./mach lint path/to/the/file/or/directory/you/changed --fix
+
+:ref:`More information <Code quality>`
+
+To test a change locally
+------------------------
+
+To run the tests, use mach test with the path. However, it isn’t
+always easy to parse the results.
+
+.. code-block:: shell
+
+ $ ./mach test dom/serviceworkers
+
+To run tests based on :ref:`GTest` (C/C++ based unit tests), run:
+
+.. code-block:: shell
+
+ $ ./mach gtest 'QuotaManager.*'
+
+To test a change remotely
+-------------------------
+
+Running all the tests for Firefox takes a very long time and requires multiple
+operating systems with various configurations. To build Firefox and run its
+tests on continuous integration servers (CI), multiple :ref:`options to select tasks <Selectors>`
+are available.
+
+To automatically select the tasks that are most likely to be affected by your changes, run:
+
+.. code-block:: shell
+
+ $ ./mach try auto
+
+To select tasks manually using a fuzzy search interface, run:
+
+.. code-block:: shell
+
+ $ ./mach try fuzzy
+
+To rerun the same tasks:
+
+.. code-block:: shell
+
+ $ ./mach try again
+
+From `Treeherder <https://treeherder.mozilla.org/>`__ (our continuous integration system), it is also possible to attach new jobs. As every review has
+a try CI run associated, it makes this work easier. See :ref:`attach-job-review` for
+more information.
+
+.. note::
+
+ This requires `level 1 commit access <https://www.mozilla.org/about/governance/policies/commit/access-policy/>`__.
+
+ You can ask your reviewer to submit the patch for you if you don't have that
+ level of access.
+
+:ref:`More information <Pushing to Try>`
+
+
+To submit a patch
+-----------------
+
+To submit a patch for review, we use a tool called `moz-phab <https://pypi.org/project/MozPhab/>`__.
+To install it, run:
+
+.. code-block:: shell
+
+ $ ./mach install-moz-phab
+
+Once you want to submit your patches (make sure you :ref:`use the right commit message <Commit message>`), run:
+
+.. code-block:: shell
+
+ $ moz-phab
+
+It will publish all the currently applied patches to Phabricator and inform the reviewer.
+
+If you wrote several patches on top of each other:
+
+.. code-block:: shell
+
+ $ moz-phab submit <first_revision>::<last_revision>
+
+`More
+information <https://moz-conduit.readthedocs.io/en/latest/phabricator-user.html>`__
+
+To update the working directory
+-------------------------------
+
+If you're finished with a patch and would like to return to the tip to make a new patch:
+
+.. code-block:: shell
+
+ $ hg pull central
+ $ hg up central
+
+To update a submitted patch
+---------------------------
+
+It is rare that a reviewer will accept the first version of patch. Moreover,
+as the code review bot might suggest some improvements, changes to your patch
+may be required.
+
+If your patch is not loaded in your working directory, you first need to re-apply it:
+
+.. code-block:: shell
+
+ $ moz-phab patch D<revision_id>
+
+Make your changes in the working folder and run:
+
+.. code-block:: shell
+
+ # Or, if you need to pass arguments, e.g., changing the commit message:
+ $ hg commit --amend
+
+ # Git
+ $ git commit --amend
+
+After amending the patch, you will need to submit it using moz-phab again.
+
+.. warning::
+
+ Don't use ``hg commit --amend -m`` or ``git commit --amend -m``.
+
+ Phabricator tracks revision by editing the commit message when a
+ revision is created to add a special ``Differential Revision:
+ <url>`` line.
+
+ When ``--amend -m`` is used, that line will be lost, leading to
+ the creation of a new revision when re-submitted, which isn't
+ the desired outcome.
+
+If you wrote many changes, you can squash or edit commits with the
+command:
+
+.. code-block:: shell
+
+ # Mercurial
+ $ hg histedit
+
+ # Git
+ $ git rebase -i
+
+The submission step is the same as for the initial patch.
+
+:ref:`More information on how to work with stack of patches <Working with stack of patches Quick Reference>`
+
+Retrieve new changes from the repository
+----------------------------------------
+
+To pull changes from the repository, run:
+
+.. code-block:: shell
+
+ # Mercurial
+ $ hg pull --rebase
+
+ # Git
+ $ git pull --rebase
+
+.. _push_a_change:
+
+To push a change in the code base
+---------------------------------
+
+Once the change has been accepted and you've fixed any remaining issues
+the reviewer identified, the reviewer should land the patch.
+
+If the patch has not landed on "autoland" (the integration branch) after a few days,
+feel free to contact the reviewer and/or
+@Aryx or @Sylvestre on the `#introduction <https://chat.mozilla.org/#/room/#introduction:mozilla.org>`__
+channel.
+
+The landing procedure will automatically close the review and the bug.
+
+:ref:`More information <How to submit a patch>`
+
+Contributing to GeckoView
+-------------------------
+
+Note that the GeckoView setup and contribution processes are different from those of Firefox;
+GeckoView setup and contribution docs live in `geckoview.dev <https://geckoview.dev>`__.
+
+More documentation about contribution
+-------------------------------------
+
+:ref:`Contributing to Mozilla projects`
+
+https://mozilla-version-control-tools.readthedocs.io/en/latest/devguide/contributing.html
+
+https://moz-conduit.readthedocs.io/en/latest/phabricator-user.html
+
+https://mikeconley.github.io/documents/How_mconley_uses_Mercurial_for_Mozilla_code
diff --git a/docs/contributing/debugging/capturing_minidump.rst b/docs/contributing/debugging/capturing_minidump.rst
new file mode 100644
index 0000000000..4c8f7591f5
--- /dev/null
+++ b/docs/contributing/debugging/capturing_minidump.rst
@@ -0,0 +1,259 @@
+Capturing a minidump
+====================
+
+*Minidumps* are files created by various Windows tools which record the
+complete state of a program as it's running, or as it was at the moment
+of a crash. Small minidumps are created by the Breakpad :ref:`crash
+reporting <Crash Reporter>` tool, but sometimes that's not
+sufficient to diagnose a problem. For example, if the application is
+hanging (not responding to input, but hasn't crashed) then Breakpad is
+not triggered, and it can be difficult to determine where the problem
+lies. Sometimes a more complete form of minidump is needed to see
+additional details about a crash, in which case manual capture of a
+minidump is desired.
+
+This page describes how to capture these minidumps on Windows, to permit
+better debugging.
+
+
+Privacy and minidumps
+---------------------
+
+.. warning::
+
+ **Warning!** Unlike the minidumps submitted by Breakpad, these
+ minidumps contain the **complete** contents of program memory. They
+ are therefore much more likely to contain private information, if
+ there is any in the browser. For this reason, you may prefer to
+ generate minidumps against a `clean
+ profile <http://support.mozilla.com/en-US/kb/Managing%20profiles>`__
+ where possible.
+
+
+Capturing a minidump: application crash
+---------------------------------------
+
+To capture a full minidump for an application crash, you can use a tool called
+**Debugging Tools for Windows**, which is provided by Microsoft for free.
+
+
+Install Debugging Tools for Windows
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Please follow `these instructions
+<https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/>`__.
+You can install the Debugging Tools for Windows alone, but you cannot
+download it alone. To install it, download Windows Software Development Kit
+(SDK) first, start the installer, and then select only **Debugging Tools for
+Windows** in the list of features to install.
+
+.. image:: img/sdk-installer.png
+
+The latest installer installs the tools for all CPU architectures (X86,
+X64, ARM, and ARM64). You need to choose a tool of the architecture
+matching Firefox you want to capture a minidump from. For example, if
+you want to capture a minidump from 32-bit Firefox on 64-bit Windows,
+use the X86 version of tools, not X64 tools.
+
+The default install path of SDK for 64-bit Windows is
+``%ProgramFiles(x86)%\Windows Kits\10``. The debugging tools can be found in
+the folder named **Debuggers** under your install path of SDK.
+
+The Debugging Tools for Windows contains both graphical and command line
+debugging tools that can be used to capture a minidump. If you prefer
+a graphical tool, please follow `Capture a minidump in a graphical way
+<#capture-a-minidump-in-a-graphical-way>`__. If you prefer a command
+line tool, please follow `Capture a minidump from the command line
+<#capture-a-minidump-from-the-command-line>`__.
+
+
+Capture a minidump in a graphical way
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#. Launch the graphical debugger named WinDbg from the Start Menu. WinDbg
+ icons are registered under "Windows Kits" as below.
+
+ |WinDbg in Start Menu|
+
+#. Connect Firefox to the debugger.
+
+ a. If Firefox is not already running, open the **"File"** menu on WinDbg
+ and choose **"Open Executable..."**. In the file chooser window that
+ appears, open the firefox.exe executable. If you're not sure about where
+ it is, please see `How to find the location of firefox.exe
+ <#how-to-find-the-location-of-firefox-exe>`__.
+
+ b. If Firefox is already running and you know which process you want to
+ capture a minidump from, open the **"File"** menu on WinDbg and choose
+ **"Attach to a Process..."**. In the "Attach to Process" dialog that
+ appears, select the process. To identify a process, please see
+ `Identify a process to attach a debugger to
+ <#identify-a-process-to-attach-a-debugger-to>`__.
+
+#. You should now see a "Command" text window with debug output at the
+ top and an input box at the bottom. From the menu, select
+ ``Debug → Go``, and Firefox should start. If the debugger spits out
+ some text right away and Firefox doesn't come up, select
+ ``Debug → Go`` again.
+
+#. When the program is about to crash, WinDbg will spit out more data,
+ and the prompt at the bottom will change from saying "``*BUSY*``" to
+ having a number in it. At this point, you should type
+ "``.dump /ma c:\temp\firefoxcrash.dmp``" -- without the quotes, but
+ don't forget the dot at the beginning. Once it completes, which can
+ take a fair while, you will have a very large file at
+ ``c:\temp\firefoxcrash.dmp`` that can be used to help debug your
+ problem. File size will depend on this size of Firefox running in
+ your environment, which could several GB.
+
+#. Ask in the relevant bug or thread how best to share this very large
+ file!
+
+
+Capture a minidump from the command line
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If Firefox is not already running, open the Command Prompt and run the following
+command. This command launches all Firefox processes under a debugger. This
+technique is useful when you have a startup crash or when you're not sure about
+which process will crash.
+
+To find out where firefox.exe is, please see `How to find the location
+of firefox.exe <#how-to-find-the-location-of-firefox-exe>`__.
+
+.. code::
+
+ <path to debugger>\cdb.exe -g -G -o <path to firefox>\firefox.exe
+
+
+For example, if both the debugging tools and Firefox are installed in the
+default folder and you want to capture a minidump of 64-bit Firefox,
+the command will be like this. Please note that you need to add double
+quotes when a path contains one or more whitespaces.
+
+.. code::
+
+ "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\cdb.exe" -g -G -o "C:\Program Files\Mozilla Firefox\firefox.exe"
+
+
+If a Firefox process you want to capture a minidump from is already running,
+attach the debugger to it with the following command. To identify a process,
+please see `Identify a process to attach a debugger to
+<#identify-a-process-to-attach-a-debugger-to>`__.
+
+.. code::
+
+ <path to debugger>\cdb.exe -g -G -p <PID>
+
+When the process crashes, the debugger tool captures it and waits for your
+command. At this point, you should type ``.dump /ma c:\temp\firefoxcrash.dmp``
+-- don't forget the dot at the beginning. Once it completes, which can take
+a fair while, you will have a very large file at ``c:\temp\firefoxcrash.dmp``
+that can be used to help debug your problem. File size will depend on this
+size of Firefox running in your environment, which could several GB.
+
+After a minidump is generated, type ``q`` and press Enter to quit the debugger.
+
+
+Capturing a minidump: application hang
+--------------------------------------
+
+On Windows Vista and Windows 7, you can follow `these
+instructions <http://support.microsoft.com/kb/931673>`__ to capture a
+dump file and locate it after it's been saved.
+
+
+Identify a process to attach a debugger to
+------------------------------------------
+
+When you're running Firefox, even if you have only a single tab, you may have
+noticed a bunch of firefox.exe instances in Task Manager. This means Firefox
+consists of multiple processes. Since an application crash happens per process
+and a minidump is generated per process, you need to identify which process will
+crash before starting a debugger.
+
+Identify a process type
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Each instance of firefox.exe has a type. To identify a process to attach
+a debugger to, you need to know its process type first.
+
+When a crash happens, if all Firefox windows are suddenly gone and Mozilla
+Crash Reporter window is opend, a process that crashed is the main process.
+
+.. image:: img/crashreporter.png
+
+When a crash happens, if you see the following page, a process that crashed
+is a tab (content) process.
+
+.. image:: img/tabcrashed.png
+
+There are more process types, but there isn't an easy way to detect a crash in
+a process of those types because the symptom varies. If you cannot be sure
+about the type of a crashing process, terminate Firefox and launch a new
+instance of Firefox under a debugger in the way described above.
+
+If a GPU process crashes, you may see a window is not rendered correctly as
+below. Since the main process relaunches a GPU process, this symptom will be
+transient and the window will be rendered correctly again.
+
+.. image:: img/crash-gpu.png
+
+If a GMP (= Gecko Media Plugin) process crashes, you will see an information
+bar will be displayed below the address bar.
+
+.. image:: img/crash-gmp.png
+
+If an RDD (= Remote Data Decoder) process crashes, Firefox may stop playing
+a video as below, but not limited to this symptom.
+
+.. image:: img/crash-rdd.png
+
+
+Identify a process ID (PID)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Once you understand what type of process crashes, the next step is to get a
+process ID (PID), which is a value to specify in the debugger command we
+discussed above. We present two ways to get a PID here.
+
+The first way is to use Firefox itself. Open a new tab and go to the
+**about:processes** page. This page shows the list of all processes and their
+PIDs. In the example below, the PID of the main process is **6308** and the
+PID of the tab process hosting a page of mozilla.org is **6748**.
+
+.. image:: img/about-processes.png
+
+The second way is to use `Process Explorer
+<https://docs.microsoft.com/en-us/sysinternals/downloads/process-explorer>`__,
+which is provided by Microsoft for free. You may need this technique to attach
+a debugger to a hung process or when you cannot open a new tab in the existing
+Firefox for some reason.
+
+Process Explorer is basically an advanced version of Task Manager. Since it
+displays processes in a hierarchical tree view, you can easily locate the main
+process, which is the parent of all child processes. In the example below, the
+PID of the main process is **6308** and all processes, including
+plugin-container.exe, under the main process is child processes.
+
+Another helpful feature of Process Explorer is that when you hover the mouse
+cursor on a process, it displays a tooltip window showing the process's command
+line string. For a child process of firefox.exe, the command line's last
+keyword shows the process type, so you can tell which process is which process
+type. In the example below, the tooltip window displays a command line string
+of a GPU process.
+
+.. image:: img/process-explorer.png
+
+
+How to find the location of firefox.exe
+---------------------------------------
+
+If you're not sure about the location of the executable file (firefox.exe) of
+Firefox you run, you can find it in the **about:support** page. In the
+"Application Basics" section, the path to firefox.exe is displayed in the row
+of "Application Binary".
+
+.. image:: img/about-support.png
+
+.. |WinDbg in Start Menu| image:: img/windbg-in-startmenu.png
+ :width: 50%
diff --git a/docs/contributing/debugging/debugging_a_hang_on_macos.rst b/docs/contributing/debugging/debugging_a_hang_on_macos.rst
new file mode 100644
index 0000000000..ee2bed16b2
--- /dev/null
+++ b/docs/contributing/debugging/debugging_a_hang_on_macos.rst
@@ -0,0 +1,10 @@
+Debugging A Hang On macOS
+=========================
+
+See `How to Report a Hung
+Firefox <https://developer.mozilla.org/en-US/docs/Mozilla/How_to_report_a_hung_Firefox>`_.
+
+See also
+~~~~~~~~
+
+`Debugging on macOS <https://developer.mozilla.org/en-US/docs/Mozilla/Debugging/Debugging_on_macOS>`__
diff --git a/docs/contributing/debugging/debugging_a_minidump.rst b/docs/contributing/debugging/debugging_a_minidump.rst
new file mode 100644
index 0000000000..5638c29d85
--- /dev/null
+++ b/docs/contributing/debugging/debugging_a_minidump.rst
@@ -0,0 +1,202 @@
+Debugging A Minidump
+====================
+
++--------------------------------------------------------------------+
+| This page is an import from MDN and the contents might be outdated |
++--------------------------------------------------------------------+
+
+The
+`minidump <http://msdn.microsoft.com/en-us/library/windows/desktop/ms680369%28v=vs.85%29.aspx>`__
+file format contains data about a crash on Windows. It is used by
+`rust-minidump <https://github.com/luser/rust-minidump>`__,
+`Breakpad <https://wiki.mozilla.org/Breakpad>`__, and also by various
+Windows debugging tools. Each minidump includes the following data.
+
+- Details about the exception which led to the crash.
+- Information about each thread in the process: the address which was
+ executing and the register state at the time the process stopped.
+- A list of shared libraries loaded into the process at the time of the
+ crash.
+- The stack memory of each thread.
+- The memory right around the crashing address.
+- (Optional) Other memory regions, if requested by the application.
+- (Optional) Other platform-specific data.
+
+Accessing minidumps from crash reports
+--------------------------------------
+
+Minidumps are not available to everyone. For details on how to gain
+access and where to find minidump files for crash reports, consult the
+:ref:`crash report documentation <Understanding Crash Reports>`
+
+Using rust-minidump's tooling
+-----------------------------------
+
+Most of our crash-reporting infrastructure is based on rust-minidump.
+The primary tool for this is the
+`minidump-stackwalk <https://github.com/luser/rust-minidump/tree/master/minidump-stackwalk>`__
+CLI application, which includes extensive user documentation.
+
+That documentation includes a
+`dedicated section <https://github.com/luser/rust-minidump/tree/master/minidump-stackwalk#analyzing-firefox-minidumps>`__
+on locally analyzing Firefox crashreports and minidumps.
+
+If you're looking for minidump_dump, it's included as part of
+minidump-stackwalk.
+
+Using the MS Visual Studio debugger
+-----------------------------------
+
+#. Set up the debugger to :ref:`use the Mozilla symbol
+ server <Using The Mozilla Symbol Server>` and
+ :ref:`source server <Using The Mozilla Source Server>`.
+#. Double-click on the minidump file to open it in the debugger.
+#. When it loads, click the green icon in the visual studio debugger
+ toolbar that looks like a play button.
+
+For Firefox releases older than Firefox 41, you will also need to
+install the relevant release of Firefox (for example from
+`here <https://ftp.mozilla.org/pub/mozilla.org/firefox/releases/>`__),
+and add the directory it is in (e.g., "C:\Program Files\Mozilla
+Firefox 3.6 Beta 1\") to the same dialog in which you set up the
+symbol server (in case the binary location in the minidump is not the
+same as the one on your machine). Note that you can install the
+relevant release anywhere. Just make sure to configure the symbol
+server to the directory where you installed it. For releases from 41
+onward, the binaries are available on the symbol server.
+
+If this doesn't work, downloading the exact build and crashreporter
+symbols full files. These can be found in treeherder / build folder.
+Load Visual Studio, and go to file -> open -> minidump location. Click
+on "Run Native", and Visual Studio will ask for the corresponding symbol
+files. For each .dll you wish to have symbols for, you must go to a
+console and go to the corresponding directory. E.g. (xul.dll should go
+to xul.pdf in the crashreporter symbols directory). Each directory will
+have a .pd\_ file. In a command shell run: "expand /r foo.pd\_". Then
+point Visual Studio to this directory.
+
+Then you'll be able to examine:
+
++------------------+-------------------------------------------------------------------------+
+| stack trace | The debugger shows the stack trace. You can right-click on any frame |
+| | in the stack, and then choose "go to disassembly" or "go to source". |
+| | (Choosing "go to disassembly" from the source view might not get you |
+| | to the right place due to optimizations.) When looking at the |
+| | source, beware that the debugging information will associate all |
+| | inlined functions as part of the line into which they were inlined, |
+| | and compiler (with PGO) inlines *very* aggressively (including |
+| | inlining virtual functions). You can often figure out where you |
+| | *really* are by reading the disassembly. |
++------------------+-------------------------------------------------------------------------+
+| registers | In the Registers tab (Debug->Windows->Registers if you don't have |
+| | it open), you can look at the registers associated with each stack |
+| | frame, but only at the current state (i.e., the time of the crash). |
+| | Registers that Visual Studio can't figure out will be grayed-out and |
+| | have the value 00000000. |
++------------------+-------------------------------------------------------------------------+
+| stack memory | You open a window (Memory 1, etc.) that shows contiguous segments of |
+| | memory using the Debug->Windows->Memory menu item. You can then |
+| | enter the address of the stack pointer (ESP register) in this window |
+| | and look at the memory on the stack. (The minidump doesn't have the |
+| | memory on the heap.) It's a good idea to change the "width" dropdown |
+| | in the top right corner of the window from its default "Auto" to |
+| | either "8" or "16" so that the memory display is word-aligned. If |
+| | you're interested in pointers, which is usually the case, you can |
+| | right click in this window and change the display to show 4-byte |
+| | words (so that you don't have to reverse the order due to |
+| | little-endianness). This view, combined with the disassembly, can |
+| | often be used to reconstruct information beyond what in shown the |
+| | function parameters. |
++------------------+-------------------------------------------------------------------------+
+| local variables | In the Watch 1 (etc.) window (which, if you don't have open, you can |
+| | iget from Debug->Windows->Watch), you can type an expression |
+| | (e.g., the name of a local variable) and the debugger will show you |
+| | its value (although it sometimes gets confused). If you're looking |
+| | at a pointer to a variable that happens to be on the stack, you can |
+| | even examine member variables by just typing expressions. If Visual |
+| | Studio can't figure something out from the minidump, it might show |
+| | you 00000000 (is this true?). |
++------------------+-------------------------------------------------------------------------+
+
+Using minidump-2-core on Linux
+------------------------------
+
+The `Breakpad
+source <https://chromium.googlesource.com/breakpad/breakpad/+/master/>`__
+contains a tool called
+`minidump-2-core <https://chromium.googlesource.com/breakpad/breakpad/+/master/src/tools/linux/md2core/>`__,
+which converts Linux minidumps into core files. If you checkout and
+build Breakpad, the binary will be at
+``src/tools/linux/md2core/minidump-2-core``. Running the binary with the
+path to a Linux minidump will generate a core file on stdout which can
+then be loaded in gdb as usual. You will need to manually download the
+matching Firefox binaries, but then you can use the :ref:`GDB Python
+script <Downloading symbols on Linux / Mac OS X>` to download symbols.
+
+The ``minidump-2-core`` source does not currently handle processing
+minidumps from a different CPU architecture than the system it was
+built for. If you want to use it on an ARM dump, for example, you may
+need to build the tool for ARM and run it under QEMU.
+
+Using other tools to inspect minidump data
+------------------------------------------
+
+Ted has a few tools that can be built against an already-built copy of
+Breakpad to do more targeted inspection. All of these tools assume you
+have checked out their source in a directory next to the breakpad
+checkout, and that you have built Breakpad in an objdir named
+``obj-breakpad`` at the same level.
+
+- `stackwalk-http <https://hg.mozilla.org/users/tmielczarek_mozilla.com/stackwalk-http/>`__
+ is a version of the breakpad's minidump_stackwalk that can fetch symbols
+ over HTTP, and also has the Mozilla symbol server URL baked in. If you
+ run it like ``stackwalk /path/to/dmp /tmp/syms`` it will print the stack
+ trace and save the symbols it downloaded in ``/tmp/syms``. Note that
+ symbols are only uploaded to the symbol server for nightly and
+ release builds, not per-change builds.
+- `dump-lookup <https://hg.mozilla.org/users/tmielczarek_mozilla.com/dump-lookup/>`__
+ takes a minidump and prints values on the stack that are potential
+ return addresses. This is useful when a stack trace looks truncated
+ or otherwise wrong. It needs symbol files to produce useful output,
+ so you will generally want to have run ``stackwalk-http`` to download
+ them first.
+- `get-minidump-instructions <https://hg.mozilla.org/users/tmielczarek_mozilla.com/get-minidump-instructions/>`__
+ retrieves and displays the memory range surrounding the faulting
+ instruction pointer from a minidump. You will almost always want to
+ run it with the ``--disassemble`` option, which will make it send the
+ bytes through ``objdump`` to display the disassembled instructions.
+ If you also give it a path to symbols (see ``stackwalk-http`` above)
+ it can download the matching source files from hg.mozilla.org and
+ display source interleaved with the disassembly.
+- `minidump-modules <http://hg.mozilla.org/users/tmielczarek_mozilla.com/minidump-modules>`__
+ takes a minidump and prints the list of modules from the crashed
+ process. It will print the full path to each module, whereas the
+ Socorro UI only prints the filename for each module for privacy
+ reasons. It also accepts a -v option to print the debug ID for each
+ module, and a -d option to print relative paths to the symbol files
+ that would be used instead of the module filenames.
+
+Getting a stack trace from a crashed B2G process
+------------------------------------------------
+
+#. Get the minidump file in the phone at
+ /data/b2g/mozilla/\*.default/minidump/. You can use `adb
+ pull <http://developer.android.com/tools/help/adb.html>`__ for that.
+#. Build the debug symbols using the command ./build.sh buildsymbols
+ inside the B2G tree. The symbol files will be generated in
+ $OBJDIR/dist/crashreporter-symbols.
+#. Build and install
+ `google-breakpad <https://code.google.com/p/google-breakpad/>`__.
+#. Use the
+ `minidump-stackwalk <https://github.com/luser/rust-minidump/tree/master/minidump-stackwalk>`__
+ tool to get the stack trace.
+
+.. code:: bash
+
+ Example:
+
+ $ cd B2G
+ $ adb pull /data/b2g/mozilla/*.default/minidump/*.dmp .
+ $ls *.dmp
+ 71788789-197e-d769-67167423-4e7aef32.dmp
+ $ minidump-stackwalk 71788789-197e-d769-67167423-4e7aef32.dmp objdir-debug/dist/crashreporter-symbols/
diff --git a/docs/contributing/debugging/debugging_firefox_with_gdb.rst b/docs/contributing/debugging/debugging_firefox_with_gdb.rst
new file mode 100644
index 0000000000..8b188edc85
--- /dev/null
+++ b/docs/contributing/debugging/debugging_firefox_with_gdb.rst
@@ -0,0 +1,501 @@
+Debugging Firefox with GDB
+==========================
+
++--------------------------------------------------------------------+
+| This page is an import from MDN and the contents might be outdated |
++--------------------------------------------------------------------+
+
+This page details how you can more easily debug Firefox and work around
+some GDB problems.
+
+Use GDB 5, or higher. A more recent version of GDB can be obtained from
+`sourceware <https://sourceware.org/gdb/>`__ or your Linux distro repo.
+If you are running less than 256 MB of RAM, be sure to see `Using gdb on
+wimpy computers <https://developer.mozilla.org/en/Using_gdb_on_wimpy_computers>`__.
+
+Where can I find general gdb documentation?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Using GDB is beyond the scope of this document. Documentation is likely
+available on your system if you have GDB installed, in the form of
+**info,** **man** pages, or the gnome help browser. Additionally, you
+can use a graphical front-end to GDB like
+`ddd <https://www.gnu.org/software/ddd/>`__ or
+`insight <https://sourceware.org/insight/>`__. For more information see
+https://sourceware.org/gdb/current/onlinedocs/gdb/
+
+How do I run Firefox under gdb?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The preferred method, is using the
+:ref:`mach` command-line tool to run the
+debugger, which can bypass several optional defaults. Use "mach help
+run" to get more details. If inside the source directory, you would use
+"./mach". Please note that :ref:`mach is aware of mozconfigs <mach_and_mozconfigs>`.
+
+.. code:: bash
+
+ $ ./mach run --debug [arguments to pass to firefox]
+
+If you need to direct arguments to gdb, you can use '--debugger-args'
+options via the command line parser, taking care to adhere to shell
+splitting rules. For example, if you wanted to run the command 'show
+args' when gdb starts, you would use:
+
+.. code:: bash
+
+ $ ./mach run --debug --debugger-args "-ex 'show args'"
+
+Alternatively, you can run gdb directly against Firefox. However, you
+won't get some of the more useful capabilities this way. For example,
+mach sets an environment variable (see below) to stop the JS engine from
+generating synthetic segfaults to support the slower script dialoging
+mechanism.
+
+.. code::
+
+ (gdb) OBJDIR/dist/bin/firefox
+
+How do I pass arguments in prun?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Set the arguments in GDB before calling prun. Here's an example on how
+to do that:
+
+.. code::
+
+ (gdb) set args https://www.mozilla.org
+ (gdb) prun
+
+How do I set a breakpoint in a library that hasn't been loaded?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+GDB 6.1 and above has support for "pending breakpoints". This is
+controlled by the "``set breakpoint pending``" setting, and is enabled
+by default. If a breakpoint cannot be immediately resolved, it will be
+re-checked each time a shared library is loaded, by the process being
+debugged. If your GDB is older than this, you should upgrade.
+
+In older versions, there isn't a way to set breakpoints in a library
+that has not yet been loaded. See more on `setting a breakpoint when a
+component is
+loaded <#How_do_I_set_a_breakpoint_when_a_component_is_loaded.3F>`__. If
+you have to set a breakpoint you can set a breakpoint in ``_dl_open``.
+This function is called when a new library is loaded, when you can
+finally set your breakpoint.
+
+How do I set a breakpoint when a component is loaded?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In Firefox Version 57 (and possibly earlier) XPCOM_BREAK_ON_LOAD does
+not seem to exist.
+
+There's a facility in XPCOM which allows you to set an environment
+variable to drop into the debugger when loading a certain component. You
+have to set ``XPCOM_BREAK_ON_LOAD`` variable before you run Firefox,
+setting it to a string containing the names of libraries you want to
+load. For example, if you wish to stop when a library named ``raptor``
+or ``necko`` is loaded, you set the variable to ``raptor:necko``. Here's
+an example:
+
+.. code::
+
+ (gdb) set env XPCOM_BREAK_ON_LOAD raptor:necko
+ (gdb) prun
+
+Why can't I set a breakpoint?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You probably can't set a breakpoint because its library hasn't been
+loaded. Most Firefox functionality is in libraries loaded mid-way
+through the ``main()``\ function. If you break on ``main(),``\ and step
+through until the libraries are loaded, with a call to
+``InitCOMGlue()``, you should be able to set breakpoints on many more
+symbols, source files, and continue running.
+
+.. code::
+
+ (gdb) break main
+ (gdb) run
+ Breakpoint 1, main(argc=4, argv=0x7fffffffde98, envp=0x7ffffffffdec0) .....
+ 256 {
+ (gdb) next
+ ...
+ 293 nsresult rv = InitXPCOMGlue()
+ (gdb) next
+
+If you still can't set the breakpoints, you need to confirm the library
+has loaded. You can't proceed until the library loads. See more on
+`loading shared libraries <#How_do_I_load_shared_libraries.3F>`__. If
+you wish to break as soon as the library is loaded, see the section on
+`breaking when a component is
+loaded <#How_do_I_set_a_breakpoint_when_a_component_is_loaded.3F>`__ and
+`breaking on a library
+load <#How_do_I_set_a_breakpoint_when_a_component_is_loaded.3F>`__.
+
+How do I display PRUnichar's?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+One suggestion is this:
+
+.. code::
+
+ (gdb) print ((PRUnichar*)uri.mBuffer)[0]@16
+ $47 = {114, 100, 102, 58, 110, 117, 108, 108, 0, 0, 8, 0, 0, 0, 37432,
+ 16514}
+
+
+
+.. code::
+
+ (gdb) print aURI
+ $1 = (const PRUnichar *) 0x855e6e0
+ (gdb) x/32ch aURI
+ 0x855e6e0: 104 'h' 116 't' 116 't' 112 'p' 58 ':' 47 '/' 47 '/' 119 'w'
+ 0x855e6f0: 119 'w' 119 'w' 46 '.' 109 'm' 111 'o' 122 'z' 105 'i' 108 'l'
+ 0x855e700: 108 'l' 97 'a' 46 '.' 111 'o' 114 'r' 103 'g' 47 '/' 115 's'
+ 0x855e710: 116 't' 97 'a' 114 'r' 116 't' 47 '/' 0 '\0' 25 '\031' 0 '\0'
+ (gdb)
+
+- Define helper functions in your .gdbinit
+
+.. code::
+
+ # Define a "pu" command to display PRUnichar * strings (100 chars max)
+ # Also allows an optional argument for how many chars to print as long as
+ # it's less than 100.
+ def pu
+ set $uni = $arg0
+ if $argc == 2
+ set $limit = $arg1
+ if $limit > 100
+ set $limit = 100
+ end
+ else
+ set $limit = 100
+ end
+ # scratch array with space for 100 chars plus null terminator. Make
+ # sure to not use ' ' as the char so this copy/pastes well.
+ set $scratch = "____________________________________________________________________________________________________"
+ set $i = 0
+ set $scratch_idx = 0
+ while (*$uni && $i++ < $limit)
+ if (*$uni < 0x80)
+ set $scratch[$scratch_idx++] = *(char*)$uni++
+ else
+ if ($scratch_idx > 0)
+ set $scratch[$scratch_idx] = '\0'
+ print $scratch
+ set $scratch_idx = 0
+ end
+ print /x *(short*)$uni++
+ end
+ end
+ if ($scratch_idx > 0)
+ set $scratch[$scratch_idx] = '\0'
+ print $scratch
+ end
+ end
+
+ # Define a "ps" command to display subclasses of nsAC?String. Note that
+ # this assumes strings as of Gecko 1.9 (well, and probably a few
+ # releases before that as well); going back far enough will get you
+ # to string classes that this function doesn't work for.
+ def ps
+ set $str = $arg0
+ if (sizeof(*$str.mData) == 1 && ($str.mFlags & 1) != 0)
+ print $str.mData
+ else
+ pu $str.mData $str.mLength
+ end
+ end
+
+`This is hard. Give me a .gdbinit that already has the
+functions. <#This_is_hard._Give_me_a_.gdbinit_that_works.>`__
+
+- Define a small helper function "punichar" in #ifdef NS_DEBUG code
+ somewhere.
+
+How do I display an nsString?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can call the ToNewCString() method on the nsString. It leaks a
+little memory but it shouldn't hurt anything if you only do it a few
+times in one gdb session. (via akkana@netscape.com)
+
+.. code::
+
+ (gdb) p string.ToNewCString()
+
+Another method (via bent) is the following (replace ``n`` with: the
+returned length of your string):
+
+.. code::
+
+ (gdb) p string.Length()
+ $1 = n
+ (gdb) x/ns string.BeginReading()
+
+You can of course use any of the above unichar-printing routines instead
+of x/s.
+
+This is hard. Give me a .gdbinit that works.
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+See `Boris Zbarsky's
+.gdbinit <http://web.mit.edu/bzbarsky/www/gdbinit>`__. It contained
+several function definitions including:
+
+- "prun" to start the browser and disable library loading.
+- "pu" which will display a (PRUnichar \*) string.
+- "ps" which will display a nsString.
+
+How do I determine the concrete type of an object pointed to by an interface pointer?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can determine the concrete type of any object pointed to, by an
+XPCOM interface pointer, by looking at the mangled name of the symbol
+for the object's vtable:
+
+.. code::
+
+ (gdb) p aKidFrame
+ $1 = (nsIFrame *) 0x85058d4
+ (gdb) x/wa *(void**)aKidFrame
+ 0x4210d380 <__vt_14nsRootBoxFrame>: 0x0
+ (gdb) p *(nsRootBoxFrame*)aKidFrame
+ [ all the member variables of aKidFrame ]
+
+If you're using gcc 3.x, the output is slightly different from the gcc
+2.9x output above. Pay particular attention to the vtable symbol, in
+this case ``__vt_14nsRootBoxFrame``. You won't get anything useful if
+the shared library containing the object is not loaded. See `How do I
+load shared libraries? <#How_do_I_load_shared_libraries.3F>`__ and `How
+do I see what libraries I already have
+loaded? <#How_do_I_see_what_libraries_I_already_have_loaded.3F>`__
+
+Or use the gdb command ``set print object on``.
+
+How can I debug JavaScript from gdb?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you have JavaScript Engine code on the stack, you'll probably want a
+JS stack in addition to the C++ stack.
+
+.. code::
+
+ (gdb) call DumpJSStack()
+
+See `https://developer.mozilla.org/en-US/docs/Mozilla/Debugging/Debugging_JavaScript <https://developer.mozilla.org/en-US/docs/Mozilla/Debugging/Debugging_JavaScript>`__
+for more JS debugging tricks.
+
+How can I debug race conditions and/or how can I make something different happen at NS_ASSERTION time?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+| [submitted by Dan Mosedale]
+| As Linux is unable to generate useful core files for multi-threaded
+ applications, tracking down race-conditions which don't show up under
+ the debugger can be a bit tricky. Unless you've given the
+ ``--enable-crash-on-assert`` switch to ``configure``, you can now
+ change the behavior of ``NS_ASSERTION`` (nsDebug::Break) using the
+ ``XPCOM_DEBUG_BREAK`` environment variable.
+
+How do I run the debugger in emacs/xemacs?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Emacs and XEmacs contain modes for doing visual debugging. However, you
+might want to set up environment variables, specifying the loading of
+symbols and components. The easiest way to set up these is to use the
+``run-mozilla.sh`` script, located in the dist/bin directory of your
+build. This script sets up the environment to run the editor, shell,
+debugger, or defining a preferred setup and running any commands you
+wish. For example:
+
+.. code:: bash
+
+ $ ./run-mozilla.sh /bin/bash
+ MOZILLA_FIVE_HOME=/home/USER/src/mozilla/build/dist/bin
+ LD_LIBRARY_PATH=/home/USER/src/mozilla/build/dist/bin
+ LIBRARY_PATH=/home/USER/src/mozilla/build/dist/bin
+ SHLIB_PATH=/home/USER/src/mozilla/build/dist/bin
+ LIBPATH=/home/USER/src/mozilla/build/dist/bin
+ ADDON_PATH=/home/USER/src/mozilla/build/dist/bin
+ MOZ_PROGRAM=/bin/bash
+ MOZ_TOOLKIT=
+ moz_debug=0
+ moz_debugger=
+
+GDB 5 used to work for me, but now Firefox won't start. What can I do?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A recent threading change (see `bug
+57051 <https://bugzilla.mozilla.org/show_bug.cgi?id=57051>`__ for
+details) caused a problem on some systems. Firefox would get part-way
+through its initialization, then stop before showing a window. A recent
+change to gdb has fixed this. Download and build `the latest version of
+Insight <https://sources.redhat.com/insight/>`__, or if you don't want a
+GUI, `the latest version of gdb <https://sources.redhat.com/gdb/>`__.
+
+"run" or "prun" in GDB fails with "error in loading shared libraries."
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Running mozilla-bin inside GDB fails with an error message like:
+
+.. code::
+
+ Starting program:
+ /u/dmose/s/mozilla/mozilla-all/mozilla/dist/bin/./mozilla-bin
+ /u/dmose/s/mozilla/mozilla-all/mozilla/dist/bin/./mozilla-bin: error
+ in loading shared libraries: libraptorgfx.so: cannot open shared
+ object file: No such file or directory
+
+Your LD_LIBRARY_PATH is probably being reset by your .cshrc or .profile.
+From the GDB manual:
+
+*\*Warning:\* GDB runs your program using the shell indicated by your
+'SHELL' environment variable if it exists (or '/bin/sh' if not). If your
+'SHELL' variable names a shell that runs an initialization file -- such
+as '.cshrc' for C-shell, or '.bashrc' for BASH--any variables you set in
+that file affect your program. You may wish to move the setting of
+environment variables to files that are only run when you sign on, such
+as '.login' or '.profile'.*
+
+Debian's GDB doesn't work. What do I do?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Debian's unstable distribution currently uses glibc 2.1 and GDB 4.18.
+However, there is no package of GDB for Debian with the appropriate
+threads patches that will work with glibc 2.1. I was able to get this to
+work by getting the GDB 4.18 RPM from Red Hat's rawhide server and
+installing that. It has all of the patches necessary for debugging
+threaded software. These fixes are expected to be merged into GDB, which
+will fix the problem for Debian Linux. (via `Bruce
+Mitchener <mailto:bruce@cybersight.com>`__)
+
+Firefox is aborting. Where do I set a breakpoint to find out where it is exiting?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+On Linux there are two possible symbols that are causing this:
+``PR_ASSERT()`` and ``NS_ASSERTION()``. To see where it's asserting you
+can stop at two places:
+
+.. code::
+
+ (gdb) b abort
+ (gdb) b exit
+
+I keep getting a SIGSEGV in JS/JIT code under gdb even though there is no crash when gdb is not attached. How do I fix it?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Set the JS_DISABLE_SLOW_SCRIPT_SIGNALS environment variable (in FF33,
+the shorter and easier-to-remember JS_NO_SIGNALS). For an explanation,
+read `Jan's blog
+post <https://www.jandemooij.nl/blog/2014/02/18/using-segfaults-to-interrupt-jit-code/>`__.
+
+I keep getting a SIG32 in the debugger. How do I fix it?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you are getting a SIG32 while trying to debug Firefox you might have
+turned off shared library loading before the pthreads library was
+loaded. For example, ``set auto-solib-add 0`` in your ``.gdbinit`` file.
+In this case, you can either:
+
+- Remove it and use the method explained in the section about `GDB's
+ memory
+ usage <#The_debugger_uses_a_lot_of_memory._How_do_I_fix_it.3F>`__
+- Use ``handle SIG32 noprint`` either in gdb or in your ``.gdbinit``
+ file
+
+Alternatively, the problem might lie in your pthread library. If this
+library has its symbols stripped, then GDB can't hook into thread
+events, and you end up with SIG32 signals. You can check if your
+libpthread is stripped in ``file /lib/libpthread*`` and looking for
+``'stripped'.``\ To fix this problem on Gentoo Linux, you can re-emerge
+glibc after adding ``"nostrip"`` to your ``FEATURES`` in
+``/etc/make.conf``.
+
+How do I get useful stack traces inside system libraries?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Many Linux distributions provide separate packages with debugging
+information for system libraries, such as gdb, Valgrind, profiling
+tools, etc., to give useful stack traces via system libraries.
+
+Fedora
+^^^^^^
+
+On Fedora, you need to enable the debuginfo repositories, as the
+packages are in separate repositories. Enable them permanently, so when
+you get updates you also get security updates for these packages. A way
+to do this is edit ``/etc/yum.repos.d/fedora.repo`` and
+``fedora-updates.repo`` to change the ``enabled=0`` line in the
+debuginfo section to ``enabled=1``. This may then flag a conflict when
+upgrading to a new distribution version. You would the need to perform
+this edit again.
+
+You can finally install debuginfo packages with yum or other package
+management tools. The best way is install the ``yum-utils`` package, and
+then use the ``debuginfo-install`` command to install all the debuginfo:
+
+.. code:: bash
+
+ $ yum install yum-utils
+ $ debuginfo-install firefox
+
+This can be done manually using:
+
+.. code:: bash
+
+ $ yum install GConf2-debuginfo ORBit2-debuginfo atk-debuginfo \
+ cairo-debuginfo dbus-debuginfo dbus-glib-debuginfo expat-debuginfo \
+ fontconfig-debuginfo freetype-debuginfo gcc-debuginfo glib2-debuginfo \
+ glibc-debuginfo gnome-vfs2-debuginfo gtk2-debuginfo gtk2-engines-debuginfo \
+ hal-debuginfo libX11-debuginfo libXcursor-debuginfo libXext-debuginfo \
+ libXfixes-debuginfo libXft-debuginfo libXi-debuginfo libXinerama-debuginfo \
+ libXrender-debuginfo libbonobo-debuginfo libgnome-debuginfo \
+ libselinux-debuginfo pango-debuginfo popt-debuginfo scim-bridge-debuginfo
+
+Debugging electrolysis (e10s)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+``mach run`` and ``mach test`` both accept a ``--disable-e10s``
+argument. Some debuggers can't catch child-process crashes without it.
+
+You can find some (outdated) information on
+https://wiki.mozilla.org/Electrolysis/Debugging. You may also like to
+read
+https://mikeconley.ca/blog/2014/04/25/electrolysis-debugging-child-processes-of-content-for-make-benefit-glorious-browser-of-firefox
+for a more up-to-date blog post.
+
+To get the child process id use:
+
+.. code::
+
+ MOZ_DEBUG_CHILD_PROCESS=1 mach run
+
+See also
+~~~~~~~~~
+
+- `Debugging <https://developer.mozilla.org/En/Debugging>`__
+- `Performance tools <https://wiki.mozilla.org/Performance:Tools>`__
+- `Fun with
+ gdb <https://blog.mozilla.com/sfink/2011/02/22/fun-with-gdb/>`__ by
+ Steve Fink
+- `Archer pretty printers for
+ SpiderMonkey <https://hg.mozilla.org/users/jblandy_mozilla.com/archer-mozilla>`__
+ (`blog
+ post <https://itcouldbesomuchbetter.wordpress.com/2010/12/20/debugging-spidermonkey-with-archer-2/>`__)
+- `More pretty
+ printers <https://hg.mozilla.org/users/josh_joshmatthews.net/archer-mozilla/>`__
+ for Gecko internals (`blog
+ post <https://www.joshmatthews.net/blog/2011/06/nscomptr-has-never-been-so-pretty/>`__)
+
+.. container:: originaldocinfo
+
+ .. rubric:: Original Document Information
+ :name: Original_Document_Information
+
+ - `History <http://bonsai-www.mozilla.org/cvslog.cgi?file=mozilla-org/html/unix/debugging-faq.html&rev=&root=/www/>`__
+ - Copyright Information: © 1998-2008 by individual mozilla.org
+ contributors; content available under a `Creative Commons
+ license <https://www.mozilla.org/foundation/licensing/website-content.html>`__
diff --git a/docs/contributing/debugging/debugging_firefox_with_lldb.rst b/docs/contributing/debugging/debugging_firefox_with_lldb.rst
new file mode 100644
index 0000000000..99ae5a60c0
--- /dev/null
+++ b/docs/contributing/debugging/debugging_firefox_with_lldb.rst
@@ -0,0 +1,80 @@
+Debugging Firefox with LLDB
+===========================
+
+See http://lldb.llvm.org/index.html.
+
+Mozilla-specific lldb settings
+------------------------------
+
+There's an
+``.lldbinit`` `file <https://searchfox.org/mozilla-central/source/.lldbinit>`_
+in the Mozilla source tree, which applies recommended settings and
+includes a few type summaries and Mozilla-specific debugging commands
+via the lldbutils module (see
+`python/lldbutils/README.txt <https://searchfox.org/mozilla-central/source/python/lldbutils/README.txt>`__).
+For information about available features see the links above and the `Using
+LLDB to debug Gecko <http://mcc.id.au/blog/2014/01/lldb-gecko>`__ blog
+post.
+
+The in-tree ``.lldbinit`` should be loaded automatically in most cases
+when running lldb from the command line (e.g. using
+:ref:`mach`), but **not**
+when using Xcode. See :ref:`Debugging on macOS` for information on setting up
+Xcode.
+
+.. warning::
+
+ LLDB warning: Xcode 5 only comes with lldb (gdb is gone). The
+ introduction and use of UNIFIED_SOURCES in the source starting around
+ November 2013 has broken the default LLDB configuration so that it
+ will not manage to resolve breakpoints in files that are build using
+ UNIFIED_SOURCES (the breakpoints will be listed as "pending", and
+ lldb will not stop at them). To fix this add the following to your
+ $HOME/.lldbinit file:
+
+ .. code::
+
+ # Mozilla's use of UNIFIED_SOURCES to include multiple source files into a
+ # single compiled file breaks lldb breakpoint setting. This works around that.
+ # See http://lldb.llvm.org/troubleshooting.html for more.
+ settings set target.inline-breakpoint-strategy always
+
+ Restart Xcode/lldb and restart your debugging session. If that still
+ doesn't fix things then try closing Xcode/lldb, doing a clobber
+ build, reopening Xcode/lldb, and restarting your debugging session.
+
+Starting a debugging session
+----------------------------
+
+Attaching to an existing process
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can attach to Firefox with following command:
+
+.. code::
+
+ (lldb) process attach --name firefox
+
+Some versions of lldb causes crashes after attaching to Firefox.
+
+Running a new process
+~~~~~~~~~~~~~~~~~~~~~
+
+To start Firefox under the debugger, run ``lldb`` followed by "--",
+followed by the command line you'd like to run, like this:
+
+.. code:: bash
+
+ $ lldb -- obj-ff-dbg/dist/Nightly.app/Contents/MacOS/firefox-bin -no-remote -profile /path/to/profile
+
+Then set breakpoints you need and start the process:
+
+.. code::
+
+ (lldb) breakpoint set --name nsInProcessTabChildGlobal::InitTabChildGlobal
+ Breakpoint created: 1: name = 'nsInProcessTabChildGlobal::InitTabChildGlobal', locations = 0 (pending)
+ WARNING: Unable to resolve breakpoint to any actual locations.
+
+ (lldb) r
+ Process 7602 launched: '/.../obj-ff-opt/dist/Nightly.app/Contents/MacOS/firefox-bin' (x86_64)
+ 1 location added to breakpoint 1
diff --git a/docs/contributing/debugging/debugging_firefox_with_rr.rst b/docs/contributing/debugging/debugging_firefox_with_rr.rst
new file mode 100644
index 0000000000..ef68d9c6ea
--- /dev/null
+++ b/docs/contributing/debugging/debugging_firefox_with_rr.rst
@@ -0,0 +1,98 @@
+Debugging Firefox with rr
+=========================
+
+This page is intended to help Firefox/Gecko developers get started using rr to debug Firefox.
+
+Prerequisites
+-------------
+
+You must have Linux installed with a recent kernel. If you're not running Linux already, an option is set up a virtual machine in which to record Firefox. Be forewarned though that
+
+ * rr requires a VM hypervisor that virtualizes CPU performance counters. VMWare Workstation supports that.
+ * there's a 20% or so performance hit from running in a VM; generally speaking recorder overhead increases from ~1.2x to ~1.4x. (It's a feather in the cap of the hypervisor authors that the hit is that small, though!)
+ * Some features (reverse execution) `may not work well in VMWare <https://robert.ocallahan.org/2014/09/vmware-cpuid-conditional-branch.html>`__ due to a VMWare optimization that can be disabled `this way <http://robert.ocallahan.org/2015/11/rr-in-vmware-solved.html>`__.
+
+When using VSCode, consider adding the `Midas <https://github.com/farre/midas>`__ addon to debug rr traces from the editor. You can use Midas to install / build rr and to configure the :code:`auto-load safe path` via the setup commands.
+
+Ensure that you've `installed <http://rr-project.org/>`__ or `built <https://github.com/mozilla/rr/wiki/Building-And-Installing>`__ rr and have `used it successfully <https://github.com/mozilla/rr/wiki/Usage>`__. Check that you have the latest version.
+
+You likely need to configure your :code:`auto-load safe path` for rr / gdb to work correctly. If you are on Linux and your builds are in ~/moz, then add the following to ~/.gdbinit
+
+.. code::
+
+ add-auto-load-safe-path ~/moz
+
+Firefox developers are strongly encouraged to build rr from source. If your Firefox patch triggers a bug in rr, rr developers will fix that bug with high priority. You might be able to pull a fix within a few hours or days instead of waiting for the next release.
+
+Recording Firefox
+-----------------
+
+
+To record Firefox running normally, simply launch it under rr as you would if running it under valgrind or gdb
+
+.. code:: bash
+
+ $ rr $ff-objdir/dist/bin/firefox ...
+
+or use mach
+
+.. code:: bash
+
+ $ ./mach run --debugger=rr
+
+This will save a trace to your working directory as described in the `usage instructions <https://github.com/mozilla/rr/wiki/Usage>`__. Please refer to `those instructions <https://github.com/mozilla/rr/wiki/Usage>`__ for details on how to debug the recording, which isn't covered in this document.
+
+Sandboxing reduces recording performance because of the SIGSYS signals and extra syscall. Disabling it can help.
+
+The background hang monitor might also be making things worse by causing a lot of extra syscalls. It can be disabled by setting
+toolkit.content-background-hang-monitor.disabled=true.
+
+SIGSYS
+------
+
+When recording and replaying Firefox running with the Linux sandbox, you will get SIGSYS signals frequently. This is expected behavior caused by the sandbox. In gdb, use handle SIGSYS noprint nostop to suppress the signals.
+
+Recording test suites
+---------------------
+
+You can use the test runners' --debugger feature to punch rr down through the layers of python script to where Firefox is launched. This is used in the same way you would use --debugger to run valgrind or gdb, for example:
+
+.. code:: bash
+
+ $ ./mach mochitest --debugger=rr ...
+
+The test harnesses disable the slow-script timeout when the --debugger argument is passed. That's usually sensible, because you don't want those warnings being generated while Firefox is stopped in gdb. However, this has been `observed to change Gecko behavior <https://bugzilla.mozilla.org/show_bug.cgi?id=986673>`__. rr doesn't need to have the slow-script timeout disabled, so to avoid those kinds of pitfalls, pass the --slowscript argument to the test harness.
+
+To run rr in chaos mode:
+
+.. code:: bash
+
+ $ ./mach mochitest --debugger=rr --debugger-args="record --chaos"
+
+You can also run the entire test harness in rr:
+
+.. code:: bash
+
+ $ rr ./mach mochitest ...
+
+The trace will contain many processes, so to debug the correct one, you'll want to use rr ps or rr replay -p firefox etc.
+
+Working with multiple processes
+------------------------------
+
+rr should work out of the box with multi-process Firefox. Once you have a recording you can use rr ps to show all the process that were recorded and rr replay -p <pid> to attach to a particular process.
+
+If you want to debug a particular part of code, you can use the :code:`MOZ_DBG` macro and :code:`getpid()` function to write the process id to stderr. `MOZ_LOG <https://firefox-source-docs.mozilla.org/xpcom/logging.html>`__ will include the pid in log messages by default.
+
+You can combine that with the -M and -g flags to jump to a particular point in a particular process's lifetime.
+
+Get help!
+---------
+
+If you encounter a problem with rr, please `file an issue <https://github.com/mozilla/rr/issues>`__. Firefox bugs are high priority, so usually your issue can be fixed very quickly.
+
+If you want to chat with rr developers, because you need more help or want to contribute or want to complain, we hang out in the `#rr channel <https://chat.mozilla.org/#/room/#rr:mozilla.org>`__. There is also a channel for `#midas <https://chat.mozilla.org/#/room/#midas:mozilla.org>`__.
+
+You also may find `these debugging protips <https://github.com/mozilla/rr/wiki/Debugging-protips>`__ helpful, though many are for rr developers, not users.
+
+Happy debugging!
diff --git a/docs/contributing/debugging/debugging_firefox_with_valgrind.rst b/docs/contributing/debugging/debugging_firefox_with_valgrind.rst
new file mode 100644
index 0000000000..33aab3638a
--- /dev/null
+++ b/docs/contributing/debugging/debugging_firefox_with_valgrind.rst
@@ -0,0 +1,177 @@
+Debugging Firefox with Valgrind
+===============================
+
++--------------------------------------------------------------------+
+| This page is an import from MDN and the contents might be outdated |
++--------------------------------------------------------------------+
+
+This page describes how to use Valgrind (specifically, its Memcheck
+tool) to find memory errors.
+
+Supported platforms
+-------------------
+
+Valgrind runs desktop Firefox fine on Linux, especially on x86 and
+x86-64. Firefox for Android and Firefox OS on ARMv7 should also run,
+though perhaps not as smoothly. The other architectures supported by
+Valgrind on Linux (AARCH64, PPC{32,64}, MIPS{32,64}, S390X) should also
+work, in theory.
+
+MacOS X 10.10 (Yosemite), 64-bit only, works, although it can be a bit
+of a rough ride.
+
+- Expect lower performance and a somewhat higher false positive error
+ rate than on Linux.
+- Valgrind's handling of malloc zones on Yosemite is imperfect. Regard
+ leak reports with caution.
+- Valgrind has been known to cause kernel panics, for unknown reasons.
+
+Where to get Valgrind
+---------------------
+
+Linux: Download `Valgrind <https://valgrind.org/>`__ directly, or use
+your distribution's package manager (if it has a recent enough version).
+
+MacOSX: `Get Valgrind trunk from
+SVN <https://valgrind.org/downloads/repository.html>`__ and build it.
+Don't use 3.10.x or any other tarball.
+
+Make sure you have Valgrind 3.14 or later, version 3.16.1 is known to work,
+3.13.0 did not. Newer versions tend to have better compatibility with both
+Firefox's JITs and newer toolchain components (compiler, libc and linker
+versions).
+
+Basics
+------
+
+Build
+~~~~~
+
+Build Firefox with the following options, which maximize speed and
+accuracy.
+
+.. code::
+
+ ac_add_options --disable-jemalloc
+ ac_add_options --disable-strip
+ ac_add_options --enable-valgrind
+ ac_add_options --enable-optimize="-g -O2"
+ ac_add_options --disable-sandbox
+
+Run
+~~~
+
+Note that programs run *much* more slowly under Valgrind than they do
+natively. Slow-downs of 20x or 30x aren't unexpected, and it's slower on
+Mac than on Linux. Don't try this on an underpowered machine.
+
+Linux
+^^^^^
+
+On Linux, run Valgrind with the following options.
+
+.. code::
+
+ --smc-check=all-non-file --vex-iropt-register-updates=allregs-at-mem-access --show-mismatched-frees=no --read-inline-info=yes
+
+The ``--smc-check`` and ``--vex-iropt-register-updates`` options are
+necessary to avoid crashes in JIT-generated code.
+
+The ``--show-mismatched-frees`` option is necessary due to inconsistent
+inlining of ``new`` and ``delete`` -- i.e. one gets inlined but the
+other doesn't -- which lead to false-positive mismatched-free errors.
+
+The ``--read-inline-info`` option improves stack trace readability in
+the presence of inlining.
+
+Also, run with the following environment variable set.
+
+.. code::
+
+ G_SLICE=always-malloc
+
+This is necessary to get the Gnome system libraries to use plain
+``malloc`` instead of pool allocators.
+
+Mac
+^^^
+
+On Mac, run Valgrind with the following options.
+
+.. code::
+
+ --smc-check=all-non-file --vex-iropt-register-updates=allregs-at-mem-access --show-mismatched-frees=no --dsymutil=yes
+
+The ``--dsymutil`` option ensures line number information is present in
+stack traces.
+
+Advanced usage
+--------------
+
+Shared suppression files
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+`/build/valgrind/ <https://searchfox.org/mozilla-central/source/build/valgrind/>`__
+contains the suppression files used by the periodic Valgrind jobs on
+Treeherder. Some of these files are platform-specific.
+
+Running mochitests under Valgrind?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To run a mochitest under Valgrind, use the following command.
+
+.. code:: bash
+
+ $ ./mach mochitest-plain --debugger="valgrind" --debugger-args="$VALGRIND_OPTIONS" relative/path/to/tests
+
+Where ``$VALGRIND_OPTIONS`` are the options described
+:ref:`above <Debugging Firefox With Valgrind>`. You might also
+need ``--trace-children=yes`` to trace into child processes.
+
+As of December 2014 it is possible to do a complete run of
+mochitests-plain on Valgrind in about 8 CPU hours on a Core i4910
+(Haswell) machine. Maximum process size is 5.4G, of which about 80% is
+in memory. Runs of small subsets of mochitests take far less memory.
+
+Bits and pieces
+~~~~~~~~~~~~~~~
+
+For un-released Linux distros (Fedora Rawhide, etc.) you'll need to use
+a version of Valgrind trunk build, because fixes for the latest gcc and
+glibc versions appear there first. Without them you'll be flooded with
+false errors from Memcheck, and have debuginfo reading problems.
+
+On Linux, code compiled by LLVM at high optimisation levels can cause
+Memcheck to report false uninitialised value errors. See
+`here <https://bugs.kde.org/show_bug.cgi?id=242137#c3>`__ for an easy
+workaround. On Mac, Valgrind has this workaround built in.
+
+You can make stack traces easier to read by asking for source file names
+to be given relative to the root of your source tree. Do this by using
+``--fullpath-after=`` to specify the rightmost part of the absolute path
+that you don't want to see. For example, if your source tree is rooted
+at ``/home/sewardj/MC-20-12-2014``, use ``--fullpath-after=2014/`` to
+get path names relative to the source directory.
+
+The ``--track-origins=yes`` slows down Valgrind greatly, so don't use it
+unless you are hunting down a specific uninitialised value error. But if
+you are hunting down such an error, it's extremely helpful and worth
+waiting for.
+
+Additional help
+---------------
+
+The `Valgrind Quick Start
+Guide <http://www.valgrind.org/docs/manual/quick-start.html>`__ is short
+and worth reading. The `User
+Manual <https://valgrind.org/docs/manual/manual.html>`__ is also useful.
+
+If Valgrind asserts, crashes, doesn't do what you expect, or otherwise
+acts up, first of all read this page and make sure you have both Firefox
+and Valgrind correctly configured. If that's all OK, try using the
+`Valgrind trunk from
+SVN <http://www.valgrind.org/downloads/repository.html>`__. Oftentimes
+bugs are fixed in the trunk before most users fall across them. If that
+doesn't help, consider `filing a bug
+report <http://www.valgrind.org/support/bug_reports.html>`__, and/or
+mailing Julian Seward or Nick Nethercote.
diff --git a/docs/contributing/debugging/debugging_on_macos.rst b/docs/contributing/debugging/debugging_on_macos.rst
new file mode 100644
index 0000000000..ef42c162f9
--- /dev/null
+++ b/docs/contributing/debugging/debugging_on_macos.rst
@@ -0,0 +1,359 @@
+Debugging On macOS
+==================
+
+This document explains how to debug Gecko-based applications such as
+Firefox, Thunderbird, and SeaMonkey on macOS using Xcode. If you want to
+debug from the terminal see :ref:`Debugging Mozilla with
+lldb <Debugging Firefox with LLDB>`. For specific
+information on a way to debug hangs, see :ref:`Debugging a hang on macOS <Debugging A Hang On macOS>`.
+
+Creating a debuggable build
+---------------------------
+
+First, you need to build the application you're going to debug using
+this in your .mozconfig
+
+.. code::
+
+ ac_add_options --disable-optimize
+ ac_add_options --enable-debug-symbols
+
+you can also add this flag if you want assertions etc. compiled in
+
+.. code::
+
+ ac_add_options --enable-debug
+
+See :ref:`Building Firefox for macOS <Building Firefox On MacOS>`
+if you need help creating your own build.
+
+Debugging Firefox on macOS 10.14+
+---------------------------------
+
+macOS 10.14 introduced Notarization and Hardened Runtime features for
+improved application security. macOS 10.15 went further, requiring
+applications to be Notarized with Hardened Runtime enabled in order to
+launch (ignoring workarounds). When run on earlier macOS versions,
+Notarization and Hardened Runtime settings have no effect.
+
+Official Builds
+~~~~~~~~~~~~~~~
+
+At this time, official builds of Firefox 69 and later are Notarized.
+**As a result, it is not possible to attach a debugger to these official
+Firefox releases on macOS 10.14+ without disabling System Integrity
+Protection (SIP).** This is due to Notarization requiring Hardened
+Runtime to be enabled with the ``com.apple.security.get-task-allow``
+entitlement disallowed. **Rather than disabling SIP (which has security
+implications), it is recommended to debug with try builds or local
+builds. The differences are explained below.**
+
+try Server Builds
+~~~~~~~~~~~~~~~~~
+
+In most cases, developers needing to debug a build as close as possible
+to the production environment should use a :ref:`try
+build <Pushing to Try>`. These
+builds enable Hardened Runtime and only differ from production builds in
+that they are not Notarized which should not otherwise affect
+functionality, (other than the ability to easily launch the browser on
+macOS 10.15+ -- see quarantine note below). At this time, developers can
+obtain a Hardened Runtime build with the
+``com.apple.security.get-task-allow`` entitlement allowed by submitting
+a try build and downloading the dmg generated by the "Rpk" shippable
+build job. A debugger can be attached to Firefox processes of these
+builds. try builds use the ``developer.entitlements.xml`` file from the
+source tree while production builds use ``production.entitlements.xml``.
+**On macOS 10.15+, downloaded try builds will not launch by default
+because Notarization is required. To workaround this problem, remove the
+quarantine extended attribute from the downloaded Nightly:**
+
+ ``$ xattr -r -d com.apple.quarantine /Path/to/Nightly.app``
+
+Local Builds
+~~~~~~~~~~~~
+
+Local builds of mozilla-central do not enable Hardened Runtime and hence
+do not have debugging restrictions. As a result, some functionality will
+be permitted on local builds, but blocked on production builds which
+have Hardened Runtime enabled. `Bug
+1522409 <https://bugzilla.mozilla.org/show_bug.cgi?id=1522409>`__ was
+filed to automate codesigning local builds to enable Hardened Runtime by
+default and eliminate this discrepancy.
+
+To obtain a Hardened Runtime build without using try infrastructure, a
+developer can manually codesign builds using the macOS ``codesign(1)``
+command with the ``developer.entitlements.xml`` file from the tree. This
+requires creating a codesigning identity.
+
+Disabling System Integrity Protection (SIP)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If debugging a production build is required, follow Apple's documented
+steps for disabling System Integrity Protection (SIP). Note that
+disabling SIP bypasses Hardened Runtime restrictions which can mask some
+bugs that only occur with Hardened Runtime so it is recommended to test
+fixes with SIP enabled. **Disabling SIP has system security implications
+that should be understood before taking this step.**
+
+Creating an Xcode project
+-------------------------
+
+If you try to create a new Xcode project in an existing directory
+then Xcode will delete its existing contents (Xcode will warn you
+beforehand). To work around that, the steps below have you initialize
+the project outside the Mozilla source tree, close the project, copy
+the .xcodeproj project "file" into the source tree, and then reopen
+the project to finish setting it up.
+
+Note also that since Xcode 7.3.1 it doesn't seem to be possible to
+have the Xcode project live outside the source tree. If you try to do
+that then Xcode will simply **copy** the source files under the
+project directory rather than link to them which breaks debugging and the
+possibility to modify-rebuild-relaunch from inside Xcode.
+
+These steps were last updated for Xcode 10.3:
+
+#. Open Xcode, and create a new Project with File > New Project. Select
+ the "Cross-platform" tab then under the "Other" template group select
+ the "Empty" project type. the click Next. Name the project and click
+ Next. Create/select a temporary directory to contain the project and
+ then click Create.
+#. Before going any further, close the project (File > Close Project)
+ and open Finder. Find the \*.xcodejproj directory in the temporary
+ directory, move it into your Mozilla source tree, and then
+ double-click on it to reopen it.
+#. In the left-hand pane in Xcode you should see a tree item where the
+ root item has the project name. If the temporary directory that you
+ originally created the Xcode project in is under that, right click it
+ and delete it. Now, right click on the root item, select 'Add files
+ to "<project-name>"', select all the files and directories in your
+ source directory, untick "Copy items if needed", then click Add.
+ (These will then be progressively added under the root item
+ <project-name> in the left-hand pane. Note that subdirectories may
+ initially appear to be empty, but they too will progressively be
+ populated as Xcode processes the sourse files. Once done, you should
+ be able to open any file quickly by hitting Cmd-Shift-O and typing in
+ the name of a file.)
+#. In the Product menu, select Scheme > New Scheme and name your scheme
+ (for example, "Debug"). After you click OK, Xcode should open the
+ settings window for the new scheme. (If not, then open its settings
+ from the Product > Edit Scheme menu.)
+#. Select "Run" on the left-hand side of the settings window, then
+ select the "Info" tab. Set the Executable by clicking on "None" and
+ selecting "Other...". A new dialog titled "Choose an executable to
+ launch" will pop up. Browse to the ``.app`` file that you want to
+ debug (``Firefox.app``, ``Nightly``\ ``Debug.app`` etc). The ``.app``
+ file is typically found inside the ``dist`` folder in your build
+ directory.
+#. If you are debugging Firefox, Thunderbird, or some other application
+ that supports multiple profiles, using a separate profile for
+ debugging purposes is recommended. See "Having a profile for
+ debugging purposes" below. Select the "Arguments" tab in the scheme
+ editor, and click the '+' below the "Arguments passed on launch"
+ field. Add "-P *profilename*", where *profilename* is the name of a
+ profile you created previously. Repeat that to also add the argument
+ "-no-remote".
+#. Also in the "Arguments" panel, you may want to add an environment
+ variable MOZ_DEBUG_CHILD_PROCESS set to the value 1 to help with
+ debugging e10s.
+#. Select "Build" from the left of the scheme editor window, and check
+ that there is nothing listed under Targets (otherwise it may cause
+ problems when you try to run the executable for debugging since you
+ will get build errors).
+#. Click "Close" to close the scheme editor.
+
+At this point you can run the application from Xcode, and when you pause
+or hit breakpoints it should show open the correct source file at the
+correct line.
+
+Setting up lldb
+---------------
+
+``lldb`` is the debugger Xcode provides/uses.
+
+.. warning::
+
+ One important issue that the Mozilla .lldbinit file fixes is that by
+ default some breakpoints will be listed as "pending", and Xcode will
+ not stop at them. If you don't include the Mozilla's .lldbinit, you
+ must at least put
+ ``settings set target.inline-breakpoint-strategy always`` in your
+ ``$HOME/.lldbinit`` as recommended on :ref:`Debugging Firefox with
+ lldb <Debugging Firefox with LLDB>`.
+
+The
+`.lldbinit <http://searchfox.org/mozilla-central/source/.lldbinit>`__
+file in the source tree imports many useful `Mozilla specific lldb
+settings, commands and
+formatters <https://searchfox.org/mozilla-central/source/python/lldbutils/README.txt>`__
+into ``lldb``, but you may need to take one of the following steps to
+make sure this file is used.
+
+If you are using ``lldb`` on the command line (independently of Xcode)
+and you will always run it from either the top source directory, the
+object directory or else the dist/bin subdirectory of the object
+directory, then adding the following setting to your ``$HOME/.lldbinit``
+is sufficient:
+
+::
+
+ settings set target.load-cwd-lldbinit true
+
+*However*, if you will run lldb from a different directory, or if you
+will be running it indirectly by debugging in Xcode (Xcode always runs
+lldb from "/"), then this setting will not help you. Instead, add the
+following to your ``$HOME/.lldbinit``:
+
+::
+
+ # This automatically sources the Mozilla project's .lldbinit as soon as lldb
+ # starts or attaches to a Mozilla app (that's in an object directory).
+ #
+ # This is mainly a workaround for Xcode not providing a way to specify that
+ # lldb should be run from a given directory. (Xcode always runs lldb from "/",
+ # regardless of what directory Xcode was started from, and regardless of the
+ # value of the "Custom working directory" field in the Scheme's Run options.
+ # Therefore setting `settings set target.load-cwd-lldbinit true` can't help us
+ # without Xcode providing that functionality.)
+ #
+ # The following works by setting a one-shot breakpoint to break on a function
+ # that we know will both run early (which we want when we start first start the
+ # app) and run frequently (which we want so that it will trigger ASAP if we
+ # attach to an already running app). The breakpoint runs some commands to
+ # figure out the object directory path from the attached target and then
+ # sources the .lldbinit from there.
+ #
+ # NOTE: This scripts actions take a few seconds to complete, so the custom
+ # formatters, commands etc. that are added may not be immediately available.
+ #
+ breakpoint set --name nsThread::ProcessNextEvent --thread-index 1 --auto-continue true --one-shot true
+ breakpoint command add -s python
+ # This script that we run does not work if we try to use the global 'lldb'
+ # object, since it is out of date at the time that the script runs (for
+ # example, `lldb.target.executable.fullpath` is empty). Therefore we must
+ # get the following objects from the 'frame' object.
+ target = frame.GetThread().GetProcess().GetTarget()
+ debugger = target.GetDebugger()
+
+ # Delete our breakpoint (not actually necessary with `--one-shot true`):
+ target.BreakpointDelete(bp_loc.GetBreakpoint().GetID())
+
+ # For completeness, find and delete the dummy breakpoint (the breakpoint
+ # lldb creates when it can't initially find the method to set the
+ # breakpoint on):
+ # BUG WORKAROUND! GetID() on the *dummy* breakpoint appears to be returning
+ # the breakpoint index instead of its ID. We have to add 1 to correct for
+ # that! :-(
+ dummy_bp_list = lldb.SBBreakpointList(target)
+ debugger.GetDummyTarget().FindBreakpointsByName("nsThread::ProcessNextEvent", dummy_bp_list)
+ dummy_bp_id = dummy_bp_list.GetBreakpointAtIndex(0).GetID() + 1
+ debugger.GetDummyTarget().BreakpointDelete(dummy_bp_id)
+
+ # "source" the Mozilla project .lldbinit:
+ os.chdir(target.executable.fullpath.split("/dist/")[0])
+ debugger.HandleCommand("command source -s true " + os.path.join(os.getcwd(), ".lldbinit"))
+ DONE
+
+see :ref:`Debugging Mozilla with
+lldb <Debugging Firefox with LLDB>`. for more information.
+
+Having a profile for debugging purposes
+---------------------------------------
+
+It is recommended to create a separate profile to debug with, whatever
+your task, so that you don't lose precious data like Bookmarks, saved
+passwords, etc. So that you're not bothered with the profile manager
+every time you start to debug, expand the "Executables" branch of the
+"Groups & Files" list and double click on the Executable you added for
+Mozilla. Click the plus icon under the "Arguments" list and type "-P
+<profile name>" (e.g. "-P MozillaDebug"). Close the window when you're
+done.
+
+Running a debug session
+-----------------------
+
+Make sure breakpoints are active (which implies running under the
+debugger) by opening the Product menu and selecting "Debug / Activate
+Breakpoints" (also shown by the "Breakpoints" button in the top right
+section of the main window). Then click the "Run" button or select "Run"
+from the Product menu.
+
+Setting breakpoints
+~~~~~~~~~~~~~~~~~~~
+
+Setting a breakpoint is easy. Just open the source file you want to
+debug in Xcode, and click in the margin to the left of the line of code
+where you want to break.
+
+During the debugging session, each time that line is executed, the
+debugger will break there, and you will be able to debug it.
+
+.. warning::
+
+ Note that with the default configuration, some breakpoints will be
+ listed as "pending", and Xcode will not stop at them. If you don't
+ include the Mozilla's .lldbinit, you must at least put
+ ``settings set target.inline-breakpoint-strategy always`` in your
+ ``$HOME/.lldbinit`` as recommended on :ref:`Debugging Mozilla with
+ lldb <Debugging Firefox with LLDB>`.
+
+Using Firefox-specific lldb commands
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you included the .lldbinit when `Setting up
+lldb <#setting-up-lldb>`__, you can use Mozilla-specific lldb commands
+in the console, located in the Debug area of Xcode. For example, type
+``js`` to see the JavaScript stack. For more information, see :ref:`Debugging
+Mozilla with lldb <Debugging Firefox with LLDB>`.
+
+Debugging e10s child processes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Using Xcode to debug child processes created by an e10s-enabled browser
+is a little trickier than debugging a single-process browser, but it can
+be done. These directions were written using Xcode 6.3.1
+
+#. Complete all the steps above under "Creating the Project"
+#. From the "Product" menu, ensure the scheme you created is selected
+ under "Scheme", then choose "Scheme > Edit Scheme"
+#. In the resulting popup, click "Duplicate Scheme"
+#. Give the resulting scheme a more descriptive name than "Copy of
+ Scheme"
+#. Select "Run" on the left-hand side of the settings window, then
+ select the "Info" tab. Set the Executable by clicking on the
+ "Executable" drop-down, and selecting the ``plugin-container.app``
+ that is inside the app bundle of the copy of Firefox you want to
+ debug.
+#. On the same tab, under "Launch" select "Wait for executable to be
+ launched"
+#. On the "Arguments" tab, remove all arguments passed on launch.
+
+Now you're ready to start debugging:
+
+#. From the "Product" menu, ensure the scheme you created above is
+ selected under "Scheme"
+#. Click the "Run" button. The information area at the top of the window
+ will show "Waiting for plugin-container to launch"
+#. From a command line, run your build of Firefox. When that launches a
+ child process (for example, when you start to load a webpage), Xcode
+ will notice and attach to that child process. You can then debug the
+ child process like you would any other process.
+#. When you are done debugging, click the "Stop" button and quit the
+ instance of Firefox that you were debugging in the normal way.
+
+For some help on using lldb see :ref:`Debugging Mozilla with
+lldb <Debugging Firefox with LLDB>`.
+
+Other resources
+---------------
+
+Apple has an extensive list of `debugging tips and
+techniques <https://developer.apple.com/library/mac/#technotes/tn2124/_index.html>`__.
+
+Questions? Problems?
+~~~~~~~~~~~~~~~~~~~~
+
+Try asking in our Element channels
+`#developers <https://chat.mozilla.org/#/room/#developers:mozilla.org>`__ or
+`#macdev <https://chat.mozilla.org/#/room/#macdev:mozilla.org>`__.
diff --git a/docs/contributing/debugging/debugging_on_windows.rst b/docs/contributing/debugging/debugging_on_windows.rst
new file mode 100644
index 0000000000..3213299199
--- /dev/null
+++ b/docs/contributing/debugging/debugging_on_windows.rst
@@ -0,0 +1,330 @@
+Debugging On Windows
+====================
+
+This document explains how to debug Gecko based applications such as
+Firefox, Thunderbird, and SeaMonkey on Windows using the Visual Studio IDE.
+
+If VS and your Gecko application hang shortly after you launch the
+application under the debugger, see `Problems Loading Debug
+Symbols <#problems-loading-debug-symbols>`__.
+
+Ways to start the debugger
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+First of all, it's necessary to install a Visual Studio extension to be
+able to follow child processes as they are created. Firefox, in general,
+and even in non-e10s mode, does not start the main process directly, it
+starts it via a Launcher Process. This means that Visual Studio will
+only attach to the first process it finds, and will not hit any
+break-point (and even notifies you that it cannot find their location).
+`Microsoft Child Process Debugging Power
+Tool <https://marketplace.visualstudio.com/items?itemName=vsdbgplat.MicrosoftChildProcessDebuggingPowerTool>`__
+allows automatically attaching to child processes, such as Web Content
+process, GPU process, etc. Enable it by going its configuration menu in
+"Debug > Other debugging targets > Child process debugging settings",
+and ticking the box.
+
+If you have followed the steps in :ref:`Building Firefox for
+Windows <Building Firefox On Windows>`
+and have a local debug build, you can **execute this command from same command line.**
+
+.. code::
+
+ ./mach run --debug
+
+It would open Visual Studio with Firefox's
+run options configured. You can **click "Start" button** to run Firefox
+then, already attached in the debugger.
+
+Alternatively, if you have generated the Visual Studio solution, via
+``./mach build-backend -b VisualStudio``, opening this solution allows
+you to run ``firefox.exe`` directly in the debugger. To make it the
+startup project, right click on the project and select ``Set As Startup
+Project``. It appears bold when it's the case. Breakpoints are kept
+across runs, this can be a good way to debug startup issues.
+
+**Run the program until you hit an assertion.** You will get a dialog
+box asking if you would like to debug. Hit "Cancel". The MSDEV IDE will
+launch and load the file where the assertion happened. This will also
+create a Visual Studio Mozilla project in the directory of the executable
+by default.
+
+**Attach the debugger to an existing Mozilla process**. In the Visual
+Studio, select Debug > Attach to Process. If you want to debug a content
+process, you can **hover on the tab** of page you want to debug, which
+would show the pid. You can then select the process from dialog opened
+from "Attach to Process". You can open ``about:processes`` to see the pid
+for all subprocesses, including tabs but also GPU, networking etc.
+For more information, see `Attach to Running Processes with the Visual Studio
+Debugger <http://msdn.microsoft.com/en-us/library/vstudio/3s68z0b3.aspx>`__.
+
+**Starting an MSIX installed Firefox with the debugger**. In Visual
+Studio, select Debug -> Other Debug Targets -> Debug Installed App Package.
+In the dialog, select the installed Firefox package you wish to debug
+and click "Start".
+
+Debugging Release and Nightly Builds
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Refer to the steps to :ref:`use the Mozilla symbol
+server <Using The Mozilla Symbol Server>` and :ref:`source
+server <Using The Mozilla Source Server>`
+
+Creating a Visual Studio project for Firefox
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Please refer to :ref:`this <Visual Studio Projects>`.
+
+Changing/setting the executable to debug
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To change or set the executable to debug, go to Project > Properties >
+Debugging > Command. (As of Visual Studio 2022.)
+
+It should show the executable you are debugging. If it is empty or
+incorrect, manually add the correct path to the executable.
+
+Command line parameters and environment variables
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To change or set the command line options, go to Project > Properties >
+Debugging > Command Arguments.
+
+Some common options would be the URL of the file you want the browser to
+open as soon as it starts, starting the Profile Manager, or selecting a
+profile. You can also redirect the console output to a file (by adding
+"``> filename.txt``" for example, without the quotes).
+
+Customizing the debugger's variable value view
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can customize how Visual Studio displays classes in the variable view.
+By default VS displays "{...}" and you need to click the small + icon
+to expand the members. You can change this behaviour, and make Visual
+Studio display whatever data member you want in whatever order, formatted
+however you like instead of just "{...}".
+
+You need to locate a file called "gecko.natvis" under toolkit/library.
+The file contains a list of types and how they should be displayed in
+the debugger. It is XML and after a little practice you should be well
+on your way.
+
+To understand the file in detail refer to `Create custom views of C++
+objects in the debugger using the Natvis framework
+<https://docs.microsoft.com/en-us/visualstudio/debugger/create-custom-views-of-native-objects>`__
+
+The file already comes with a number of entries that will make your life
+easier, like support for several string types. If you need to add a custom
+type, or want to change an existing entry for debugging purposes, you can
+easily edit the file. For your convenience it is included in all generated
+Visual Studio projects, and if you edit and save it within Visual Studio, it
+will pick up the changes immediately.
+
+Handling multiple processes in Visual Studio
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Turn off "Break all processes when one process breaks" to single step a single
+process.
+
+Turning off "Break all processes when one process breaks" adds "Step Into
+Current Process", "Step Over Current Process" and "Step Out Current Process" to
+the "Debug" menu.
+
+To single step a single process with the other processes paused:
+
+- Turn on "Break all processes when one process breaks"
+- Hit a breakpoint which stops all processes
+- Turn off "Break all processes when one process breaks"
+- Now using "Step Into Current Process" will leave the other processes stopped
+ and just advance the current one.
+
+Obtaining ``stdout`` and other ``FILE`` handles
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Running the following command in the Command Window in Visual Studio
+returns the value of ``stdout``, which can be used with various
+debugging methods (such as ``nsGenericElement::List``) that take a
+``FILE*`` param:
+
+.. code::
+
+ Debug.EvaluateStatement {,,msvcr80d}(&__iob_func()[1])
+
+(Alternatively you can evaluate ``{,,msvcr80d}(&__iob_func()[1])`` in
+the Immediate window)
+
+Similarly, you can open a file on the disk using ``fopen``:
+
+.. code::
+
+ >Debug.EvaluateStatement {,,msvcr80d}fopen("c:\\123", "w")
+ 0x10311dc0 { ..snip.. }
+ >Debug.EvaluateStatement ((nsGenericElement*)0x03f0e710)->List((FILE*)0x10311dc0, 1)
+ <void>
+ >Debug.EvaluateStatement {,,msvcr80d}fclose((FILE*)0x10311dc0)
+ 0x00000000
+
+Note that you may not see the debugging output until you flush or close
+the file handle.
+
+Disabling ASSERTIONS
+~~~~~~~~~~~~~~~~~~~~
+
+There are basically two ways to disable assertions. One requires setting
+an environment variable, while the other affects only the currently
+running program instance in memory.
+
+Environment variable
+^^^^^^^^^^^^^^^^^^^^
+
+There is an environment variable that can disable breaking for
+assertions. This is how you would normally set it:
+
+.. code::
+
+ set XPCOM_DEBUG_BREAK=warn
+
+The environment variable takes also other values besides ``warn``, see
+``XPCOM_DEBUG_BREAK`` for more details.
+
+Note that unlike Unix, the default for Windows is not warn, it's to pop
+up a dialog. To set the environment variable for Visual Studio, use
+Project > Properties > Debugging > Environment and click the little box.
+Then use
+
+.. code::
+
+ XPCOM_DEBUG_BREAK=warn
+
+Changing running code
+^^^^^^^^^^^^^^^^^^^^^
+
+You normally shouldn't need to do this (just quit the application, set
+the environment variable described above, and run it again). And this
+can be **dangerous** (like **trashing your hard disc and corrupting your
+system**). So unless you feel comfortable with this, don't do it. **You
+have been warned!**
+
+It is possible to change the interrupt code in memory (which causes you
+to break into debugger) to be a NOP (no operation).
+
+You do this by running the program in the debugger until you hit an
+assertion. You should see some assembly code. One assembly code
+instruction reads "int 3". Check the memory address for that line. Now
+open memory view. Type/copy/drag the memory address of "int 3" into the
+memory view to get it to update on that part of the memory. Change the
+value of the memory to "90", close the memory view and hit "F5" to
+continue.
+
+Automatically handling ASSERTIONS without a debugger attached
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When an assertion happens and there is not a debugger attached, a small
+helper application
+(```windbgdlg.exe`` </En/Automatically_Handle_Failed_Asserts_in_Debug_Builds>`__)
+is run. That application can automatically select a response to the "Do
+you want to debug" dialog instead of prompting if you configure it, for
+more info, see
+```windbgdlg.exe`` </En/Automatically_Handle_Failed_Asserts_in_Debug_Builds>`__.
+
+Debugging optimized builds
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To effectively debug optimized builds, you should enable debugging
+information which effectively leaves the debug symbols in optimized code
+so you can still set breakpoints etc. Because the code is optimized,
+stepping through the code may occasionally provide small surprises when
+the debugger jumps over something.
+
+You need to make sure this configure parameter is set:
+
+.. code::
+
+ ac_add_options --enable-debug
+
+You can also choose to include or exclude specific modules.
+
+Console debugging
+~~~~~~~~~~~~~~~~~
+
+When printing to STDOUT from a content process, the console message will
+not appear on Windows. One way to view it is simply to disable e10s
+(``./mach run --disable-e10s``) but in order to debug with e10s enabled
+one can run
+
+::
+
+ ./mach run ... 2>&1 | tee
+
+It may also be necessary to disable the content sandbox
+(``MOZ_DISABLE_CONTENT_SANDBOX=1 ./mach run ...``).
+
+Running two instances of Mozilla simultaneously
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can run two instances of Mozilla (e.g. debug and optimized)
+simultaneously by setting the environment variable ``MOZ_NO_REMOTE``:
+
+.. code::
+
+ set MOZ_NO_REMOTE=1
+
+Or, starting with Firefox 2 and other Gecko 1.8.1-based applications,
+you can use the ``-no-remote`` command-line switch instead (implemented
+in
+`bug 325509 <https://bugzilla.mozilla.org/show_bug.cgi?id=325509>`__).
+
+You can also specify the profile to use with the ``-P profile_name``
+command-line argument.
+
+Debugging JavaScript
+~~~~~~~~~~~~~~~~~~~~
+
+You can use helper functions from
+`nsXPConnect.cpp <https://searchfox.org/mozilla-central/source/js/xpconnect/src/nsXPConnect.cpp>`__
+to inspect and modify the state of JavaScript code from the MSVS
+debugger.
+
+For example, to print current JavaScript stack to stdout, evaluate this
+in Immediate window:
+
+.. code::
+
+ {,,xul}DumpJSStack()
+
+Visual Studio will show you something in the quick watch window, but
+not the stack, you have to look in the OS console for the output.
+
+Also this magical command only works when you have JS on the VS stack.
+
+Debugging minidumps
+~~~~~~~~~~~~~~~~~~~
+
+See :ref:`debugging a minidump <Debugging A Minidump>`.
+
+Problems post-mortem debugging on Windows 7 SP1 x64?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you attempt to use ``NS_DebugBreak`` etc to perform post-mortem
+debugging on a 64bit Windows 7, but as soon as you try and continue
+debugging the program crashes with an Access Violation, you may be
+hitting a Windows bug relating to AVX support. For more details,
+including a work-around see `this blog
+post <http://www.os2museum.com/wp/?p=960>`__ or `this social.msdn
+thread <http://social.msdn.microsoft.com/Forums/vstudio/en-US/392ca62c-e502-42d9-adbc-b4e22d5da0c3/jit-debugging-32bit-app-crashing-with-access-violation>`__.
+(And just in-case those links die, the work-around is to execute
+
+::
+
+ bcdedit /set xsavedisable 1
+
+from an elevated command-prompt to disable AVX support.)
+
+Got a tip?
+~~~~~~~~~~
+
+If you think you know a cool Mozilla debugging trick, feel free to
+discuss it with `#developers <https://chat.mozilla.org/#/room/#developers:mozilla.org>`__ and
+then post it here.
+
+.. |Screenshot of disabling assertions| image:: https://developer.mozilla.org/@api/deki/files/420/=Win32-debug-nop.png
+ :class: internal
diff --git a/docs/contributing/debugging/img/about-processes.png b/docs/contributing/debugging/img/about-processes.png
new file mode 100644
index 0000000000..6f1563bcd1
--- /dev/null
+++ b/docs/contributing/debugging/img/about-processes.png
Binary files differ
diff --git a/docs/contributing/debugging/img/about-support.png b/docs/contributing/debugging/img/about-support.png
new file mode 100644
index 0000000000..2b12d1027d
--- /dev/null
+++ b/docs/contributing/debugging/img/about-support.png
Binary files differ
diff --git a/docs/contributing/debugging/img/crash-gmp.png b/docs/contributing/debugging/img/crash-gmp.png
new file mode 100644
index 0000000000..595e439307
--- /dev/null
+++ b/docs/contributing/debugging/img/crash-gmp.png
Binary files differ
diff --git a/docs/contributing/debugging/img/crash-gpu.png b/docs/contributing/debugging/img/crash-gpu.png
new file mode 100644
index 0000000000..9e40b63917
--- /dev/null
+++ b/docs/contributing/debugging/img/crash-gpu.png
Binary files differ
diff --git a/docs/contributing/debugging/img/crash-rdd.png b/docs/contributing/debugging/img/crash-rdd.png
new file mode 100644
index 0000000000..5548672d27
--- /dev/null
+++ b/docs/contributing/debugging/img/crash-rdd.png
Binary files differ
diff --git a/docs/contributing/debugging/img/crashlist.jpg b/docs/contributing/debugging/img/crashlist.jpg
new file mode 100644
index 0000000000..4cb376d0f2
--- /dev/null
+++ b/docs/contributing/debugging/img/crashlist.jpg
Binary files differ
diff --git a/docs/contributing/debugging/img/crashreporter.png b/docs/contributing/debugging/img/crashreporter.png
new file mode 100644
index 0000000000..3137c71c78
--- /dev/null
+++ b/docs/contributing/debugging/img/crashreporter.png
Binary files differ
diff --git a/docs/contributing/debugging/img/process-explorer.png b/docs/contributing/debugging/img/process-explorer.png
new file mode 100644
index 0000000000..26e7837a8d
--- /dev/null
+++ b/docs/contributing/debugging/img/process-explorer.png
Binary files differ
diff --git a/docs/contributing/debugging/img/sdk-installer.png b/docs/contributing/debugging/img/sdk-installer.png
new file mode 100644
index 0000000000..c7dcc1f1a4
--- /dev/null
+++ b/docs/contributing/debugging/img/sdk-installer.png
Binary files differ
diff --git a/docs/contributing/debugging/img/tabcrashed.png b/docs/contributing/debugging/img/tabcrashed.png
new file mode 100644
index 0000000000..9982e527ed
--- /dev/null
+++ b/docs/contributing/debugging/img/tabcrashed.png
Binary files differ
diff --git a/docs/contributing/debugging/img/windbg-in-startmenu.png b/docs/contributing/debugging/img/windbg-in-startmenu.png
new file mode 100644
index 0000000000..0819d4b424
--- /dev/null
+++ b/docs/contributing/debugging/img/windbg-in-startmenu.png
Binary files differ
diff --git a/docs/contributing/debugging/local_symbols.rst b/docs/contributing/debugging/local_symbols.rst
new file mode 100644
index 0000000000..875717e1a1
--- /dev/null
+++ b/docs/contributing/debugging/local_symbols.rst
@@ -0,0 +1,64 @@
+Symbolicating TreeHerder stacks locally
+=======================================
+
+When using tools like the :ref:`Dark Matter Detector (DMD)` or
+:ref:`refcount logging<Refcount Tracing and Balancing>` to
+investigate issues occurring on TreeHerder that you can't reproduce locally, you
+can often end up with unsymbolicated stacks. Fortunately, there is a way to
+symbolicate these stacks on your own machine.
+
+These instructions are for a Linux TreeHerder build for MacOS, so they might
+require some modifications for other combinations of platforms.
+
+Download ``target.tar.bz2`` and ``target.crashreporter-symbols.zip`` from the
+Build job. **Note that these files are very large so you'll want to delete
+them and the extracted files when you are done.**
+
+These files each contain a large number of files, so I'd recommend creating
+a directory for each of them. Call these ``<TARGET_DIR>`` and ``<SYMB_DIR>``,
+and move the prior two files into these two directories, respectively.
+
+Go to ``<TARGET_DIR>`` and run something like
+
+.. code-block:: shell
+
+ tar xf target.tar.bz2
+
+then go to ``<SYMB_DIR>`` and run something like
+
+.. code-block:: shell
+
+ unzip target.crashreporter-symbols.zip
+
+You should be able to delete the two original files now.
+
+Next we need to ensure that the locations of binaries are rewritten from
+where they are on TreeHerder to where we have them locally. We'll do this by
+editing ``fix_stacks.py``. This file is located in the ``tools/rb/`` directory of
+the Firefox source directory. You need to add these two lines to the function
+``fixSymbols``, after ``line_str`` is defined and before it is written to
+``fix_stacks.stdin``. I've done this right before the definition of
+``is_missing_newline``.
+
+.. code-block:: python
+
+ line_str = line_str.replace("/builds/worker/workspace/build/application/firefox/firefox",
+ "<TARGET_DIR>/firefox/firefox-bin")
+ line_str = line_str.replace("/builds/worker/workspace/build/application/firefox/libxul.so",
+ "<TARGET_DIR>/firefox/libxul.so")
+
+The initial locations should appear verbatim in the stack you are trying to
+symbolicate, so double check that they match. Also, ``<TARGET_DIR>`` of course
+needs to be replaced with the actual local directories where those files are
+located. Note that the ``firefox`` executable is changed to ``firefox-bin``.
+I don't know why that is necessary, but only the latter existed for me.
+
+Finally, we need to make it so that the stack fixer can find the location of
+the breakpad symbols we downloaded. If you are running ``fix_stacks.py`` via
+``dmd.py`` or directly (in a recent version), you can do this by running with the
+environment variable ``BREAKPAD_SYMBOLS_PATH`` set to the ``<SYMB_DIR>`` from above.
+If that doesn't work, you'll have to edit ``initFixStacks`` in ``fix_stacks.py`` to
+set ``breakpadSymsDir`` to ``<SYMB_DIR>``.
+
+With all of that done, you should now be able to run ``dmd.py`` or ``fix_stacks.py``
+to fix the stacks. Note that the stack fixing process can take a minute or two.
diff --git a/docs/contributing/debugging/process_dump_task_manager.rst b/docs/contributing/debugging/process_dump_task_manager.rst
new file mode 100644
index 0000000000..d345171856
--- /dev/null
+++ b/docs/contributing/debugging/process_dump_task_manager.rst
@@ -0,0 +1,69 @@
+How to get a process dump with Windows Task Manager
+===================================================
+
+Introduction
+------------
+
+When tracking down the causes of process hangs, it is often helpful to
+obtain a process dump while the process is experiencing a hang. This
+article describes how to get a process dump with Task Manager on
+Windows. (To get a process dump for Thunderbird or some other product,
+substitute the product name where ever you see Firefox in these
+instructions.)
+
+
+Caution
+-------
+
+The memory dump that will be created through this process is a complete
+snapshot of the state of Firefox when you create the file, so it
+contains URLs of active tabs, history information, and possibly even
+passwords depending on what you are doing when the snapshot is taken. It
+is advisable to create a new, blank profile to use when reproducing the
+hang and capturing the memory dump. Please ask for help doing this!
+
+
+Requirements
+------------
+
+Windows
+ To get a process dump, you need to be using Windows Vista or above.
+A Firefox nightly or release
+ You need a Firefox version for which symbols are available from the
+ :ref:`symbol server <Using The Mozilla Symbol Server>`. You
+ can use any `official nightly
+ build <https://ftp.mozilla.org/pub/firefox/nightly/>`__ or released
+ version of Firefox from Mozilla. You can find the latest trunk
+ nightly builds under
+ `http://ftp.mozilla.org/pub/mozilla.o.../latest-trunk/ <http://ftp.mozilla.org/pub/mozilla.org/firefox/nightly/latest-trunk/>`__.
+
+
+Creating the Dump File
+----------------------
+
+Ensure that Firefox is not already running.
+
+
+Run Firefox, reproduce the hang
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Start Firefox and perform whatever steps are necessary to cause Firefox
+to hang. Once the browser hangs, continue with the steps below.
+
+
+After the hang
+~~~~~~~~~~~~~~
+
+#. Open Windows Task Manager (CTRL+SHIFT+ESC).
+#. Find Firefox.exe among the list of processes.
+#. Right-click Firefox.exe and select "Create dump file". Task manager
+ should indicate where the dump file was written to.
+
+
+See also
+--------
+
+- :ref:`How to get a stacktrace for a bug report <How to get a stacktrace for a bug report>`
+- `How to create a user-mode process dump file in Windows Vista and in
+ Windows 7
+ (MSDN) <https://docs.microsoft.com/en-us/windows/client-management/generate-kernel-or-complete-crash-dump#manually-generate-a-memory-dump-file>`__
diff --git a/docs/contributing/debugging/stacktrace_report.rst b/docs/contributing/debugging/stacktrace_report.rst
new file mode 100644
index 0000000000..2cae230745
--- /dev/null
+++ b/docs/contributing/debugging/stacktrace_report.rst
@@ -0,0 +1,153 @@
+How to get a stacktrace for a bug report
+========================================
+
+If you file a bug report in Bugzilla about a crash you should include a
+stacktrace (call stack) in your report. A stacktrace will tell Mozilla
+developers what crashed and provide a starting point for investigating
+its cause. This article describes how to use the Mozilla Crash Reporter
+(Breakpad) to get a crash ID, which our engineers can use to get a
+stacktrace, and alternative ways to get a stacktrace if you can't get a
+crash ID.
+
+Requirements
+------------
+
+You need a binary build of Firefox from
+`Mozilla.org <https://www.mozilla.org/firefox/>`__. SeaMonkey and
+Thunderbird also support crash reporting.
+
+Mozilla's crash report server currently only has debug information for
+Mozilla builds and thus the crash reporter cannot work if you use a
+build from a Linux distribution or if you compile from source code. In
+these cases you will need to use one of the :ref:`alternative
+methods <Alternative ways to get a stacktrace>` listed below.
+
+.. note::
+
+ **Note:** When filing a crash report, it is important to know whether
+ the crash occurs with `Firefox safe
+ mode <http://support.mozilla.com/kb/Safe+Mode>`__. This helps
+ engineers determine whether a particular
+ `extension <http://support.mozilla.com/kb/Troubleshooting+extensions+and+themes>`__
+ or
+ `plugin <http://support.mozilla.com/kb/Troubleshooting+plugins>`__
+ is the cause of the crash.
+
+
+How to get a crash ID with the Mozilla Crash Reporter
+-----------------------------------------------------
+
+1 - Crash and submit a report to the system.
+
+.. image:: img/crashreporter.png
+
+The Mozilla Crash Reporter window should automatically come up after Firefox crashes.
+If you have any additional information about the crash, such as additional detail on
+what you were doing at the time that may have triggered the crash, please enter it
+into the comments box. Be sure that you **check the "Tell Mozilla about this crash"**
+checkbox and click the restart button. The crash reporter should now submit the
+crash report and Firefox should open again.
+
+.. note::
+
+ The "Details" button gives additional data about the incident,
+ however this is not useful in a bug report.
+
+
+2 - Tell us the ID of the report you submitted.
+
+.. image:: img/crashlist.jpg
+
+To access all of your submitted reports type "about:crashes" into the Firefox address bar
+and press enter. Firefox should open a list of IDs for your submitted crash reports.
+Copy two or three of the IDs for the appropriate crashes and paste them into your
+Bugzilla report. Please check the listed times to avoid copying the ID of an unrelated
+crash report.
+
+.. note::
+
+ You can prefix a "bp-" to the beginning of an ID to make Bugzilla turn it
+ into a link: bp-a70759c6-1295-4160-aa30-bc4772090918
+
+
+How to get the crash ID if Firefox crashes on startup
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If Firefox crashes on startup you can still access your submitted crash
+reports. Crash reports are accessible from all Firefox profiles, so if a
+`new
+profile <https://support.mozilla.org/kb/profile-manager-create-remove-switch-firefox-profiles>`__
+does not crash you can use it to access them through "about:crashes" as above.
+
+
+Accessing crash report IDs outside of Firefox
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If you cannot load Firefox at all you can find the crash report files at
+this location depending on your operating system:
+
+* Windows : ``%APPDATA%\Mozilla\Firefox\Crash Reports\submitted\``
+* macOS : ``~/Library/Application Support/Firefox/Crash Reports/submitted/``
+* Linux : ``~/.mozilla/firefox/Crash Reports/submitted/``
+
+Each file in this folder contains one submitted crash report ID. You can
+check the modified or creation time for each file to discern which crash
+reports are relevant to your bug report.
+
+.. _Alternative ways to get a stacktrace:
+
+Alternative ways to get a stacktrace
+------------------------------------
+
+If the Mozilla crash reporter doesn't come up or isn't available you
+will need to obtain a stacktrace manually:
+
+
+Windows
+~~~~~~~
+
+See the article :ref:`Create a stacktrace with Windbg <How to get a stacktrace with WinDbg>` for information
+on how to do this.
+
+For a full process dump, see :ref:`How to get a process dump with Windows
+Task Manager`.
+
+
+macOS
+~~~~~
+
+Run /Applications/Utilities/Console.app. Expand "~/Library/Logs" and
+"CrashReporter", then look for logs for "firefox-bin".
+
+
+Linux
+~~~~~
+
+Note that for most distros, the package you need to get symbols for will
+be something like "xulrunner", not "firefox".
+
+
+Crash reports files on your computer
+------------------------------------
+
+When Breakpad initially catches a crash it first writes crash report
+files (e.g. .dump and .extra files) into the 'pending' subdirectory of
+its 'Crash Reports' directory.
+
+If Breakpad successfully sends the crash report to the reporting server
+then, by default, the files added to the 'pending' subdirectory for the
+crash are removed, and a .txt file is placed in the 'submitted'
+directory containing the crash ID created by the reporting server.
+
+If you want Breakpad to leave the .dump and .extra files on your
+computer so that you can examine them locally, then set the
+MOZ_CRASHREPORTER_NO_DELETE_DUMP environment variable to 1.
+
+- Ubuntu: `Instructions from the Ubuntu
+ Team <https://wiki.ubuntu.com/MozillaTeam/Bugs#Obtain%20a%20backtrace%20from%20an%20apport%20crash%20report%20(using%20gdb)>`__
+- openSUSE: `General instructions from
+ openSUSE <https://en.opensuse.org/openSUSE:Bugreport_application_crashed>`__
+- Fedora: `Capturing Stack
+ Traces <https://fedoraproject.org/wiki/StackTraces>`__
+- Gentoo: `Debugging using
+ GDB <https://wiki.gentoo.org/wiki/Debugging_with_GDB>`__
diff --git a/docs/contributing/debugging/stacktrace_windbg.rst b/docs/contributing/debugging/stacktrace_windbg.rst
new file mode 100644
index 0000000000..a0d2abfc25
--- /dev/null
+++ b/docs/contributing/debugging/stacktrace_windbg.rst
@@ -0,0 +1,232 @@
+How to get a stacktrace with WinDbg
+===================================
+
++--------------------------------------------------------------------+
+| This page is an import from MDN and the contents might be outdated |
++--------------------------------------------------------------------+
+
+Introduction
+------------
+
+Sometimes you need to get a stacktrace (call stack) for a crash or hang
+but `Breakpad <http://kb.mozillazine.org/Breakpad>`__ fails because it's
+a special crash or a hang. This article describes how to get a
+stacktrace in those cases with WinDbg on Windows. (To get a stacktrace
+for Thunderbird or some other product, substitute the product name where
+ever you see Firefox in this instructions.)
+
+Requirements
+------------
+
+To get such a stacktrace you need to install the following software:
+
+Debugging Tools for Windows
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Microsoft distributes the Debugging Tools for Windows for free, those
+include WinDbg which you will need here. Download it from `Install
+Debugging Tools for
+Windows <https://docs.microsoft.com/en-us/windows-hardware/drivers/download-the-wdk>`__.
+(*You'll want the 32-bit version*, even if you are using a 64-bit
+version of Windows) Then install it, the standard settings in the
+installation process are fine.
+
+A Firefox nightly or release
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You need a Firefox version for which symbols are availables from the
+:ref:`symbol server <Using The Mozilla Symbol Server>` to use
+with WinDbg. You can use any `official nightly
+build <https://ftp.mozilla.org/pub/firefox/nightly/>`__ or released
+version of Firefox from Mozilla. You can find the latest trunk nightly
+builds under
+`http://ftp.mozilla.org/pub/mozilla.o.../latest-trunk/ <https://ftp.mozilla.org/pub/firefox/nightly/latest-mozilla-central/>`__.
+
+
+Debugging
+---------
+
+To begin debugging, ensure that Firefox is not already running and open
+WinDbg from the Start menu. (Start->All Programs->Debugging Tools for
+Windows->WinDbg) Next, open the **"File"** menu and choose **"Open
+Executable..."**. In the file chooser window that appears, open the
+firefox.exe executable in your Firefox program folder (C:\Program
+Files\Mozilla Firefox).
+
+You should now see a "Command" text window with debug output at the top
+and an input box at the bottom. Before debugging can start, several
+commands must be entered into the one-line input box at the bottom of
+the Command window.
+
+.. note::
+
+ Tip: All commands must be entered exactly as written, one line at a
+ time, into the bottom of the Command box.
+
+ - Copying and pasting each line is the easiest method to avoid
+ mistakes
+ - Some commands start with a period (.) or a pipe character (|),
+ which is required. (The keystroke for a pipe character on US
+ keyboards is SHIFT+\)
+ - Submit the log file on a bug or via the support site, even if
+ nothing seems to happen during the debug process
+
+
+Start debugging
+~~~~~~~~~~~~~~~
+
+Now that Firefox is opened in the debugger, you need to configure your
+WinDbg to download symbols from the Mozilla symbol server. To load the
+symbols, enter the three commands below, pressing enter after each one.
+(More details are available at :ref:`symbol server <Using The Mozilla Symbol Server>`.)
+
+::
+
+ .sympath SRV*c:\symbols*http://symbols.mozilla.org/firefox;SRV*c:\symbols*http://msdl.microsoft.com/download/symbols
+ .symfix+ c:\symbols
+ .reload /f
+
+Now wait for the symbols to download. This may take some time depending
+on your connection speed; the total size of the Mozilla and Microsoft
+symbols download is around 1.4GB. WinDbg will show "Busy" at the bottom
+of the application window until the download is complete.
+
+Once the download is complete, you need to configure WinDbg to examine
+child processes, ignore a specific event caused by Flash Player, and
+record a log of loaded modules. You will also want to open a log file to
+save data you collect. To do this, enter these four commands, pressing
+enter after each one.
+
+::
+
+ .logopen /t c:\temp\firefox-debug.log
+ .childdbg 1
+ .tlist
+ sxn gp
+ lm
+
+If you see firefox.exe listed in the output from .tlist more than once,
+then you are already running the application and need to close the
+running instance first before you start debugging, otherwise you won't
+get useful results.
+
+Now run Firefox by opening the **Debug** menu and clicking **Go**.
+**While Firefox is running, you will not be able to type any commands
+into the debugger.** After it starts, try to reproduce the crash or
+hanging issue that you are seeing.
+
+.. note::
+
+ If Firefox fails to start, and you see lines of text followed by a
+ command prompt in the debugger, a "breakpoint" may have been
+ triggered. If you are prompted for a command but don't see an error
+ about a crash, go back to the **Debug** menu and press **Go**.
+
+Once the browser crashes, you will see an error (such as "Access
+violation") in the WinDbg Command window. If Firefox hangs and there is
+no command prompt available in the debugger, open the **Debug** menu and
+choose **Break.** Once the browser has crashed or been stopped, continue
+with the steps below.
+
+
+After the crash or hang
+~~~~~~~~~~~~~~~~~~~~~~~
+
+You need to capture the debug information to include in a bug comment or
+support request. Enter these three commands, one at a time, to get the
+stacktrace, crash/hang analysis and log of loaded modules. (Again, press
+Enter after each command.)
+
+::
+
+ ~* kp
+ !analyze -v -f
+ lm
+
+After these steps are completed, find the file
+**c:\temp\firefox-debug-(Today's Date).txt** on your hard drive. To
+provide the information to the development community, submit this file
+with a `support request <https://support.mozilla.com/>`__ or attach it
+to a related bug on `Bugzilla <https://bugzilla.mozilla.org/>`__.
+
+
+Producing a minidump
+~~~~~~~~~~~~~~~~~~~~
+
+Sometimes the stacktrace alone is not enough information for a developer
+to figure out what went wrong. A developer may ask you for a "minidump"
+or a "full memory dump", which are files containing more information
+about the process. :ref:`You can easily produce minidumps from WinDBG and
+provide them to developers <Capturing a minidump>`.
+
+FAQ
+
+Q: I am running Windows 7 (32-bit or 64-bit) and I see an exception in
+the WinDbg command window that says 'ntdll32!LdrpDoDebuggerBreak+0x2c'
+or 'ntdll32!LdrpDoDebuggerBreak+0x30'. What do I do now?
+
+A: If you see 'int 3' after either of those exceptions, you will need to
+execute the following commands in WinDbg.
+
+::
+
+ bp ntdll!LdrpDoDebuggerBreak+0x30
+ bp ntdll!LdrpDoDebuggerBreak+0x2c
+ eb ntdll!LdrpDoDebuggerBreak+0x30 0x90
+ eb ntdll!LdrpDoDebuggerBreak+0x2c 0x90
+
+| Make sure you enter them one at a time and press enter after each one.
+ If you use the 64-bit version of Windows, you need to replace "ntdll"
+ in these commands with "ntdll32".
+| Q: The first four frames of my stack trace look like this:
+
+::
+
+ 0012fe20 7c90e89a ntdll!KiFastSystemCallRet
+ 0012fe24 7c81cd96 ntdll!ZwTerminateProcess+0xc
+ 0012ff20 7c81cdee kernel32!_ExitProcess+0x62
+
+ 0012ff34 6000179e kernel32!ExitProcess+0x14
+
+This looks wrong to me?!
+
+A: You ran the application without the "Debug child processes also"
+check box being checked. You need to detach the debugger and open the
+application again, this time with the check box being checked.
+
+Q: WinDbg tells me that it is unable to verify checksum for firefox.exe.
+Is this normal?
+
+A: Yes, this is normal and can be ignored.
+
+Q: Should I click yes or no when WinDbg asks me to "Save information for
+workspace?"
+
+A: Click yes and WinDbg will save you from having to enter in the symbol
+location for Firefox.exe in the future. Click no if you'd rather not
+having WinDbg save this information.
+
+Q: I'm seeing "wow64" on top of each thread, is that ok ?
+
+A: No, you are running a 64 bit version of Windbg and trying to debug a
+32 bit version of the mozilla software. Redownload and install the 32
+bit version of windbg.
+
+
+Troubleshooting: Symbols will not download
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If symbols will not download no matter what you do, the problem may be
+that Internet Explorer has been set to the **Work Offline** mode. You
+will not receive any warnings of this in Windbg, Visual C++ or Visual
+Studio. Even using the command line with symchk.exe to download symbols
+will fail. This is because Microsoft uses Internet Explorer's internet &
+proxy settings to download the symbol files. Check the File menu of
+Internet Explorer to ensure "Work Offline" is unchecked.
+
+
+See also
+--------
+
+- :ref:`symbol server <Using The Mozilla Symbol Server>` Maps addresses to human readable strings.
+- :ref:`source server <Using The Mozilla Source Server>` Maps addresses to source code lines
diff --git a/docs/contributing/debugging/understanding_crash_reports.rst b/docs/contributing/debugging/understanding_crash_reports.rst
new file mode 100644
index 0000000000..9f8e5bbe03
--- /dev/null
+++ b/docs/contributing/debugging/understanding_crash_reports.rst
@@ -0,0 +1,325 @@
+Understanding Crash Reports
+===========================
+
++--------------------------------------------------------------------+
+| This page is an import from MDN and the contents might be outdated |
++--------------------------------------------------------------------+
+
+If a user experiences a crash they will be prompted to submit a raw
+crash report, which is generated by Breakpad. The raw crash report is
+received by `Socorro <https://github.com/mozilla/socorro>`__ which
+`creates <https://github.com/mozilla/socorro/blob/master/socorro/processor/mozilla_processor_2015.py>`__
+a processed crash report. The processed crash report is based on the raw
+crash report but also has a signature, classifications, and a number of
+improved fields (e.g. OS, product, version). Many of the fields in both
+the raw crash report and the processed crash report are viewable and
+searchable on `crash-stats <https://crash-stats.mozilla.org/>`__.
+Although there are two distinct crash reports, the raw and the
+processed, people typically talk about a single "crash report" because
+crash-stats mostly presents them in a combined way.
+
+Each crash report contains a wealth of data about the crash
+circumstances. Despite this, many crash reports lack sufficient data for
+a developer to understand why the crash occurred. As well as providing a
+general overview, this page aims to highlight parts of a crash report
+that may provide non-obvious insights.
+
+Note that most crash report fields are visible, but a few
+privacy-sensitive parts of it are only available to users who are logged
+in and have "minidump access". A relatively small number of users have
+minidump access, and they are required to follow certain rules. For
+access, see the `Protected Data Access docs on Crash Stats
+<https://crash-stats.mozilla.org/documentation/protected_data_access/>`__.
+
+Each crash report has the following tabs: Details, Metadata, Modules,
+Raw Dump, Extensions, and (optional) Correlations.
+
+Details tab
+-----------
+
+The Details tab is the first place to look because it contains the most
+important pieces of information.
+
+Primary fields
+~~~~~~~~~~~~~~
+
+| The first part of the Details tab shows a table containing the most
+ important crash report fields. It includes such things as when the
+ crash occurred, in which product and version, the crash kind, and
+ various details about the OS and configuration of the machine on which
+ the crash occurred. The following screenshot shows some of these
+ fields.
+| |Example fields in the "Details" tab of a crash report|
+
+All fields have a tool-tip. For many fields, the tool-tip describes its
+meaning. For all fields, the tool-tip indicates the key to use when you
+want to do searches involving this field. (The field name is usually but
+not always similar to the search key. E.g. the field "Adapter Device ID"
+has the search key "adapter_device_id".) These descriptions are shown in
+the `SuperSearchFields
+API <https://crash-stats.mozilla.org/api/SuperSearchFields/>`__ and can be
+`modified in super_search_fields.py <https://github.com/mozilla-services/socorro/blob/main/socorro/external/es/super_search_fields.py>`__
+or by writing up a `bug in Socorro <https://bugzilla.mozilla.org/enter_bug.cgi?format=__standard__&product=Socorro>`__.
+
+The fields present in this tab vary depending on the crash kind. Not all
+fields are always present.
+
+The "Signature" field is the main identifier or label for a crash report.
+Rather than considering each crash report in isolation, we want to put
+crash reports into clusters so we can deal with groups of them at once.
+An ideal clustering algorithm would put all crash reports with the same
+root cause into a single cluster, and all crash reports with different
+root causes into different clusters. The crash signature is our
+imperfect but still useful attempt at such an algorithm. Most crash
+signatures are based on the crashing stack trace, but some
+special-purpose annotations are used to indicate particular kinds of
+crashes.
+
+- ``Abort``: A controlled abort, e.g. via ``NS_RUNTIMEABORT``.
+ (Controlled aborts that occur via ``MOZ_CRASH`` or
+ ``MOZ_RELEASE_ASSERT`` currently don't get an ``Abort`` annotation,
+ but they do get a "MOZ_CRASH Reason" field.)
+- ``OOM | <size>``, where ``<size>`` is one of ``large``, ``small``,
+ ``unknown``: an out-of-memory (OOM) abort. The ``<size>`` annotation
+ is determined by the "OOM Allocation Size" field; if that field is
+ missing ``<size>`` will be ``unknown``.
+- ``hang``: a hang prior to shutdown.
+- ``shutdownhang``: a hang during shutdown.
+- ``IPCError-browser``: a problem involving IPC. If the parent Firefox
+ process detects that the child process has sent broken or
+ unprocessable IPDL data, or is not shutting down in a timely manner,
+ it kills the child process with a crash report. These crashes will
+ now have a signature that indicates why the process was killed,
+ rather than the child stack at the moment.
+
+When no special-purpose annotation is present and the signature begins
+with a stack frame, it's usually a vanilla uncontrolled crash. The crash
+cause can be determined from the "Crash Reason" field. Most commonly
+it's a bad memory access. In that case, on Windows you can tell from the
+reason field if the crash occurred while reading, writing or executing
+memory (e.g. ``EXCEPTION_VIOLATION_ACCESS_READ`` indicates a bad memory
+read). On Mac and Linux the reason will be SIGSEGV or SIGBUS and you
+cannot tell from this field what kind of memory access it was.
+
+See `this
+file <https://github.com/mozilla-services/socorro/blob/master/socorro/signature/README.rst>`__
+for a detailed explanation of the crash report signature generation
+procedure, and for information on how modify this procedure.
+
+There are no fields that uniquely identify the user that a crash report
+came from, but if you want to know if multiple crashes come from a
+single user the "Install Time" field is a good choice. Use it in
+conjunction with other fields that don't change, such as those
+describing the OS or graphics card, for additional confidence.
+
+For bad memory accesses, the "Crash Address" field can give additional
+indications what went wrong.
+
+- 0x0 is probably a null pointer deference[*].
+- Small addresses like 0x8 can indicate an object access (e.g.
+ ``this->mFoo``) via a null ``this`` pointer.
+- Addresses like 0xfffffffffd8 might be stack accesses, depending on
+ the platform[*].
+- Addresses like 0x80cdefd3 might be heap accesses, depending on the
+ platform.
+- Addresses may be poisoned: 0xe4 indicates the address comes from
+ memory that has been allocated by jemalloc but not yet initialized;
+ 0xe5 indicates the address comes from memory freed by jemalloc. The
+ JS engine also has multiple poison values defined in
+ ``js/src/jsutil.h``.
+
+[*] Note that due to the way addressing works on x86-64, if the crash
+address is 0x0 for a Linux/macOS crash report, or 0xffffffffffffffff for
+a Windows crash report, it's highly likely that the value is incorrect.
+(There is a `bug
+report <https://bugzilla.mozilla.org/show_bug.cgi?id=1493342>`__ open
+for this problem.) You can sanity-check these crashes by looking at the
+raw dump or minidump in the Raw Dump tab (see below).
+
+Note that for non-release builds the "Version" field represents multiple
+different builds since nightly and beta version numbers are reused for
+builds created over a series of days until the version number is bumped.
+(The "Build ID" field can disambiguate.) It's not currently possible to
+`restrict searches to a given version or
+later <https://bugzilla.mozilla.org/show_bug.cgi?id=1401517>`__ (using
+>= with a build ID and a given release channel may work around this).
+
+Some fields, such as "URL" and "Email Address", are privacy-sensitive
+and are only visible to users with minidump access.
+
+The Windows-only "Total Virtual Memory" field indicates if the Firefox
+build and OS are 32-bit or 64-bit.
+
+- A value of 2 GiB indicates 32-bit Firefox on 32-bit Windows.
+- A value of 3 or 4 GiB indicates 32-bit Firefox on 64-bit Windows
+ (a.k.a. "WoW64"). Such a user could switch to 64-bit Firefox.
+- A value much larger than 4 GiB (e.g. 128 TiB) indicates 64-bit
+ Firefox. (The "Build Architecture" field should be "amd64" in this
+ case.)
+
+Some crash reports might contain a memory report. This memory report will
+have been made some time before the crash, at a time when available
+memory was low. In this case, a number of key measurements from the
+memory report are shown in the Details tab, each one having a field name
+starting with "MR:", short for "memory report". The full memory report
+can be obtained in the Raw Dump tab (see below).
+
+Bug-related information
+~~~~~~~~~~~~~~~~~~~~~~~
+
+The second part of the Details tab shows bug-related information, as the
+following screenshot shows.
+
+|Information relating to bug reports in the "Details" tab of a crash
+report|
+
+The "Report this bug in" links can be used to easily file bug reports.
+Each one links to a Bugzilla bug report creation page that has various
+fields pre-filled, such as the crash signature.
+
+The "Related Bugs" section shows related bug reports, as determined by
+the crash signature.
+
+Stack traces
+~~~~~~~~~~~~
+
+The third part of the Details tab shows the stack trace and thread
+number of the crashing thread, as the following screenshot shows.
+
+|Information relating to threads in the "Details" tab of a crash report|
+
+Each stack frame has a link to the source code, when possible. If a
+crash is new, the regressing changeset can often be identified by
+looking for recent changes in the blame annotations for one or more of
+the top stack frames. Blame annotations are also good for identifying
+who might know about the code in question.
+
+Sometimes the highlighted source code is puzzling, e.g. the identified
+line may not touch memory even though the crash is memory-related. This
+can be caused by compiler optimizations. It's often better to look at
+the disassembly (e.g. in a minidump) to understand exactly what code is
+being executed.
+
+Stack frame entries take on a variety of forms.
+
+- The simplest are functions names, such as ``NS_InitXPCOM2``.
+- Name/address pairs such as ``nss3.dll@0x1eb720`` are within system
+ libraries.
+- Names such as ``F1398665248_____________________________`` ('F'
+ followed by many numbers then many underscores) are in Flash.
+- Addresses such as ``@0xe1a850ac`` may indicate an address that wasn't
+ part of any legitimate code. If an address such as this occurs in the
+ first stack frame, the crash may be
+ `exploitable <https://developer.mozilla.org/en-US/docs/Mozilla/Security/Exploitable_crashes>`__.
+
+Stack traces for other threads can be viewed by clicking on the small
+"Show other threads" link.
+
+If the crash report is for a hang, the crashing thread will be the
+"watchdog" thread, which exists purely to detect hangs; its top stack
+frame will be something
+like\ :literal:`mozilla::`anonymous namespace'::RunWatchdog`. In that
+case you should look at the other threads' stack traces to determine the
+problem; many of them will be waiting on some kind of response, as shown
+by a top stack frame containing a function like
+``NtWaitForSingleObject`` or ``ZwWaitForMultipleObjects``.
+
+Metadata tab
+------------
+
+The Metadata tab is similar to the first part of the Details tab,
+containing a table with various fields. These are the fields from the
+raw crash report, ordered alphabetically by field name, but with
+privacy-sensitive fields shown only to users with minidump access. There
+is some overlap with the fields shown in the Details tab.
+
+Modules tab
+-----------
+
+The modules tab shows all the system libraries loaded at the time of the
+crash, as the following screenshot shows.
+
+|Table of modules in the "Modules" tab of a crash report|
+
+On Windows these are mostly DLLs, on Mac they are mostly ``.dylib``
+files, and on Linux they are mostly ``.so`` files.
+
+This information is most useful for Windows crashes, because DLLs loaded
+by antivirus software or malware often cause Firefox to crash.
+Correlations between loaded modules and crash signatures can be seen in
+the "Correlations" tab (see below).
+
+`This page <https://support.mozilla.org/en-US/kb/helping-crashes>`__
+says that files lacking version/debug identifier/debug filename are
+likely to be malware.
+
+Raw Dump tab
+------------
+
+The first part of the Raw Dump tab shows the raw crash report, in JSON
+format. Once again, privacy-sensitive fields are shown only to users
+with minidump access.
+
+|JSON data in the "Raw Dump" tab of a crash report|
+
+For users with minidump access, the second part of the Raw Dump tab has
+some links, as the following screenshot shows.
+
+|Links to downloadable files in the "Raw Dump" tab of a crash report|
+
+These links are to the following items.
+
+#. A minidump. Minidumps can be extremely useful in understanding a
+ crash report; see :ref:`this page <Debugging A Minidump>` for an
+ explanation how to use them.
+#. The aforementioned JSON raw crash report.
+#. The memory report contained within the crash report.
+#. The unredacted crash report, which has additional information.
+
+Extensions tab
+--------------
+
+The Extensions tab shows which extensions are installed and enabled.
+
+|Table of extensions in the "Extensions" tab of a crash report|
+
+Usually it just shows an ID rather than the proper extension name.
+
+Note that several extensions ship by default with Firefox and so will be
+present in almost all crash reports. (The exact set of default
+extensions depends on the release channel.) The least obvious of these
+has an Id of ``{972ce4c6-7e08-4474-a285-3208198ce6fd}``, which is the
+default Firefox theme. Some (but not all) of the other extensions
+shipped by default have the following Ids: ``webcompat@mozilla.org``,
+``e10srollout@mozilla.org``, ``firefox@getpocket.com``,
+``flyweb@mozilla.org``, ``loop@mozilla.org``.
+
+If an extension only has a hexadecimal identifier, a Google search of
+that identifier is usually enough to identify the extension's name.
+
+This information is useful because some crashes are caused by
+extensions. Correlations between extensions and crash signatures can be
+seen in the "Correlations" tab (see below).
+
+Correlations tab
+----------------
+
+This tab is only shown when crash-stats identifies correlations between
+a crash and modules or extensions that are present, which happens
+occasionally.
+
+See also
+--------
+
+- `A talk about understanding crash
+ reports <https://air.mozilla.org/a-talk-about-understanding-crash-reports/>`__,
+ by David Baron, from March 2016.
+- :ref:`A guide to searching crash reports`
+
+.. |Example fields in the "Details" tab of a crash report| image:: https://mdn.mozillademos.org/files/13579/Details1.png
+.. |Information relating to bug reports in the "Details" tab of a crash report| image:: https://mdn.mozillademos.org/files/13581/Details2.png
+.. |Information relating to threads in the "Details" tab of a crash report| image:: https://mdn.mozillademos.org/files/13583/Details3.png
+.. |Table of modules in the "Modules" tab of a crash report| image:: https://mdn.mozillademos.org/files/13593/Modules1.png
+.. |JSON data in the "Raw Dump" tab of a crash report| image:: https://mdn.mozillademos.org/files/13595/RawDump1.png
+.. |Links to downloadable files in the "Raw Dump" tab of a crash report| image:: https://mdn.mozillademos.org/files/14047/raw-dump-links.png
+.. |Table of extensions in the "Extensions" tab of a crash report| image:: https://mdn.mozillademos.org/files/13599/Extensions1.png
diff --git a/docs/contributing/directory_structure.rst b/docs/contributing/directory_structure.rst
new file mode 100644
index 0000000000..fd447278c2
--- /dev/null
+++ b/docs/contributing/directory_structure.rst
@@ -0,0 +1,565 @@
+Firefox Source Code Directory Structure
+=======================================
+
+This article provides an overview of what the various directories contain.
+
+To simply take a look at the Firefox source code, you do not need to
+download it. You can look at the source directly with your web browser
+using Searchfox (start at https://searchfox.org/mozilla-central/source for
+the complete firefox source code of branch HEAD).
+
+In order to modify the source, you have to acquire it either by
+downloading a :ref:`snapshot <Mercurial Overview>` of the sources or
+by checking out the current sources from
+:ref:`the repository <Firefox Contributors' Quick Reference>`.
+
+This document describes the directory structure -- i.e., directories that
+are used by at least some of the
+Mozilla project's client products. There are other directories in the
+other Mozilla repository, such as those for Web tools and those for the
+Classic codebase.
+
+See the `more detailed overview of the pieces of Gecko <https://wiki.mozilla.org/Gecko:Overview>`__.
+
+.cargo
+------
+
+Configuration files for the `Cargo package
+manager <https://crates.io/>`__.
+
+.vscode
+-------
+
+Configuration files used by the `Visual Studio Code
+IDE <https://code.visualstudio.com/>`__ when working in the
+mozilla-central tree.
+
+accessible
+----------
+
+Files for accessibility (i.e., MSAA (Microsoft Active Accessibility),
+ATK (Accessibility Toolkit, used by GTK) support files). See
+`Accessibility <https://developer.mozilla.org/docs/Web/Accessibility>`__.
+
+
+browser
+-------
+
+Contains the front end code (in XUL, Javascript, XBL, and C++) for the
+Firefox desktop browser. Many of these files started off as a copy of files in
+`xpfe <#xpfe>`__.
+
+browser/extensions
+------------------
+
+Contains `PDF.js <https://mozilla.github.io/pdf.js/>`__ and
+`WebCompat <https://github.com/mozilla/webcompat-addon>`__ built-in extensions.
+
+browser/themes
+--------------
+
+Contains images and CSS files to skin the browser for each OS (Linux,
+Mac and Windows)
+
+build
+-----
+
+Miscellaneous files used by the build process. See also `config <#config>`__.
+
+caps
+----
+
+Capability-based web page security management. It contains C++ interfaces
+and code for determining the capabilities of content based on the
+security settings or certificates (e.g., VeriSign). See `Component
+Security <https://www.mozilla.org/projects/security/components/>`__ .
+
+chrome
+------
+
+:ref:`Chrome registry <Chrome Registration>` used with `toolkit <#toolkit>`__/.
+These files were originally copies of files in `rdf/chrome/`.
+
+config
+------
+
+More files used by the build process, common includes for the makefiles,
+etc.
+
+
+devtools
+--------
+
+The Firefox Developer Tools server and client components. See :ref:`contributor <devtools-contributor-doc>` and :ref:`user <devtools-user-doc>` documentation.
+
+
+docs
+----
+
+Contains the documentation configuration (`Sphinx <http://www.sphinx-doc.org/>`__ based), the index page
+and the contribution pages.
+
+
+docshell
+--------
+
+Implementation of the docshell, the main object managing things related
+to a document window. Each frame has its own docshell. It contains
+methods for loading URIs, managing URI content listeners, etc. It is the
+outermost layer of the embedding API used to embed a Gecko browser into
+an application.
+
+dom
+---
+
+- :ref:`IDL definitions <XPIDL>` of the interfaces defined by
+ the DOM specifications and Mozilla extensions to those interfaces
+ (implementations of these interfaces are primarily, but not
+ completely, in `content <#content>`__).
+- The parts of the connection between JavaScript and the
+ implementations of DOM objects that are specific both to JavaScript
+ and to the DOM.
+- Implementations of a few of the core "DOM Level 0" objects, such as
+ `window <https://developer.mozilla.org/docs/Web/API/Window>`__ , `window.navigator <https://developer.mozilla.org/docs/Web/API/Window/navigator>`__, `window.location <https://developer.mozilla.org/docs/Web/API/Window/location>`__, etc.
+
+editor
+------
+
+The editor directory contains XUL/Javascript for the embeddable editor
+component, which is used for the HTML Editor("Composer"), for plain and
+HTML mail composition, and for text fields and text areas throughout the
+product. The editor is designed like a
+"browser window with editing features": it adds some special classes for
+editing text and managing transaction undo/redo, but reuses browser code
+for nearly everything else.
+
+extensions
+----------
+
+Contains several extensions to mozilla, which can be enabled at
+compile-time using the ``--enable-extensions`` configure argument.
+
+Note that some of these are now built specially and not using the
+``--enable-extensions`` option. For example, disabling xmlextras is done
+using ``--disable-xmlextras``.
+
+
+extensions/auth
+---------------
+
+Implementation of the negotiate auth method for HTTP and other
+protocols. Has code for SSPI, GSSAPI, etc. See `Integrated
+Authentication <https://www.mozilla.org/projects/netlib/integrated-auth.html>`__.
+
+
+extensions/pref
+---------------
+
+Preference-related extensions.
+
+extensions/spellcheck
+---------------------
+
+Spellchecker for mailnews and composer.
+
+extensions/universalchardet
+---------------------------
+
+Detects the character encoding of text.
+
+gfx
+---
+
+Contains interfaces that abstract the capabilities of platform specific
+graphics toolkits, along with implementations on various platforms.
+These interfaces provide methods for things like drawing images, text,
+and basic shapes. It also contains basic data structures such as points
+and rectangles used here and in other parts of Mozilla.
+
+gradle
+------
+
+Containing files related to a Java build system.
+
+hal
+---
+
+Contains platform specified functions (e.g. obtaining battery status,
+sensor information, memory information, Android
+alarms/vibrate/notifications/orientation, etc)
+
+image
+-----
+
+Image rendering library. Contains decoders for the image formats Firefox
+supports.
+
+intl
+----
+
+Internationalization and localization support. See
+`L10n:NewProjects <https://wiki.mozilla.org/L10n:NewProjects>`__.
+
+intl/locale
+-----------
+
+Code related to determination of locale information from the operating
+environment.
+
+intl/lwbrk
+----------
+
+Code related to line breaking and word breaking.
+
+intl/strres
+-----------
+
+Code related to string resources used for localization.
+
+intl/uconv
+----------
+
+Code that converts (both ways: encoders and decoders) between UTF-16 and
+many other character encodings.
+
+intl/unicharutil
+----------------
+
+Code related to implementation of various algorithms for Unicode text,
+such as case conversion.
+
+ipc
+---
+
+Container for implementations of IPC (Inter-Process Communication).
+
+js/src
+------
+
+The JavaScript engine, also known as
+:ref:`SpiderMonkey <SpiderMonkey>`.
+See also `JavaScript <https://developer.mozilla.org/docs/JavaScript>`__.
+
+js/xpconnect
+------------
+
+Support code for calling JavaScript code from C++ code and C++ code from
+JavaScript code, using XPCOM interfaces. See
+`XPConnect <https://developer.mozilla.org/docs/XPConnect>`__.
+
+layout
+------
+
+Code that implements a tree of rendering objects that describe the types
+and locations of the objects that are displayed on the screen (such as
+CSS boxes, tables, form controls, XUL boxes, etc.), and code that
+manages operations over that rendering tree (such as creating and
+destroying it, doing layout, painting, and event handling). See
+`documentation <https://www.mozilla.org/newlayout/doc/>`__ and `other
+information <https://www.mozilla.org/newlayout/>`__.
+
+layout/base
+-----------
+
+Code that deals with the rendering tree.
+
+layout/forms
+------------
+
+Rendering tree objects for HTML form controls.
+
+layout/generic
+--------------
+
+The basic rendering object interface and the rendering tree objects for
+basic CSS boxes.
+
+layout/mathml
+-------------
+
+Rendering tree objects for `MathML <https://developer.mozilla.org/docs/Web/MathML>`__.
+
+layout/svg
+----------
+
+Rendering tree objects for `SVG <https://developer.mozilla.org/docs/Web/SVG>`__.
+
+layout/tables
+-------------
+
+Rendering tree objects for CSS/HTML tables.
+
+layout/xul
+----------
+
+Additional rendering object interfaces for `XUL <https://developer.mozilla.org/docs/XUL>`__ and
+the rendering tree objects for XUL boxes.
+
+media
+-----
+
+Contains sources of used media libraries for example *libpng*.
+
+memory
+------
+
+Cross-platform wrappers for *memallocs* functions etc.
+
+mfbt
+----
+
+Implementations of classes like *WeakPtr*. Multi-platform *assertions*
+etc.
+
+mobile
+------
+
+mobile/android
+--------------
+
+Firefox for Android and Geckoview
+
+modules
+-------
+
+Compression/Archiving, math library, font (and font compression),
+Preferences Library
+
+modules/libjar
+--------------
+
+Code to read zip files, used for reading the .jar files that contain the
+files for the mozilla frontend.
+
+modules/libpref
+---------------
+
+Library for reading and writing preferences.
+
+modules/zlib
+------------
+
+Source code of zlib, used at least in the networking library for
+compressed transfers.
+
+mozglue
+-------
+
+Glue library containing various low-level functionality, including a
+dynamic linker for Android, a DLL block list for Windows, etc.
+
+netwerk
+-------
+
+:ref:`Networking library <Networking>`, also known as Necko.
+Responsible for doing actual transfers from and to servers, as well as
+for URI handling and related stuff.
+
+netwerk/cookie
+--------------
+
+Permissions backend for cookies, images, etc., as well as the user
+interface to these permissions and other cookie features.
+
+nsprpub
+-------
+
+Netscape Portable Runtime. Used as an abstraction layer to things like
+threads, file I/O, and socket I/O. See :ref:`NSPR`.
+
+nsprpub/lib
+-----------
+
+Mostly unused; might be used on Mac?
+
+other-licenses
+--------------
+
+Contains libraries that are not covered by the MPL but are used in some
+Firefox code.
+
+parser
+------
+
+Group of structures and functions needed to parse files based on
+XML/HTML.
+
+parser/expat
+------------
+
+Copy of the expat source code, which is the XML parser used by mozilla.
+
+parser/html
+-----------
+
+The HTML parser (for everything except about:blank).
+
+parser/htmlparser
+-----------------
+
+The legacy HTML parser that's still used for about:blank. Parts of it
+are also used for managing the conversion of the network bytestream into
+Unicode in the XML parsing case.
+
+parser/xml
+----------
+
+The code for integrating expat (from parser/expat) into Gecko.
+
+python
+------
+
+Cross module python code.
+
+python/mach
+-----------
+
+The code for the :ref:`Mach` building tool.
+
+security
+--------
+
+Contains NSS and PSM, to support cryptographic functions in mozilla
+(like S/MIME, SSL, etc). See :ref:`Network Security Services (NSS)`
+and
+`Personal Security Manager
+(PSM) <https://www.mozilla.org/projects/security/pki/psm/>`__.
+
+services
+--------
+
+Firefox accounts and sync (history, preferences, tabs, bookmarks,
+telemetry, startup time, which addons are installed, etc). See
+`here <https://docs.services.mozilla.com/>`__.
+
+servo
+-----
+
+`Servo <https://servo.org/>`__, the parallel browser engine project.
+
+startupcache
+------------
+
+XXX this needs a description.
+
+storage
+-------
+
+`Storage <https://developer.mozilla.org/docs/Mozilla/Tech/XPCOM/Storage>`__: XPCOM wrapper for sqlite. Wants to
+unify storage of all profile-related data. Supersedes mork. See also
+`Unified Storage <https://wiki.mozilla.org/Mozilla2:Unified_Storage>`__.
+
+taskcluster
+-----------
+
+Scripts and code to automatically build and test Mozilla trees for the
+continuous integration and release process.
+
+testing
+-------
+
+Common testing tools for mozilla codebase projects, test suite
+definitions for automated test runs, tests that don't fit anywhere else,
+and other fun stuff.
+
+third_party
+-----------
+
+Vendored dependencies maintained outside of Mozilla.
+
+toolkit
+-------
+
+The "new toolkit" used by Thunderbird, Firefox, etc. This contains
+numerous front-end components shared between applications as well as
+most of the XBL-implemented parts of the XUL language (most of which was
+originally forked from versions in `xpfe/`).
+
+toolkit/mozapps/extensions/test/xpinstall
+-----------------------------------------
+
+The installer, which contains code for installing Mozilla and for
+installing XPIs/extensions. This directory also contains code needed to
+build installer packages. See `XPInstall <https://developer.mozilla.org/docs/XPInstall>`__ and
+the `XPInstall project
+page <https://www.mozilla.org/projects/xpinstall/>`__.
+
+tools
+-----
+
+Some tools which are optionally built during the mozilla build process.
+
+tools/lint
+----------
+
+The linter declarations and configurations.
+See `linting documentation </code-quality/lint/>`_
+
+uriloader
+---------
+
+uriloader/base
+--------------
+
+Content dispatch in Mozilla. Used to load uris and find an appropriate
+content listener for the data. Also manages web progress notifications.
+See `Document Loading: From Load Start to Finding a
+Handler <https://www.mozilla.org/docs/docshell/uri-load-start.html>`__
+and `The Life Of An HTML HTTP
+Request <https://www.mozilla.org/docs/url_load.html>`__.
+
+
+uriloader/exthandler
+--------------------
+
+Used to handle content that Mozilla can't handle itself. Responsible for
+showing the helper app dialog, and generally for finding information
+about helper applications.
+
+uriloader/prefetch
+------------------
+
+Service to prefetch documents in order to have them cached for faster
+loading.
+
+view
+----
+
+View manager. Contains cross-platform code used for painting, scrolling,
+event handling, z-ordering, and opacity. Soon to become obsolete,
+gradually.
+
+widget
+------
+
+A cross-platform API, with implementations on each platform, for dealing
+with operating system/environment widgets, i.e., code related to
+creation and handling of windows, popups, and other native widgets and
+to converting the system's messages related to painting and events into
+the messages used by other parts of Mozilla (e.g., `view/` and
+`content/`, the latter of which converts many of the
+messages to yet another API, the DOM event API).
+
+xpcom
+-----
+
+`Cross-Platform Component Object Model </en-US/docs/XPCOM>`__. Also
+contains data structures used by the rest of the mozilla code. See also
+`XPCOM Project <https://www.mozilla.org/projects/xpcom/>`__.
+
+xpfe
+----
+
+XPFE (Cross Platform Front End) is the SeaMonkey frontend. It contains
+the XUL files for the browser interface, common files used by the other
+parts of the mozilla suite, and the XBL files for the parts of the XUL
+language that are implemented in XBL. Much of this code has been copied
+to `browser/` and `toolkit/` for use in
+Firefox, Thunderbird, etc.
+
+
+xpfe/components
+---------------
+
+Components used by the Mozilla frontend, as well as implementations of
+interfaces that other parts of mozilla expect.
diff --git a/docs/contributing/editor.rst b/docs/contributing/editor.rst
new file mode 100644
index 0000000000..1ce083d4fc
--- /dev/null
+++ b/docs/contributing/editor.rst
@@ -0,0 +1,26 @@
+Editor / IDE integration
+========================
+
+You can use any editor or IDE to contribute to Firefox, as long as it can edit
+text files. However, there are some steps specific to mozilla-central that may
+be useful for a better development experience. This page attempts to document
+them.
+
+.. note::
+
+ Visual Studio Code is the recommended editor for Firefox development.
+ Not because it is better than the other editors but because we decided to
+ focus our energy on a single editor.
+
+.. note::
+
+ This page is a work in progress. Please enhance this page with instructions
+ for your favourite editor.
+
+.. toctree::
+ :maxdepth: 1
+
+ editors/vscode
+ editors/emacs
+ editors/vim
+ editors/others
diff --git a/docs/contributing/editors/emacs.rst b/docs/contributing/editors/emacs.rst
new file mode 100644
index 0000000000..fbd41195a1
--- /dev/null
+++ b/docs/contributing/editors/emacs.rst
@@ -0,0 +1,121 @@
+Emacs
+=====
+
+ESLint
+------
+
+See `the devtools documentation <https://wiki.mozilla.org/DevTools/CodingStandards#Running_ESLint_in_Emacs>`__
+that describes how to integrate ESLint into Emacs.
+
+C/C++ Development Packages
+--------------------------
+
+General Guidelines to Emacs C++ Programming
+-------------------------------------------
+
+The following guides give an overview of the C++ editing capabilities of emacs.
+
+It is worth reading through these guides to see what features are available.
+The rest of this section is dedicated to Mozilla/Gecko specific setup for
+packages.
+
+
+ * `C/C++ Development Environment for Emacs <https://tuhdo.github.io/c-ide.html>`__
+ * `Emacs as C++ IDE <https://syamajala.github.io/c-ide.html>`__
+
+rtags (LLVM/Clang-based Code Indexing)
+--------------------------------------
+
+Instructions for the installation of rtags are available at the
+`rtags github repo <https://github.com/Andersbakken/rtags>`__.
+
+rtags requires a :ref:`compilation database <CompileDB back-end-compileflags>`.
+
+In order for rtags to index correctly, included files need to be copied and
+unified compilation files need to be created. Either run a full build of the
+tree, or if you only want indexes to be generated for the moment, run the
+following commands (assuming you're in the gecko repo root):
+
+.. code::
+
+ cd gecko_build_directory
+ make export
+ ./config.status
+
+To increase indexing speed, it's best to remove unified build files and test
+files from database updates. This can be done by creating a :code:`~/.rdmrc`
+file with the following contents, with :code:`[src_dir]` replaced with either
+the repo or build directory for your checkout:
+
+.. code::
+
+ -X */[src_dir]/*Unified*;*/[src_dir]/*/test/*;*/[src_dir]/*/tests/*
+
+Once the rdm daemon is running, the compilation database can be added to rtags
+like so:
+
+.. code::
+
+ rc -J [gecko_build_directory]/compile_commands.json
+
+Note that this process will take a while initially. However, once the database
+is built, it will only require incremental updates. As long as the rdm daemon
+is running in the background, the database will be updated based on changes to
+files.
+
+irony (LLVM/Clang-based Code Completion)
+----------------------------------------
+
+Instructions on the installation of irony-mode are available at the
+`irony-mode github repo <https://github.com/Sarcasm/irony-mode>`__.
+
+irony-mode requires a :ref:`compilation database <CompileDB back-end-compileflags>`.
+
+Note that irony-mode, by default, uses elisp to parse the
+:code:`compile_commands.json` file. As gecko is a very large codebase, this
+file can easily be multiple megabytes, which can make irony-mode take multiple
+seconds to load on a gecko file.
+
+It is recommended to use `this fork of irony-mode <https://github.com/Hylen/irony-mode/tree/compilation-database-guessing-4-pull-request>`__,
+which requires the boost System and Filesystem libraries.
+
+`Checking the bug to get this patch into the mainline of irony-mode <https://github.com/Sarcasm/irony-mode/issues/176>`__
+is recommended, to see if the fork can be used or if the mainline repo can be
+used. Using the Boost version of the irony-mode server brings file load times
+to under 1s.
+
+Projectile (Project Management)
+-------------------------------
+
+Instructions on the installation of projectile are available at the
+`projectile github repo <https://github.com/bbatsov/projectile>`__.
+
+Projectile comes preconfigured for many project types. Since, gecko uses its
+own special build system (mach), a new project type needs to be added. This can
+be done via adding the following elisp configuration command to your emacs
+configuration file.
+
+.. code::
+
+ (projectile-register-project-type 'gecko
+ '("mach" "moz.build")
+ "python mach --log-no-times build"
+ "python mach mochitest"
+ "python mach run")
+
+Assuming projectile-global-mode is on, this will allow projectile to run the
+correct commands whenever it is working in a gecko repo.
+
+gdb
+^^^
+
+Emacs comes with great integration with gdb, especially when using
+`gdb-many-windows <https://www.gnu.org/software/emacs/manual/html_node/emacs/GDB-User-Interface-Layout.html>`__.
+
+However, when gdb is invoked via mach, some special arguments
+need to be passed in order to make sure the correct display mode is used. To
+use M-x gdb with mach on firefox, use the following command:
+
+.. code::
+
+ gecko_repo_directory/mach run --debug --debugparams=-i=mi
diff --git a/docs/contributing/editors/others.rst b/docs/contributing/editors/others.rst
new file mode 100644
index 0000000000..b753ff1042
--- /dev/null
+++ b/docs/contributing/editors/others.rst
@@ -0,0 +1,41 @@
+Eclipse
+=======
+
+You can generate an Eclipse project by running:
+
+.. code::
+
+ ./mach ide eclipse
+
+See also the `Eclipse CDT <https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Eclipse/Eclipse_CDT>`__ docs on MDN.
+
+Visual Studio
+=============
+
+You can run a Visual Studio project by running:
+
+.. code::
+
+ ./mach ide visualstudio
+
+.. _CompileDB back-end-compileflags:
+
+CompileDB back-end / compileflags
+=================================
+
+You can generate a :code:`compile_commands.json` in your object directory by
+running:
+
+.. code::
+
+ ./mach build-backend --backend=CompileDB
+
+This file, the compilation database, is understood by a variety of C++ editors / IDEs
+to provide auto-completion capabilities. You can also get an individual compile command by
+running:
+
+.. code::
+
+ ./mach compileflags path/to/file
+
+This is how the :ref:`VIM <VIM>` integration works, for example.
diff --git a/docs/contributing/editors/vim.rst b/docs/contributing/editors/vim.rst
new file mode 100644
index 0000000000..0cce21d4e9
--- /dev/null
+++ b/docs/contributing/editors/vim.rst
@@ -0,0 +1,75 @@
+Vim / Neovim
+============
+
+AutoCompletion
+--------------
+
+For C++, anything that can use an LSP like :code:`coc.nvim`,
+:code:`nvim-lspconfig`, or what not, should work as long as you generate a
+:ref:`compilation database <CompileDB back-end-compileflags>` and point to it.
+
+Additionally, `YouCompleteMe <https://github.com/ycm-core/YouCompleteMe/>`__
+works without the need of a C++ compilation database as long as you have run
+:code:`./mach build` or :code:`./mach configure`. Configuration for this lives
+in :searchfox:`.ycm_extra_conf <.ycm_extra_conf>` at the root of the repo.
+
+Rust auto-completion should work both with Rust's LSP :code:`rust-analyzer`.
+
+Make sure that the LSP is configured in a way that it detects the root of the
+tree as a workspace, not the crate you happen to be editing. For example, the
+default of :code:`nvim-lspconfig` is to search for the closest
+:code:`Cargo.toml` file, which is not what you want. You'd want something like:
+
+.. code ::
+
+ root_dir = lspconfig.util.root_pattern(".git", ".hg")
+
+You also need to set some options to get full diagnostics:
+
+.. code ::
+
+ "rust-analyzer.server.extraEnv": {
+ "CARGO_TARGET_DIR": "/path/to/objdir"
+ },
+ "rust-analyzer.check.overrideCommand": [ "/path/to/mach", "--log-no-times", "cargo", "check", "--all-crates", "--message-format-json" ],
+ "rust-analyzer.cargo.buildScripts.overrideCommand": [ "/path/to/mach", "--log-no-times", "cargo", "check", "--all-crates", "--message-format-json" ],
+
+The easiest way to make these work out of the box is using
+`neoconf <https://github.com/folke/neoconf.nvim/>`__, which
+automatically supports importing VSCode configuration files.
+:code:`./mach ide vscode --no-interactive` will then generate the right
+configuration for you.
+
+ESLint
+------
+
+The easiest way to integrate ESLint with VIM is using the `Syntastic plugin
+<https://github.com/vim-syntastic/syntastic>`__.
+
+In order for VIM to detect jsm files as JS you might want something like this
+in your :code:`.vimrc`:
+
+.. code::
+
+ autocmd BufRead,BufNewFile *.jsm set filetype=javascript
+
+:code:`mach eslint --setup` installs a specific ESLint version and some ESLint
+plugins into the repositories' :code:`node_modules`.
+
+You need something like this in your :code:`.vimrc` to run the checker
+automatically on save:
+
+.. code::
+
+ autocmd FileType javascript,html,xhtml let b:syntastic_checkers = ['javascript/eslint']
+
+You need to have :code:`eslint` in your :code:`PATH`, which you can get with
+:code:`npm install -g eslint`. You need at least version 6.0.0.
+
+You can also use something like `eslint_d
+<https://github.com/mantoni/eslint_d.js#editor-integration>`__ which should
+also do that automatically:
+
+.. code::
+
+ let g:syntastic_javascript_eslint_exec = 'eslint_d'
diff --git a/docs/contributing/editors/vscode.rst b/docs/contributing/editors/vscode.rst
new file mode 100644
index 0000000000..9830dcbe4f
--- /dev/null
+++ b/docs/contributing/editors/vscode.rst
@@ -0,0 +1,179 @@
+Visual Studio Code
+==================
+
+.. toctree::
+ :maxdepth: 1
+ :glob:
+
+General Knowledge
+-----------------
+
+`VSCode <https://code.visualstudio.com/>`__ is a multi-platform open-source programming editor developed by Microsoft and volunteers.
+It has support for many programming languages using extensions.
+This is the recommended editor for Firefox development.
+
+For more general information on the VSCode project see the `repository <https://github.com/Microsoft/vscode/>`__.
+
+Recommended extensions
+----------------------
+
+VS Code provides number of extensions for JavaScript, Rust, etc. By default,
+Firefox source tree comes with its own set of recommendations of Visual Studio
+Code extensions. They will be offered when you first open the project.
+
+If you need to refer to them later, the extensions are listed in
+`.vscode/extensions.json <https://searchfox.org/mozilla-central/source/.vscode/extensions.json>`__.
+
+For Rust development, the `rust-analyzer <https://marketplace.visualstudio.com/items?itemName=matklad.rust-analyzer>`__ extension is recommended.
+`See the manual <https://rust-analyzer.github.io/manual.html>`__ for more information.
+
+Getting setup
+-------------
+
+Close `VS Code` if it is already open, then build the configuration for `VS Code`
+by simplying running from the terminal:
+
+.. code::
+
+ ./mach ide vscode
+
+This will automatically set some of the recommended preferences for the workspace,
+and if you are set up for a full build, it will enable clangd and rust integrations.
+
+If successful, `VS Code` will open at the end. You do not need to run this command
+every time to open `VS Code`, you may open it in the normal way.
+
+If you are running full builds, the command above will set up the `Clangd`
+integration so that subsequent invocations of ``./mach build`` run and update the
+integration.
+
+.. note::
+
+ If `VS Code` is already open with a previous configuration generated, please make sure to
+ restart `VS Code` otherwise the new configuration will not be used, and the `compile_commands.json`
+ needed by `clangd` server will not be refreshed. This is a known `bug <https://github.com/clangd/vscode-clangd/issues/42>`__
+ in `clangd-vscode` extension
+
+Ignore Files in Mercurial Repositories
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When using Mercurial in mozilla-central, VS Code will treat your build directories as ordinary directories by default, causing some undesirable behavior including long indexing times, Go to Definition will open files in the build directory instead of the source tree, and Search Files by Name will find duplicate files from the source tree and the build directory (note: when using Git, VS Code will not do this since it reads ``.gitignore``). You can follow these directions to have VS Code largely ignore your build directories:
+
+#. Go to Preferences -> Settings
+#. Search "exclude" in the Settings
+#. (optional) Select "Workspace" below the search bar to only change this setting for the mozilla-central repository
+#. Under "Files: Exclude", click "Add Pattern", type ``obj-*`` (assuming your build directory names start with the default text, ``obj-``), and click "OK"
+#. Repeat the step above for the "Files: Watcher Exclude" setting
+#. Reload VS Code: the easiest way to do this is to quit and reopen it.
+
+Despite excluding the build directories above, Go to Definition will still correctly open files that only appear in the build directory such as generated source code. See `Bug 1790517 <https://bugzilla.mozilla.org/show_bug.cgi?id=1790517>`_ for our effort to automatically exclude the build directories.
+
+Recommended Preferences
+~~~~~~~~~~~~~~~~~~~~~~~
+
+.. note::
+
+ These are automatically set when running ``./mach ide vscode`` but may be
+ changed manually. These are set only for particular file types.
+
+* ``"editor.formatOnSave": true``
+ * This will turn on automatically fixing formatting issues when you save a file.
+* ``"editor.defaultFormatter": "esbenp.prettier-vscode"``
+ * This sets the default formatter to prettier using the recommended prettier
+ extension.
+
+``*.jsm`` and ``*.sjs`` file extensions should also be associated with JavaScript:
+
+.. code::
+
+ "files.associations": {
+ "*.jsm": "javascript",
+ "*.sjs": "javascript",
+ },
+
+C/C++ Features and Support
+--------------------------
+
+For C++ support we offer an out of the box configuration based on
+`clangd <https://clangd.llvm.org>`__.
+
+Leveraging the `clang` toolchain compiler we now have support in the IDE for the following features:
+
+**1.** Syntax highlighting
+
+**2.** IntelliSense with comprehensive code completion and suggestion
+
+.. image:: ../img/auto_completion.gif
+
+**3.** Go-to definition and Go-to declaration
+
+.. image:: ../img/goto_definition.gif
+
+**4.** Find all references
+
+.. image:: ../img/find_references.gif
+
+**5.** Open type hierarchy
+
+.. image:: ../img/type_hierarchy.gif
+
+**6.** Rename symbol, all usages of the symbol will be renamed, including declaration, definition and references
+
+.. image:: ../img/rename_symbol.gif
+
+**7.** Code formatting, based on `clang-format` that respects our coding standard using the `.clang-format` and `.clang-format-ignore` files. Format can be performed on an entire file or on a code selection
+
+.. image:: ../img/format_selection.gif
+
+**8.** Inline parsing errors with limited auto-fix hints
+
+.. image:: ../img/diagnostic_error.gif
+
+**9.** Basic static-code analysis using `clang-tidy` and our list of enabled checkers. (This is still in progress not all checkers are supported by `clangd`)
+
+Clangd-specific Commands
+------------------------
+
+Clangd supports some commands that are specific to C/C++:
+
+.. code::
+
+ "clangd.switchheadersource"
+
+This command navigates from the currently open header file to its corresponding source file (if there is one), or vice versa.
+
+This command can be invoked from the command menu (activated via ``F1``), or using its keybinding of ``Alt+o`` (``Alt+cmd+o`` on Mac). The keybinding can also be customized in ``Keyboard Shortcuts``.
+
+Remote Development over SSH
+---------------------------
+
+VS Code provides an `extension <https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh>`__ that lets you use any remote machine with a SSH server as your development environment. No matter if it's Linux based, macOS or Windows, as long as the target machine offers a SSH connection, it can be used for development.
+
+No source code needs to be on your local machine to use VS Code remotely since the extension runs commands and other extensions directly on the remote machine.
+
+In order to setup a connection please follow these steps:
+
+**1.** Open VS Code and select from the left side panel ``Remote Explorer``
+
+.. image:: ../img/remote_explorer.png
+
+**2.** From the ``Remote Explorer`` panel select ``SSH Targets`` and click on ``Add`` and enter the connection details
+
+.. image:: ../img/remote_explorer_add.png
+
+.. image:: ../img/remote_explorer_add_wind.png
+
+**3.** Click on the connection that you just configured at the previous step
+
+**4.** Finally you should be connected to the desired remote SSH server
+
+.. image:: ../img/connection_done.png
+
+Please note that during the first connection VS Code will install itself remotely and also install all of the needed dependencies.
+
+
+
+Filing Bugs
+-----------
+
+Bugs should be filed in the `Firefox Build System` product under `Developer Environment Integration`, preferably blocking `Bug 1662709 <https://bugzilla.mozilla.org/show_bug.cgi?id=1662709>`__.
diff --git a/docs/contributing/engineering_show_and_tell.rst b/docs/contributing/engineering_show_and_tell.rst
new file mode 100644
index 0000000000..87c2013bb8
--- /dev/null
+++ b/docs/contributing/engineering_show_and_tell.rst
@@ -0,0 +1,83 @@
+Engineering Show and Tell
+=========================
+
+The engineering teams at Mozilla have put together a series of 5 minute long
+tips and tricks to help boost productivity. You can find the recordings below.
+
+August 2021
+-----------
+
+`Link to the August 2021 recording <https://mozilla.hosted.panopto.com/Panopto/Pages/Viewer.aspx?id=bd0c503d-903f-4829-9c9d-ad7c011cee9b>`_
+
+Links
+
+- Mossop: Mercurial Tips n Tricks
+
+ - https://github.com/Mossop/dotfiles/blob/main/shared/hg/hgrc
+ - https://mikeconley.github.io/documents/How_mconley_uses_Mercurial_for_Mozilla_code
+
+- mak: Gmail filtering with Google Apps Script
+
+ - https://script.google.com/home
+ - https://paste.mozilla.org/9zEDnAiA#L9
+ - https://developers.google.com/apps-script/reference/
+
+- mhowell: Windows Tools You Might Not Know About
+
+ - https://docs.google.com/document/d/1DyeTPIEz3SIcw7ldvSGKUVp4XP34Ue-_pV8lYmRJX6I/
+
+- bryce: Debugging Remote Resources Via Proxy
+
+ - https://www.telerik.com/fiddler
+ - https://www.charlesproxy.com/
+ - https://mitmproxy.org/
+
+- jody: CircleCI for VS Code
+
+ - https://marketplace.visualstudio.com/items?itemName=jodyh.circleci-vscode
+
+- sfink: Moz workflow aids for post-landing pruning, taskcluster job replication & more
+
+ - https://github.com/hotsphink/sfink-tools
+ - https://hg.sr.ht/~sfink/sfink-tools
+
+- mconley: Multiple mozconfigs with mozconfigwrapper
+
+ - https://github.com/ahal/mozconfigwrapper
+
+February 2021
+-------------
+
+`Link to the February 2021 recording <https://mozilla.zoom.us/rec/share/RvN62-Y3ByGmeyQChmsqXy6WXAE3iJETeB2yNj1xq4-z3c80ewwFaz-EAVnoHSo-.8FbmZF3nQEWBPFSr>`_
+
+Slide decks:
+
+- `bash scripts and keyboard shortcuts <https://docs.google.com/presentation/d/1T8z99Hy0rI-_W3wJIZsG-edKRiicxgl-QeBoJuM90qQ/edit?usp=sharing>`_
+- `sccache-dist <https://docs.google.com/presentation/d/1_mN5rgV2LrzRKEOn06j4uaryYC9zRQJOdomgeBrJ8RA/edit#slide=id.g832b271044_1_1173>`_
+- `How to animate things smoothly <https://docs.google.com/presentation/d/11csNTR1GnVs2BdjN1alJcTbjXMhKyhpv9GIx2jNMJ6U/edit#slide=id.p>`_
+- `How I learned to love the M-C <https://docs.google.com/presentation/d/13O06nyDWqfbZyLeGRdLztd234zJ2SQcJu7LkBFDLnYg/edit?usp=sharing>`_
+
+Links for "Editor, Lint, & More" talk:
+
+- :ref:`Linting`
+- :ref:`lint-vcs-hook`
+- https://eslint.org/docs/user-guide/integrations
+
+Links for "Stupid Mac Tricks" talk:
+
+- https://www.obdev.at/products/launchbar/index.html
+- Moom: https://itunes.apple.com/app/id496437906?mt=12&ls=1
+- https://kapeli.com/dash
+- https://git-fork.com/
+- https://www.usboverdrive.com/
+- https://manytricks.com/download/butler/
+- https://bjango.com/mac/istatmenus/
+- https://iterm2.com/
+
+Other links:
+
+- https://twitter.com/asutherland/status/1274942012316319744
+- https://scootersoftware.com/
+- https://pasteapp.io/
+- https://github.com/acreskeyMoz/browsertime_scripts
+- https://chuttenblog.wordpress.com/2021/01/18/doubling-the-speed-of-windows-firefox-builds-using-sccache-dist/
diff --git a/docs/contributing/how_to_submit_a_patch.rst b/docs/contributing/how_to_submit_a_patch.rst
new file mode 100644
index 0000000000..0ceba7f39c
--- /dev/null
+++ b/docs/contributing/how_to_submit_a_patch.rst
@@ -0,0 +1,245 @@
+How to submit a patch
+=====================
+
++--------------------------------------------------------------------+
+| This page is an import from MDN and the contents might be outdated |
++--------------------------------------------------------------------+
+
+Submitting a patch, getting it reviewed, and committed to the Firefox
+source tree involves several steps. This article explains how.
+
+.. note::
+
+ We are also providing a :ref:`Firefox Contributors Quick Reference <Firefox Contributors' Quick Reference>` for contributors.
+
+The process of submission is illustrated by the following diagram, and
+each step is detailed below:
+
+.. mermaid::
+
+ graph TD;
+ Preparation --> c[Working on a patch];
+ c[Working on a patch] --> Testing;
+ Testing --> c[Working on a patch];
+ Testing --> e[Submit the patch];
+ e[Submit the patch] --> d[Getting Reviews]
+ d[Getting Reviews] -- Addressing Review comment --> c[Working on a patch];
+ d[Getting Reviews] --> h[Push the change];
+
+
+
+
+Preparation
+-----------
+
+Every change to the code is tracked by a bug report
+in `bugzilla.mozilla.org <https://bugzilla.mozilla.org/>`__. Without a
+bug, code will not be reviewed, and without review, code will not be
+accepted. To avoid duplication, `search for an existing
+bug <https://bugzilla.mozilla.org/query.cgi?format=specific>`__ about
+your change, and only if none exists, file a new one. Most communication
+about code changes take place in the associated code
+review, so be sure the bug describes the exact problem being solved.
+
+Please verify the bug is for the correct product and component. For more
+information, ask questions on the newsgroups, or on the #developers room
+on `chat.mozilla.org <https://chat.mozilla.org>`__.
+
+The person working on a bug should be the 'assignee' of that bug in
+Bugzilla. If somebody else is currently the assignee of a bug, email
+this person to coordinate changes. If the bug is unassigned, leave a
+message in the bug's comments, stating that you intend working on it,
+and suggest that someone with bug-editing privileges assign it to you.
+
+Some teams wait for new contributors to attach their first patch before
+assigning a bug. This makes it available for other contributors, in case
+the new contributor is unable to level up to patch creation. By
+expressing interest in a bug comment, someone from that team should
+guide you through their process.
+
+
+Module ownership
+----------------
+
+All code is supervised by a `module
+owner <https://www.mozilla.org/en-US/about/governance/policies/module-ownership/>`__.
+This person will be responsible for reviewing and accepting the change.
+Before writing your code, determine the module owner, verifying your
+proposed change is considered acceptable. They may want to look over any
+new user interface (UI review), functions (API review), or testcases for
+the proposed change.
+
+If module ownership is not clear, ask on the newsgroups or `on
+Matrix <https://chat.mozilla.org>`__. The revision log for the relevant
+file might also be helpful. For example, see the change log for
+``browser/base/content/browser.js``, by clicking the "Hg Log"
+link at the top of `Searchfox <https://searchfox.org/mozilla-central/source/>`__, or
+by running ``hg log browser/base/content/browser.js``. The corresponding
+checkin message will contain something like "r=nickname", identifying
+active code submissions, and potential code reviewers.
+
+
+Working on a patch
+------------------
+
+Changes to the Firefox source code are presented in the form of a patch.
+A patch is a commit to version control. Firefox and related code is
+stored in our `Mercurial
+server <https://hg.mozilla.org/mozilla-central>`__. We have extensive
+documentation on using Mercurial in our guide, :ref:`Mercurial Overview`.
+
+Each patch should represent a single complete change, separating
+distinct changes into multiple individual patches. If your change
+results in a large, complex patch, seek if it can be broken into
+`smaller, easy to understand patches representing complete
+steps <https://secure.phabricator.com/book/phabflavor/article/writing_reviewable_code/#many-small-commits>`__,
+applied on top of each other. This makes it easier to review your
+changes, `leading to quicker
+reviews, <https://groups.google.com/group/mozilla.dev.planning/msg/2f99460f57f776ef?hl=en>`__
+and improved confidence in this review outcome.
+
+Also ensure that your commit message is formatted appropriately. A
+simple commit message should look like this:
+
+::
+
+ Bug 123456 - Change this thing to work better by doing something. r=reviewers
+
+The ``r=reviewers`` part is optional; if you are using Phabricator,
+Lando will add it automatically based on who actually granted review,
+and in any case the person who does the final check-in of the patch will
+make sure it's added.
+
+The text of the message should be what you did to fix the bug, not a
+description of what the bug was. If it is not obvious why this change is
+appropriate, then `explain why in the commit
+message <https://mozilla-version-control-tools.readthedocs.io/en/latest/mozreview/commits.html#write-detailed-commit-messages>`__.
+If this does not fit on one line, then leave a blank line and add
+further lines for more detail and/or reasoning.
+
+You can edit the message of the current commit at any time using
+``hg commit --amend`` or ``hg histedit``.
+
+Also look at our :ref:`Reviewer Checklist` for a list
+of best practices for patch content that reviewers will check for or
+require.
+
+
+Testing
+-------
+
+All changes must be tested. In most cases, an `automated
+test <https://developer.mozilla.org/docs/Mozilla/QA/Automated_testing>`__ is required for every
+change to the code.
+
+While we desire to have automated tests for all code, we also have a
+linter tool which runs static analysis on our JavaScript, for best
+practices and common mistakes. See :ref:`ESLint` for more information.
+
+Ensure that your change has not caused regressions, by running the
+automated test suite locally, or using the `Mozilla try
+server <https://wiki.mozilla.org/Build:TryServer>`__. Module owners, or
+developers `on Matrix <https://chat.mozilla.org>`__ may be willing to
+submit jobs for those currently without try server privileges.
+
+
+Submit the patch
+----------------
+
+.. note::
+
+ Make sure you rebase your patch on top of the latest build before you
+ submit to prevent any merge conflicts.
+
+Mozilla uses Phabricator for code review. See the `Mozilla Phabricator
+User
+Guide <https://moz-conduit.readthedocs.io/en/latest/phabricator-user.html>`__
+for instructions.
+
+Don't be shy in posting partial patches, demonstrating potential
+approaches, and asking for preliminary feedback. It is easier for others
+to comment, and offer suggestions, when a question is accompanied by
+some code.
+
+
+Getting reviews for my patch
+----------------------------
+
+See the dedicated page :ref:`Getting reviews`
+
+
+Addressing review comments
+--------------------------
+
+It is unusual for patches to be perfect the first time around. The
+reviewer may use the ‘Request Changes’
+`action <http://moz-conduit.readthedocs.io/en/latest/phabricator-user.html#reviewing-patches>`__
+and list problems that must be addressed before the patch can be
+accepted. Please remember that requesting revisions is not meant to
+discourage participation, but rather to encourage the best possible
+resolution of a bug. Carefully work through the changes that the
+reviewer recommends, attach a new patch, and request review again.
+
+Sometimes a reviewer will grant conditional review with the ‘Accept
+Revision’ action but will also indicate minor necessary changes, such as
+spelling, or indentation fixes. All recommended corrections should be
+made, but a re-review is unnecessary. Make the changes and submit a new
+patch. If there is any confusion about the revisions, another review
+should be requested.
+
+Sometimes, after a patch is reviewed, but before it can be committed,
+someone else makes a conflicting change. If the merge is simple, and
+non-invasive, post an updated version of the patch. For all non-trivial
+changes, another review is necessary.
+
+If at any point the review process stalls for more than two weeks, see
+the previous 'Getting attention' section.
+
+In many open source projects, developers will accept patches in an
+unfinished state, finish them, and apply the completed code. In
+Mozilla's culture, **the reviewer will only review and comment on a
+patch**. If a submitter declines to make the revisions, the patch will
+sit idle, until someone chooses to take it on.
+
+
+Pushing the change
+------------------
+
+A patch can be pushed (aka. 'landed') after it has been properly
+reviewed.
+
+.. note::
+
+ Note: Be sure to build the application with the patch applied. This
+ ensures it runs as expected, passing automated tests, and/or runs
+ through the `try
+ server <https://wiki.mozilla.org/Build:TryServerAsBranch>`__. In the
+ bug, please also mention you have completed this step.
+
+ Submitting untested patches wastes the committer's time, and may burn
+ the release tree. Please save everyone's time and effort by
+ completing all necessary verifications.
+
+
+Ask the reviewer to land the patch for you.
+For more details, see :ref:`push_a_change`
+
+If pushing the patch yourself, please follow :ref:`Committing rules and responsibilities`.
+`Lando <https://moz-conduit.readthedocs.io/en/latest/lando-user.html>`__ is used
+to automatically land your code.
+
+
+Regressions
+-----------
+
+It is possible your code causes functional or performance regressions.
+There is a tight
+`policy <https://www.mozilla.org/about/governance/policies/regressions/>`__ on
+performance regressions, in particular. This means your code may be
+dropped, leaving you to fix and resubmit it. Regressions, ultimately
+mean the tests you ran before checking in are not comprehensive enough.
+A resubmitted patch, or a patch to fix the regression, should be
+accompanied by appropriate tests.
+
+After authoring a few patches, consider `getting commit access to
+Mozilla source code <https://www.mozilla.org/about/governance/policies/commit/>`__.
diff --git a/docs/contributing/img/auto_completion.gif b/docs/contributing/img/auto_completion.gif
new file mode 100644
index 0000000000..4d545c29fd
--- /dev/null
+++ b/docs/contributing/img/auto_completion.gif
Binary files differ
diff --git a/docs/contributing/img/connection_done.png b/docs/contributing/img/connection_done.png
new file mode 100644
index 0000000000..fc87571e4e
--- /dev/null
+++ b/docs/contributing/img/connection_done.png
Binary files differ
diff --git a/docs/contributing/img/diagnostic_error.gif b/docs/contributing/img/diagnostic_error.gif
new file mode 100644
index 0000000000..25d50a7d7c
--- /dev/null
+++ b/docs/contributing/img/diagnostic_error.gif
Binary files differ
diff --git a/docs/contributing/img/example-stack.png b/docs/contributing/img/example-stack.png
new file mode 100644
index 0000000000..c12265ae5f
--- /dev/null
+++ b/docs/contributing/img/example-stack.png
Binary files differ
diff --git a/docs/contributing/img/find_references.gif b/docs/contributing/img/find_references.gif
new file mode 100644
index 0000000000..e986894566
--- /dev/null
+++ b/docs/contributing/img/find_references.gif
Binary files differ
diff --git a/docs/contributing/img/format_selection.gif b/docs/contributing/img/format_selection.gif
new file mode 100644
index 0000000000..28c88b3fa3
--- /dev/null
+++ b/docs/contributing/img/format_selection.gif
Binary files differ
diff --git a/docs/contributing/img/goto_definition.gif b/docs/contributing/img/goto_definition.gif
new file mode 100644
index 0000000000..9b3dcbf90f
--- /dev/null
+++ b/docs/contributing/img/goto_definition.gif
Binary files differ
diff --git a/docs/contributing/img/remote_explorer.png b/docs/contributing/img/remote_explorer.png
new file mode 100644
index 0000000000..3f7a97bdc0
--- /dev/null
+++ b/docs/contributing/img/remote_explorer.png
Binary files differ
diff --git a/docs/contributing/img/remote_explorer_add.png b/docs/contributing/img/remote_explorer_add.png
new file mode 100644
index 0000000000..9faf9750fb
--- /dev/null
+++ b/docs/contributing/img/remote_explorer_add.png
Binary files differ
diff --git a/docs/contributing/img/remote_explorer_add_wind.png b/docs/contributing/img/remote_explorer_add_wind.png
new file mode 100644
index 0000000000..6aa9f8d54d
--- /dev/null
+++ b/docs/contributing/img/remote_explorer_add_wind.png
Binary files differ
diff --git a/docs/contributing/img/rename_symbol.gif b/docs/contributing/img/rename_symbol.gif
new file mode 100644
index 0000000000..d41fcd7ed6
--- /dev/null
+++ b/docs/contributing/img/rename_symbol.gif
Binary files differ
diff --git a/docs/contributing/img/type_hierarchy.gif b/docs/contributing/img/type_hierarchy.gif
new file mode 100644
index 0000000000..139bdf88cb
--- /dev/null
+++ b/docs/contributing/img/type_hierarchy.gif
Binary files differ
diff --git a/docs/contributing/index.rst b/docs/contributing/index.rst
new file mode 100644
index 0000000000..6dca72aa02
--- /dev/null
+++ b/docs/contributing/index.rst
@@ -0,0 +1,41 @@
+Working on Firefox
+==================
+
+Welcome to the Firefox codebase. This is the home of the Firefox
+development process and source code documentation.
+
+.. toctree::
+ :caption: Making Changes To Firefox
+ :maxdepth: 1
+
+ contribution_quickref
+ stack_quickref
+ pocket-guide-shipping-firefox
+ editor
+ reviews
+
+.. toctree::
+ :caption: The Mercurial Version Control System
+ :maxdepth: 1
+ :glob:
+
+ vcs/*
+
+
+.. toctree::
+ :caption: Debugging
+ :maxdepth: 1
+ :glob:
+
+ debugging/*
+
+
+.. toctree::
+ :caption: Additional Information
+ :maxdepth: 1
+
+ directory_structure
+ build/artifact_builds
+ build/building_mobile_firefox
+ build/supported
+ engineering_show_and_tell
diff --git a/docs/contributing/pocket-guide-shipping-firefox.rst b/docs/contributing/pocket-guide-shipping-firefox.rst
new file mode 100644
index 0000000000..fdd37b9620
--- /dev/null
+++ b/docs/contributing/pocket-guide-shipping-firefox.rst
@@ -0,0 +1,523 @@
+Pocket Guide: Shipping Firefox
+==============================
+
+*Estimated read time:* 15min
+
+
+Introduction
+------------
+
+The purpose of this document is to provide a high level understanding of
+how Mozilla ships Firefox. With the intention of helping new Mozillians
+(and those who would like a refresher) understand the basics of our
+release process, tools, common terms, and mechanisms employed in
+shipping Firefox to our users. Often this document will introduce a
+concept, explain how it fits into the process, and then provide a link
+to learn more if interested.
+
+Repositories & Channels
+-----------------------
+
+Shipping Firefox follows a software release :ref:`train model <train model>`
+along 3 primary code :ref:`repositories <repositories>`; mozilla-central
+(aka “m-c”), mozilla-beta, and mozilla-release. Each of these repositories are
+updated within a defined cadence and built into one of our Firefox
+products which are released through what is commonly referred to as
+:ref:`Channels <channels>`: Firefox Nightly, Firefox Beta, and Firefox Release.
+
+`Firefox Nightly <https://whattrainisitnow.com/release/?version=nightly>`__ offers access to the latest cutting edge features
+still under active development. Released every 12 hours with all the
+changes that have :ref:`landed <landing>` on mozilla-central for Desktop and on
+`main in firefox-android <https://github.com/mozilla-mobile/firefox-android/tree/main>`__ for Android.
+
+Every `4 weeks <https://whattrainisitnow.com/calendar/>`__, we
+:ref:`merge <merge>` the code from mozilla-central to our
+mozilla-beta branch.
+For Android, we branch from main on firefox-android to a release branch.
+New code or features can be added to mozilla-beta
+outside of this 4 week cadence but will be required to land in
+mozilla-central and then be :ref:`uplifted <uplift>` into
+mozilla-beta.
+Similarly for Android, uplifts are required to land in main on firefox-android before
+backporting to the firefox-android release branch.
+
+`Firefox Beta <https://whattrainisitnow.com/release/?version=beta>`__ is for developers and early adopters who want to see
+and test what’s coming next in Firefox. We release a new Desktop/Android Beta version
+three times a week.
+
+.. note::
+
+ The first and second beta builds of a new cycle are shipped to a
+ subset of our Beta population. The full Beta population gets updated
+ starting with Beta 3 only.*
+
+Each Beta cycle lasts a total of 4 weeks where a final build is
+validated by our QA and tagged for release into the mozilla-release
+branch for Desktop. On Android we release from the same release branch
+used during the Beta cycle.
+
+.. note::
+
+ **Firefox Developer Edition** *is a separate product based on
+ the mozilla-beta repo and is specifically tailored for Web Developers.*
+
+`Firefox Release <https://whattrainisitnow.com/release/?version=release>`__ is released every 4 weeks and is the end result
+of our Beta cycle. This is our primary product shipping to hundreds of
+millions of users. While a release is live, interim updates (dot releases)
+are used to ship important bug fixes to users prior to the next major release.
+These can happen on an as-needed basis when there is an important-enough
+:ref:`driver <dot release drivers>` to do so (such as a critical bug severely
+impairing the usability of the product for some users). In order to provide
+better predictability, there is also a planned dot release scheduled for two
+weeks after the initial go-live for less-critical fixes and other
+:ref:`ride-along fixes <ride alongs>` deemed low-risk enough to include.
+
+.. note::
+ `Firefox ESR (Extended Support Release) <https://whattrainisitnow.com/release/?version=esr>`__ *is a separate
+ product intended for Enterprise use. Major updates are rolled out once
+ per year to maintain stability and predictability. ESR also
+ contains a number of policy options not available in the standard
+ Firefox Release. Minor updates are shipped in sync with the Firefox
+ Release schedule for security and select quality fixes only.*
+
+Further Reading/Useful links:
+
+- `Firefox
+ Trains <https://whattrainisitnow.com/>`__
+- `Release
+ Calendar <https://whattrainisitnow.com/calendar/>`__
+- `Firefox Release
+ Process <https://wiki.mozilla.org/Release_Management/Release_Process>`__
+- `Firefox Delivery
+ dashboard <https://mozilla.github.io/delivery-dashboard/>`__
+
+Landing Code and Shipping Features
+----------------------------------
+
+Mozillians (those employed by MoCo and the broader community) land lots
+of code in the Mozilla repositories: fixes, enhancements, compatibility,
+new features, etc. and is managed by :ref:`Mercurial <Mercurial Overview>` (aka
+hg). All new code is tracked in :ref:`Bugzilla <bugzilla>`, reviewed
+in :ref:`Phabricator <Phabricator>`, and then checked into the
+mozilla-central repository using :ref:`Lando <Lando>`.
+
+.. note::
+
+ Some teams use :ref:`GitHub <github>` during development
+ but will still be required to use Phabricator (tracked in Bugzilla) to
+ check their code into the mozilla-central hg repository.
+
+The standard process for code to be delivered to our users is by ‘riding
+the trains’, meaning that it’s landed in mozilla-central where it waits
+for the next Beta cycle to begin. After merging to Beta the code will
+stabilize over a 4 week period (along with everything else that merged
+from mozilla-central). At the end of the beta cycle a release candidate
+(:ref:`RC <rc>`) build will be generated, tested thoroughly, and
+eventually become the next version of Firefox.
+
+Further Reading/Useful links:
+
+- `Phabricator and why we use it <https://wiki.mozilla.org/Phabricator>`__
+- `Firefox Release Notes Process <https://wiki.mozilla.org/Release_Management/Release_Notes>`__
+- `Firefox Release Notes Nomination <https://wiki.mozilla.org/Release_Management/Release_Notes_Nomination>`__
+
+An exception to this process...
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Not all code can simply wait for the normal train model to be included
+in a Firefox build. There are a variety of reasons for this; critical
+fixes, security concerns, stabilizing a feature that’s already in Beta,
+shipping high priority features faster, and so on.
+
+In these situations an uplift can be requested to take a recent landing
+in mozilla-central and merge specific bits to another repository outside
+the standard train model. After the request is made within Bugzilla,
+:ref:`Release Management <release management>` will assess the potential risk
+and will make a decision on whether it’s accepted.
+
+Further Reading/Useful links:
+
+- `Patch uplifting
+ rules <https://wiki.mozilla.org/Release_Management/Uplift_rules>`__
+- `Requesting an
+ uplift <https://wiki.mozilla.org/Release_Management/Requesting_an_Uplift>`__
+
+Ensuring build stability
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Throughout the process of landing code in mozilla-central to riding the
+trains to Firefox Release, there are many milestones and quality
+checkpoints from a variety of teams. This process is designed to ensure
+a quality and compelling product will be consistently delivered to our
+users with each new version. See below for a distilled list of those
+milestones.
+
+=========================================== ================ ================= ===============================================================================
+Milestone Week Day of Week
+------------------------------------------- ---------------- ----------------- -------------------------------------------------------------------------------
+Merge Day Nightly W1 Monday Day 1 of the new Nightly Cycle
+PI Request deadline Nightly W1 Friday Manual QA request deadline for high risk features
+Feature technical documentation due Nightly W2 Friday Deadline for features requiring manual QA
+Beta release notes draft Nightly W4 Wednesday
+Nightly features Go/No-Go decisions Nightly W4 Wednesday
+Feature Complete Milestone Nightly W4 Wednesday Last day to land risky patches and/or enable new features
+Nightly soft code freeze start Nightly W4 Thursday Stabilization period in preparation to merge to Beta
+String freeze Nightly W4 Thursday Modification or deletion of strings exposed to the end-users is not allowed
+QA pre-merge regression testing completed Nightly W4 Friday
+Merge Day Beta W1 Monday Day 1 of the new Beta cycle
+Pre-release sign off Beta W3 Friday Final round of QA testing prior to Release
+Firefox RC week Beta W4 Monday Validating Release Candidate builds in preparation for the next Firefox Release
+Release Notes ready Beta W4 Tuesday
+What’s new page ready Beta W4 Wednesday
+Firefox go-live @ 6am PT Release W1 Tuesday Day 1 of the new Firefox Release to 25% of Release users
+Firefox Release bump to 100% Release W1 Thursday Increase deployment of new Firefox Release to 100% of Release users
+Scheduled dot release approval requests due Release W2 Friday All requests required by EOD
+Scheduled dot release go-live Release W3 Tuesday By default, ships when ready. Specific time available upon request.
+=========================================== ================ ================= ===============================================================================
+
+
+The Release Management team (aka “Relman”) monitors and enforces this
+process to protect the stability of Firefox. Each member of Relman
+rotates through end-to-end ownership of a given :ref:`release
+cycle <release cycle>`. The Relman owner of a cycle will focus on the
+overall release, blocker bugs, risks, backout rates, stability/crash
+reports, etc. Go here for a complete overview of the `Relman Release
+Process
+Checklist <https://wiki.mozilla.org/Release_Management/Release_Process_Checklist_Documentation>`__.
+
+.. note::
+
+ While Relman will continually monitor the overall health of each
+ Release it is the responsibility of the engineering organization to
+ ensure the code they are landing is of high quality and the potential
+ risks are understood. Every Release has an assigned :ref:`Regression
+ Engineering Owner <reo>` (REO) to ensure a decision is made
+ about each regression reported in the release.*
+
+Further Reading/Useful links:
+
+- `Release Tracking
+ Rules <https://wiki.mozilla.org/Release_Management/Tracking_rules>`__
+- `Release
+ Owners <https://wiki.mozilla.org/Release_Management/Release_owners>`__
+- `Regression Engineering
+ Owners <https://wiki.mozilla.org/Platform#Regression_Engineering_Owner_.28REO.29>`__
+- `Commonly used Bugzilla queries for all
+ Channels <https://trainqueries.herokuapp.com/>`__
+
+Enabling/Disabling code (Prefs)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Within Firefox we allow the ability to Enable/Disable bits of code or
+entire features using `Preferences <preferences>`. There are many
+reasons why this is useful. Here are some examples:
+
+- Continual development over multiple release cycles without exposing
+ partially completed features to our users
+- Provide the ability to quickly disable a feature if there is a
+ problem found during the release process
+- Control features which are experimental or not ready to be shown to a
+ specific channel population (e.g. enabled for Beta but disabled for
+ Release)
+- A/B testing via :ref:`telemetry <telemetry>` experiments
+
+.. note::
+
+ :ref:`Normandy <normandy>` Pref Rollout is a feature that
+ allows Mozilla to change the state of a preference for a targeted set of
+ users, without deploying an update to Firefox. This is especially useful
+ when conducting experiments or a gradual rollout of high risk features
+ to our Release population.
+
+Further Reading/Useful links:
+
+- `Brief guide to Mozilla
+ preferences <https://developer.mozilla.org/en-US/docs/Mozilla/Preferences/A_brief_guide_to_Mozilla_preferences>`__
+- `Normandy Pref
+ rollout <https://wiki.mozilla.org/Firefox/Normandy/PreferenceRollout>`__
+
+Release & Feature QA
+~~~~~~~~~~~~~~~~~~~~
+
+Release QA is performed regularly and throughout the Release Cycle.
+Organized in two-week sprints its primary goals are:
+
+- Qualifying builds for release
+- Feature testing
+- Product Integrity requests
+- Bug work
+- Community engagement
+
+Features that can have significant impact and/or pose risk to the code
+base should be nominated for QA support by the :ref:`feature
+owner <feature owner>` in its intended release. This process is kicked
+off by filing a :ref:`Product Integrity <product integrity>` team request
+:ref:`PI request <pi request>`. These are due by the end of week 2
+of the Nightly cycle.
+
+.. note::
+
+ Manual QA testing is only required for features as they go
+ through the Beta cycle. Nightly Feature testing is always optional.
+
+Further Reading/Useful links:
+
+- `QA Feature
+ Testing <https://wiki.mozilla.org/QA/Feature_Testing_v2>`__
+- `Release QA
+ overview <https://docs.google.com/document/d/1ic_3TO9-kNmZr11h1ZpyQbSlgiXzVewr3kSAP5ML4mQ/edit#heading=h.pvvuwlkkvtc4>`__
+- `PI Request template and
+ overview <https://mana.mozilla.org/wiki/pages/viewpage.action?spaceKey=PI&title=PI+Request>`__
+
+Experiments
+~~~~~~~~~~~
+
+As we deliver new features to our users we continually ask ourselves
+about the potential impacts, both positive and negative. In many new
+features we will run an experiment to gather data around these impacts.
+A simple definition of an experiment is a way to measure how a change to
+our product affects how people use it.
+
+An experiment has three parts:
+
+1. A new feature that can be selectively enabled
+2. A group of users to test the new feature
+3. Telemetry to measure how people interact with the new feature
+
+Experiments are managed by an in-house tool called
+`Experimenter <https://experimenter.services.mozilla.com/>`__.
+
+Further Reading/Useful links:
+
+- `More about experiments and
+ Experimenter <https://github.com/mozilla/experimenter>`__
+- `Requesting a new
+ Experiment <https://experimenter.services.mozilla.com/experiments/new/>`__
+ (Follow the ‘help’ links to learn more)
+- `Telemetry <https://wiki.mozilla.org/Telemetry>`__
+
+Definitions
+-----------
+
+.. _approval flag:
+
+**Approval Flag** - A flag that represents a security approval or uplift
+request on a patch.
+
+.. _bugzilla:
+
+**Bugzilla** - Web-based general purpose bug tracking system and testing
+tool.
+
+.. _channel:
+
+**Channel** - Development channels producing concurrent releases of
+Firefox for Windows, Mac, Linux, and Android.
+
+.. _chemspill:
+
+**Chemspill** - Short for Chemical Spill. A chemspill is a rapid
+security-driven or critical stsbility dot release of our product.
+
+.. _channel meeting:
+
+**Channel Meeting** - A twice weekly time to check in on the status
+of the active releases with the release team.
+
+.. _dot release drivers:
+
+**Dot Release Drivers** - Issues/Fixes that are significant enough to
+warrant a minor dot release to the Firefox Release Channel. Usually to
+fix a stability (top-crash) or Security (Chemspill) issue.
+
+.. _early beta:
+
+**Early Beta** - Beta releases with the features gated by EARLY_BETA_OR_EARLIER
+enabled. The first 2 weeks of Beta releases during the cycle are early beta releases.
+
+.. _feature owner:
+
+**Feature Owner** - The person who is ultimately responsible for
+developing a high quality feature. This is typically an Engineering
+Manager or Product Manager.
+
+.. _fenix:
+
+**Fenix** - Also known as Firefox Preview is an all-new browser for
+Android based on GeckoView and Android Components
+
+.. _github:
+
+**Github** - Web-based version control and collaboration platform for
+software developers
+
+.. _gtb:
+
+**GTB** - Acronym for Go to build. Mostly used in the release schedule
+communication ("Go to build on March 18"), this means that we initiate the
+building of a specific release.
+
+.. _landing:
+
+**Landing** - A general term used for when code is merged into a
+particular source code repository
+
+.. _lando:
+
+**Lando** - Automated code lander for Mozilla. It is integrated with
+our `Phabricator instance <https://phabricator.services.mozilla.com>`__
+and can be used to land revisions to various repositories.
+
+.. _mercurial:
+
+**Mercurial** - A source-code management tool (just like git)
+which allows users to keep track of changes to the source code
+locally and share their changes with others. It is also called hg.
+
+.. _merge:
+
+**Merge** - General term used to describe the process of integrating and
+reconciling file changes within the mozilla repositories
+
+.. _nightly soft code freeze:
+
+**Nightly Soft Code Freeze** - Last week of the nightly cycle on mozilla-central
+just before the merge to beta during which landing risky or experimental code
+in the repository is discouraged.
+
+.. _normandy:
+
+**Normandy** - Normandy is a collection of servers, workflows, and
+Firefox components that enables Mozilla to remotely control Firefox
+clients in the wild based on precise criteria
+
+.. _nucleus:
+
+**Nucleus** - Name of the internal application used by release managers
+to prepare and publish release notes. The data in this application is
+fetched by mozilla.org.
+
+.. _orange_factor:
+
+**Orange** - Also called flaky or intermittent tests. Describes a state
+when a test or a testsuite can intermittently fail.
+
+.. _phabricator:
+
+**Phabricator** - Mozilla’s instance of the web-based software
+development collaboration tool suite. Read more about `Phabricator as a
+product <https://phacility.com/phabricator/>`__.
+
+.. _pi request:
+
+**PI Request** - Short for Product Integrity Request is a form
+submission request that’s used to engage the PI team for a variety of
+services. Most commonly used to request Feature QA it can also be used
+for Security, Fuzzing, Performance, and many other services.
+
+.. _preferences:
+
+**Preferences** - A preference is any value or defined behavior that can
+be set (e.g. enabled or disabled). Preference changes via user interface
+usually take effect immediately. The values are saved to the user’s
+Firefox profile on disk (in prefs.js).
+
+.. _rc:
+
+**Release Candidate** - Beta version with potential to be a final
+product, which is ready to release unless significant bugs emerge.
+
+.. _rc week:
+
+**RC Week** - The week prior to release go-live is known as RC week.
+During this week an RC is produced and tested.
+
+.. _release cycle:
+
+**Release Cycle** - The sum of stages of development and maturity for
+the Firefox Release Product.
+
+.. _reo:
+
+**Regression Engineering Owner** - A partner for release management
+assigned to each release. They both keep a mental state of how we are
+doing and ensure a decision is made about each regression reported in
+the release. AKA *REO*.
+
+.. _release engineering:
+
+**Release engineering** - Team primarily responsible for maintaining
+the build pipeline, the signature mechanisms, the update servers, etc. aka *releng*
+
+.. _release management:
+
+**Release Management** - Team primarily responsible for the process of
+managing, planning, scheduling and controlling a software build through
+different stages and environments. aka *relman*.
+
+.. _relnotes:
+
+**Relnotes** - Short for release notes. Firefox Nightly, Beta, and Release each ship
+with release notes.
+
+.. _Repository:
+
+**Repository** - a collection of stored data from existing databases
+merged into one so that it may be shared, analyzed or updated throughout
+an organization.
+
+.. _ride alongs:
+
+**Ride Alongs** - Bug fixes that are impacting release users but not
+considered severe enough to ship without an identified dot release
+driver.
+
+.. _rollout:
+
+**Rollout** - Shipping a release to a percentage of the release population.
+
+.. _status flags:
+
+**Status Flags** - A flag that represents the status of the bug with
+respect to a Firefox release.
+
+.. _string freeze:
+
+**String Freeze** - Period during which the introduction, modification, or
+deletion of strings exposed to the end-users is not allowed so as to allow our
+localizers to translate our product.
+
+.. _taskcluster:
+
+**taskcluster** - Our execution framework to build, run tests on multiple
+operating system, hardware and cloud providers.
+
+.. _telemetry:
+
+**Telemetry** - Firefox measures and collects non-personal information,
+such as performance, hardware, usage and customizations. This
+information is used by Mozilla to improve Firefox.
+
+.. _train model:
+
+**Train model** - a form of software release schedule in which a number
+of distinct series of versioned software releases are released as a
+number of different "trains" on a regular schedule.
+
+.. _tracking flags:
+
+**Tracking Flags** - A Bugzilla flag that shows whether a bug is being investigated
+for possible resolution in a Firefox release. Bugs marked tracking-Firefox XX are
+bugs that must be resolved one way or another before a particular release ship.
+
+.. _throttle unthrottle:
+
+**Throttle/Unthrottle a rollout** - Throttle is restricting a release rollout to 0%
+of the release population, users can still choose to update but are not updated
+automatically. Unthrottle is removing the release rollout restriction.
+
+.. _uplift:
+
+**Uplift** - the action of taking parts from a newer version of a
+software system (mozilla-central or mozilla-beta) and porting them to an
+older version of the same software (mozilla-beta, mozilla-release or ESR)
diff --git a/docs/contributing/reviewer_checklist.rst b/docs/contributing/reviewer_checklist.rst
new file mode 100644
index 0000000000..cfe772dba9
--- /dev/null
+++ b/docs/contributing/reviewer_checklist.rst
@@ -0,0 +1,181 @@
+Reviewer Checklist
+==================
+
+ Submitting patches to Mozilla source code needn't be complex. This
+ article provides a list of best practices for your patch content that
+ reviewers will check for or require. Following these best practices
+ will lead to a smoother, more rapid process of review and acceptance.
+
+
+Good web citizenship
+--------------------
+
+- Make sure new web-exposed APIs actually make sense and are either
+ standards track or preffed off by default.
+- In C++, wrapper-cache as needed. If your object can be gotten from
+ somewhere without creating it in the process, it needs to be
+ wrapper-cached.
+
+
+Correctness
+-----------
+
+- The bug being fixed is a valid bug and should be fixed.
+- The patch fixes the issue.
+- The patch is not unnecessarily complicated.
+- The patch does not add duplicates of existing code ('almost
+ duplicates' could mean a refactor is needed). Commonly this results
+ in "part 0" of a bug, which is "tidy things up to make the fix easier
+ to write and review".
+- If QA needs to verify the fix, you should provide steps to reproduce
+ (STR).
+
+
+Quality
+-------
+
+- If you can unit-test it, you should unit-test it.
+- If it's JS, try to design and build so that xpcshell can exercise
+ most functionality. It's quicker.
+- Make sure the patch doesn't create any unused code (e.g., remove
+ strings when removing a feature)
+- All caught exceptions should be logged at the appropriate level,
+ bearing in mind personally identifiable information, but also
+ considering the expense of computing and recording log output.
+ [Fennec: Checking for log levels is expensive unless you're using
+ Logger.]
+
+
+Style
+-----
+
+- Follow the `style
+ guide <https://firefox-source-docs.mozilla.org/code-quality/coding-style/index.html>`__
+ for the language and module in question.
+- Follow local style for the surrounding code, even if that local style
+ isn't formally documented.
+- New files have license declarations and modelines.
+- New JS files should use strict mode.
+- Trailing whitespace (git diff and splinter view both highlight this,
+ as does hg with the color extension enabled). Whitespace can be fixed
+ easily in Mercurial using the `CheckFiles
+ extension <https://www.mercurial-scm.org/wiki/CheckFilesExtension>`__.
+ In git, you can use git rebase --whitespace=fix.
+
+
+Security issues
+---------------
+
+- There should be no writing to arbitrary files outside the profile
+ folder.
+- Be careful when reading user input, network input, or files on disk.
+ Assume that inputs will be too big, too short, empty, malformed, or
+ malicious.
+- Tag for sec review if unsure.
+- If you're writing code that uses JSAPI, chances are you got it wrong.
+ Try hard to avoid doing that.
+
+
+Privacy issues
+--------------
+
+- There should be no logging of URLs or content from which URLs may be
+ inferred.
+- [Fennec: Android Services has Logger.pii() for this purpose (e.g.,
+ logging profile dir)].
+- Tag for privacy review if needed.
+
+
+Resource leaks
+--------------
+
+- In Java, memory leaks are largely due to singletons holding on to
+ caches and collections, or observers sticking around, or runnables
+ sitting in a queue.
+- In C++, cycle-collect as needed. If JavaScript can see your object,
+ it probably needs to be cycle-collected.
+- [Fennec: If your custom view does animations, it's better to clean up
+ runnables in onDetachFromWindow().]
+- Ensure all file handles and other closeable resources are closed
+ appropriately.
+- [Fennec: When writing tests that use PaintedSurface, ensure the
+ PaintedSurface is closed when you're done with it.]
+
+
+Performance impact
+------------------
+
+- Check for main-thread IO [Fennec: Android may warn about this with
+ strictmode].
+- Remove debug logging that is not needed in production.
+
+
+Threading issues
+----------------
+
+- Enormous: correct use of locking and volatility; livelock and
+ deadlock; ownership.
+- [Fennec: All view methods should be touched only on UI thread.]
+- [Fennec: Activity lifecycle awareness (works with "never keep
+ activities"). Also test with oom-fennec
+ (`https://hg.mozilla.org/users/blassey_mozilla.com/oom-fennec/) <https://hg.mozilla.org/users/blassey_mozilla.com/oom-fennec/%29>`__].
+
+
+Compatibility
+-------------
+
+- Version files, databases, messages
+- Tag messages with ids to disambiguate callers.
+- IDL UUIDs are updated when the interface is updated.
+- Android permissions should be 'grouped' into a common release to
+ avoid breaking auto-updates.
+- Android APIs added since Froyo should be guarded by a version check.
+
+
+Preffability
+------------
+
+- If the feature being worked on is covered by prefs, make sure they
+ are hooked up.
+- If working on a new feature, consider adding prefs to control the
+ behavior.
+- Consider adding prefs to disable the feature entirely in case bugs
+ are found later in the release cycle.
+- [Fennec: "Prefs" can be Gecko prefs, SharedPreferences values, or
+ build-time flags. Which one you choose depends on how the feature is
+ implemented: a pure Java service can't easily check Gecko prefs, for
+ example.]
+
+
+Strings
+-------
+
+- There should be no string changes in patches that will be uplifted
+ (including string removals).
+- Rev entity names for string changes.
+- When making UI changes, be aware of the fact that strings will be
+ different lengths in different locales.
+
+
+Documentation
+-------------
+
+- The commit message should describe what the patch is changing (not be
+ a copy of the bug summary). The first line should be a short
+ description (since only the first line is shown in the log), and
+ additional description, if needed, should be present, properly
+ wrapped, in later lines.
+- Adequately document any potentially confusing pieces of code.
+- Flag a bug with dev-doc-needed if any addon or web APIs are affected.
+- Use Javadocs extensively, especially on any new non-private methods.
+- When moving files, ensure blame/annotate is preserved.
+
+
+Accessibility
+-------------
+
+- For HTML pages, images should have the alt attribute set when
+ appropriate. Similarly, a button that is not a native HTML button
+ should have role="button" and the aria-label attribute set.
+- [Fennec: Make sure contentDescription is set for parts of the UI that
+ should be accessible]
diff --git a/docs/contributing/reviews.rst b/docs/contributing/reviews.rst
new file mode 100644
index 0000000000..0cb0dfb59c
--- /dev/null
+++ b/docs/contributing/reviews.rst
@@ -0,0 +1,145 @@
+Getting reviews
+===============
+
+
+Thorough code reviews are one of Mozilla's ways of ensuring code quality.
+Every patch must be reviewed by the module owner of the code, or one of their designated peers.
+
+To request a review, you will need to specify a review group (starts with #). If there is not, you should select one or more usernames either when you submit the patch, or afterward in the UI.
+If you have a mentor, the mentor can usually either also review or find a suitable reviewer on your behalf.
+
+For example, the syntax to request review from a group should be:
+
+.. code-block::
+
+ Bug xxxx - explain what you are doing and why r?#group-name
+
+ or
+
+ Bug xxxx - explain what you are doing and why r?developer-nickname
+
+Getting attention: If a reviewer doesn't respond within a week, or so of the review request:
+
+ * Contact the reviewer directly (either via e-mail or on Matrix).
+ * Join developers on `Mozilla's Matrix server <https://chat.mozilla.org>`_, and ask if anyone knows why a review may be delayed. Please link to the bug too.
+ * If the review is still not addressed, mail the reviewer directly, asking if/when they'll have time to review the patch, or might otherwise be able to review it.
+
+For simple documentation changes, reviews are not required.
+
+For more information about the review process, see the :ref:`Code Review FAQ`.
+
+Review groups
+-------------
+
+
+.. list-table::
+ :header-rows: 1
+
+ * - Name
+ - Owns
+ - Members
+ * - #anti-tracking
+ - `Core: Anti-Tracking </mots/index.html#core-anti-tracking>`__
+ - `Member list <https://phabricator.services.mozilla.com/project/members/157/>`__
+ * - #build or #firefox-build-system-reviewers
+ - The configure & build system
+ - `Member list <https://phabricator.services.mozilla.com/project/members/20/>`__
+ * - #cookies
+ - `Core: Cookies </mots/index.html#core-cookies>`__
+ - `Member list <https://phabricator.services.mozilla.com/project/members/177/>`__
+ * - #dom-workers-and-storage-reviewers
+ - DOM Workers & Storage
+ - `Member list <https://phabricator.services.mozilla.com/project/members/115/>`__
+ * - #devtools-reviewers
+ - Firefox DevTools
+ - `Member list <https://phabricator.services.mozilla.com/project/members/153/>`__
+ * - #fluent-reviewers
+ - Changes to Fluent (FTL) files (translation).
+ - `Member list <https://phabricator.services.mozilla.com/project/members/105/>`__
+ * - #firefox-source-docs-reviewers
+ - Documentation files and its build
+ - `Member list <https://phabricator.services.mozilla.com/project/members/118/>`__
+ * - #firefox-ux-team
+ - User experience (UX)
+ - `Member list <https://phabricator.services.mozilla.com/project/members/91/>`__
+ * - #firefox-svg-reviewers
+ - SVG-related changes
+ - `Member list <https://phabricator.services.mozilla.com/project/members/97/>`__
+ * - #geckoview-reviewers
+ - Changes to GeckoView
+ - `Member list <https://phabricator.services.mozilla.com/project/members/92/>`__
+ * - #gfx-reviewers
+ - Changes to Graphics code
+ - `Member list <https://phabricator.services.mozilla.com/project/members/122/>`__
+ * - #webgpu-reviewers
+ - Changes to WebGPU code
+ - `Member list <https://phabricator.services.mozilla.com/project/members/170/>`__
+ * - #intermittent-reviewers
+ - Test manifest changes
+ - `Member list <https://phabricator.services.mozilla.com/project/members/110/>`__
+ * - #layout-reviewers
+ - Layout changes.
+ - `Member list <https://phabricator.services.mozilla.com/project/members/126/>`__
+ * - #linter-reviewers
+ - tools/lint/*
+ - `Member list <https://phabricator.services.mozilla.com/project/members/119/>`__
+ * - #mac-reviewers
+ - Changes to Mac-specific code
+ - `Member list <https://phabricator.services.mozilla.com/project/members/149/>`__
+ * - #mozbase
+ - Changes to Mozbase
+ - `Member list <https://phabricator.services.mozilla.com/project/members/113/>`__
+ * - #mozbase-rust
+ - Changes to Mozbase in Rust
+ - `Member list <https://phabricator.services.mozilla.com/project/members/114/>`__
+ * - #necko-reviewers
+ - Changes to network code (aka necko, aka netwerk)
+ - `Member list <https://phabricator.services.mozilla.com/project/members/127/>`__
+ * - #nss-reviewers
+ - Changes to Network Security Services (NSS)
+ - `Member list <https://phabricator.services.mozilla.com/project/members/156/>`__
+ * - #perftest-reviewers
+ - Perf Tests
+ - `Member list <https://phabricator.services.mozilla.com/project/members/102/>`__
+ * - #permissions or #permissions-reviewers
+ - `Permissions </mots/index.html#core-permissions>`__
+ - `Member list <https://phabricator.services.mozilla.com/project/members/158/>`__
+ * - #platform-i18n-reviewers
+ - Platform Internationalization
+ - `Member list <https://phabricator.services.mozilla.com/project/members/150/>`__
+ * - #preferences-reviewers
+ - Firefox for Desktop Preferences (Options) user interface
+ - `Member list <https://phabricator.services.mozilla.com/project/members/132/>`__
+ * - #remote-debugging-reviewers
+ - Remote Debugging UI & tools
+ - `Member list <https://phabricator.services.mozilla.com/project/members/108/>`__
+ * - #spidermonkey-reviewers
+ - SpiderMonkey JS/Wasm Engine
+ - `Member list <https://phabricator.services.mozilla.com/project/members/173/>`__
+ * - #static-analysis-reviewers
+ - Changes related to Static Analysis
+ - `Member list <https://phabricator.services.mozilla.com/project/members/120/>`__
+ * - #style or #firefox-style-system-reviewers
+ - Firefox style system (servo, layout/style).
+ - `Member list <https://phabricator.services.mozilla.com/project/members/90/>`__
+ * - #webcompat-reviewers
+ - System addons maintained by the Web Compatibility team
+ - `Member list <https://phabricator.services.mozilla.com/project/members/124/>`__
+ * - #webdriver-reviewers
+ - Marionette and geckodriver (including MozBase Rust), and Remote Protocol with WebDriver BiDi, and CDP.
+ - `Member list <https://phabricator.services.mozilla.com/project/members/103/>`__
+ * - #webidl
+ - Changes related to WebIDL
+ - `Member list <https://phabricator.services.mozilla.com/project/members/112/>`__
+ * - #xpcom-reviewers
+ - Changes related to XPCOM
+ - `Member list <https://phabricator.services.mozilla.com/project/members/125/>`__
+ * - #media-playback-reviewers
+ - `Media playback <https://wiki.mozilla.org/Modules/All#Media_Playback>`__
+ - `Member list <https://phabricator.services.mozilla.com/project/profile/159/>`__
+ * - #cubeb-reviewers
+ - Changes related to cubeb, Gecko's audio input/output library and associated projects (audioipc, cubeb-rs, rust cubeb backends)
+ - `Member list <https://phabricator.services.mozilla.com/project/profile/129/>`__
+
+To create a new group, fill a `new bug in Conduit::Administration <https://bugzilla.mozilla.org/enter_bug.cgi?product=Conduit&component=Administration>`__.
+See `bug 1613306 <https://bugzilla.mozilla.org/show_bug.cgi?id=1613306>`__ as example.
diff --git a/docs/contributing/stack_quickref.rst b/docs/contributing/stack_quickref.rst
new file mode 100644
index 0000000000..cd298fde1a
--- /dev/null
+++ b/docs/contributing/stack_quickref.rst
@@ -0,0 +1,166 @@
+Working with stack of patches Quick Reference
+=============================================
+
+Working on Firefox, we strongly recommend working with stack of patches.
+Patches should be small and could be landed in the order used to push them.
+This also helps to breakdown the work for different reviewers.
+
+As it can be complex for new comers, this documentation explains the
+various commands.
+
+In Phabricator, the stack can be seen in the `Revision Contents` section.
+The top of the stack (most recent change) is first in the list.
+
+This is also sometimes called "stack of revisions", "stack of commits" or "series of commits".
+
+**Example:**
+
+.. image:: img/example-stack.png
+
+
+For the overall quick reference guide, see the :ref:`Firefox Contributors Quick Reference <Firefox Contributors' Quick Reference>`
+
+Visualize the stack
+-------------------
+
+.. code-block:: shell
+
+ # Mercurial
+ $ hg wip
+
+ # Git
+ $ git log
+
+
+Merge two patches
+-----------------
+
+It can happen that, instead of updating a patch, a new revision is
+created on Phabricator. For this, merge the patches locally:
+
+.. code-block:: shell
+
+ # Mercurial
+ # Mark the patch to be merged with "roll" (key: "r")
+ # or "fold" (key: "f")
+ $ hg histedit
+
+ # Git
+ # Replace "pick" by "squash" or "fixup"
+ $ git rebase -i
+
+Then, push to Phabricator and abandon the old change.
+
+
+Submitting the first patch on the stack
+---------------------------------------
+
+There are times when you are working on multiple patches and
+just want to submit the first one. For this, you can use:
+
+.. code-block:: shell
+
+ $ moz-phab submit .
+
+
+Reorder the stack
+-----------------
+
+Sometimes, we want to change the order the patches in the stack.
+Fortunately, VCS support this easily.
+
+.. code-block:: shell
+
+ # Mercurial
+ # Just change the order the patch. The tool should highlight
+ # potential risks of conflicts.
+ # Note that ctrl+c works well if used
+ $ hg histedit
+
+ # Git
+ # In the editor, just move the line below/above
+ # Remove everything if you want to cancel the operation
+ $ git rebase -i
+
+
+Make a change on a patch at the beginning of the stack
+------------------------------------------------------
+
+In some cases, the reviewer is asking for a change at the bottom of the stack (ie not at the top).
+So, a simple `hg/git commit --amend` would not work.
+
+In such case, the following approach can be used:
+
+.. code-block:: shell
+
+ # Mercurial
+ # hg will try to guess in which an unambiguous prior commit
+ $ hg absorb
+
+ # if this doesn't work, create a temporary commit
+ # and merge it using "fold" or "roll"
+ $ hg histedit
+
+ # Git
+ $ git commit --fixup <hash of the commit>
+
+
+Removing patches in the stack
+-----------------------------
+
+To remove a patch in the stack:
+
+.. code-block:: shell
+
+ # Mercurial
+ # select "drop" (letter "d")
+ $ hg histedit
+
+ # Git
+ # Replace "pick" by "drop"
+ # Or simply remove the line for this commit
+ $ git rebase -i
+
+
+Rebasing the stack
+------------------
+
+As the codebase moves fast, it can be necessary to pull changes from
+mozilla-central before landing the changes.
+
+.. code-block:: shell
+
+ # Mercurial
+ # First, see where your patches are in the stack
+ $ hg wip
+ # Then, rebase it:
+ # If you are a beginner, don't hesitate to add "--dry-run"
+ $ hg pull
+ $ hg rebase -b . -d central
+
+
+ # Git
+ $ git remote update
+ $ git rebase mozilla/central
+
+
+Reorganizing the stack in Phabricator
+-------------------------------------
+
+.. code-block:: shell
+
+ $ moz-phab reorg [start_rev] [end_rev]
+
+allows you to reorganize the stack in Phabricator.
+
+If you've changed the local stack by adding, removing or moving the commits around, you need to change the parent/child relation of the revisions in Phabricator.
+
+.. code-block:: shell
+
+ $ moz-phab reorg
+
+command will compare the stack, display what will be changed and ask for permission before taking any action.
+
+.. note::
+
+ Note that reviewbot will not restart the analysis.
diff --git a/docs/contributing/vcs/mercurial.rst b/docs/contributing/vcs/mercurial.rst
new file mode 100644
index 0000000000..f5f7cef39d
--- /dev/null
+++ b/docs/contributing/vcs/mercurial.rst
@@ -0,0 +1,194 @@
+Mercurial Overview
+==================
+
+Mercurial is a source-code management tool which allows users to keep track of changes to the source code locally and share their changes with others.
+We use it for the development of Firefox.
+
+Installation
+------------
+
+See `Mercurial Page <https://www.mercurial-scm.org/downloads>`__ for installation.
+
+
+Using `hg clone`
+----------------
+
+If you are not worried about network interruptions, then you can simply
+use Mercurial to directly clone the repository you're interested in
+using its URL, as given below. For example, to use the command line to
+clone ``mozilla-central`` into a directory called ``firefox-source``,
+you would use the following:
+
+.. code-block:: shell
+
+ hg clone https://hg.mozilla.org/mozilla-central/ firefox-source
+ cd firefox-source
+
+Using Mercurial bundles
+-----------------------
+
+If you are worried that your Internet connection is not fast or robust
+enough to download such a large amount of data all in one go without
+being interrupted and cannot clone using the command given above, then you are recommended to try :ref:`Mercurial bundles <Mercurial bundles>`. If interrupted, they can be resumed (continued without downloading
+from the beginning) if the app you're using to download supports it. For
+example, in Firefox you would right click on the download and select
+`Resume` once your connection to the Internet was reestablished.
+
+Basic configuration
+-------------------
+
+You should configure Mercurial before submitting patches to Mozilla.
+
+If you will be pulling the Firefox source code or one of the derived repositories, the easiest way to configure Mercurial is to run the vcs-setup mach command:
+
+.. code-block:: shell
+
+ $ ./mach vcs-setup
+
+This command starts an interactive wizard that will help ensure your Mercurial is configured with the latest recommended settings. This command will not change any files on your machine without your consent.
+
+
+Other configuration tips
+------------------------
+
+If you don't have the Firefox source code available, you should edit your Mercurial configuration file to look like the following:
+
+.. code-block:: shell
+
+ [ui]
+ username = Your Real Name <user@example.com>
+ merge = your-merge-program (or internal:merge)
+
+ [diff]
+ git = 1
+ showfunc = 1
+ unified = 8
+
+ [defaults]
+ commit = -v
+
+These settings can be added to ``$HOME/.hgrc`` (Linux/macOS) or ``$HOME\Mercurial.ini`` (Windows).
+
+You can configure the editor to use for commit messages using the `editor` option in the `[ui]` section or by setting the `EDITOR` environment variable.
+
+If you are trying to access the repository through a proxy server, see `these
+instructions <http://www.selenic.com/mercurial/hgrc.5.html#http-proxy>`__
+
+
+Selecting a repository (tree)
+-----------------------------
+
+There are multiple hg repositories hosted at mozilla.org to choose from.
+A summary of the main trees is given below, but see
+https://hg.mozilla.org/ for the full list.
+
+mozilla-central
+---------------
+
+This is the main development tree for Firefox. Most developers write
+patches against mozilla-central.
+
+URL: https://hg.mozilla.org/mozilla-central/
+
+
+mozilla-beta
+------------
+
+The source for the current beta version of Firefox (and the next and all
+previous betas). This code represents the expected next release of the
+Firefox browser, and should be pretty stable.
+
+URL: https://hg.mozilla.org/releases/mozilla-beta/
+
+mozilla-release
+---------------
+
+The source for the current release of Firefox (and the next and all
+previous releases).
+
+URL: https://hg.mozilla.org/releases/mozilla-release/
+
+autoland
+--------
+
+This is the integration tree for Firefox. Patches land in this repository first,
+and then are merged by the sheriffs in mozilla-central.
+
+URL: https://hg.mozilla.org/integration/autoland/
+
+L10n repos
+----------
+
+Mainly useful for localizers working on localizing Firefox. Code for all
+l10n projects lives here and is organized into separate repos that (in
+most cases) have the locale's two character ISO code. To get the repo
+that you need look for the repo you're interested in on the following
+page.
+
+URL: https://hg.mozilla.org/l10n-central/
+
+Unified Repositories
+--------------------
+
+It is common for advanced users to want to interact with more than one
+firefox repository. If you get to the point where having individual
+copies of repositories is annoying you, then see
+https://mozilla-version-control-tools.readthedocs.org/en/latest/hgmozilla/unifiedrepo.html
+for instructions on doing this efficiently.
+
+Selecting a revision to build
+-----------------------------
+
+Most of the time the `tip` revision of most repositories will build
+without issue. If you are worried about it not, then you may want to
+stick to mozilla-central.
+
+Building
+--------
+
+By default with no configuration a similar-to-release build is done. If
+you wish you can :ref:`configure <Configuring Build Options>` the build using a ``.mozconfig`` file
+and ``mach build``.
+Different OSs have different prerequisites for a successful build,
+please refer to the :ref:`build documentation <Getting Set Up To Work On The Firefox Codebase>`
+to verify they are available on your build machine.
+
+Extensions
+----------
+
+There's a number of extensions you can enable. See http://mercurial.selenic.com/wiki/UsingExtensions. Almost everyone should probably enable the following, most of them are enabled by ``mach boostrap``:
+
+#. color - Colorize terminal output
+#. histedit - Provides git rebase --interactive behavior.
+#. rebase - Ability to easily rebase patches on top of other heads.
+#. evolve - Enable and enhance the inprogress ChangesetEvolution work.
+#. firefoxtree - Enhances the interaction with Firefox repositories.
+
+These can all be turned on by just adding this to your `.hgrc` file:
+
+.. code-block:: shell
+
+ [extensions]
+ color =
+ rebase =
+ histedit =
+ firefoxtree =
+ evolve =
+
+In addition, there are some 3rd party extensions that are incredibly
+useful for basic development:
+
+`mozext <https://hg.mozilla.org/hgcustom/version-control-tools/file/default/hgext/mozext>`__
+ Mozilla-specific functionality to aid in developing Firefox/Gecko.
+
+Configuring the try repository
+------------------------------
+
+About :ref:`Pushing to Try <Pushing to Try>`.
+
+Learning to use Mercurial
+-------------------------
+
+If you are new to Mercurial, you should start with the `official guide <https://www.mercurial-scm.org/guide>`__.
+
+Then, move on to the `version control tool docs <https://mozilla-version-control-tools.readthedocs.io/en/latest/hgmozilla/>`__ for Mozilla-centric Mercurial information.
diff --git a/docs/contributing/vcs/mercurial_bundles.rst b/docs/contributing/vcs/mercurial_bundles.rst
new file mode 100644
index 0000000000..66d6c26c72
--- /dev/null
+++ b/docs/contributing/vcs/mercurial_bundles.rst
@@ -0,0 +1,65 @@
+Mercurial Bundles
+=================
+
+If you have a poor network connection that is preventing ``hg clone`` from completing, you may want to try downloading a bundle of the repository you're interested in. This is useful since a file download, unlike ``hg clone``, can be resumed if the connection is interrupted. Once you have the bundle, staying up-to-date shouldn't take much time at all, if you keep up with it regularly.
+
+This document explains the steps to setup the `mozilla-unified <https://hg.mozilla.org/mozilla-unified/>`__ repository using a bundle file. Be sure to replace "``mozilla-unified``" with the project you're working with as appropriate.
+
+Download the bundle
+-------------------
+
+1. Open https://hg.cdn.mozilla.net/ :
+
+It lists up-to-date bundles for some of the repositories listed at https://hg.mozilla.net/ .
+Each row corresponds to each repository, and each column corresponds to each compression format.
+
+2. Download the bundle file for the ``mozilla-unified`` repository:
+
+Click the link in the "mozilla-unified" row, the "zstd (max)" column.
+
+Setting up the repository
+-------------------------
+
+Once you have downloaded the repository bundle, follow the steps below to recreate the repository locally based upon that bundle.
+
+1. Initialize a new repository (in a directory called ``mozilla-unified`` here):
+
+.. code-block:: shell
+
+ mkdir mozilla-unified
+ hg init mozilla-unified
+
+2. Un-bundle the bundle file to that repository:
+
+Move the bundle file next to ``mozilla-unified`` directory, and rename it to ``bundle.hg``.
+
+.. code-block:: shell
+
+ cd mozilla-unified
+ hg unbundle ../bundle.hg
+
+Get comfortable. Grab a coffee (or your favorite tasty beverage). Maybe a nap. This unbundling process is going to take quite a lot of time.
+
+3. Create the repository's config file ``.hg/hgrc``, and add the following lines, so that Mercurial will automatically know where to pull changes from future updates. You can open the template config file in your editor by running ``hg config --local --edit`` or ``EDITOR=<editor-of-your-choice> hg config --local --edit``
+
+.. code-block:: shell
+
+ [paths]
+ default = https://hg.mozilla.org/mozilla-unified/
+
+4. Update the repository to get all the changes since the bundle was created (this step also doubles as a check of the bundle integrity since if its contents are not exactly the same as what's in the official repository then the ``hg pull`` will fail):
+
+.. code-block:: shell
+
+ hg pull
+
+5. Check out a working copy from your new up to date repository:
+
+.. code-block:: shell
+
+ hg update
+
+You now have a clone of ``mozilla-unified`` that is identical to one made via ``hg clone``. You can adjust your build settings, or you can go straight ahead and build Firefox!
+
+If at any point you are stuck, feel free to ask on Riot/Matrix at `https://chat.mozilla.org <https://chat.mozilla.org>`__
+in `#introduction <https://chat.mozilla.org/#/room/#introduction:mozilla.org>`__ channel.
diff --git a/docs/crash-reporting/img/default-search-results.png b/docs/crash-reporting/img/default-search-results.png
new file mode 100644
index 0000000000..394f997554
--- /dev/null
+++ b/docs/crash-reporting/img/default-search-results.png
Binary files differ
diff --git a/docs/crash-reporting/img/default-search-results2.png b/docs/crash-reporting/img/default-search-results2.png
new file mode 100644
index 0000000000..03fb33d8c3
--- /dev/null
+++ b/docs/crash-reporting/img/default-search-results2.png
Binary files differ
diff --git a/docs/crash-reporting/img/facet-search-results.png b/docs/crash-reporting/img/facet-search-results.png
new file mode 100644
index 0000000000..a53db96b65
--- /dev/null
+++ b/docs/crash-reporting/img/facet-search-results.png
Binary files differ
diff --git a/docs/crash-reporting/img/facet-search-results2.png b/docs/crash-reporting/img/facet-search-results2.png
new file mode 100644
index 0000000000..7166302974
--- /dev/null
+++ b/docs/crash-reporting/img/facet-search-results2.png
Binary files differ
diff --git a/docs/crash-reporting/img/facet-search-results3.png b/docs/crash-reporting/img/facet-search-results3.png
new file mode 100644
index 0000000000..bc96d30ee9
--- /dev/null
+++ b/docs/crash-reporting/img/facet-search-results3.png
Binary files differ
diff --git a/docs/crash-reporting/img/narrower-search-results.png b/docs/crash-reporting/img/narrower-search-results.png
new file mode 100644
index 0000000000..38b410b362
--- /dev/null
+++ b/docs/crash-reporting/img/narrower-search-results.png
Binary files differ
diff --git a/docs/crash-reporting/img/super-search-form.png b/docs/crash-reporting/img/super-search-form.png
new file mode 100644
index 0000000000..63b35a23ad
--- /dev/null
+++ b/docs/crash-reporting/img/super-search-form.png
Binary files differ
diff --git a/docs/crash-reporting/img/super-search-form2.png b/docs/crash-reporting/img/super-search-form2.png
new file mode 100644
index 0000000000..02dd3a541e
--- /dev/null
+++ b/docs/crash-reporting/img/super-search-form2.png
Binary files differ
diff --git a/docs/crash-reporting/img/super-search-form3.png b/docs/crash-reporting/img/super-search-form3.png
new file mode 100644
index 0000000000..473706e548
--- /dev/null
+++ b/docs/crash-reporting/img/super-search-form3.png
Binary files differ
diff --git a/docs/crash-reporting/index.rst b/docs/crash-reporting/index.rst
new file mode 100644
index 0000000000..c1273c6927
--- /dev/null
+++ b/docs/crash-reporting/index.rst
@@ -0,0 +1,52 @@
+Crash reporting
+===============
+
+Firefox ships with an open-source crash reporting system. This system is
+combination of projects:
+
+- `Google
+ Breakpad <https://chromium.googlesource.com/breakpad/breakpad>`__
+ client and server libraries
+- Mozilla-specific crash reporting user interface and bootstrap code
+- `Socorro <https://github.com/mozilla-services/socorro>`__ Collection
+ and reporting server
+
+
+Where did my crash get submitted?
+---------------------------------
+
+Crash data submitted using the Mozilla Crash Reporter is located on
+`crash-stats <https://crash-stats.mozilla.org/>`__. If you want to find
+a specific crash that you submitted, you first need to find the Crash ID
+that the server has assigned your crash. Type ``about:crashes`` into
+your location bar to get a page listing both submitted and unsubmitted
+crash reports. For more information, see :ref:`How to get a stacktrace for a bug report`.
+
+
+Reports and queries
+-------------------
+
+crash-stats has built-in reports of "topcrashes" for each release
+grouped by signature. There is also a `custom query tool <https://crash-stats.mozilla.org/search/>`__
+which allows users to limit searches on more precise information.
+
+Finally, a set of Mozilla employees have access to directly query the
+underlying data in either SQL summary or using mapreduce on the storage
+cluster. If you are interested in obtaining this advanced access, read
+`Crash Stats Documentation: Protected Data Access <https://crash-stats.mozilla.org/documentation/protected_data_access/>`__
+
+
+See also
+--------
+
+- :ref:`Understanding crash reports`
+- :ref:`A guide to searching crash reports`
+- `crash-stats <https://crash-stats.mozilla.org/>`__
+- `Crash pings (Telemetry) and crash reports (Socorro/Crash
+ Stats) <https://bluesock.org/~willkg/blog/mozilla/crash_pings_crash_reports.html>`__
+- :ref:`Building with Debug Symbols`
+- :ref:`Environment variables affecting crash reporting <Crash Reporter#Environment variables affecting crash reporting>`
+- :ref:`Uploading symbols to Mozilla's symbol server`
+- :ref:`Crash reporter`
+- :ref:`Crash manager`
+- :ref:`Crash ping`
diff --git a/docs/crash-reporting/searching_crash_reports.rst b/docs/crash-reporting/searching_crash_reports.rst
new file mode 100644
index 0000000000..0668a03654
--- /dev/null
+++ b/docs/crash-reporting/searching_crash_reports.rst
@@ -0,0 +1,257 @@
+A guide to searching crash reports
+==================================
+
+.. note::
+
+ Please read the :ref:`documentation about individual crash
+ reports <Understanding crash reports>` before reading
+ this page.
+
+The Mozilla `crash-stats <https://crash-stats.mozilla.org/>`__ site
+provides facilities for investigating large numbers of Firefox `crash
+reports <Understanding crash reports>`__. This guide to
+searching through crash reports may help you locate the crash reports
+that will help you find and fix the Firefox bug you're working on.
+
+Specifically, crash-stats offers two basic functions:
+
+Searching
+ You can search the crash reports database by over 100 criteria: crash
+ signature, date, platform, product, version, etc.
+Grouping
+ You can cluster the results of each search into groups using the same
+ criteria.
+
+To achieve full power and flexibility requires a good understanding of
+both of these functions. Search is easy to understand, but the grouping
+capabilities are easy to overlook.
+
+Searching
+---------
+
+The search form
+~~~~~~~~~~~~~~~
+
+You can get to the `search
+page <https://crash-stats.mozilla.org/search/?product=Firefox&_dont_run=1>`__
+by clicking on the "Super Search" link near the toolbar at the top right
+of any page in crash-stats. This brings up a search form like the one in
+the following screenshot.
+
+|Search in crash-stats|
+
+Fields are provided for four common search criteria: product, version,
+platform, and process type. The product field is pre-populated with
+"Firefox" because that is a common case. As the fine print says, the
+default date range is the past week.
+
+The default search: Signature facet
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you click on the "Search" button, you will get
+`results <https://crash-stats.mozilla.org/search/?product=Firefox&_sort=-date&_facets=signature&_columns=date&_columns=signature&_columns=product&_columns=version&_columns=build_id&_columns=platform#facet-signature>`__
+like the ones in the following screenshot.
+
+|Results of a default search in crash-stats|
+
+By default, the "Signature facet" tab is selected. ("Facet" is a term
+that means "group".) In these results, the found crash reports are
+grouped according to crash signature and ranked by group size. The
+columns show each group's rank, signature, size (both a count and a
+proportion of matching crash reports), and finally a list of bugs that
+have been marked as relating to this signature.
+
+The numbers are large because this search matched all Firefox crash
+reports from the past seven days. The first group has over 100,000 crash
+reports, which accounts for 7.77% of all matching crashes. This
+indicates there are over 1.3 million crash reports matching this search.
+
+You can reorder the groups in various ways by clicking on the column
+headers. The links within the results do the following things.
+
+- The first link in each "Signature" column cell links to a signature
+ report, which contains additional details about crash reports with
+ that signature.
+- The "Add term" link in each "Signature" column cell lets you perform
+ a narrower subsequent search among crash reports with that signature.
+- The links in each "Bugs" column cell link to bug reports in Bugzilla.
+
+The default search: Crash reports
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you switch to the "Crash Reports" tab you will see
+`results <https://crash-stats.mozilla.org/search/?product=Firefox&_sort=-date&_facets=signature&_columns=date&_columns=signature&_columns=product&_columns=version&_columns=build_id&_columns=platform#crash-reports>`__
+like the ones in the following screenshot.
+
+|Results of a default search in crash-stats (crash reports tab)|
+
+This is a list of all the individual crash reports that match the search
+criteria. If the number of matches is large -- in this case it exceeds
+1.3 million, just as we saw in the "Signature facet" tab -- the results
+will be spread across multiple pages, which you can visit by clicking
+the links at the top right of the tab.
+
+The links within the results do the following things.
+
+- The link in each "Crash ID" column cell links to an individual crash
+ report.
+- The links in each "Signature" column cell have the same effect that
+ they did in the "Signature facet" tab.
+- The links in the remaining column cells also let you perform a
+ narrower subsequent search with that link's value added to the search
+ criteria.
+
+A narrower search
+~~~~~~~~~~~~~~~~~
+
+You can add criteria to perform a narrower search. For example, to
+perform a search for all Mac crash reports that occurred while
+JavaScript garbage collection was running, do the following.
+
+- Add "Mac OS X" to the "Platform" field.
+- Select "New line", and then choose a field ("is garbage collecting")
+ and an operator ("is true"). The operators available for each field
+ depends on its type.
+
+With these criteria added the search form looks like the following
+screenshot.
+
+|crash-stats Super Search form with additional criteria|
+
+After clicking on "Search" we get
+`results <https://crash-stats.mozilla.org/search/?is_garbage_collecting=__true__&product=Firefox&platform=Mac%20OS%20X&_sort=-date&_facets=signature&_columns=date&_columns=signature&_columns=product&_columns=version&_columns=build_id&_columns=platform>`__
+like those in the following screenshot.
+
+|Results of a narrower search in crash-stats|
+
+The number of crash reports matching this search is in the thousands,
+i.e. much smaller than the previous search.
+
+Proto signature
+~~~~~~~~~~~~~~~
+
+The "proto signature" field is just the raw unprocessed crash stack
+concatenated together.
+
+You can do things like:
+
+- Search for crashes where the signature is Foo, and the proto
+ signature contains Bar. This is helpful if you have a fairly generic
+ signature and you want to see how many of them are a particular case
+ of it that you've come across. Or instead of a signature Foo, a moz
+ crash reason or something else.
+- Use it as a facet. This lets you skim the full signatures of crashes
+ at a glance, bucketed together a bit. Note that because the proto
+ signature includes the entire signature, things aren't grouped all
+ that well.
+
+Grouping
+--------
+
+In the previous section we saw one example of grouping, in the
+"Signature facet" tab that is shown by default. But there are many other
+interesting ways to group searches.
+
+Facets in the search form
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+To do a search with non-signature grouping first click on the "More
+options..." text, which reveals the additional fields shown in the
+following screenshot.
+
+|crash-stats Super Search form with different facets|
+
+(The "Show columns" and "Sort by" fields are straightforward. They apply
+to the "Crash reports" tab of any search results, and are not related to
+grouping.)
+
+The "Facet on" field is the one that controls grouping. By default, it
+contains the value "signature", which explains why we saw a "Signature
+facet" tab in the earlier search results. But we can change the values
+in this field and get different facet tabs in the search results.
+
+Grouping by platform
+~~~~~~~~~~~~~~~~~~~~
+
+For example, if we start with a default search for all Firefox crashes
+in the past week, but then replace the "signature" facet with "platform"
+and "moz crash reason", we get search results with two facet tabs. The
+first of these is a "Platform facet" tab, with
+`results <https://crash-stats.mozilla.org/search/?product=Firefox&_sort=-date&_facets=platform&_facets=moz_crash_reason&_columns=date&_columns=signature&_columns=product&_columns=version&_columns=build_id&_columns=platform#facet-platform>`__
+like those shown in the following screenshot.
+
+|Results of a faceted search in crash-stats|
+
+This has the same columns as the "Signature facet" tab we saw earlier,
+except for the "Bugs" column, because that is a special column that only
+applies to the signature facet. This tab shows the distribution of crash
+reports across the various platforms. Crash reports always include a
+platform field (though it may be empty if something has gone wrong) and
+so the percentages add up to 100.
+
+Grouping by "moz crash reason"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The second facet tab is a "Moz crash reason facet" tab, with
+`results <https://crash-stats.mozilla.org/search/?product=Firefox&_sort=-date&_facets=platform&_facets=moz_crash_reason&_columns=date&_columns=signature&_columns=product&_columns=version&_columns=build_id&_columns=platform#facet-moz_crash_reason>`__
+like those shown in the following screenshot.
+
+|Results of a faceted search in crash-stats (moz crash reason tab)|
+
+This immediately shows which ``MOZ_CRASH`` calls are being hit
+frequently by users. Only a subset of crash reports have the "moz crash
+reason" field -- those that crashed due to hitting a ``MOZ_CRASH`` call
+-- so all crashes that lack that field are omitted from this tab. For
+that reason, the percentages do not add up to 100.
+
+An example of less useful grouping
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The usefulness of grouping varies from field to field. In particular,
+fields that can have many possible values (such as numeric fields) often
+don't group well. For example, if we do a default search grouped by
+uptime we get
+`results <https://crash-stats.mozilla.org/search/?product=Firefox&_sort=-date&_facets=uptime&_columns=date&_columns=signature&_columns=product&_columns=version&_columns=build_id&_columns=platform#facet-uptime>`__
+like those in the following screenshot.
+
+|Results of a faceted search in crash-stats (uptime)|
+
+In this example the top 10 groups account for less than 12% of all
+crashes, and there is an extremely long tail. These results would be
+improved by using numeric ranges instead of individual values, but
+unfortunately that isn't supported.
+
+Advanced Usage
+--------------
+
+The combination of searching and grouping is powerful. Searches find
+crash reports that match particular criteria, and grouping organizes
+those crash reports into interesting groups.
+
+When a search is performed, the page's URL is updated to include the
+search parameters. This means that the results of any search can be
+easily shared by copying and pasting the page's URL.
+
+To become an expert at searching and grouping requires understanding the
+full range of the 100+ fields available for searching and grouping. One
+way to learn about them is to read lots of individual crash reports;
+note that all fields shown in the Details tab of an individual crash
+report have a tool-tip that indicates its key for search. Alternatively,
+you can browse the `complete
+list <https://crash-stats.mozilla.org/documentation/supersearch/api/#section-filters>`__.
+
+There is also an API through which searches can be performed
+programmatically. See the `API
+documentation <https://crash-stats.mozilla.org/documentation/supersearch/>`__
+for full details; note that it uses the term "aggregation" for
+grouping/faceting.
+
+.. |Search in crash-stats| image:: img/super-search-form.png
+.. |Results of a default search in crash-stats| image:: img/default-search-results.png
+.. |Results of a default search in crash-stats (crash reports tab)| image:: img/default-search-results2.png
+.. |crash-stats Super Search form with additional criteria| image:: img/super-search-form2.png
+.. |Results of a narrower search in crash-stats| image:: img/narrower-search-results.png
+.. |crash-stats Super Search form with different facets| image:: img/super-search-form3.png
+.. |Results of a faceted search in crash-stats| image:: img/facet-search-results.png
+.. |Results of a faceted search in crash-stats (moz crash reason tab)| image:: img/facet-search-results2.png
+.. |Results of a faceted search in crash-stats (uptime)| image:: img/facet-search-results3.png
diff --git a/docs/crash-reporting/uploading_symbol.rst b/docs/crash-reporting/uploading_symbol.rst
new file mode 100644
index 0000000000..1a7624ba07
--- /dev/null
+++ b/docs/crash-reporting/uploading_symbol.rst
@@ -0,0 +1,49 @@
+Uploading symbols to Mozilla's symbol server
+============================================
+
+As a third-party releasing your own builds of Firefox or B2G, you should
+consider uploading debug symbols from the builds to Mozilla's symbol
+server. If you have not disabled crash reporting in your builds, crash
+reports will be submitted to `Mozilla's crash reporting
+server <https://crash-stats.mozilla.org/>`__. Without the debug symbols
+that match your build the crash reports will not contain actionable
+information.
+
+Symbols can be uploaded either via a web browser or a web API.
+
+
+Building a Symbol Package
+-------------------------
+
+To upload symbols, you need to build a symbol package. This is a
+.zip file which contains the symbol files in a specific directory structure.
+
+If you are building Firefox,or a similar application using the Mozilla
+build system, you can build the symbol package using a make target:
+
+::
+
+ ./mach buildsymbols
+
+This will create a symbol package in ``dist/`` named something like
+``firefox-77.0a1.en-US.linux-x86_64.crashreporter-symbols.zip`` .
+
+This step requires the ``dump_syms`` tool which should have been automatically
+installed when you setup the Firefox build with ``./mach bootstrap``. If for
+some reason it's missing or outdated running the bootstrap step again will
+retrieve and install an up-to-date version of the tool.
+
+Uploading symbols
+-----------------
+
+Symbols are uploaded via your account on symbols.mozilla.org. Visit
+https://symbols.mozilla.org and log in. Then request upload
+permission by filing a bug in the Socorro component using `this
+template <https://bugzilla.mozilla.org/enter_bug.cgi?assigned_to=nobody%40mozilla.org&amp;bug_ignored=0&amp;bug_severity=--&amp;bug_status=NEW&amp;bug_type=task&amp;cc=gsvelto%40mozilla.com&amp;cc=willkg%40mozilla.com&amp;cf_fx_iteration=---&amp;cf_fx_points=---&amp;comment=What%20e-mail%20account%20are%20you%20requesting%20access%20for%3F%0D%0A...%0D%0A%0D%0AWhat%20symbols%20will%20you%20be%20uploading%20using%20this%20account%3F%0D%0A...%0D%0A%0D%0AIs%20there%20somebody%20at%20Mozilla%20who%20can%20vouch%20for%20you%3F%0D%0A...%0D%0A&amp;component=Upload&amp;contenttypemethod=list&amp;contenttypeselection=text%2Fplain&amp;defined_groups=1&amp;filed_via=standard_form&amp;flag_type-4=X&amp;flag_type-607=X&amp;flag_type-800=X&amp;flag_type-803=X&amp;flag_type-936=X&amp;form_name=enter_bug&amp;maketemplate=Remember%20values%20as%20bookmarkable%20template&amp;op_sys=Unspecified&amp;priority=--&amp;product=Tecken&amp;rep_platform=Unspecified&amp;short_desc=Symbol-upload%20permission%20for%20%3CPerson%3E&amp;target_milestone=---&amp;version=unspecified>`__.
+If you don't have an account yet use the template to request one.
+
+After symbol upload is turned on, you can upload the symbol archive
+directly using the web form at https://symbols.mozilla.org/uploads.
+It is also possible to upload via automated scripts: see the `symbol upload API
+docs <https://tecken.readthedocs.io/en/latest/>`__ for more
+details.
diff --git a/docs/gtest/index.rst b/docs/gtest/index.rst
new file mode 100644
index 0000000000..2e3eb2d37f
--- /dev/null
+++ b/docs/gtest/index.rst
@@ -0,0 +1,312 @@
+GTest
+=====
+
+GTest (googletest) is Google's framework for writing C++ tests on a
+variety of platforms (Linux, Mac OS X, Windows, ...).
+Based on the xUnit architecture, it supports automatic test
+discovery, a rich set of assertions, user-defined assertions, death
+tests, fatal and non-fatal failures, value- and type-parameterized
+tests, various options for running the tests, and XML test report
+generation.
+
+Integration
+-----------
+
+GTest is run as a standard test task on Win/Mac/Linux and Android, under
+treeherder symbol 'GTest'.
+
+
+Running tests
+-------------
+
+The Firefox build process will build GTest on supported platforms as
+long as you don't disable tests in your mozconfig. However xul-gtest will
+only be built when tests are required to save an expensive second
+linking process.
+
+To run the unit tests use 'mach gtest' when invoking Gecko.
+
+Running selected tests
+~~~~~~~~~~~~~~~~~~~~~~
+
+Tests can be selected using mach. You can also use environment variables
+support by GTest. See `Running Test Programs: Running a Subset of the
+Tests <https://github.com/google/googletest/blob/master/docs/advanced.md#running-a-subset-of-the-tests>`__
+for more details.
+
+::
+
+ mach gtest Moz2D.*
+
+
+Configuring GTest
+~~~~~~~~~~~~~~~~~
+
+GTest can be controlled from other environment variables. See `Running
+Test Programs: Advanced
+Options <https://github.com/google/googletest/blob/master/docs/advanced.md#running-test-programs-advanced-options>`__
+for more details.
+
+
+Debugging a GTest Unit Test
+---------------------------
+
+To debug a gtest, pass --debug to the normal command.
+
+.. code-block:: shell
+
+ ./mach gtest --debug [ Prefix.Test ]
+
+If that doesn't work, you can try running the firefox binary under the
+debugger with the MOZ_RUN_GTEST environment variable set to 1.
+
+.. code-block:: shell
+
+ MOZ_RUN_GTEST=1 ./mach run --debug [--debugger gdb]
+
+.. warning::
+
+ Don't forget to build + run 'mach gtest' to relink when using
+ MOZ_RUN_GTEST since it's not part of a top level build.
+
+Note that this will load an alternate libxul - the one which has the
+test code built in, which resides in a gtest/subdirectory of your
+objdir. This gtest-enabled libxul is not built as part of the regular
+build, so you must ensure that it is built before running the above
+command. A simple way to do this is to just run "mach gtest" which will
+rebuild libxul and run the tests. You can also extract the commands
+needed to just rebuild that libxul `from
+mach <https://hg.mozilla.org/mozilla-central/file/3673d2c688b4/python/mozbuild/mozbuild/mach_commands.py#l486>`__
+and run those directly. Finally, note that you may have to run through
+the tests once for gdb to load all the relevant libraries and for
+breakpoint symbols to resolve properly.
+
+Note that you can debug a subset of the tests (including a single test)
+by using the GTEST_FILTER environment variable:
+
+.. code-block:: shell
+
+ GTEST_FILTER='AsyncPanZoom*' MOZ_RUN_GTEST=1 ./mach run --debug [--debugger gdb]
+
+
+Debugging with Xcode
+~~~~~~~~~~~~~~~~~~~~
+
+See `Debugging on Mac OS
+X </en-US/docs/Mozilla/Debugging/Debugging_on_Mac_OS_X>`__ for initial
+setup. You'll likely want to create a separate scheme for running GTest
+("Product" > "Scheme" > "New Scheme…"). In addition to GTEST_FILTER, Set
+the following environment variables:
+
+::
+
+ MOZ_XRE_DIR=/path-to-object-directory/obj-ff-dbg/dist/bin
+ MOZ_RUN_GTEST=True
+
+and under the "Options" tab for the scheme, set the working directory
+to:
+
+::
+
+ ☑️ Use custom working directory: /path-to-object-directory/obj-ff-dbg/_tests/gtest
+
+
+Debugging with Visual Studio Code
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Add a configuration like this to your launch.json file (you can edit it
+via Run / Open Configurations):
+
+::
+
+ {
+ "name": "(gdb) Launch gtest",
+ "type": "cppdbg",
+ "request": "launch",
+ "program": "${workspaceFolder}/obj-x86_64-pc-linux-gnu/dist/bin/firefox",
+ "args": [],
+ "stopAtEntry": false,
+ "cwd": "${workspaceFolder}/obj-x86_64-pc-linux-gnu/_tests/gtest",
+ "environment": [{"name": "MOZ_RUN_GTEST", "value": "True"},
+ {"name": "GTEST_FILTER", "value": "AsyncPanZoom*"}],
+ "externalConsole": false,
+ "MIMode": "gdb",
+ "setupCommands": [
+ {
+ "description": "Enable pretty-printing for gdb",
+ "text": "-enable-pretty-printing",
+ "ignoreFailures": true
+ }
+ ]
+ },
+
+
+Writing a GTest Unit Test
+-------------------------
+
+Most of the `GTest
+documentation <https://github.com/google/googletest/blob/master/googletest/README.md>`__
+will apply here. The `GTest
+primer <https://github.com/google/googletest/blob/master/docs/primer.md>`__
+is a recommended read.
+
+.. warning::
+
+ GTest will run tests in parallel. Don't add unit tests that are not
+ threadsafe, such as tests that require focus or use specific sockets.
+
+.. warning::
+
+ GTest will run without initializing mozilla services. Initialize and
+ tear down any dependencies you have in your test fixtures. Avoid
+ writing integration tests and focus on testing individual units.
+
+See https://hg.mozilla.org/mozilla-central/rev/ed612eec41a44867a for an
+example of how to add a simple test.
+
+If you're converting an existing C++ unit test to a GTest, `this
+commit <https://hg.mozilla.org/mozilla-central/rev/40740cddc131>`__ may
+serve as a useful reference.
+
+
+Setting prefs for a test
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+If tests cover functionality that is disabled by default, you'll have to
+change the relevant preferences either in the individual test:
+
+::
+
+ bool oldPref = Preferences::GetBool(prefKey);
+ Preferences::SetBool(prefKey, true);
+ … // test code
+ Preferences::SetBool(prefKey, oldPref);
+
+or, if it applies more broadly, the change can be applied to the whole
+fixture (see `the GTest
+docs <https://github.com/google/googletest/blob/master/googletest/README.md>`__,
+or
+`AutoInitializeImageLib <https://searchfox.org/mozilla-central/search?q=AutoInitializeImageLib%3A%3AAutoInitializeImageLib&path=>`__
+as an example).
+
+
+Adding a test to the build system
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Find a gtest directory appropriate for the module. If none exist create
+a directory using the following convention: '<submodule>/tests/gtest'.
+Create a moz.build file (in the newly created directory) with a module
+declaration, replacing gfxtest with a unique name, and set
+UNIFIED_SOURCES to contain all of the test file names.
+
+What we're doing here is creating a list of source files that will be
+compiled and linked only against the gtest version of libxul. This will
+let these source files call internal xul symbols without making them
+part of the binary we ship to users.
+
+.. code-block::
+
+ # -*- 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 https://mozilla.org/MPL/2.0/.
+
+ Library('gfxtest')
+
+ UNIFIED_SOURCES = [
+ <ListTestFiles>,
+ ]
+
+ FINAL_LIBRARY = 'xul-gtest'
+
+Update '<submodule>/moz.build' in the parent directory to build your new
+subdirectory in:
+
+.. code-block:: python
+
+ TEST_DIRS += [
+ "gtest",
+ ]
+
+When adding tests to an existing moz.build file (it has FINAL_LIBRARY =
+'xul-gtest'), add the following. That's it--there is no test manifest
+required. Your tests will be automatically registered using a static
+constructor.
+
+.. code-block:: python
+
+ UNIFIED_SOURCES = [
+ 'TestFoo.cpp',
+ ]
+
+Notes
+~~~~~
+
+The include file for the class you are testing may not need to be
+globally exported, but it does need to be made available to the unit
+test you are writing. In that case, add something like this to the
+Makefile.in inside of the testing directory.
+
+.. code-block:: python
+
+ LOCAL_INCLUDES += [
+ '/gfx/2d',
+ '/gfx/2d/unittest',
+ '/gfx/layers',
+ ]
+
+Gtests currently run from the test package under the **GTest** symbol on
+`Treeherder <https://treeherder.mozilla.org/>`__ if you want to verify
+that your test is working. Formerly they were run under the **B**
+symbol, during \`make check`.
+
+
+MozGTestBench
+-------------
+
+A Mozilla GTest Microbench is just a GTest that reports the test
+duration to perfherder. It's an easy way to add low level performance
+test. Keep in mind that there's a non-zero cost to monitoring
+performance test so use them sparingly. You can still perform test
+assertions.
+
+
+Writing a Microbench GTest
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Use 'MOZ_GTEST_BENCH' instead of 'TEST' to time the execution of your
+test. Example:
+
+.. code-block:: cpp
+
+ #include "gtest/MozGTestBench.h" // For MOZ_GTEST_BENCH
+
+ ...
+
+ MOZ_GTEST_BENCH(GfxBench, TEST_NAME, []{
+ // Test to time the execution
+ });
+
+Make sure this file is registered with the file system using the
+instructions above. If everything worked correctly you should see this
+in the GTest log for your corresponding test:
+
+.. code-block:: js
+
+ PERFHERDER_DATA: {"framework": {"name": "platform_microbench"}, "suites": [{"name": "GfxBench", "subtests": [{"name": "CompositorSimpleTree", "value": 252674, "lowerIsBetter": true}]}]}
+
+
+Sheriffing policy
+~~~~~~~~~~~~~~~~~
+
+Microbench tests measure the speed of a very specific operation. A
+regression in a micro-benchmark may not lead to a user visible
+regression and should not be treated as strictly as a Talos regression.
+Large changes in microbench scores will also be expected when the code
+is directly modified and should be accepted if the developer intended to
+change that code. Micro-benchmarks however provide a framework for
+adding performance tests for platform code and regression tests for
+performance fixes. They will catch unintended regressions in code and
+when correlated with a Talos regression might indicate the source of the
+regression.
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000000..3a4afdb408
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,71 @@
+=================================
+Firefox Source Tree Documentation
+=================================
+
+.. toctree::
+ :caption: Getting Started
+ :maxdepth: 1
+
+ {setup_doc}
+
+.. toctree::
+ :caption: Working On Firefox
+ :maxdepth: 2
+
+ {contributing_doc}
+
+.. toctree::
+ :caption: Firefox User Guide
+ :maxdepth: 2
+
+ {user_guide}
+
+.. toctree::
+ :caption: Source Code Documentation
+ :maxdepth: 2
+
+ {source_doc}
+
+.. toctree::
+ :caption: The Firefox Build System
+ :maxdepth: 1
+
+ {build_doc}
+
+.. toctree::
+ :caption: Testing & Test Infrastructure
+ :maxdepth: 1
+
+ {testing_doc}
+
+.. toctree::
+ :caption: Releases & Updates
+ :maxdepth: 1
+
+ {release_doc}
+
+.. toctree::
+ :caption: Localization & Internationalization
+ :maxdepth: 2
+
+ {l10n_doc}
+
+.. toctree::
+ :caption: Firefox and Python
+ :maxdepth: 1
+
+ {python_doc}
+
+.. toctree::
+ :caption: Metrics Collected in Firefox
+ :maxdepth: 1
+
+ {metrics_doc}
+
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/docs/jsdoc.json b/docs/jsdoc.json
new file mode 100644
index 0000000000..b4cfae277c
--- /dev/null
+++ b/docs/jsdoc.json
@@ -0,0 +1,5 @@
+{
+ "source": {
+ "includePattern": ".+\\.m?jsm?$"
+ }
+}
diff --git a/docs/metrics/index.md b/docs/metrics/index.md
new file mode 100644
index 0000000000..50e9c897ab
--- /dev/null
+++ b/docs/metrics/index.md
@@ -0,0 +1,6 @@
+# Metrics
+
+The metrics collected by Firefox using the
+[Glean SDK](https://mozilla.github.io/glean/book/index.html)
+are documented in
+[The Glean Dictionary](https://dictionary.telemetry.mozilla.org/apps/firefox_desktop/).
diff --git a/docs/nspr/about_nspr.rst b/docs/nspr/about_nspr.rst
new file mode 100644
index 0000000000..622bdeac52
--- /dev/null
+++ b/docs/nspr/about_nspr.rst
@@ -0,0 +1,154 @@
+About NSPR
+==========
+
+NetScape Portable Runtime (NSPR) provides platform independence for
+non-GUI operating system facilities. These facilities include threads,
+thread synchronization, normal file and network I/O, interval timing and
+calendar time, basic memory management (malloc and free) and shared
+library linking.
+
+History
+~~~~~~~
+
+A good portion of the library's purpose, and perhaps the primary purpose
+in the Gromit environment, was to provide the underpinnings of the Java
+VM, more or less mapping the *sys layer* that Sun defined for the
+porting of the Java VM to various platforms. NSPR went beyond that
+requirement in some areas and since it was also the platform independent
+layer for most of the servers produced by Netscape. It was expected and
+preferred that existing code be restructured and perhaps even rewritten
+in order to use the NSPR API. It is not a goal to provide a platform for
+the porting into Netscape of externally developed code.
+
+At the time of writing the current generation of NSPR was known as
+NSPR20. The first generation of NSPR was originally conceived just to
+satisfy the requirements of porting Java to various host environments.
+NSPR20, an effort started in 1996, built on that original idea, though
+very little is left of the original code. (The "20" in "NSPR20" does not
+mean "version 2.0" but rather "second generation".) Many of the concepts
+have been reformed, expanded, and matured. Today NSPR may still be
+appropriate as the platform dependent layer under Java, but its primary
+application is supporting clients written entirely in C or C++.
+
+.. _How_It_Works:
+
+How It Works
+~~~~~~~~~~~~
+
+NSPR's goal is to provide uniform service over a wide range of operating
+system environments. It strives to not export the *lowest common
+denominator*, but to exploit the best features of each operating system
+on which it runs, and still provide a uniform service across a wide
+range of host offerings.
+
+Threads
+^^^^^^^
+
+Threads are the major feature of NSPR. The industry's offering of
+threads is quite sundry. NSPR, while far from perfect, does provide a
+single API to which clients may program and expect reasonably consistent
+behavior. The operating systems provide everything from no concept of
+threading at all up to and including sophisticated, scalable and
+efficient implementations. NSPR makes as much use of what the systems
+offer as it can. It is a goal of NSPR that NSPR impose as little
+overhead as possible in accessing those appropriate system features.
+
+.. _Thread_synchronization:
+
+Thread synchronization
+^^^^^^^^^^^^^^^^^^^^^^
+
+Thread synchronization is loosely based on Monitors as described by
+C.A.R. Hoare in *Monitors: An operating system structuring concept* ,
+Communications of the ACM, 17(10), October 1974 and then formalized by
+Xerox' Mesa programming language ("Mesa Language Manual", J.G. Mitchell
+et al, Xerox PARC, CSL-79-3 (Apr 1979)). This mechanism provides the
+basic mutual exclusion (mutex) and thread notification facilities
+(condition variables) implemented by NSPR. Additionally, NSPR provides
+synchronization methods more suited for use by Java. The Java-like
+facilities include monitor *reentrancy*, implicit and tightly bound
+notification capabilities with the ability to associate the
+synchronization objects dynamically.
+
+.. _I.2FO:
+
+I/O
+^^^
+
+NSPR's I/O is a slightly augmented BSD sockets model that allows
+arbitrary layering. It was originally intended to export synchronous I/O
+methods only, relying on threads to provide the concurrency needed for
+complex applications. That method of operation is preferred though it is
+possible to configure the network I/O channels as *non-blocking* in the
+traditional sense.
+
+.. _Network_addresses:
+
+Network addresses
+^^^^^^^^^^^^^^^^^
+
+Part of NSPR deals with manipulation of network addresses. NSPR defines
+a network address object that is Internet Protocol (IP) centric. While
+the object is not declared as opaque, the API provides methods that
+allow and encourage clients to treat the addresses as polymorphic items.
+The goal in this area is to provide a migration path between IPv4 and
+IPv6. To that end it is possible to perform translations of ASCII
+strings (DNS names) into NSPR's network address structures, with no
+regard to whether the addressing technology is IPv4 or IPv6.
+
+Time
+^^^^
+
+Timing facilities are available in two forms: interval timing and
+calendar functions.
+
+Interval timers are based on a free running, 32-bit, platform dependent
+resolution timer. Such timers are normally used to specify timeouts on
+I/O, waiting on condition variables and other rudimentary thread
+scheduling. Since these timers have finite namespace and are free
+running, they can wrap at any time. NSPR does not provide an *epoch* ,
+but expects clients to deal with that issue. The *granularity* of the
+timers is guaranteed to be between 10 microseconds and 1 millisecond.
+This allows a minimal timer *period* in of approximately 12 hours. But
+in order to deal with the wrap-around issue, only half that namespace
+may be utilized. Therefore, the minimal usable interval available from
+the timers is slightly less than six hours.
+
+Calendar times are 64-bit signed numbers with units of microseconds. The
+*epoch* for calendar times is midnight, January 1, 1970, Greenwich Mean
+Time. Negative times extend to times before 1970, and positive numbers
+forward. Use of 64 bits allows a representation of times approximately
+in the range of -30000 to the year 30000. There is a structural
+representation (*i.e., exploded* view), routines to acquire the current
+time from the host system, and convert them to and from the 64-bit and
+structural representation. Additionally there are routines to convert to
+and from most well-known forms of ASCII into the 64-bit NSPR
+representation.
+
+.. _Memory_management:
+
+Memory management
+^^^^^^^^^^^^^^^^^
+
+NSPR provides API to perform the basic malloc, calloc, realloc and free
+functions. Depending on the platform, the functions may be implemented
+almost entirely in the NSPR runtime or simply shims that call
+immediately into the host operating system's offerings.
+
+Linking
+^^^^^^^
+
+Support for linking (shared library loading and unloading) is part of
+NSPR's feature set. In most cases this is simply a smoothing over of the
+facilities offered by the various platform providers.
+
+Where It's Headed
+~~~~~~~~~~~~~~~~~
+
+NSPR is applicable as a platform on which to write threaded applications
+that need to be ported to multiple platforms.
+
+NSPR is functionally complete and has entered a mode of sustaining
+engineering. As operating system vendors issue new releases of their
+operating systems, NSPR will be moved forward to these new releases by
+interested players.
diff --git a/docs/nspr/creating_a_cookie_log.rst b/docs/nspr/creating_a_cookie_log.rst
new file mode 100644
index 0000000000..06a0e90596
--- /dev/null
+++ b/docs/nspr/creating_a_cookie_log.rst
@@ -0,0 +1,65 @@
+Creating a cookie log
+=====================
+
+Creating a cookie log is often necessary to troubleshoot problems with
+Firefox's cookie handling. If you are reading this, you have probably
+been directed here from a bug report. Please follow the instructions
+below to run Firefox with cookie logging enabled.
+
+.. _Enabling_Cookie_Logging:
+
+Enabling Cookie Logging
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Windows
+^^^^^^^
+
+Open a command prompt (this is under Programs or Programs/Accessories in
+normal installations of Windows).
+
+#. Change to your Firefox directory (usually C:\Program Files\Mozilla
+ Firefox)
+#. Type "set NSPR_LOG_FILE=C:\temp\cookie-log.txt", enter
+#. Type "set NSPR_LOG_MODULES=cookie:4" and press Enter
+#. Run Firefox by typing "firefox.exe" and pressing Enter.
+
+Linux
+^^^^^
+
+Start a command shell (these instructions are for bash, if you use
+something else, you probably know how to modify these instructions
+already).
+
+#. Change to the installation directory for Firefox.
+#. Type "export NSPR_LOG_FILE=~/cookie-log.txt" and press Enter.
+#. Type "export NSPR_LOG_MODULES=cookie:4" and press Enter.
+#. Run Firefox by typing "./firefox" and pressing Enter
+
+Mac OS X
+^^^^^^^^
+
+Open Terminal.app, which is located in the /Applications/Utilities
+folder (these instructions are for bash, the default shell in Mac OS X
+10.3 and higher; if you use something else, you probably know how to
+modify these instructions already).
+
+#. Change to the installation directory for Firefox, e.g. type "cd
+ /Applications/Firefox.app/Contents/MacOS" and press Return.
+#. Type "export NSPR_LOG_FILE=~/Desktop/cookie-log.txt" and press
+ Return.
+#. Type "export NSPR_LOG_MODULES=cookie:4" and press Return.
+#. Run Firefox by typing "./firefox-bin" and pressing Return (note that
+ Firefox will launch behind windows for other applications).
+
+Creating the Log
+~~~~~~~~~~~~~~~~
+
+Now that you have Firefox running with logging enabled, please try to
+replicate the bug using the steps to reproduce from the bug report. Once
+you have reproduced the bug, shut down Firefox. Close out of the command
+prompt/shell/Terminal, and then launch Firefox normally. Finally, attach
+the cookie-log.txt file to the bug where it was requested (by clicking
+on Create New Attachment). It should be in C:\temp on Windows, your home
+directory on Linux, or the Desktop on Mac OS X.
+
+Thanks for helping us make Firefox better!
diff --git a/docs/nspr/index.rst b/docs/nspr/index.rst
new file mode 100644
index 0000000000..89e81d2517
--- /dev/null
+++ b/docs/nspr/index.rst
@@ -0,0 +1,66 @@
+NSPR
+====
+
+**Netscape Portable Runtime (NSPR)** provides a platform-neutral API for
+system level and libc-like functions. The API is used in the Mozilla
+clients, many of Red Hat's and Oracle's server applications, and other
+software offerings.
+
+Documentation
+-------------
+
+:ref:`About NSPR`
+ This topic describes, in general terms, the goals of NSPR and a bit
+ about how it does it.
+:ref:`NSPR API Reference`
+ The reference describes each API public macro, structure and function
+ in the NSPR API.
+:ref:`NSPR build instructions`
+ How to checkout and build from source.
+:ref:`NSPR listing`
+ All NSPR pages
+
+.. _Getting_NSPR:
+
+Getting NSPR
+------------
+
+NSPR is available in various source and binary packages, depending on
+your platform:
+
+- **Windows:** Build the source package, using the :ref:`NSPR build
+ instructions`.
+- **Mac:** Install the `MacPorts <http://www.macports.org/>`__ *nspr*
+ package, or the `Homebrew <http://brew.sh>`__ *nspr* package.
+- **Ubuntu:** Install the *libnspr4-dev* package via ``apt-get.``
+- **Debian:** Install the *libnspr4-dev* package via ``apt-get``.
+- **openSUSE Linux:** Install one or more of the following via ``yast``
+ or ``zypper`` :
+
+ - *mozilla-nspr* : Binary libraries for your platform
+ - *mozilla-nspr-32bit* : Binary libraries needed to run 32-bit
+ programs on a 64-bit OS
+ - *mozilla-nspr-devel* : Files needed (in addition to the above
+ libraries) to compile programs using NSPR
+ - *mozilla-nspr-debuginfo* : Debug information (including build
+ symbols) for package *mozilla-nspr*
+ - *mozilla-nspr-debuginfo-32bit* : Debug information (including
+ build symbols) for package *mozilla-nspr-32bit*
+ - *mozilla-nspr-debugsource* : Debug sources for all of the above
+
+Community
+---------
+
+View Mozilla forums:
+
+- `Mailing list <https://lists.mozilla.org/listinfo/dev-tech-nspr>`__
+- `Newsgroup <http://groups.google.com/group/mozilla.dev.tech.nspr>`__
+- `RSS
+ feed <http://groups.google.com/group/mozilla.dev.tech.nspr/feeds>`__
+
+.. _Related_Topics:
+
+Related Topics
+--------------
+
+- :ref:`Networking`, :ref:`Network Security Services (NSS)`
diff --git a/docs/nspr/layeredpoll.rst b/docs/nspr/layeredpoll.rst
new file mode 100644
index 0000000000..6f5cb80efe
--- /dev/null
+++ b/docs/nspr/layeredpoll.rst
@@ -0,0 +1,118 @@
+PR_Poll() and the layered I/O
+=============================
+
+*[last edited by AOF 8 August 1998]*
+This memo discusses some of the nuances of using PR_Poll() in
+conjunction with *layered I/O*. This is a relatively new feature in NSPR
+2.0, not that it hasn't been in the source tree for a while, but in that
+it has had no clients.
+
+Implementation
+--------------
+
+NSPR provides a public API function, PR_Poll() that is modeled after
+UNIX' ``poll()`` system call.
+
+The implementation of :ref:`PR_Poll` is somewhat complicated. Not only
+does it map the :ref:`PRPollDesc` array into structures needed by the
+underlying OS, it also must deal with layered I/O. This is done despite
+the fact that :ref:`PR_Poll` itself is *not* layered. For every element
+of the :ref:`PRPollDesc` array that has a non-NULL :ref:`PRFileDesc` and whose
+``in_flags`` are not zero, it calls the file descriptor's
+``poll() method``.
+The ``poll()`` method is one of the vector contained in the
+:ref:`PRIOMethods` table. In the case of layered I/O, the elements (the
+methods) of the methods table may be overridden by the implementor of
+that layer. The layers are then *stacked.* I/O using that *stack* will
+call through the method at the top layer, and each layer may make
+altering decisions regarding how the I/O operation should proceed.
+
+The purpose of the ``poll()`` method is to allow a layer to modify the
+flags that will ultimately be used in the call to the underlying OS'
+``poll()`` (or equivalent) function. Such modification might be useful
+if one was implementing an augmented stream protocol (*e.g.,* **SSL**).
+SSL stands for **Secure Socket Layer**, hence the obvious applicability
+as an example. But it is way to complicated to describe in this memo, so
+this memo will use a much simpler layered protocol.
+The example protocol is one that, in order to send *n* bytes, it must
+first ask the connection's peer if the peer is willing to receive that
+many bytes. The form of the request is 4 bytes (binary) stating the
+number of bytes the sender wishes to transmit. The peer will send back
+the number of bytes it is willing to receive (in the test code there are
+no error conditions, so don't even ask).
+
+The implication of the protocol is obvious. In order to do a
+:ref:`PR_Send` operation, the layer must first do a *different* send and
+then *receive* a response. Doing this and keeping the *stack's* client
+unaware is the goal. **It is not a goal of NSPR 2.0 to hide the nuances
+of synchronous verses non-blocking I/O**.
+
+The layered methods
+-------------------
+
+Each layer must implement a suitable function for *every* element of the
+methods table. One can get a copy of default methods by calling
+:ref:`PR_GetDefaultIOMethods` These methods simply pass all calls
+through the layer on to the next lower layer of the stack.
+
+A layer implementor might copy the elements of the ``PRIOMethods``
+acquired from this function into a methods table of its own, then
+override just those methods of interest. *Usually* (with only a single
+exception) a layered method will perform its design duties and then call
+the next lower layer's equivalent function.
+
+Layered ``poll()``
+------------------
+
+One of the more interesting methods is the ``poll()``. It is called by
+the runtime whenever the client calls :ref:`PR_Poll`. It may be called at
+the *top* layer for *every* file descriptor in the poll descriptor. It
+may be called zero or more times. The purpose of the ``poll()`` method
+is to provide the layer an opportunity to adjust the polling bits as
+needed. For instance, if a client (*i.e.*, top layer) is calling
+:ref:`PR_Poll` for a particular file descriptor with a *read* poll
+request, a lower layer might decide that it must perform a *write*
+first.
+In that case, the layer's ``poll()`` method would be called with
+**``in_flags``** including a ``PR_POLL_READ`` flag. However, the
+``poll()`` method would call the next lower layer's ``poll()`` method
+with a ``PR_POLL_WRITE`` bit set. This process of re-assigning the poll
+flags can happen as many times as there are layers in the stack. It is
+the final value, the one returned to the caller of the top layer's
+``poll()`` method (:ref:`PR_Poll`) that will be used by the runtime when
+calling the OS' ``poll()`` (or equivalent) system call.
+
+It is expected that the modification of the polling bits propagate from
+the top of the stack down, allowing the layer closest to the bottom of
+the stack to provide the final setting. The implication is that there
+should be no modifications of the **``in_flags``** during the *return*
+phase of the layered function.
+
+For example:
+
+It is not advised to modify the ``final_in_flags`` between the call to
+the lower layer's ``poll()`` method and the ``return`` statement.
+The third argument of the ``poll()`` method is a pointer to a 16-bit
+word. If the layer sets a value in memory through that pointer *and*
+returns with a value that has *corresponding* bits, the runtime assumes
+that the file descriptor is ready immediately.
+
+There are two important deviations from the normal. First, this is the
+one (known) exception to having a layered routine call the stack's next
+lower layer method. If bits are set in the ``out_flags`` the method
+should return *directly*. Second, the runtime will observe that the
+layer claims this file descriptor is ready and suppress the call to the
+OS' ``poll()`` system call.
+
+At this time the only known use for this feature is to allow a layer to
+indicate it has buffered *input*. Note that it is not appropriate for
+buffered *output* since in order to write/send output the runtime must
+still confirm with the OS that such an operation is permitted.
+
+Since the ``poll()`` method may be called zero or more times it must
+therefore be *idempotent* or at least *functional*. It will need to look
+at the layer's state, but must not make modifications to that state that
+would cause subsequent calls within the same :ref:`PR_Poll` call to
+return a different answer. Since the ``poll()`` method may not be called
+at all, so there is not guarantee that any modifications that would have
+been performed by the routine will every happen.
diff --git a/docs/nspr/listing.rst b/docs/nspr/listing.rst
new file mode 100644
index 0000000000..33d0359e01
--- /dev/null
+++ b/docs/nspr/listing.rst
@@ -0,0 +1,10 @@
+NSPR listing
+============
+
+This page lists all the NSPR page.
+
+.. toctree::
+ :glob:
+
+ *
+ reference/*
diff --git a/docs/nspr/nonblocking_io_in_nspr.rst b/docs/nspr/nonblocking_io_in_nspr.rst
new file mode 100644
index 0000000000..a5ba816412
--- /dev/null
+++ b/docs/nspr/nonblocking_io_in_nspr.rst
@@ -0,0 +1,153 @@
+Nonblocking IO in NSPR
+======================
+
+
+Introduction
+------------
+
+Previously, all I/O in the NetScape Portable Runtime (NSPR) was
+*blocking* (or *synchronous*). A thread invoking an io function is
+blocked until the io operation is finished. The blocking io model
+encourages the use of multiple threads as a programming model. A thread
+is typically created to attend to one of the simultaneous I/O operations
+that may potentially block.
+
+In the *nonblocking* io model, a file descriptor may be marked as
+nonblocking. An io function on a nonblocking file descriptor either
+succeeds immediately or fails immediately with
+<tt>PR_WOULD_BLOCK_ERROR</tt>. A single thread is sufficient to attend
+to multiple nonblocking file descriptors simultaneously. Typically, this
+central thread invokes <tt>PR_Poll()</tt> on a set of nonblocking file
+descriptors. (Note: <tt>PR_Poll()</tt> also works with blocking file
+descriptors, although it is less useful in the blocking io model.) When
+<tt>PR_Poll()</tt> reports that a file descriptor is ready for some io
+operation, the central thread invokes that io function on the file
+descriptor.
+
+.. _Creating_a_Nonblocking_Socket:
+
+Creating a Nonblocking Socket
+-----------------------------
+
+*Only sockets can be made nonblocking*. Regular files always operate in
+blocking mode. This is not a serious constraint as one can assume that
+disk I/O never blocks. Fundamentally, this constraint is due to the fact
+that nonblocking I/O and <tt>select()</tt> are only available to sockets
+on some platforms (e.g., Winsock).
+
+In NSPR, a new socket returned by <tt>PR_NewTCPSocket()</tt> or
+<tt>PR_NewUDPSocket()</tt> is always created in blocking mode. One can
+make the new socket nonblocking by using <tt>PR_SetSockOpt()</tt> as in
+the example below (error checking is omitted for clarity):
+
+|
+| <tt>PRFileDesc \*sock;</tt>
+| **<tt>PRIntn optval = 1;</tt>**
+
+<tt>sock = PR_NewTCPSocket();</tt>
+
+::
+
+ /*
+ * Make the socket nonblocking
+ */
+
+ PR_SetSockOpt(sock, PR_SockOpt_Nonblocking, &optval, sizeof(optval));
+
+.. _Programming_Constraints:
+
+Programming Constraints
+-----------------------
+
+There are some constraints due to the use of NT asynchronous I/O in the
+NSPR. In NSPR, blocking sockets on NT are associated with an I/O
+completion port. Once associated with an I/O completion port, we can't
+disassociate the socket from the I/O completion port. I have seen some
+strange problems with using a nonblocking socket associated with an I/O
+completion port. So the first constraint is:
+
+ **The blocking/nonblocking io mode of a new socket is committed the
+ first time a potentially-blocking io function is invoked on the
+ socket. Once the io mode of a socket is committed, it cannot be
+ changed.**
+
+The potentially-blocking io functions include <tt>PR_Connect()</tt>,
+<tt>PR_Accept()</tt>, <tt>PR_AcceptRead()</tt>, <tt>PR_Read()</tt>,
+<tt>PR_Write()</tt>, <tt>PR_Writev()</tt>, <tt>PR_Recv()</tt>,
+<tt>PR_Send()</tt>, <tt>PR_RecvFrom()</tt>, <tt>PR_SendTo()</tt>, and
+<tt>PR_TransmitFile(),</tt> and do not include <tt>PR_Bind()</tt> and
+<tt>PR_Listen()</tt>.
+
+In blocking mode, any of these potentially-blocking functions requires
+the use of the NT I/O completion port. So at that point we must
+determine whether to associate the socket with the I/O completion or
+not, and that decision cannot be changed later.
+
+There is a second constraint, due to the use of NT asynchronous I/O and
+the recycling of used sockets:
+
+ **The new socket returned by <tt>PR_Accept()</tt> or
+ <tt>PR_AcceptRead()</tt> inherits the blocking/nonblocking io mode of
+ the listening socket and this cannot be changed.**
+
+The socket returned by <tt>PR_Accept()</tt> or <tt>PR_AcceptRead()</tt>
+on a blocking, listening socket may be a recycled socket previously used
+in a <tt>PR_TransmitFile()</tt> call. Since <tt>PR_TransmitFile()</tt>
+only operates in blocking mode, this recycled socket can only be reused
+in blocking mode, hence the above constraint.
+
+Because these constraints only apply to NT, it is advised that you test
+your cross-platform code that uses nonblocking io on NT early in the
+development cycle. These constraints are enforced in the debug NSPR
+library by assertions.
+
+.. _Differences_from_Blocking_IO:
+
+Differences from Blocking IO
+----------------------------
+
+- In nonblocking mode, the timeout argument for the io functions is
+ ignored.
+- <tt>PR_AcceptRead()</tt> and <tt>PR_TransmitFile()</tt> only work on
+ blocking sockets. They do not make sense in nonblocking mode.
+- <tt>PR_Write()</tt>, <tt>PR_Send()</tt>, <tt>PR_Writev()</tt> in
+ blocking mode block until the entire buffer is sent. In nonblocking
+ mode, they cannot block, so they may return with just sending part of
+ the buffer.
+
+.. _PR_Poll()_or_PR_Select():
+
+PR_Poll() or PR_Select()?
+-------------------------
+
+<tt>PR_Select()</tt> is deprecated, now declared in
+<tt>private/pprio.h</tt>. Use <tt>PR_Poll()</tt> instead.
+
+The current implementation of <tt>PR_Select()</tt> simply calls
+<tt>PR_Poll()</tt>, so it is sure to have worse performance. Also,
+native file descriptors (socket handles) cannot be added to
+<tt>PR_fd_set</tt>, i.e., the functions <tt>PR_FD_NSET</tt>,
+<tt>PR_FD_NCLR</tt>, <tt>PR_FD_NISSET</tt> do not work.
+
+PR_Available()
+--------------
+
+When <tt>PR_Available()</tt> returns 0, it may mean one of two things:
+
+- There is no data available for reading on that socket. I.e.,
+ <tt>PR_Recv()</tt> would block (a blocking socket) or fail with
+ <tt>PR_WOULD_BLOCK_ERROR</tt> (a nonblocking socket).
+- The TCP connection on that socket has been closed (end of stream).
+
+These two cases can be distinguished by <tt>PR_Poll()</tt>. If
+<tt>PR_Poll()</tt> reports that the socket is readable (i.e.,
+<tt>PR_POLL_READ</tt> is set in <tt>out_flags</tt>), and
+<tt>PR_Available()</tt> returns 0, this means that the socket connection
+is closed.
+
+.. _Current_Status:
+
+Current Status
+--------------
+
+Implemented across all supported platforms.
diff --git a/docs/nspr/nonblockinglayeredio.rst b/docs/nspr/nonblockinglayeredio.rst
new file mode 100644
index 0000000000..19de77a888
--- /dev/null
+++ b/docs/nspr/nonblockinglayeredio.rst
@@ -0,0 +1,94 @@
+Non-blocking layered I/O
+========================
+
+*[last edited by AOF 24 March 1998 14:15]*
+I've recently been working on a long standing issue regarding NSPR's I/O
+model. For a long time I've believed that the non-blocking I/O prevalent
+in classic operating systems (e.g., UNIX) was the major determent for
+having an reasonable layered protocols. Now that I have some first hand
+experience, albeit just a silly little test program, I am more convinced
+that ever of this truth.
+
+This memo is some of what I think must be done in NSPR's I/O subsystem
+to make layered, non-blocking protocols workable. It is just a proposal.
+There is an API change.
+
+Layered I/O
+-----------
+
+NSPR 2.0 defines a structure by which one may define I/O layers. Each
+layer looks basically like any other in that it still uses a
+:ref:`PRFileDesc` as a object identifier, complete with the
+**``IOMethods``** table of functions. However, each layer may override
+default behavior of a particular operation to implement other services.
+For instance, the experiment at hand is one that implements a little
+reliable echo protocol; the client sends *n* bytes, and the same bytes
+get echoed back by the server. In the non-layered design of this it is
+straight forward.
+The goal of the experiment was to put a layer between the client and
+the network, and not have the client know about it. This additional
+layer is one that, before sending the client's data, must ask permission
+from the peer layer to send that many bytes. It imposes an additional
+send and response inside of each client visible send operation. The
+receive operations parallel the sends. Before actually receiving real
+client data, the layer receives a notification that the other would like
+to send some bytes. The layer is responsible for granting permission for
+that data to be sent, then actually receiving the data itself, which is
+delivered to the client.
+
+The synchronous form of the layer's operation is straight forward. A
+call to receive (:ref:`PR_Recv`) first receives the request to send,
+sends (:ref:`PR_Send`) the grant, then receives the actual data
+(:ref:`PR_Recv`). All the client of the layer sees is the data coming
+in. Similar behavior is observed on the sending side.
+
+Non-blocking layered
+--------------------
+
+The non-blocking method is not so simple. Any of the I/O operations
+potentially result in an indication that no progress can be made. The
+intermediate layers cannot act directly on this information, but must
+store the state of the I/O operation until it can be resumed. The method
+for determining that a I/O operation can make progress is to call
+:ref:`PR_Poll` and indicating what type of progress is desired,
+either input or output (or some others). Therein lies the problem.
+The intermediate layer is performing operations that the client is
+unaware. So when the client calls send (:ref:`PR_Send`) and is told
+that the operation would block, it is possible that the layer below is
+actually doing a receive (:ref:`PR_Recv`). The problem is that the
+flag bits passed to :ref:`PR_Poll` are only reflective of the
+client's knowledge and desires. This is further complicated by the fact
+that :ref:`PR_Poll` is not layered. That is each layer does not have
+the opportunity to override the behavior. It operates, not on a single
+file descriptor (:ref:`PRFileDesc`), but on an arbitrary collection of
+file descriptors.
+
+Into the picture comes another I/O method, **``poll()``**. Keep in mind
+that all I/O methods are those that are part of the I/O methods table
+structure (:ref:`PRIOMethods`). These functions are layered, and layers
+may and sometimes must override their behavior by offering unique
+implementations. The **``poll()``** method is used to provide two
+modifying aspects to the semantics of :ref:`PR_Poll`: redefining the
+polling bits (i.e., what to poll for) and to indicate that a layer is
+already able to make progress in the manner suggested by the polling
+bits.
+
+The **``poll()``** method is called by :ref:`PR_Poll` as the latter
+is building the structure to provide the operating system call. The
+stack's top layer will be called first. Each layer's implementation is
+responsible for performing appropriate operations and possibly calling
+the next lower layer's **``poll()``** method.
+What the poll method is returning are the appropriate flags to assign to
+the operating system's call. A layer would compute these based on the
+values of the argument **``in_flags``** and possibly some state
+maintained by the layer for the particular file descriptor.
+
+Additionally, if the layer has buffered data that will allow the
+operation defined by **``in_flags``** to make progress, it will set
+corresponding bits in **``out_flags``**. For instance, if
+**``in_flags``** indicates that the client (or higher layer) wishes to
+test for read ready and the layer has input data buffered, it would set
+the read bits in the **``out_flags``**. If that is the case, then it
+should also suppress the calling of the next lower layer's
+**``poll()``** method and return a value equal to that of
+**``in_flags``**.
diff --git a/docs/nspr/nspr_build_instructions.rst b/docs/nspr/nspr_build_instructions.rst
new file mode 100644
index 0000000000..d927a6bd05
--- /dev/null
+++ b/docs/nspr/nspr_build_instructions.rst
@@ -0,0 +1,136 @@
+NSPR build instructions
+=======================
+
+Prerequisites
+~~~~~~~~~~~~~
+
+On Windows, the NSPR build system needs GNU make and a Unix command-line
+utility suite such as MKS Toolkit, Cygwin, and MSYS. The easiest way to
+get these tools is to install the
+:ref:`MozillaBuild` package.
+
+Introduction
+~~~~~~~~~~~~
+
+The top level of the NSPR source tree is the ``mozilla/nsprpub``
+directory. Although ``nsprpub`` is a subdirectory under ``mozilla``,
+NSPR is independent of the Mozilla client source tree.
+
+Building NSPR consists of three steps:
+
+#. run the configure script. You may override the compilers (the CC
+ environment variable) or specify options.
+#. build the libraries
+#. build the test programs
+
+For example,
+
+::
+
+ # check out the source tree from Mercurial
+ hg clone https://hg.mozilla.org/projects/nspr
+ # create a build directory
+ mkdir target.debug
+ cd target.debug
+ # run the configure script
+ ../nspr/configure [optional configure options]
+ # build the libraries
+ gmake
+ # build the test programs
+ cd pr/tests
+ gmake
+
+On Mac OS X, use ``make``, which is GNU ``make``.
+
+.. _Configure_options:
+
+Configure options
+~~~~~~~~~~~~~~~~~
+
+Although NSPR uses autoconf, its configure script has two default values
+that are different from most open source projects.
+
+#. If the OS vendor provides a compiler (for example, Sun and HP), NSPR
+ uses that compiler instead of GCC by default.
+#. NSPR build generates a debug build by default.
+
+.. _--disable-debug_--enable-optimize:
+
+--disable-debug --enable-optimize
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Specify these two options to generate an optimized (release) build.
+
+These two options can actually be used independently, but it's not
+recommended.
+
+--enable-64bit
+^^^^^^^^^^^^^^
+
+On a dual 32-bit/64-bit platform, NSPR build generates a 32-bit build by
+default. To generate a 64-bit build, specify the ``--enable-64bit``
+configure option.
+
+.. _--targetx86_64-pc-mingw32:
+
+--target=x86_64-pc-mingw32
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+For 64-bit builds on Windows, when using the mozbuild environment.
+
+.. _--enable-win32-target.3DWIN95:
+
+--enable-win32-target=WIN95
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+This option is only used on Windows. NSPR build generates a "WINNT"
+configuration by default on Windows for historical reasons. We recommend
+most applications use the "WIN95" configuration. The "WIN95"
+configuration supports all versions of Windows. The "WIN95" name is
+historical; it should have been named "WIN32".
+
+To generate a "WIN95" configuration, specify the
+``--enable-win32-target=WIN95`` configure option.
+
+.. _--enable-debug-rtl:
+
+--enable-debug-rtl
+^^^^^^^^^^^^^^^^^^
+
+This option is only used on Windows. NSPR debug build uses the release C
+run-time library by default. To generate a debug build that uses the
+debug C run-time library, specify the ``--enable-debug-rtl`` configure
+option.
+
+.. _Makefile_targets:
+
+Makefile targets
+~~~~~~~~~~~~~~~~
+
+- all (default)
+- clean
+- realclean
+- distclean
+- install
+- release
+
+.. _Running_the_test_programs:
+
+Running the test programs
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The tests were built above, in the ``pr/tests`` directory.
+
+On Mac OS X, they can be executed with the following:
+
+.. code::
+
+ /bin/sh:
+
+ $ cd pr/tests
+ $ DYLD_LIBRARY_PATH=../../dist/lib ./accept
+ PASS
+ $
+ $ # to run all the NSPR tests...
+ $
+ $ DYLD_LIBRARY_PATH=../../dist/lib ../../../nspr/pr/tests/runtests.sh ../..
diff --git a/docs/nspr/nspr_contributor_guide.rst b/docs/nspr/nspr_contributor_guide.rst
new file mode 100644
index 0000000000..08a0ac2f99
--- /dev/null
+++ b/docs/nspr/nspr_contributor_guide.rst
@@ -0,0 +1,182 @@
+NSPR contributor guide
+======================
+
+**Abstract:**
+
+ NSPR accepts contributions in the form of bugfixes, new features,
+ libraries, platform ports, documentation, test cases and other items
+ from many sources. We (the NSPR module owners) sometimes disappoint
+ our contributors when we must reject their contributions. We reject
+ contributions for a variety of reasons. Some of these reasons are not
+ obvious to an outside observer. NSPR wishes to document some
+ guidelines for those who would contribute to NSPR. These guidelines
+ should help the contributor in crafting his contribution, increasing
+ its likelihood for acceptance.
+
+General Guidelines
+~~~~~~~~~~~~~~~~~~
+
+*Downward Compatibility*
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+Because many different applications, besides the mozilla client, use the
+NSPR API, the API must remain downward compatible across even major
+releases. This means that the behavior of an existing public API item in
+NSPR cannot change. Should you need to have a similar API, with some
+slightly different behavior or different function prototype, then
+suggest a new API with a different name.
+
+*C Language API*
+^^^^^^^^^^^^^^^^
+
+The NSPR API is a C Language API. Please do not contribute Java, C or
+other language wrappers.
+
+*Coding Style*
+^^^^^^^^^^^^^^
+
+NSPR does not have a documented coding style guide. Look at the extant
+code. Make yours look like that. Some guidelines concerning naming
+conventions can be found in :ref:`NSPR_Naming_Conventions`.
+in the :ref:`NSPR API Reference`.
+
+*Ownership of your contribution*
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+When you contribute something to NSPR, you must have intellectual
+property rights to that contribution. This means that you cannot give us
+something you snatched from somewhere else;. it must be your own
+invention, free and clear of encumberment of anyone or anything else;
+pay close attention to the rights of your "Day-Job" employer. If you
+snatched it from somewhere else, tell us where; show us where the right
+to incorporate it into NSPR exists.
+
+*License under MPL or GPL*
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+When you contribute material to NSPR, you agree to allow your
+contribution to be licensed under the MPL or GPL.
+
+BugFixes
+~~~~~~~~
+
+Use `Bugzilla <https://bugzilla.mozilla.org/>`__ to track bugs. Document
+the bug or use an existing report. Be verbose in describing what you are
+doing and why.
+
+Include your changes as diffs in an attachment to the BugZilla report.
+
+Use a coding style consistent with the source file you are changing.
+
+New Features
+~~~~~~~~~~~~
+
+For purposes of this paper, a "new feature" is defined as some API
+addition that goes into the core NSPR library, for example:
+``libnspr4.dll``
+
+NSPR is mostly complete. New APIs are driven mostly by the OS vendors as
+they add new features. Should you decide that there's something that
+NSPR does not cover that should be covered, let's talk. Your proposed
+API should encapsulate a relatively low level capability as would be
+found in a system call or libc.
+
+Your new feature must be implemented on all platforms supported by NSPR.
+When you consider a new API for NSPR ask yourself if your proposed
+feature can implement it across all platforms supported by NSPR. If
+several platforms cannot be made to implement your API, then it is not a
+good candidate for inclusion in NSPR.
+
+Before you begin what may be a substantial effort in making a candidate
+feature for NSPR, talk to us. We may tell you that you have a good idea;
+we may say that it really is not a good candidate for inclusion in NSPR;
+we may give you suggestions on what would make it more generalized,
+hence a good candidate for inclusion in NSPR.
+
+Use `Bugzilla <https://bugzilla.mozilla.org>`__ to track your work. Be
+verbose.
+
+NSPR wants you to document your work. If we accept it, we are going to
+have to answer questions about it and/or maintain it. These are some
+guidelines for new APIs that you may add to NSPR.
+
+**Header File Descriptions**. Provide header file descriptions that
+fully document your public typedefs, enums, macros and functions.
+
+See:
+`prshm.h <http://lxr.mozilla.org/nspr/source/nsprpub/pr/include/prshm.h>`__
+as an example of how your header file(s) should be documented.
+
+*Source File Descriptions*o. Provide descriptive documentation in your
+source (*.c) files. Alas, we have no source files documented as we think
+they should be.
+
+The following are some general guidelines to use when implementing new
+features:
+
+- Don't export global variables
+- Your code must be thread safe
+- You must provide test cases that test all APIs you are adding. See:
+ [#TestCases Test Cases]
+
+New Libraries
+~~~~~~~~~~~~~
+
+All the guidelines applicable to [#NewFeatures New Features] applies to
+new libraries.
+
+For purposes of this paper, a "new library" is defined as a library under
+the ``mozilla/nsprpub/lib`` directory tree and built as a separate
+library. These libraries exist, for the most part, as "legacy" code from
+NSPR 1.0. [Note that the current NSPR module owners do not now nor never
+have been involved with NSPR 1.0.]. Such is life. That said: There are
+some libraries that implement functions intended for use with
+applications using NSPR, such as ``...nsprpub/lib/libc/plgetopt.*.``
+
+- generally useful
+- platform abstractions
+- you agree to sustain, bug fix
+- May rely on the NSPR API
+- May NOT rely on any other library API
+
+New Platform Ports
+~~~~~~~~~~~~~~~~~~
+
+- all NSPR API items must be implemented
+- platform specific headers in ``pr/include/md/_platformname.[h!cfg]``
+- platform specific code in ``pr/src/md/platform/*.c``
+- make rules in ``config/_platform.mk``
+
+Documentation
+~~~~~~~~~~~~~
+
+The files for NSPR's documentation are maintained using a proprietary
+word processing system [don't ask]. Document your work as described in
+[#NewFeatures New Features]. Use the style of other NSPR documentation.
+We will see that your documentation is transcribed into the appropriate
+word processor and the derived HTML shows up on mozilla.org
+
+Test Cases
+~~~~~~~~~~
+
+You should provide test cases for all new features and new libraries.
+
+Give consideration to providing a test case when fixing a bug if an
+existing test case did not catch a bug it should have caught.
+
+The new test cases should be implemented in the style of other NSPR test
+cases.
+
+Test cases should prove that the added API items work as advertised.
+
+Test cases should serve as an example of how to use the API items.
+
+Test cases should provoke failure of every API item and report its
+failure.
+
+Frequently Asked Questions (FAQ)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**Q:** Why was my contribution rejected?
+
+**A:** Check the Bugzilla report covering your contribution.
diff --git a/docs/nspr/nspr_poll_method.rst b/docs/nspr/nspr_poll_method.rst
new file mode 100644
index 0000000000..6c15861562
--- /dev/null
+++ b/docs/nspr/nspr_poll_method.rst
@@ -0,0 +1,134 @@
+NSPR pool method
+================
+
+This technical note documents the poll method of PRFileDesc. The poll
+method is not to be confused with the PR_Poll function. The poll method
+operates on a single NetScape Portable Runtime (NSPR) file descriptor,
+whereas PR_Poll operates on a collection of NSPR file descriptors.
+PR_Poll uses the poll method behind the scene, but it is also possible
+to use the poll method directly.
+
+We consider a stack of *NSPR I/O layers* on top of the *network
+transport*. Each I/O layer is represented by a PRFileDesc structure and
+the protocol of that layer is implemented by a PRIOMethods table. The
+bottom layer is a wrapper for the underlying network transport. The NSPR
+library provides a reference implementation of the bottom layer using
+the sockets API, but you can provide your own implementation of the
+bottom layer using another network transport API. The poll method is one
+of the functions in the PRIOMethods table. The prototype of the poll
+method is
+
+.. code::
+
+ PRInt16 poll_method(PRFileDesc *fd, PRInt16 in_flags, PRInt16 *out_flags);
+
+The purpose of the poll method is to allow a layer to modify that flags
+that will ultimately be used in the call to the underlying network
+transport's select (or equivalent) function, and to indicate that a
+layer is already able to make progress in the manner suggested by the
+polling flags. The arguments and return value of the poll method are
+described below.
+
+.. _in_flags_input_argument:
+
+in_flags [input argument]
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The in_flags argument specifies the events at the **top layer** of the
+I/O layer stack that the caller is interested in.
+
+- For PR_Recv, you should pass PR_POLL_READ as the in_flags argument to
+ the poll method
+- For PR_Send, you should pass PR_POLL_WRITE as the in_flags argument
+ to the poll method
+
+.. _out_flags_output_argument:
+
+out_flags [output argument]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If an I/O layer is ready to satisfy the I/O request defined by in_flags
+without involving the underlying network transport, its poll method sets
+the corresponding event in \*out_flags on return.
+
+For example, consider an I/O layer that buffers input data. If the
+caller wishes to test for read ready (that is, PR_POLL_READ is set in
+in_flags) and the layer has input data buffered, the poll method would
+set the PR_POLL_READ event in \*out_flags. It can determine that without
+asking the underlying network transport.
+
+The current implementation of PR_Poll (the primary user of the poll
+method) requires that the events in \*out_flags reflect the caller's
+view. This requirement may be relaxed in a future NSPR release. To
+remain compatible with this potential semantic change, NSPR clients
+should only use \*out_flags as described in the *How to use the poll
+method* section below.
+
+.. _Return_value:
+
+Return value
+~~~~~~~~~~~~
+
+If the poll method stores a nonzero value in \*out_flags, the return
+value will be the value of in_flags. (Note: this may change in a future
+NSPR release if we make the semantic change to \*out_flags mentioned
+above. Therefore, NSPR clients should only use the return value as
+described in *How to use the poll method* section below.) If the poll
+method stores zero in \*out_flags, the return value will be the bottom
+layer's desires with respect to the in_flags. Those are the events that
+the caller should poll the underlying network transport for. These
+events may be different from the events in in_flags (which reflect the
+caller's view) for some protocols.
+
+.. _How_to_use_the_poll_method:
+
+How to use the poll method
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The poll method should only be used with a NSPR file descriptor in
+**non-blocking** mode. Most NSPR clients call PR_Poll and do not call
+the poll method directly. However, PR_Poll can only used with a stack
+whose bottom layer is NSPR's reference implementation. If you are using
+your own implementation of the bottom layer, you must call the poll
+method as follows.
+
+Declare two PRInt16 variables to receive the return value and the
+out_flags output argument of the poll method.
+
+.. code::
+
+ PRInt16 new_flags, out_flags;
+
+If you are going to call PR_Recv, pass PR_POLL_READ as the in_flags
+argument.
+
+.. code::
+
+ new_flags = fd->methods->poll(fd, PR_POLL_READ, &out_flags);
+
+If you are going to call PR_Send, pass PR_POLL_WRITE as the in_flags
+argument.
+
+.. code::
+
+ new_flags = fd->methods->poll(fd, PR_POLL_WRITE, &out_flags);
+
+If you are interested in calling both PR_Recv and PR_Send on the same
+file descriptor, make two separate calls to the poll method, one with
+PR_POLL_READ as in_flags and the other with PR_POLL_WRITE as in_flags,
+so that you know what events at the network transport layer PR_POLL_READ
+and PR_POLL_WRITE are mapped to, respectively.
+
+On return, if (new_flags & out_flags) is nonzero, you can try PR_Recv or
+PR_Send immediately.
+
+Otherwise ((new_flags & out_flags) is 0), you should do the following.
+
+- If new_flags contains PR_POLL_READ, you should try PR_Recv or PR_Send
+ when the underlying network transport is readable
+- If new_flags contains PR_POLL_WRITE, you should try PR_Recv or
+ PR_Send when the underlying network transport is writable
+
+**Important** do not use out_flags in any way other than testing if
+(new_flags & out_flags) is 0. This is how PR_Poll (the primary user and
+hence the de facto specification of the poll method) uses out_flags.
diff --git a/docs/nspr/nspr_release_procedure.rst b/docs/nspr/nspr_release_procedure.rst
new file mode 100644
index 0000000000..c482c882d9
--- /dev/null
+++ b/docs/nspr/nspr_release_procedure.rst
@@ -0,0 +1,50 @@
+NSPR release procedure
+======================
+
+Release checklist
+~~~~~~~~~~~~~~~~~
+
+#. Change the NSPR version in ``mozilla/nsprpub/pr/include/prinit.h``.
+#. Change the NSPR version in
+ ``mozilla/nsprpub/{configure.in,configure}``.
+#. Change the NSPR version in ``mozilla/nsprpub/pr/tests/vercheck.c``.
+#. Change the NSPR version in ``mozilla/nsprpub/admin/repackage.sh``.
+
+.. _Source_tarball:
+
+Source tarball
+~~~~~~~~~~~~~~
+
+.. _Binary_distributions:
+
+Binary distributions
+~~~~~~~~~~~~~~~~~~~~
+
+Right now I use the ``mozilla/nsprpub/admin/repackage.sh`` script to
+generate the binary distributions published on ftp.mozilla.org. As the
+name of the shell script implies, ``repackage.sh`` merely repackages
+binary distributions in a different format.
+
+Before you run ``repackage.sh``, you need to have built the binary
+distributions using the "gmake release" makefile target. These binary
+distributions are jar files, which are really zip files, and they are
+published in the directory ``/share/builds/components``. This design
+comes from the Netscape days.
+
+The ``repackage.sh`` script repackages the jar files into the form most
+commonly used on that platform. So on Unix it repackages the jar files
+into gzipped tar files, and on Windows it repackages the jar files into
+zip files.
+
+Edit the ``repackage.sh`` script to customize it for your environment.
+
+After you have run ``repackage.sh``, follow the
+`instructions <http://www.mozilla.org/build/ftp-stage.html>`__ in to
+upload the files to ftp.mozilla.org's staging server, so that they
+eventually show up on ftp.mozilla.org. The host ftp.mozilla.org can be
+accessed via the ftp, http, and https protocols. We recommend using
+https://ftp.mozilla.org/.
+
+**Note:** For NSS, the script equivalent to NSPR's ``repackage.sh`` is
+``/u/robobld/bin/sbsinit/nss/push/buildbindist.sh`` in the "SVBuild"
+source tree.
diff --git a/docs/nspr/nspr_s_position_on_abrupt_thread_termination.rst b/docs/nspr/nspr_s_position_on_abrupt_thread_termination.rst
new file mode 100644
index 0000000000..7cbda0c2c6
--- /dev/null
+++ b/docs/nspr/nspr_s_position_on_abrupt_thread_termination.rst
@@ -0,0 +1,88 @@
+NSPR's position on abrupt thread termination
+============================================
+
+This memo describes my position on a facility that is currently under
+discussion for inclusion in the NetScape Portable Runtime (NSPR); the
+ability of a thread to abruptly exit. I resist including this function
+in NSPR because it results in bad programming practice and unsupportable
+programs.
+
+ *Threads are not processes.*
+
+Abrupt termination has been available in the UNIX/C environment for some
+time (``exit()``), and I assume that the basic semantics defined there
+are applicable here. In that environment, ``exit()`` may be called and
+any time, and results in the calling thread's immediate termination. In
+the situation where it was defined (UNIX), which has only a single
+thread of execution, that is equivalent to terminating the process. The
+process abstraction is then responsible for closing all open files and
+reclaiming all storage that may have been allocated during the process'
+lifetime.
+
+This practice does not extend to threads. Threads run within the
+confines of a process (or similar abstractions in other environments).
+Threads are lightweight because they do not maintain the full protection
+domain provided by a process. So in a threaded environment, what is the
+parallel to UNIX' ``exit()``?
+
+NSPR has defined a function, callable by any thread within a process at
+any time, called ``PR_ProcessExit()``. This is identical to UNIX
+``exit()`` and was so named in an effort to make the obvious even more
+so. When called, the process exits, closing files and reclaiming the
+process' storage.
+
+Certain people have been disappointed when NSPR did not provide a
+functional equivalent to exit just a particular thread. Apparently they
+have failed to consider the ramifications. If a thread was to abruptly
+terminate, there is no recording of what resources it owns and should
+therefore be reclaimed. Those resources are in fact, owned by the
+process and shared by all the threads within the process.
+
+In the general course of events when programming with threads, it is
+very advantageous for a thread to have resources that it and only it
+knows about. In the natural course of events, these resources will be
+allocated by a thread, used for some period of time, and then freed as
+the stack unwinds. In these cases, the presence of the data is recorded
+only on the stack, known only to the single thread (normally referred to
+as *encapsulated*).
+
+The problem with abrupt termination is that it can happen at any time,
+to a thread that is coded correctly to handle both normal and
+exceptional situations, but will be unable to do so since it will be
+denied the opportunity to complete execution. It can happen because it
+called out of its own scope into some lazily implemented library.
+
+NSPR's answer to this is that there is no abrupt thread termination. All
+threads must unwind and return from their root function. If they cannot,
+because of some state corruption, then they must assume that the
+corruption, like the state, is shared, and their only resource is for
+the process to terminate.
+
+To make this solution work requires that a function that encounters an
+error be designed such that it first repairs its immediate state, and
+then reports that error to its caller. If the caller cannot deal with
+the failure, it must do the same. This process continues until the
+thread either recovers from the malady or returns from the root
+function. This is not all that difficult (though having done it a number
+of times to already existing code, I will admit it isn't much fun
+either).
+
+The implementation of either strategy within the NSPR runtime is not
+difficult. That is not what this memo is about. This is about providing
+an API that coaxes people to do the right thing in as many ways as
+possible. The existence of ``exit()`` in the UNIX/C environment is a
+perfect example of how programmers will employ the most expediant
+solution available. The definition of the language C is such that
+returning from ``main()`` is a perfectly fine thing to do. But what
+percentage of C programs actually bother? In UNIX, with its complex
+definition of a protection domain, it happens to work (one might even
+say it's more efficient) to exit from anywhere. But threads are not
+processes. If threads have to maintain the same type of resource
+knowledge as a process, they loose all of their benefit.
+
+Threads are an implementation strategy to provide the illusion of
+concurrency within a process. They are alternatives to large state
+machines with mostly non-blocking library functions. When the latter is
+used to provide concurrency, calling ``exit()`` will terminate the
+entire process. Why would anyone expect a thread to behave differently?
+Threads are not processes.
diff --git a/docs/nspr/optimizing_applications_for_nspr.rst b/docs/nspr/optimizing_applications_for_nspr.rst
new file mode 100644
index 0000000000..1bced161a8
--- /dev/null
+++ b/docs/nspr/optimizing_applications_for_nspr.rst
@@ -0,0 +1,45 @@
+Optimizing applications for NSPR
+================================
+
+NetScape Portable Runtime (NSPR) tries to provide a consistent level of
+service across the platforms it supports. This has proven to be quite
+challenging, a challenge that was met to a large degree, but there is
+always room for improvement. The casual client may not encounter a need
+to know the details of the shortcomings to the level described here, but
+if and when clients become more sophisticated, these issues will
+certainly surface.
+
+ *This memo is by no way complete.*
+
+Multiplatform
+-------------
+
+- Do not call any blocking system call from a local thread. The only
+ exception to this rule is the <tt>select()</tt> and <tt>poll()</tt>
+ system calls on Unix, both of which NSPR has overridden to make sure
+ they are aware of the NSPR local threads.
+- In the combined (MxN) model, which includes NT, IRIX (sprocs), and
+ pthreads-user, the primordial thread is always a local thread.
+ Therefore, if you call a blocking system call from the primordial
+ thread, it is going to block more than just the primordial thread and
+ the system may not function correctly. On NT, this problem is
+ especially obvious because the idle thread, which is in charge of
+ driving the asynch io completion port, is also blocked. Do not call
+ blocking system calls from the primordial thread. Create a global
+ thread and call the system call in that thread, and have the
+ primordial thread join that thread.
+- NSPR uses timer signals to implement thread preemption for local
+ threads on some platforms. If all the software linked into the
+ application is not ported to the NSPR API, the application may fail
+ because of threads being preempted during critical sections. To
+ disable thread preemption call
+ <tt>PR_DisableClockInterrupts()</tt>during initialization.
+- Interrupting threads (via <tt>PR_Interrupt()</tt>) on threads blocked
+ in I/O functions is implemented to various degrees on different
+ platforms. The UNIX based platforms all implement the function though
+ there may be up to a 5 second delay in processing the request.
+- The mechanism used to implement <tt>PR_Interrupt()</tt> on the
+ *pthreads* versions of NSPR is flawed. No failure attributable to the
+ flaw has shown up in any tests or products - yet. The specific area
+ surrounding pthread's *continuation thread* has been both observed
+ and empirically proven faulty, and a correction identified.
diff --git a/docs/nspr/platforms.rst b/docs/nspr/platforms.rst
new file mode 100644
index 0000000000..6e251a4d71
--- /dev/null
+++ b/docs/nspr/platforms.rst
@@ -0,0 +1,145 @@
+NSPR platforms
+==============
+
+Build System and Supported Platforms
+------------------------------------
+
+NSPR has been implemented on over 20 platforms. A platform may have
+more than one implementation strategy of its multi threading and I/O
+facilities. This article explains the NSPR build system and how to
+specify a particular implementation strategy, compiler or compiler
+switches, or the target OS for your build on each platform.
+
+Implementation Strategies
+-------------------------
+
+Threads are at the core of NSPR, and the I/O functions are tied to the
+multi threading facilities because many I/O functions may block the
+calling threads. NSPR has multiple implementation strategies of its
+multi threading and I/O functions. The various implementation strategies
+can be organized into the hierarchy below:
+
+- **Classic NSPR** (This is our first code base, hence the term
+ "classic NSPR"):
+
+**Local threads only**: All threads are user-level threads implemented
+by NSPR.
+**Global threads only**: All threads are native threads supplied by the
+OS vendor. For example, Solaris UI (Unix International) threads and
+Win32 threads.
+**Combined**: NSPR multiplexes user-level threads on top of native,
+kernel-level threads. This is also called the MxN model. At present,
+there are three implementations of the combined model.
+
+- IRIX: sprocs + NSPR user-level threads
+- Windows NT: Win32 threads + NT fibers
+- **Pthreads-user**: kernel-level pthreads + NSPR user-level threads
+
+**Pthreads**: All threads are pthreads. The relevant code is in
+``mozilla/nsprpub/pr/src/pthreads`` (threads and I/O).
+Classic NSPR and pthreads have relatively disjoint code bases in the
+threads and I/O areas:
+
+- Classic NSPR: ``mozilla/nsprpub/pr/src/threads/combined`` (threads),
+ ``mozilla/nsprpub/pr/src/io`` (I/O)
+- Pthreads: ``mozilla/nsprpub/pr/src/pthreads`` (threads and I/O)
+
+Note that some files under ``mozilla/nsprpub/pr/src/io`` are shared by
+both classic NSPR and pthreads. Consult
+``mozilla/nsprpub/pr/src/Makefile`` for the definitive list of files
+used by each implementation strategy (see the definition of the makefile
+variable ``OBJS``).
+
+Compilers
+---------
+
+For ease of integration with third-party libraries, which may use native
+threads, NSPR uses the native threads whenever possible. As a result,
+native compilers are used to build NSPR on most platforms because they
+have better debugging support for native threads. The only exception is
+Solaris, where both cc and gcc are used.
+
+NSPR Build System
+-----------------
+
+NSPR build system is based on GNU make.
+We use GNU make 3.74 on Unix, but our makefiles should
+work fine under newer versions of GNU make.
+
+Every directory in NSPR has a makefile named ``Makefile``, which
+includes the makefile fragments in ``mozilla/nsprpub/config``. NSPR
+makefiles implement the common Makefile targets such as
+``export``, ``libs``, and ``install``. However, some makefiles targets
+are no-op in NSPR because they are not necessary for NSPR.
+
+To build NSPR, change directory to the root of our source tree
+``cd mozilla/nsprpub``
+and then issue the command
+``gmake``
+Make will recursively go into all the subdirectories and the right
+things will happen.
+
+The table below lists the common NSPR makefile targets.
+
++-----------------------------------+-----------------------------------+
+| ``all`` | The default target. Same as |
+| | ``export`` ``libs`` ``install``. |
++-----------------------------------+-----------------------------------+
+| ``export`` | Do a complete build. |
++-----------------------------------+-----------------------------------+
+| ``libs`` | No-op. |
++-----------------------------------+-----------------------------------+
+| ``install`` | No-op. |
++-----------------------------------+-----------------------------------+
+| ``depend`` | No-op. **This means that NSPR |
+| | makefiles do not have header file |
+| | dependencies.** |
++-----------------------------------+-----------------------------------+
+| ``clean`` | Remove ``.o`` files. |
++-----------------------------------+-----------------------------------+
+| ``clobber`` | Remove ``.o`` files, libraries, |
+| | and executable programs. |
++-----------------------------------+-----------------------------------+
+| ``realclean`` | Remove all generated files and |
+| | directories. |
++-----------------------------------+-----------------------------------+
+| ``clobber_all`` | Same as ``realclean``. |
++-----------------------------------+-----------------------------------+
+
+The table below lists common makefile variables that one can specify
+on the command line to customize a build..
+
++-----------------------------------+-----------------------------------+
+| ``BUILD_OPT`` | Optimized build (default: debug |
+| | build). |
++-----------------------------------+-----------------------------------+
+| ``OS_TARGET`` | Set to the target OS (``WIN95`` |
+| | or ``WIN16``) when doing |
+| | cross-compilation on NT (default: |
+| | same as the host OS). |
++-----------------------------------+-----------------------------------+
+| ``NS_USE_GCC`` | Use gcc and g++ (default: native |
+| | compilers). |
++-----------------------------------+-----------------------------------+
+| ``USE_PTHREADS`` | Build pthreads version. |
++-----------------------------------+-----------------------------------+
+| ``CLASSIC_NSPR`` | Build classic NSPR version |
+| | (usually local threads only). |
++-----------------------------------+-----------------------------------+
+| ``PTHREADS_USER`` | Build pthreads-user version. |
++-----------------------------------+-----------------------------------+
+| ``LOCAL_THREADS_ONLY`` | Build local threads only version. |
++-----------------------------------+-----------------------------------+
+| ``USE_DEBUG_RTL`` | On Win32, compile with ``/MDd`` |
+| | in the debug build (default: |
+| | ``/MD``). Optimized build always |
+| | uses ``/MD``. |
++-----------------------------------+-----------------------------------+
+| ``USE_N32`` | On IRIX, compile with ``-n32`` |
+| | (default: ``-32``). |
++-----------------------------------+-----------------------------------+
+| ``USE_IPV6`` | Enable IPv6. |
++-----------------------------------+-----------------------------------+
+| ``MOZILLA_CLIENT`` | Adjust NSPR build system for |
+| | Netscape Client (mozilla). |
++-----------------------------------+-----------------------------------+
diff --git a/docs/nspr/process_forking_in_nspr.rst b/docs/nspr/process_forking_in_nspr.rst
new file mode 100644
index 0000000000..d73291edf0
--- /dev/null
+++ b/docs/nspr/process_forking_in_nspr.rst
@@ -0,0 +1,23 @@
+Process forking in NSPR
+=======================
+
+The threads provided in NetScape Portable Runtime (NSPR) are implemented
+using different mechanisms on the various platforms. On some platforms,
+NSPR threads directly map one-to-one to the threads provided by the
+platform vendor, on other platforms NSPR threads are basically
+user-level threads within a single process (with no kernel threads) and
+on still others NSPR threads are user-level threads implemented on top
+of one or more kernel threads within single address space.
+
+NSPR does not override the fork function and so, when fork is called
+from the NSPR thread the results are different on the various platforms.
+All the threads present in the parent process may be replicated in the
+child process, only the calling thread may be replicated in the child
+process or only the calling kernel thread may be replicated.
+
+So, to be consistent across all platforms, it is suggested that when
+using fork in a NSPR thread;
+
+#. The exec function should be called in the child process.
+#. No NSPR functions should be called in the child process before the
+ exec call is made.
diff --git a/docs/nspr/reference/anonymous_shared_memory.rst b/docs/nspr/reference/anonymous_shared_memory.rst
new file mode 100644
index 0000000000..3daea2cbb0
--- /dev/null
+++ b/docs/nspr/reference/anonymous_shared_memory.rst
@@ -0,0 +1,118 @@
+Anonymous Shared Memory
+=======================
+
+This chapter describes the NSPR API for anonymous shared memory.
+
+- `Anonymous Memory Protocol <#Anonymous_Memory_Protocol>`__
+- `Anonymous Shared Memory
+ Functions <#Anonymous_Shared_Memory_Functions>`__
+
+.. _Anonymous_Memory_Protocol:
+
+Anonymous Memory Protocol
+-------------------------
+
+NSPR provides an anonymous shared memory based on NSPR's :ref:`PRFileMap`
+type. The anonymous file-mapped shared memory provides an inheritable
+shared memory, as in: the child process inherits the shared memory.
+Compare the file-mapped anonymous shared memory to to a named shared
+memory described in prshm.h. The intent is to provide a shared memory
+that is accessible only by parent and child processes. ... It's a
+security thing.
+
+Depending on the underlying platform, the file-mapped shared memory may
+be backed by a file. ... surprise! ... On some platforms, no real file
+backs the shared memory. On platforms where the shared memory is backed
+by a file, the file's name in the filesystem is visible to other
+processes for only the duration of the creation of the file, hopefully a
+very short time. This restricts processes that do not inherit the shared
+memory from opening the file and reading or writing its contents.
+Further, when all processes using an anonymous shared memory terminate,
+the backing file is deleted. ... If you are not paranoid, you're not
+paying attention.
+
+The file-mapped shared memory requires a protocol for the parent process
+and child process to share the memory. NSPR provides two protocols. Use
+one or the other; don't mix and match.
+
+In the first protocol, the job of passing the inheritable shared memory
+is done via helper-functions with PR_CreateProcess. In the second
+protocol, the parent process is responsible for creating the child
+process; the parent and child are mutually responsible for passing a
+``FileMap`` string. NSPR provides helper functions for extracting data
+from the :ref:`PRFileMap` object. ... See the examples below.
+
+Both sides should adhere strictly to the protocol for proper operation.
+The pseudo-code below shows the use of a file-mapped shared memory by a
+parent and child processes. In the examples, the server creates the
+file-mapped shared memory, the client attaches to it.
+
+.. _First_protocol:
+
+First protocol
+~~~~~~~~~~~~~~
+
+**Server:**
+
+.. code::
+
+ fm = PR_OpenAnonFileMap(dirName, size, FilemapProt);
+ addr = PR_MemMap(fm);
+ attr = PR_NewProcessAttr();
+ PR_ProcessAttrSetInheritableFileMap( attr, fm, shmname );
+ PR_CreateProcess(Client);
+ PR_DestroyProcessAttr(attr);
+ ... yadda ...
+ PR_MemUnmap( addr );
+ PR_CloseFileMap(fm);
+
+**Client:**
+
+.. code::
+
+ ... started by server via PR_CreateProcess()
+ fm = PR_GetInheritedFileMap( shmname );
+ addr = PR_MemMap(fm);
+ ... yadda ...
+ PR_MemUnmap(addr);
+ PR_CloseFileMap(fm);
+
+.. _Second_protocol:
+
+Second protocol
+~~~~~~~~~~~~~~~
+
+**Server:**
+
+.. code::
+
+ fm = PR_OpenAnonFileMap(dirName, size, FilemapProt);
+ fmstring = PR_ExportFileMapAsString( fm );
+ addr = PR_MemMap(fm);
+ ... application specific technique to pass fmstring to child
+ ... yadda ... Server uses his own magic to create child
+ PR_MemUnmap( addr );
+ PR_CloseFileMap(fm);
+
+**Client:**
+
+.. code::
+
+ ... started by server via his own magic
+ ... application specific technique to find fmstring from parent
+ fm = PR_ImportFileMapFromString( fmstring )
+ addr = PR_MemMap(fm);
+ ... yadda ...
+ PR_MemUnmap(addr);
+ PR_CloseFileMap(fm);
+
+.. _Anonymous_Shared_Memory_Functions:
+
+Anonymous Shared Memory Functions
+---------------------------------
+
+- :ref:`PR_OpenAnonFileMap`
+- :ref:`PR_ProcessAttrSetInheritableFileMap`
+- :ref:`PR_GetInheritedFileMap`
+- :ref:`PR_ExportFileMapAsString`
+- :ref:`PR_ImportFileMapFromString`
diff --git a/docs/nspr/reference/atomic_operations.rst b/docs/nspr/reference/atomic_operations.rst
new file mode 100644
index 0000000000..1599d39ce8
--- /dev/null
+++ b/docs/nspr/reference/atomic_operations.rst
@@ -0,0 +1,32 @@
+This chapter describes the global functions you use to perform atomic
+operations. The functions define a portable API that may be reliably
+used in any environment. Since not all operating environments provide
+access to such functions, their performance may vary considerably.
+
+.. _Atomic_Operations_Functions:
+
+Atomic Operations Functions
+---------------------------
+
+The API defined for the atomic functions is consistent across all
+supported platforms. However, the implementation may vary greatly, and
+hence the performance. On systems that do not provide direct access to
+atomic operators, NSPR emulates the capabilities by using its own
+locking mechanisms. For such systems, NSPR performs atomic operations
+just as efficiently as the client could. Therefore, to preserve
+portability, it is recommended that clients use the NSPR API for atomic
+operations.
+
+These functions operate on 32-bit integers:
+
+- :ref:`PR_AtomicIncrement`
+- :ref:`PR_AtomicDecrement`
+- :ref:`PR_AtomicSet`
+- :ref:`PR_AtomicAdd`
+
+These functions implement a simple stack data structure:
+
+- :ref:`PR_CreateStack`
+- :ref:`PR_StackPush`
+- :ref:`PR_StackPop`
+- :ref:`PR_DestroyStack`
diff --git a/docs/nspr/reference/cached_monitors.rst b/docs/nspr/reference/cached_monitors.rst
new file mode 100644
index 0000000000..647bdb1b54
--- /dev/null
+++ b/docs/nspr/reference/cached_monitors.rst
@@ -0,0 +1,37 @@
+This chapter describes the functions you use when you work with cached
+monitors. Unlike a plain monitor, a cached monitor is associated with
+the address of a protected object, and the association is maintained
+only while the protection is needed. This arrangement allows a cached
+monitor to be associated with another object without preallocating a
+monitor for all objects. A hash table is used to quickly map addresses
+to their respective monitors. The system automatically enlarges the hash
+table as needed.
+
+Important
+---------
+
+Cached monitors are slower to use than their uncached counterparts.
+
+See `Monitors <Monitors>`__ for information about uncached monitors.
+
+.. _Cached_Monitors_Functions:
+
+Cached Monitors Functions
+-------------------------
+
+Cached monitors allow the client to associate monitoring protection and
+state change synchronization in a lazy fashion. The monitoring
+capability is associated with the protected object only during the time
+it is required, allowing the monitor object to be reused. This
+additional flexibility comes at the cost of a small loss in performance.
+
+ - :ref:`PR_CEnterMonitor` enters the lock associated with a cached
+ monitor.
+ - :ref:`PR_CExitMonitor` decrements the entry count associated with a
+ cached monitor.
+ - :ref:`PR_CWait` waits for a notification that a monitor's state has
+ changed.
+ - :ref:`PR_CNotify` notifies a thread waiting for a change in the state of
+ monitored data.
+ - :ref:`PR_CNotifyAll` notifies all the threads waiting for a change in
+ the state of monitored data.
diff --git a/docs/nspr/reference/condition_variables.rst b/docs/nspr/reference/condition_variables.rst
new file mode 100644
index 0000000000..b5a1b5abcb
--- /dev/null
+++ b/docs/nspr/reference/condition_variables.rst
@@ -0,0 +1,50 @@
+This chapter describes the API for creating and destroying condition
+variables, notifying condition variables of changes in monitored data,
+and making a thread wait on such notification.
+
+- `Condition Variable Type <#Condition_Variable_Type>`__
+- `Condition Variable Functions <#Condition_Variable_Functions>`__
+
+Conditions are closely associated with a single monitor, which typically
+consists of a mutex, one or more condition variables, and the monitored
+data. The association between a condition and a monitor is established
+when a condition variable is created, and the association persists for
+its life. In addition, a static association exists between the condition
+and some data within the monitor. This data is what will be manipulated
+by the program under the protection of the monitor.
+
+A call to :ref:`PR_WaitCondVar` causes a thread to block until a specified
+condition variable receives notification of a change of state in its
+associated monitored data. Other threads may notify the condition
+variable when changes occur.
+
+For an introduction to NSPR thread synchronization, including locks and
+condition variables, see `Introduction to
+NSPR <Introduction_to_NSPR>`__.
+
+For reference information on NSPR locks, see
+`Locks <NSPR_API_Reference/Locks>`__.
+
+NSPR provides a special type, :ref:`PRMonitor`, for use with Java. Unlike a
+mutex of type :ref:`PRLock`, which can have multiple associated condition
+variables of type :ref:`PRCondVar`, a mutex of type :ref:`PRMonitor` has a
+single, implicitly associated condition variable. For information about
+:ref:`PRMonitor`, see `Monitors <Monitors>`__.
+
+.. _Condition_Variable_Type:
+
+Condition Variable Type
+-----------------------
+
+ - :ref:`PRCondVar`
+
+.. _Condition_Variable_Functions:
+
+Condition Variable Functions
+----------------------------
+
+ - :ref:`PR_NewCondVar`
+ - :ref:`PR_DestroyCondVar`
+ - :ref:`PR_WaitCondVar`
+ - :ref:`PR_NotifyCondVar`
+ - :ref:`PR_NotifyAllCondVar`
diff --git a/docs/nspr/reference/date_and_time.rst b/docs/nspr/reference/date_and_time.rst
new file mode 100644
index 0000000000..b86929b44e
--- /dev/null
+++ b/docs/nspr/reference/date_and_time.rst
@@ -0,0 +1,83 @@
+This chapter describes the date and time functions in NSPR.
+
+NSPR represents time in two ways, absolute time and clock/calendar time.
+NSPR provides types and constants for both representations, and
+functions to convert time values between the two.
+
+- Absolute time representation treats time instants as points along the
+ time line. A time instant is represented by its position on the time
+ line relative to the origin, called the epoch. NSPR defines the epoch
+ to be midnight (00:00:00) 1 January 1970 UTC (Coordinated Universal
+ Time). In this form, time is just a point on the time line. There is
+ no notion of time zone.
+
+- Clock/calendar time, used for human interfaces, represents time in
+ the familiar year, month, day, hour, minute, second components. In
+ this form, the time zone information is important. For example,
+ without specifying the time zone, the time 8:00AM 1 May 1998 is
+ ambiguous. The NSPR data type for clock/calendar time, called an
+ exploded time, has the time zone information in it, so that its
+ corresponding point in absolute time is uniquely specified.
+
+Note that absolute and clock times are not normally used in timing
+operations. For functions that deal with the measurement of elapsed time
+and with timeouts, see `Interval Timing <Interval_Timing>`__.
+
+- `Macros for Time Unit
+ Conversion <#Macros_for_Time_Unit_Conversion>`__
+- `Types and Constants <#Types_and_Constants>`__
+- `Time Parameter Callback
+ Functions <#Time_Parameter_Callback_Functions>`__
+- `Functions <#Functions>`__
+
+.. _Macros_for_Time_Unit_Conversion:
+
+Macros for Time Unit Conversion
+-------------------------------
+
+Macros for converting between seconds, milliseconds, microseconds, and
+nanoseconds.
+
+- :ref:`PR_MSEC_PER_SEC`
+- :ref:`PR_USEC_PER_SEC`
+- :ref:`PR_NSEC_PER_SEC`
+- :ref:`PR_USEC_PER_MSEC`
+- :ref:`PR_NSEC_PER_MSEC`
+
+.. _Types_and_Constants:
+
+Types and Constants
+-------------------
+
+Types and constants defined for NSPR dates and times are:
+
+- :ref:`PRTime`
+- :ref:`PRTimeParameters`
+- :ref:`PRExplodedTime`
+
+.. _Time_Parameter_Callback_Functions:
+
+Time Parameter Callback Functions
+---------------------------------
+
+In some geographic locations, use of Daylight Saving Time (DST) and the
+rule for determining the dates on which DST starts and ends have changed
+a few times. Therefore, a callback function is used to determine time
+zone information.
+
+You can define your own time parameter callback functions, which must
+conform to the definition :ref:`PRTimeParamFn`. Two often-used callback
+functions of this type are provided by NSPR:
+
+- :ref:`PRTimeParamFn`
+- :ref:`PR_LocalTimeParameters` and :ref:`PR_GMTParameters`
+
+Functions
+---------
+
+The functions that create and manipulate time and date values are:
+
+- :ref:`PR_Now`
+- :ref:`PR_ExplodeTime`
+- :ref:`PR_ImplodeTime`
+- :ref:`PR_NormalizeTime`
diff --git a/docs/nspr/reference/dynamic_library_linking.rst b/docs/nspr/reference/dynamic_library_linking.rst
new file mode 100644
index 0000000000..6ab8d33840
--- /dev/null
+++ b/docs/nspr/reference/dynamic_library_linking.rst
@@ -0,0 +1,114 @@
+Dynamic Library Search
+======================
+
+This section describes NSPR's programming interface to load, unload and
+resolve symbols in dynamic libraries. It also provides a method by which
+to condition symbols of statically linked code so that to other clients
+it appears as though they are dynamically loaded.
+
+.. _Library_Linking_Types:
+
+Library Linking Types
+---------------------
+
+These data types are defined for dynamic library linking:
+
+ - :ref:`PRLibrary`
+ - :ref:`PRStaticLinkTable`
+
+.. _Library_Linking_Functions:
+
+Library Linking Functions
+-------------------------
+
+The library linking functions are:
+
+ - :ref:`PR_SetLibraryPath`
+ - :ref:`PR_GetLibraryPath`
+ - :ref:`PR_GetLibraryName`
+ - :ref:`PR_FreeLibraryName`
+ - :ref:`PR_LoadLibrary`
+ - :ref:`PR_UnloadLibrary`
+ - :ref:`PR_FindSymbol`
+ - :ref:`PR_FindSymbolAndLibrary`
+
+.. _Finding_Symbols_Defined_in_the_Main_Executable_Program:
+
+Finding Symbols Defined in the Main Executable Program
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:ref:`PR_LoadLibrary` cannot open a handle that references the main
+executable program. (This is admittedly an omission that should be
+fixed.) However, it is possible to look up symbols defined in the main
+executable program as follows.
+
+.. code::
+
+ PRLibrary *lib;
+ void *funcPtr;
+
+ funcPtr = PR_FindSymbolAndLibrary("FunctionName", &lib);
+
+When :ref:`PR_FindSymbolAndLibrary` returns, ``funcPtr`` is the value of
+the function pointer you want to look up, and the variable lib
+references the main executable program. You can then call
+:ref:`PR_FindSymbol` on lib to look up other symbols defined in the main
+program. Remember to call ``PR_UnloadLibrary(lib)`` to close the library
+handle when you are done.
+
+.. _Platform_Notes:
+
+Platform Notes
+--------------
+
+To use the dynamic library loading functions on some platforms, certain
+environment variables must be set at run time, and you may need to link
+your executable programs using special linker options.
+
+This section summarizes these platform idiosyncrasies. For more
+information, consult the man pages for ``ld`` and ``dlopen`` (or
+``shl_load`` on HP-UX) for Unix, and the ``LoadLibrary`` documentation
+for Win32.
+
+- `Dynamic Library Search Path <#Dynamic_Library_Search_Path>`__
+- `Exporting Symbols from the Main Executable
+ Program <#Exporting_Symbols_from_the_Main_Executable_Program>`__
+
+Dynamic Library Search Path
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The dynamic library search path is the list of directories in which to
+look for a dynamic library. Each platform has its own standard
+directories in which to look for dynamic libraries, plus a customizable
+list of directories specified by an environment variable.
+
+- On most Unix systems, this environment variable is
+ ``LD_LIBRARY_PATH``. These systems typically use ``dlopen`` to load a
+ dynamic library.
+- HP-UX uses ``shl_load`` to load dynamic libraries, and the
+ environment variable specifying the dynamic library search path is
+ ``SHLIB_PATH``. Moreover, the executable program must be linked with
+ the +s option so that it will search for shared libraries in the
+ directories specified by ``SHLIB_PATH`` at run time. Alternatively,
+ you can enable the +s option as a postprocessing step using the
+ ``chatr`` tool. For example, link your executable program a.out
+ without the +s option, then execute the following:
+
+.. code::
+
+ chatr +s enable a.out
+
+- On Rhapsody, the environment variable is ``DYLD_LIBRARY_PATH``.
+- On Win32, the environment variable is ``PATH``. The same search path
+ is used to search for executable programs and DLLs.
+
+.. _Exporting_Symbols_from_the_Main_Executable_Program:
+
+Exporting Symbols from the Main Executable Program
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+On some systems, symbols defined in the main executable program are not
+exported by default. On HP-UX, you must link the executable program with
+the -E linker option in order to export all symbols in the main program
+to shared libraries. If you use the GNU compilers (on any platform), you
+must also link the executable program with the -E option.
diff --git a/docs/nspr/reference/floating_point_number_to_string_conversion.rst b/docs/nspr/reference/floating_point_number_to_string_conversion.rst
new file mode 100644
index 0000000000..6c9b36003f
--- /dev/null
+++ b/docs/nspr/reference/floating_point_number_to_string_conversion.rst
@@ -0,0 +1,24 @@
+NSPR provides functions that convert double-precision floating point
+numbers to and from their character string representations.
+
+These conversion functions were originally written by David M. Gay of
+AT&T. They use IEEE double-precision (not IEEE double-extended)
+arithmetic.
+
+The header file ``prdtoa.h`` declares these functions. The functions
+are:
+
+ - :ref:`PR_strtod`
+ - :ref:`PR_dtoa`
+ - :ref:`PR_cnvtf`
+
+References
+----------
+
+Gay's implementation is inspired by these two papers.
+
+[1] William D. Clinger, "How to Read Floating Point Numbers Accurately,"
+Proc. ACM SIGPLAN '90, pp. 92-101.
+
+[2] Guy L. Steele, Jr. and Jon L. White, "How to Print Floating-Point
+Numbers Accurately," Proc. ACM SIGPLAN '90, pp. 112-126.
diff --git a/docs/nspr/reference/hash_tables.rst b/docs/nspr/reference/hash_tables.rst
new file mode 100644
index 0000000000..bafef49c4b
--- /dev/null
+++ b/docs/nspr/reference/hash_tables.rst
@@ -0,0 +1,42 @@
+This chapter describes the hash table functions in the plds (portable
+library — data structures) library of NSPR. The hash table library
+functions are declared in the header file ``plhash.h.``
+
+.. warning::
+
+ **Warning**: The NSPR hash table library functions are not thread
+ safe.
+
+A hash table lookup may change the internal organization of the hash
+table (to speed up future lookups).
+
+- `Hash Table Types and Constants <#Hash_Table_Types_and_Constants>`__
+- `Hash Table Functions <#Hash_Table_Functions>`__
+
+.. _Hash_Table_Types_and_Constants:
+
+Hash Table Types and Constants
+------------------------------
+
+ - :ref:`PLHashEntry`
+ - :ref:`PLHashTable`
+ - :ref:`PLHashNumber`
+ - :ref:`PLHashFunction`
+ - :ref:`PLHashComparator`
+ - :ref:`PLHashEnumerator`
+ - :ref:`PLHashAllocOps`
+
+.. _Hash_Table_Functions:
+
+Hash Table Functions
+--------------------
+
+ - :ref:`PL_NewHashTable`
+ - :ref:`PL_HashTableDestroy`
+ - :ref:`PL_HashTableAdd`
+ - :ref:`PL_HashTableRemove`
+ - :ref:`PL_HashTableLookup`
+ - :ref:`PL_HashTableEnumerateEntries`
+ - :ref:`PL_HashString`
+ - :ref:`PL_CompareStrings`
+ - :ref:`PL_CompareValues`
diff --git a/docs/nspr/reference/i_o_functions.rst b/docs/nspr/reference/i_o_functions.rst
new file mode 100644
index 0000000000..a13d0b3b74
--- /dev/null
+++ b/docs/nspr/reference/i_o_functions.rst
@@ -0,0 +1,240 @@
+I/O functions
+=============
+
+This chapter describes the NSPR functions used to perform operations
+such as system access, normal file I/O, and socket (network) I/O.
+
+For sample code that illustrates basic I/O operations, see :ref:`Introduction_to_NSPR>`.
+For information about the types most
+commonly used with the functions described in this chapter, see `I/O
+Types <I%2fO_Types>`__.
+
+- `Functions that Operate on
+ Pathnames <#Functions_that_Operate_on_Pathnames>`__
+- `Functions that Act on File
+ Descriptors <#Functions_that_Act_on_File_Descriptors>`__
+- `Directory I/O Functions <#Directory_I/O_Functions>`__
+- `Socket Manipulation Functions <#Socket_Manipulation_Functions>`__
+- `Converting Between Host and Network
+ Addresses <#Converting_Between_Host_and_Network_Addresses>`__
+- `Memory-Mapped I/O Functions <#Memory-Mapped_I/O_Functions>`__
+- `Anonymous Pipe Function <#Anonymous_Pipe_Function>`__
+- `Polling Functions <#Polling_Functions>`__
+- `Pollable Events <#Pollable_Events>`__
+- `Manipulating Layers <#Manipulating_Layers>`__
+
+.. _Functions_that_Operate_on_Pathnames:
+
+Functions that Operate on Pathnames
+-----------------------------------
+
+A file or directory in a file system is specified by its pathname. NSPR
+uses Unix-style pathnames, which are null-terminated character strings.
+Only the ASCII character set is supported. The forward slash (/)
+separates the directories in a pathname. NSPR converts the slashes in a
+pathname to the directory separator of the native OS--for example,
+backslash (\) on Windows and colon (:) on Mac OS--before passing it to
+the native system calls.
+
+Some file systems also differentiate drives or volumes.
+
+- :ref:`PR_Open`
+- :ref:`PR_Delete`
+- :ref:`PR_GetFileInfo`
+- :ref:`PR_GetFileInfo64`
+- :ref:`PR_Rename`
+- :ref:`PR_Access`
+
+ - type :ref:`PRAccessHow`
+
+.. _Functions_that_Act_on_File_Descriptors:
+
+Functions that Act on File Descriptors
+--------------------------------------
+
+- :ref:`PR_Close`
+- :ref:`PR_Read`
+- :ref:`PR_Write`
+- :ref:`PR_Writev`
+- :ref:`PR_GetOpenFileInfo`
+- :ref:`PR_GetOpenFileInfo64`
+- :ref:`PR_Seek`
+- :ref:`PR_Seek64`
+- :ref:`PR_Available`
+- :ref:`PR_Available64`
+- :ref:`PR_Sync`
+- :ref:`PR_GetDescType`
+- :ref:`PR_GetSpecialFD`
+- :ref:`PR_CreatePipe`
+
+.. _Directory_I.2FO_Functions:
+
+Directory I/O Functions
+-----------------------
+
+- :ref:`PR_OpenDir`
+- :ref:`PR_ReadDir`
+- :ref:`PR_CloseDir`
+- :ref:`PR_MkDir`
+- :ref:`PR_RmDir`
+
+.. _Socket_Manipulation_Functions:
+
+Socket Manipulation Functions
+-----------------------------
+
+The network programming interface presented here is a socket API modeled
+after the popular Berkeley sockets. Differences include the following:
+
+- The blocking socket functions in NSPR take a timeout parameter.
+- Two new functions, :ref:`PR_TransmitFile` and :ref:`PR_AcceptRead`, can
+ exploit the new system calls of some operating systems for higher
+ performance.
+
+List of functions:
+
+- :ref:`PR_OpenUDPSocket`
+- :ref:`PR_NewUDPSocket`
+- :ref:`PR_OpenTCPSocket`
+- :ref:`PR_NewTCPSocket`
+- :ref:`PR_ImportTCPSocket`
+- :ref:`PR_Connect`
+- :ref:`PR_ConnectContinue`
+- :ref:`PR_Accept`
+- :ref:`PR_Bind`
+- :ref:`PR_Listen`
+- :ref:`PR_Shutdown`
+- :ref:`PR_Recv`
+- :ref:`PR_Send`
+- :ref:`PR_RecvFrom`
+- :ref:`PR_SendTo`
+- :ref:`PR_TransmitFile`
+- :ref:`PR_AcceptRead`
+- :ref:`PR_GetSockName`
+- :ref:`PR_GetPeerName`
+- :ref:`PR_GetSocketOption`
+- :ref:`PR_SetSocketOption`
+
+.. _Converting_Between_Host_and_Network_Addresses:
+
+Converting Between Host and Network Addresses
+---------------------------------------------
+
+- :ref:`PR_ntohs`
+- :ref:`PR_ntohl`
+- :ref:`PR_htons`
+- :ref:`PR_htonl`
+- :ref:`PR_FamilyInet`
+
+.. _Memory-Mapped_I.2FO_Functions:
+
+Memory-Mapped I/O Functions
+---------------------------
+
+The memory-mapped I/O functions allow sections of a file to be mapped to
+memory regions, allowing read-write accesses to the file to be
+accomplished by normal memory accesses.
+
+Memory-mapped I/O functions are currently implemented for Unix, Linux,
+Mac OS X, and Win32 only.
+
+- :ref:`PR_CreateFileMap`
+- :ref:`PR_MemMap`
+- :ref:`PR_MemUnmap`
+- :ref:`PR_CloseFileMap`
+
+.. _Anonymous_Pipe_Function:
+
+Anonymous Pipe Function
+-----------------------
+
+- :ref:`PR_CreatePipe`
+
+.. _Polling_Functions:
+
+Polling Functions
+-----------------
+
+This section describes two of the most important polling functions
+provided by NSPR:
+
+- :ref:`PR_Poll`
+- :ref:`PR_GetConnectStatus`
+
+.. _Pollable_Events:
+
+Pollable Events
+---------------
+
+A pollable event is a special kind of file descriptor. The only I/O
+operation you can perform on a pollable event is to poll it with the
+:ref:`PR_POLL_READ` flag. You cannot read from or write to a pollable
+event.
+
+The purpose of a pollable event is to combine event waiting with I/O
+waiting in a single :ref:`PR_Poll` call. Pollable events are implemented
+using a pipe or a pair of TCP sockets connected via the loopback
+address, therefore setting and/or waiting for pollable events are
+expensive operating system calls. Do not use pollable events for general
+thread synchronization; use condition variables instead.
+
+A pollable event has two states: set and unset. Events are not queued,
+so there is no notion of an event count. A pollable event is either set
+or unset.
+
+- :ref:`PR_NewPollableEvent`
+- :ref:`PR_DestroyPollableEvent`
+- :ref:`PR_SetPollableEvent`
+- :ref:`PR_WaitForPollableEvent`
+
+One can call :ref:`PR_Poll` with the :ref:`PR_POLL_READ` flag on a pollable
+event. When the pollable event is set, :ref:`PR_Poll` returns the the
+:ref:`PR_POLL_READ` flag set in the out_flags.
+
+.. _Manipulating_Layers:
+
+Manipulating Layers
+-------------------
+
+File descriptors may be layered. For example, SSL is a layer on top of a
+reliable bytestream layer such as TCP.
+
+Each type of layer has a unique identity, which is allocated by the
+runtime. The layer implementor should associate the identity with all
+layers of that type. It is then possible to scan the chain of layers and
+find a layer that one recognizes and therefore predict that it will
+implement a desired protocol.
+
+A layer can be pushed onto or popped from an existing stack of layers.
+The file descriptor of the top layer can be passed to NSPR I/O
+functions, which invoke the appropriate version of the I/O methods
+polymorphically.
+
+NSPR defines three identities:
+
+.. code::
+
+ #define PR_INVALID_IO_LAYER (PRDescIdentity)-1
+ #define PR_TOP_IO_LAYER (PRDescIdentity)-2
+ #define PR_NSPR_IO_LAYER (PRDescIdentity)0
+
+- :ref:`PR_INVALID_IO_LAYER`: An invalid layer identify (for error
+ return).
+- :ref:`PR_TOP_IO_LAYER`: The identity of the top of the stack.
+- :ref:`PR_NSPR_IO_LAYER`: The identity for the layer implemented by NSPR.
+
+:ref:`PR_TOP_IO_LAYER` may be used as a shorthand for identifying the
+topmost layer of an existing stack. For example, the following lines of
+code are equivalent:
+
+| ``rv = PR_PushIOLayer(stack, PR_TOP_IO_LAYER, my_layer);``
+| ``rv = PR_PushIOLayer(stack, PR_GetLayersIdentity(stack), my_layer);``
+
+- :ref:`PR_GetUniqueIdentity`
+- :ref:`PR_GetNameForIdentity`
+- :ref:`PR_GetLayersIdentity`
+- :ref:`PR_GetIdentitiesLayer`
+- :ref:`PR_GetDefaultIOMethods`
+- :ref:`PR_CreateIOLayerStub`
+- :ref:`PR_PushIOLayer`
+- :ref:`PR_PopIOLayer`
diff --git a/docs/nspr/reference/i_o_types.rst b/docs/nspr/reference/i_o_types.rst
new file mode 100644
index 0000000000..16ec1bd705
--- /dev/null
+++ b/docs/nspr/reference/i_o_types.rst
@@ -0,0 +1,105 @@
+This chapter describes the most common NSPR types, enumerations, and
+structures used with the functions described in `I/O
+Functions <I%2f%2fO_Functions>`__ and `Network
+Addresses <Network_Addresses>`__. These include the types used for
+system access, normal file I/O, and socket (network) I/O.
+
+Types unique to a particular function are described with the function
+itself.
+
+For sample code that illustrates basic I/O operations, see `Introduction
+to NSPR <Introduction_to_NSPR>`__.
+
+- `Directory Type <#Directory_Type>`__
+- `File Descriptor Types <#File_Descriptor_Types>`__
+- `File Info Types <#File_Info_Types>`__
+- `Network Address Types <#Network_Address_Types>`__
+- `Types Used with Socket Options
+ Functions <#Types_Used_with_Socket_Options_Functions>`__
+- `Type Used with Memory-Mapped
+ I/O <#Type_Used_with_Memory-Mapped_I/O>`__
+- `Offset Interpretation for Seek
+ Functions <#Offset_Interpretation_for_Seek_Functions>`__
+
+.. _Directory_Type:
+
+Directory Type
+--------------
+
+ - :ref:`PRDir`
+
+.. _File_Descriptor_Types:
+
+File Descriptor Types
+---------------------
+
+NSPR represents I/O objects, such as open files and sockets, by file
+descriptors of type :ref:`PRFileDesc`. This section introduces
+:ref:`PRFileDesc` and related types.
+
+ - :ref:`PRFileDesc`
+ - :ref:`PRIOMethods`
+ - :ref:`PRFilePrivate`
+ - :ref:`PRDescIdentity`
+
+Note that the NSPR documentation follows the Unix convention of using
+the term\ *files* to refer to many kinds of I/O objects. To refer
+specifically to the files in a file system (that is, disk files), this
+documentation uses the term\ *normal files*.
+
+:ref:`PRFileDesc` has an object-oriented flavor. An I/O function on a
+:ref:`PRFileDesc` structure is carried out by invoking the corresponding
+"method" in the I/O methods table (a structure of type :ref:`PRIOMethods`)
+of the :ref:`PRFileDesc` structure (the "object"). Different kinds of I/O
+objects (such as files and sockets) have different I/O methods tables,
+thus implementing different behavior in response to the same I/O
+function call.
+
+NSPR supports the implementation of layered I/O. Each layer is
+represented by a :ref:`PRFileDesc` structure, and the :ref:`PRFileDesc`
+structures for the layers are chained together. Each :ref:`PRFileDesc`
+structure has a field (of type :ref:`PRDescIdentity`) to identify itself in
+the layers. For example, the Netscape implementation of the Secure
+Sockets Layer (SSL) protocol is implemented as an I/O layer on top of
+NSPR's socket layer.
+
+.. _File_Info_Types:
+
+File Info Types
+---------------
+
+ - :ref:`PRFileInfo`
+ - :ref:`PRFileInfo64`
+ - :ref:`PRFileType`
+
+.. _Network_Address_Types:
+
+Network Address Types
+---------------------
+
+ - :ref:`PRNetAddr`
+ - :ref:`PRIPv6Addr`
+
+.. _Types_Used_with_Socket_Options_Functions:
+
+Types Used with Socket Options Functions
+----------------------------------------
+
+ - :ref:`PRSocketOptionData`
+ - :ref:`PRSockOption`
+ - :ref:`PRLinger`
+ - :ref:`PRMcastRequest`
+
+.. _Type_Used_with_Memory-Mapped_I.2FO:
+
+Type Used with Memory-Mapped I/O
+--------------------------------
+
+ - :ref:`PRFileMap`
+
+.. _Offset_Interpretation_for_Seek_Functions:
+
+Offset Interpretation for Seek Functions
+----------------------------------------
+
+ - :ref:`PRSeekWhence`
diff --git a/docs/nspr/reference/index.rst b/docs/nspr/reference/index.rst
new file mode 100644
index 0000000000..8502db1e33
--- /dev/null
+++ b/docs/nspr/reference/index.rst
@@ -0,0 +1,289 @@
+NSPR API Reference
+==================
+
+.. toctree::
+ :maxdepth: 2
+
+Introduction to NSPR
+--------------------
+
+- :ref:`NSPR_Naming_Conventions`
+- :ref:`NSPR_Threads`
+
+ - :ref:`Thread_Scheduling`
+
+ - :ref:`Setting_Thread_Priorities`
+ - :ref:`Preempting_Threads`
+ - :ref:`Interrupting_Threads`
+
+- :ref:`NSPR_Thread_Synchronization`
+
+ - :ref:`Locks_and_Monitors`
+ - :ref:`Condition_Variables`
+
+- :ref:`NSPR_Sample_Code`
+
+NSPR Types
+----------
+
+- :ref:`Calling_Convention_Types`
+- :ref:`Algebraic_Types`
+
+ - :ref:`8-.2C_16-.2C_and_32-bit_Integer_Types`
+
+ - :ref:`Signed_Integers`
+ - :ref:`Unsigned_Integers`
+
+ - :ref:`64-bit_Integer_Types`
+ - :ref:`Floating-Point_Number_Type`
+ - :ref:`Native_OS_Integer_Types`
+
+- :ref:`Miscellaneous_Types`
+
+ - :ref:`Size_Type`
+ - :ref:`Pointer_Difference_Types`
+ - :ref:`Boolean_Types`
+ - :ref:`Status_Type_for_Return_Values`
+
+Threads
+-------
+
+- :ref:`Threading_Types_and_Constants`
+- :ref:`Threading_Functions`
+
+ - :ref:`Creating.2C_Joining.2C_and_Identifying_Threads`
+ - :ref:`Controlling_Thread_Priorities`
+ - :ref:`Controlling_Per-Thread_Private_Data`
+ - :ref:`Interrupting_and_Yielding`
+ - :ref:`Setting_Global_Thread_Concurrency`
+ - :ref:`Getting_a_Thread.27s_Scope`
+
+Process Initialization
+----------------------
+
+- :ref:`Identity_and_Versioning`
+
+ - :ref:`Name_and_Version_Constants`
+
+- :ref:`Initialization_and_Cleanup`
+- :ref:`Module_Initialization`
+
+Locks
+-----
+
+- :ref:`Lock_Type`
+- :ref:`Lock_Functions`
+
+Condition_Variables
+-------------------
+
+- :ref:`Condition_Variable_Type`
+- :ref:`Condition_Variable_Functions`
+
+Monitors
+--------
+
+- :ref:`Monitor_Type`
+- :ref:`Monitor_Functions`
+
+Cached Monitors
+---------------
+
+- :ref:`Cached_Monitors_Functions`
+
+I/O Types
+---------
+
+- :ref:`Directory_Type`
+- :ref:`File_Descriptor_Types`
+- :ref:`File_Info_Types`
+- :ref:`Network_Address_Types`
+- :ref:`Types_Used_with_Socket_Options_Functions`
+- :ref:`Type_Used_with_Memory-Mapped_I.2FO`
+- :ref:`Offset_Interpretation_for_Seek_Functions`
+
+I/O Functions
+-------------
+
+- :ref:`Functions_that_Operate_on_Pathnames`
+- :ref:`Functions_that_Act_on_File_Descriptors`
+- :ref:`Directory_I.2FO_Functions`
+- :ref:`Socket_Manipulation_Functions`
+- :ref:`Converting_Between_Host_and_Network_Addresses`
+- :ref:`Memory-Mapped_I.2FO_Functions`
+- :ref:`Anonymous_Pipe_Function`
+- :ref:`Polling_Functions`
+- :ref:`Pollable_Events`
+- :ref:`Manipulating_Layers`
+
+Network Addresses
+-----------------
+
+- :ref:`Network_Address_Types_and_Constants`
+- :ref:`Network_Address_Functions`
+
+Atomic Operations
+-----------------
+
+- :ref:`PR_AtomicIncrement`
+- :ref:`PR_AtomicDecrement`
+- :ref:`PR_AtomicSet`
+
+Interval Timing
+---------------
+
+- :ref:`Interval_Time_Type_and_Constants`
+- :ref:`Interval_Functions`
+
+Date and Time
+-------------
+
+- :ref:`Types_and_Constants`
+- :ref:`Time_Parameter_Callback_Functions`
+- :ref:`Functions`
+
+Memory_Management Operations
+----------------------------
+
+- :ref:`Memory_Allocation_Functions`
+- :ref:`Memory_Allocation_Macros`
+
+String Operations
+-----------------
+
+- :ref:`PL_strlen`
+- :ref:`PL_strcpy`
+- :ref:`PL_strdup`
+- :ref:`PL_strfree`
+
+Floating Point Number to String Conversion
+------------------------------------------
+
+- :ref:`PR_strtod`
+- :ref:`PR_dtoa`
+- :ref:`PR_cnvtf`
+
+Linked Lists
+------------
+
+- :ref:`Linked_List_Types`
+
+ - :ref:`PRCList`
+
+- :ref:`Linked_List_Macros`
+
+ - :ref:`PR_INIT_CLIST`
+ - :ref:`PR_INIT_STATIC_CLIST`
+ - :ref:`PR_APPEND_LINK`
+ - :ref:`PR_INSERT_LINK`
+ - :ref:`PR_NEXT_LINK`
+ - :ref:`PR_PREV_LINK`
+ - :ref:`PR_REMOVE_LINK`
+ - :ref:`PR_REMOVE_AND_INIT_LINK`
+ - :ref:`PR_INSERT_BEFORE`
+ - :ref:`PR_INSERT_AFTER`
+
+Dynamic Library Linking
+-----------------------
+
+- :ref:`Library_Linking_Types`
+
+ - :ref:`PRLibrary`
+ - :ref:`PRStaticLinkTable`
+
+- :ref:`Library_Linking_Functions`
+
+ - :ref:`PR_SetLibraryPath`
+ - :ref:`PR_GetLibraryPath`
+ - :ref:`PR_GetLibraryName`
+ - :ref:`PR_FreeLibraryName`
+ - :ref:`PR_LoadLibrary`
+ - :ref:`PR_UnloadLibrary`
+ - :ref:`PR_FindSymbol`
+ - :ref:`PR_FindSymbolAndLibrary`
+ - :ref:`Finding_Symbols_Defined_in_the_Main_Executable_Program`
+
+- :ref:`Platform_Notes`
+
+ - :ref:`Dynamic_Library_Search_Path`
+ - :ref:`Exporting_Symbols_from_the_Main_Executable_Program`
+
+Process Management and Interprocess Communication
+-------------------------------------------------
+
+- :ref:`Process_Management_Types_and_Constants`
+
+ - :ref:`PRProcess`
+ - :ref:`PRProcessAttr`
+
+- :ref:`Process_Management_Functions`
+
+ - :ref:`Setting_the_Attributes_of_a_New_Process`
+ - :ref:`Creating_and_Managing_Processes`
+
+Logging
+-------
+
+- :ref:`Conditional_Compilation_and_Execution`
+- :ref:`Log_Types_and_Variables`
+
+ - :ref:`PRLogModuleInfo`
+ - :ref:`PRLogModuleLevel`
+ - :ref:`NSPR_LOG_MODULES`
+ - :ref:`NSPR_LOG_FILE`
+
+- :ref:`Logging_Functions_and_Macros`
+
+ - :ref:`PR_NewLogModule`
+ - :ref:`PR_SetLogFile`
+ - :ref:`PR_SetLogBuffering`
+ - :ref:`PR_LogPrint`
+ - :ref:`PR_LogFlush`
+ - :ref:`PR_LOG_TEST`
+ - :ref:`PR_LOG`
+ - :ref:`PR_Assert`
+ - :ref:`PR_ASSERT`
+ - :ref:`PR_NOT_REACHED`
+
+- :ref:`Use_Example`
+
+Named Shared Memory
+-------------------
+
+- :ref:`Shared_Memory_Protocol`
+- :ref:`Named_Shared_Memory_Functions`
+
+Anonymous Shared_Memory
+-----------------------
+
+- :ref:`Anonymous_Memory_Protocol`
+- :ref:`Anonymous_Shared_Memory_Functions`
+
+IPC Semaphores
+--------------
+
+- :ref:`IPC_Semaphore_Functions`
+
+Thread Pools
+------------
+
+- :ref:`Thread_Pool_Types`
+- :ref:`Thread_Pool_Functions`
+
+Random Number Generator
+-----------------------
+
+- :ref:`Random_Number_Generator_Function`
+
+Hash Tables
+-----------
+
+- :ref:`Hash_Table_Types_and_Constants`
+- :ref:`Hash_Table_Functions`
+
+NSPR Error Handling
+-------------------
+
+- :ref:`Error_Type`
+- :ref:`Error_Functions`
+- :ref:`Error_Codes`
diff --git a/docs/nspr/reference/interval_timing.rst b/docs/nspr/reference/interval_timing.rst
new file mode 100644
index 0000000000..2d19d6004b
--- /dev/null
+++ b/docs/nspr/reference/interval_timing.rst
@@ -0,0 +1,72 @@
+NSPR defines a platform-dependent type, :ref:`PRIntervalTime`, for timing
+intervals of fewer than approximately 6 hours. This chapter describes
+:ref:`PRIntervalTime` and the functions that allow you to use it for timing
+purposes:
+
+- `Interval Time Type and
+ Constants <#Interval_Time_Type_and_Constants>`__
+- `Interval Functions <#Interval_Functions>`__
+
+.. _Interval_Time_Type_and_Constants:
+
+Interval Time Type and Constants
+--------------------------------
+
+All timed functions in NSPR require a parameter that depicts the amount
+of time allowed to elapse before the operation is declared failed. The
+type of such arguments is :ref:`PRIntervalTime`. Such parameters are common
+in NSPR functions such as those used for I/O operations and operations
+on condition variables.
+
+NSPR 2.0 provides interval times that are efficient in terms of
+performance and storage requirements. Conceptually, they are based on
+free-running counters that increment at a fixed rate without possibility
+of outside influence (as might be observed if one was using a
+time-of-day clock that gets reset due to some administrative action).
+The counters have no fixed epoch and have a finite period. To make use
+of these counters, the application must declare a point in time, the
+epoch, and an amount of time elapsed since that **epoch**, the
+**interval**. In almost all cases the epoch is defined as the value of
+the interval timer at the time it was sampled.
+
+ - :ref:`PRIntervalTime`
+
+.. _Interval_Functions:
+
+Interval Functions
+------------------
+
+Interval timing functions are divided into three groups:
+
+- `Getting the Current Interval and Ticks Per
+ Second <#Getting_the_Current_Interval_and_Ticks_Per_Second>`__
+- `Converting Standard Clock Units to Platform-Dependent
+ Intervals <#Converting_Standard_Clock_Units_to_Platform-Dependent_Intervals>`__
+- `Converting Platform-Dependent Intervals to Standard Clock
+ Units <#Converting_Platform-Dependent_Intervals_to_Standard_Clock_Units>`__
+
+.. _Getting_the_Current_Interval_and_Ticks_Per_Second:
+
+Getting the Current Interval and Ticks Per Second
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ - :ref:`PR_IntervalNow`
+ - :ref:`PR_TicksPerSecond`
+
+.. _Converting_Standard_Clock_Units_to_Platform-Dependent_Intervals:
+
+Converting Standard Clock Units to Platform-Dependent Intervals
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ - :ref:`PR_SecondsToInterval`
+ - :ref:`PR_MillisecondsToInterval`
+ - :ref:`PR_MicrosecondsToInterval`
+
+.. _Converting_Platform-Dependent_Intervals_to_Standard_Clock_Units:
+
+Converting Platform-Dependent Intervals to Standard Clock Units
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ - :ref:`PR_IntervalToSeconds`
+ - :ref:`PR_IntervalToMilliseconds`
+ - :ref:`PR_IntervalToMicroseconds`
diff --git a/docs/nspr/reference/introduction_to_nspr.rst b/docs/nspr/reference/introduction_to_nspr.rst
new file mode 100644
index 0000000000..f1187d4ebe
--- /dev/null
+++ b/docs/nspr/reference/introduction_to_nspr.rst
@@ -0,0 +1,408 @@
+Introduction to NSPR
+====================
+
+The Netscape Portable Runtime (NSPR) API allows compliant applications
+to use system facilities such as threads, thread synchronization, I/O,
+interval timing, atomic operations, and several other low-level services
+in a platform-independent manner. This chapter introduces key NSPR
+programming concepts and illustrates them with sample code.
+
+NSPR does not provide a platform for porting existing code. It must be
+used from the beginning of a software project.
+
+.. _NSPR_Naming_Conventions:
+
+NSPR Naming Conventions
+-----------------------
+
+Naming of NSPR types, functions, and macros follows the following
+conventions:
+
+- Types exported by NSPR begin with ``PR`` and are followed by
+ intercap-style declarations, like this: :ref:`PRInt`, :ref:`PRFileDesc`
+- Function definitions begin with ``PR_`` and are followed by
+ intercap-style declarations, like this: :ref:`PR_Read``,
+ :ref:`PR_JoinThread``
+- Preprocessor macros begin with the letters ``PR`` and are followed by
+ all uppercase characters separated with the underscore character
+ (``_``), like this: :ref:`PR_BYTES_PER_SHORT`, :ref:`PR_EXTERN`
+
+.. _NSPR_Threads:
+
+NSPR Threads
+------------
+
+NSPR provides an execution environment that promotes the use of
+lightweight threads. Each thread is an execution entity that is
+scheduled independently from other threads in the same process. A thread
+has a limited number of resources that it truly owns. These resources
+include the thread stack and the CPU register set (including PC).
+
+To an NSPR client, a thread is represented by a pointer to an opaque
+structure of type :ref:`PRThread``. A thread is created by an explicit
+client request and remains a valid, independent execution entity until
+it returns from its root function or the process abnormally terminates.
+(:ref:`PRThread` and functions for creating and manipulating threads are
+described in detail in `Threads <Threads>`__.)
+
+NSPR threads are lightweight in the sense that they are cheaper than
+full-blown processes, but they are not free. They achieve the cost
+reduction by relying on their containing process to manage most of the
+resources that they access. This, and the fact that threads share an
+address space with other threads in the same process, makes it important
+to remember that *threads are not processes* .
+
+NSPR threads are scheduled in two separate domains:
+
+- **Local threads** are scheduled within a process only and are handled
+ entirely by NSPR, either by completely emulating threads on each host
+ operating system (OS) that doesn't support threads, or by using the
+ threading facilities of each host OS that does support threads to
+ emulate a relatively large number of local threads by using a
+ relatively small number of native threads.
+
+- **Global threads** are scheduled by the host OS--not by NSPR--either
+ within a process or across processes on the entire host. Global
+ threads correspond to native threads on the host OS.
+
+NSPR threads can also be either user threads or system threads. NSPR
+provides a function, :ref:`PR_Cleanup`, that synchronizes process
+termination. :ref:`PR_Cleanup` waits for the last user thread to exit
+before returning, whereas it ignores system threads when determining
+when a process should exit. This arrangement implies that a system
+thread should not have volatile data that needs to be safely stored
+away.
+
+Priorities for NSPR threads are based loosely on hints provided by the
+client and sometimes constrained by the underlying operating system.
+Therefore, priorities are not rigidly defined. For more information, see
+`Thread Scheduling <#Thread_Scheduling>`__.
+
+In general, it's preferable to create local user threads with normal
+priority and let NSPR take care of the details as appropriate for each
+host OS. It's usually not necessary to create a global thread explicitly
+unless you are planning to port your code only to platforms that provide
+threading services with which you are familiar or unless the thread will
+be executing code that might directly call blocking OS functions.
+
+Threads can also have "per-thread-data" attached to them. Each thread
+has a built-in per-thread error number and error string that are updated
+when NSPR operations fail. It's also possible for NSPR clients to define
+their own per-thread-data. For details, see `Controlling Per-Thread
+Private Data <Threads#Controlling_Per-Thread_Private_Data>`__.
+
+.. _Thread_Scheduling:
+
+Thread Scheduling
+~~~~~~~~~~~~~~~~~
+
+NSPR threads are scheduled by priority and can be preempted or
+interrupted. The sections that follow briefly introduce the NSPR
+approach to these three aspects of thread scheduling.
+
+- `Setting Thread Priorities <#Setting_Thread_Priorities>`__
+- `Preempting Threads <#Preempting_Threads>`__
+- `Interrupting Threads <#Interrupting_Threads>`__
+
+For reference information on the NSPR API used for thread scheduling,
+see `Threads <Threads>`__.
+
+.. _Setting_Thread_Priorities:
+
+Setting Thread Priorities
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The host operating systems supported by NSPR differ widely in the
+mechanisms they use to support thread priorities. In general, an NSPR
+thread of higher priority has a statistically better chance of running
+relative to threads of lower priority. However, because of the multiple
+strategies to provide execution vehicles for threads on various host
+platforms, priorities are not a clearly defined abstraction in NSPR. At
+best they are intended to specify a preference with respect to the
+amount of CPU time that a higher-priority thread might expect relative
+to a lower-priority thread. This preference is still subject to resource
+availability, and must not be used in place of proper synchronization.
+For more information on thread synchronization, see `NSPR Thread
+Synchronization <#NSPR_Thread_Synchronization>`__.
+
+The issue is further muddied by inconsistent offerings from OS vendors
+regarding the priority of their kernel-supported threads. NSPR assumes
+that the priorities of global threads are not manageable, but that the
+host OS will perform some sort of fair scheduling. It's usually
+preferable to create local user threads with normal priority and let
+NSPR and the host take care of the details.
+
+In some NSPR configurations, there may be an arbitrary (and perhaps
+large) number of local threads being supported by a more limited number
+of **virtual processors** (an internal application of global threads).
+In such situations, each virtual processor will have some number of
+local threads associated with it, though exactly which local threads and
+how many may vary over time. NSPR guarantees that for each virtual
+processor the highest-priority, schedulable local thread is the one
+executing. This thread implementation strategy is referred to as the **M
+x N model.**
+
+.. _Preempting_Threads:
+
+Preempting Threads
+^^^^^^^^^^^^^^^^^^
+
+Preemption is the act of taking control away from a ready thread at an
+arbitrary point and giving control to another appropriate thread. It
+might be viewed as taking the executing thread and adding it to the end
+of the ready queue for its appropriate priority, then simply running the
+scheduling algorithm to find the most appropriate thread. The chosen
+thread may be of higher priority, of the same priority, or even the same
+thread. It will not be a thread of lower priority.
+
+Some operating systems cannot be made preemptible (for example, Mac OS
+and Win 16). This puts them at some risk in supporting arbitrary code,
+even if the code is interpreted (Java). Other systems are not
+thread-aware, and their runtime libraries not thread-safe (most versions
+of Unix). These systems can support local level thread abstractions that
+can be made preemptible, but run the risk of library corruption
+(``libc``). Still other operating systems have a native notion of
+threads, and their libraries are thread-aware and support locking.
+However, if local threads are also present, and they are preemptible,
+they are subject to deadlock. At this time, the only safe solutions are
+to turn off preemption (a runtime decision) or to preempt global threads
+only.
+
+.. _Interrupting_Threads:
+
+Interrupting Threads
+^^^^^^^^^^^^^^^^^^^^
+
+NSPR threads are interruptible, with some constraints and
+inconsistencies.
+
+To interrupt a thread, the caller of :ref:`PR_Interrupt` must have the NSPR
+reference to the target thread (:ref:`PRThread`). When the target is
+interrupted, it is rescheduled from the point at which it was blocked,
+with a status error indicating that it was interrupted. NSPR recognizes
+only two areas where a thread may be interrupted: waiting on a condition
+variable and waiting on I/O. In the latter case, interruption does
+cancel the I/O operation. In neither case does being interrupted imply
+the demise of the thread.
+
+.. _NSPR_Thread_Synchronization:
+
+NSPR Thread Synchronization
+---------------------------
+
+Thread synchronization has two aspects: locking and notification.
+Locking prevents access to some resource, such as a piece of shared
+data: that is, it enforces mutual exclusion. Notification involves
+passing synchronization information among cooperating threads.
+
+In NSPR, a **mutual exclusion lock** (or **mutex**) of type :ref:`PRLock`
+controls locking, and associated **condition variables** of type
+:ref:`PRCondVar` communicate changes in state among threads. When a
+programmer associates a mutex with an arbitrary collection of data, the
+mutex provides a protective **monitor** around the data.
+
+.. _Locks_and_Monitors:
+
+Locks and Monitors
+~~~~~~~~~~~~~~~~~~
+
+In general, a monitor is a conceptual entity composed of a mutex, one or
+more condition variables, and the monitored data. Monitors in this
+generic sense should not be confused with the monitor type used in Java
+programming. In addition to :ref:`PRLock`, NSPR provides another mutex
+type, :ref:`PRMonitor`, which is reentrant and can have only one associated
+condition variable. :ref:`PRMonitor` is intended for use with Java and
+reflects the Java approach to thread synchronization.
+
+To access the data in the monitor, the thread performing the access must
+hold the mutex, also described as being "in the monitor." Mutual
+exclusion guarantees that only one thread can be in the monitor at a
+time and that no thread may observe or modify the monitored data without
+being in the monitor.
+
+Monitoring is about protecting data, not code. A **monitored invariant**
+is a Boolean expression over the monitored data. The expression may be
+false only when a thread is in the monitor (holding the monitor's
+mutex). This requirement implies that when a thread first enters the
+monitor, an evaluation of the invariant expression must yield a
+``true``. The thread must also reinstate the monitored invariant before
+exiting the monitor. Therefore, evaluation of the expression must also
+yield a true at that point in execution.
+
+A trivial example might be as follows. Suppose an object has three
+values, ``v1``, ``v2``, and ``sum``. The invariant is that the third
+value is the sum of the other two. Expressed mathematically, the
+invariant is ``sum = v1 + v2``. Any modification of ``v1`` or ``v2``
+requires modification of ``sum``. Since that is a complex operation, it
+must be monitored. Furthermore, any type of access to ``sum`` must also
+be monitored to ensure that neither ``v1`` nor ``v2`` are in flux.
+
+.. note::
+
+ **Note**: Evaluation of the invariant expression is a conceptual
+ requirement and is rarely done in practice. It is valuable to
+ formally define the expression during design, write it down, and
+ adhere to it. It is also useful to implement the expression during
+ development and test it where appropriate. The thread makes an
+ absolute assertion of the expression's evaluation both on entering
+ and on exiting the monitor.
+
+Acquiring a lock is a synchronous operation. Once the lock primitive is
+called, the thread returns only when it has acquired the lock. Should
+another thread (or the same thread) already have the lock held, the
+calling thread blocks, waiting for the situation to improve. That
+blocked state is not interruptible, nor is it timed.
+
+.. _Condition_Variables:
+
+Condition Variables
+~~~~~~~~~~~~~~~~~~~
+
+Condition variables facilitate communication between threads. The
+communication available is a semantic-free notification whose context
+must be supplied by the programmer. Conditions are closely associated
+with a single monitor.
+
+The association between a condition and a monitor is established when a
+condition variable is created, and the association persists for the life
+of the condition variable. In addition, a static association exists
+between the condition and some data within the monitor. This data is
+what will be manipulated by the program under the protection of the
+monitor. A thread may wait on notification of a condition that signals
+changes in the state of the associated data. Other threads may notify
+the condition when changes occur.
+
+Condition variables are always monitored. The relevant operations on
+conditions are always performed from within the monitor. They are used
+to communicate changes in the state of the monitored data (though still
+preserving the monitored invariant). Condition variables allow one or
+more threads to wait for a predetermined condition to exist, and they
+allow another thread to notify them when the condition occurs. Condition
+variables themselves do not carry the semantics of the state change, but
+simply provide a mechanism for indicating that something has changed. It
+is the programmer's responsibility to associate a condition with the
+state of the data.
+
+A thread may be designed to wait for a particular situation to exist in
+some monitored data. Since the nature of the situation is not an
+attribute of the condition, the program must test that itself. Since
+this testing involves the monitored data, it must be done from within
+the monitor. The wait operation atomically exits the monitor and blocks
+the calling thread in a waiting condition state. When the thread is
+resumed after the wait, it will have reentered the monitor, making
+operations on the data safe.
+
+There is a subtle interaction between the thread(s) waiting on a
+condition and those notifying it. The notification must take place
+within a monitor--the same monitor that protects the data being
+manipulated by the notifier. In pseudocode, the sequence looks like
+this:
+
+.. code::
+
+ enter(monitor);
+ ... manipulate the monitored data
+ notify(condition);
+ exit(monitor);
+
+Notifications to a condition do not accumulate. Nor is it required that
+any thread be waiting on a condition when the notification occurs. The
+design of the code that waits on a condition must take these facts into
+account. Therefore, the pseudocode for the waiting thread might look
+like this:
+
+.. code::
+
+ enter(monitor)
+ while (!expression) wait(condition);
+ ... manipulate monitored data
+ exit(monitor);
+
+The need to evaluate the Boolean expression again after rescheduling
+from a wait may appear unnecessary, but it is vital to the correct
+execution of the program. The notification promotes a thread waiting on
+a condition to a ready state. When that thread actually gets scheduled
+is determined by the thread scheduler and cannot be predicted. If
+multiple threads are actually processing the notifications, one or more
+of them could be scheduled ahead of the one explicitly promoted by the
+notification. One such thread could enter the monitor and perform the
+work indicated by the notification, and exit. In this case the thread
+would resume from the wait only to find that there's nothing to do.
+
+For example, suppose the defined rule of a function is that it should
+wait until there is an object available and that it should return a
+reference to that object. Writing the code as follows could potentially
+return a null reference, violating the invariant of the function:
+
+.. code::
+
+ void *dequeue()
+ {
+ void *db;
+ enter(monitor);
+ if ((db = delink()) == null)
+ {
+ wait(condition);
+ db = delink();
+ }
+ exit(monitor);
+ return db;
+ }
+
+The same function would be more appropriately written as follows:
+
+.. code::
+
+ void *dequeue()
+ {
+ void *db;
+ enter(monitor);
+ while ((db = delink()) == null)
+ wait(condition);
+ exit(monitor);
+ return db;
+ }
+
+.. note::
+
+ **Caution**: The semantics of :ref:`PR_WaitCondVar` assume that the
+ monitor is about to be exited. This assumption implies that the
+ monitored invariant must be reinstated before calling
+ :ref:`PR_WaitCondVar`. Failure to do this will cause subtle but painful
+ bugs.
+
+To modify monitored data safely, a thread must be in the monitor. Since
+no other thread may modify or (in most cases) even observe the protected
+data from outside the monitor, the thread can safely make any
+modifications needed. When the changes have been completed, the thread
+notifies the condition associated with the data and exits the monitor
+using :ref:`PR_NotifyCondVar`. Logically, each such notification promotes
+one thread that was waiting on the condition to a ready state. An
+alternate form of notification (:ref:`PR_NotifyAllCondVar`) promotes all
+threads waiting on a condition to the ready state. If no threads were
+waiting, the notification is a no-op.
+
+Waiting on a condition variable is an interruptible operation. Another
+thread could target the waiting thread and issue a :ref:`PR_Interrupt`,
+causing a waiting thread to resume. In such cases the return from the
+wait operation indicates a failure and definitively indicates that the
+cause of the failure is an interrupt.
+
+A call to :ref:`PR_WaitCondVar` may also resume because the interval
+specified on the wait call has expired. However, this fact cannot be
+unambiguously delivered, so no attempt is made to do so. If the logic of
+a program allows for timing of waits on conditions, then the clock must
+be treated as part of the monitored data and the amount of time elapsed
+re-asserted when the call returns. Philosophically, timeouts should be
+treated as explicit notifications, and therefore require the testing of
+the monitored data upon resumption.
+
+.. _NSPR_Sample_Code:
+
+NSPR Sample Code
+----------------
+
+The documents linked here present two sample programs, including
+detailed annotations: ``layer.html`` and ``switch.html``. In addition to
+these annotated HTML versions, the same samples are available in pure
+source form.
diff --git a/docs/nspr/reference/ipc_semaphores.rst b/docs/nspr/reference/ipc_semaphores.rst
new file mode 100644
index 0000000000..2391346c9c
--- /dev/null
+++ b/docs/nspr/reference/ipc_semaphores.rst
@@ -0,0 +1,23 @@
+This chapter describes the NSPR API for using interprocess communication
+semaphores.
+
+NSPR provides an interprocess communication mechanism using a counting
+semaphore model similar to that which is provided in Unix and Windows
+platforms.
+
+.. note::
+
+ **Note:** See also `Named Shared Memory <Named_Shared_Memory>`__
+
+- `IPC Semaphore Functions <#IPC_Semaphore_Functions>`__
+
+.. _IPC_Semaphore_Functions:
+
+IPC Semaphore Functions
+-----------------------
+
+ - :ref:`PR_OpenSemaphore`
+ - :ref:`PR_WaitSemaphore`
+ - :ref:`PR_PostSemaphore`
+ - :ref:`PR_CloseSemaphore`
+ - :ref:`PR_DeleteSemaphore`
diff --git a/docs/nspr/reference/linked_lists.rst b/docs/nspr/reference/linked_lists.rst
new file mode 100644
index 0000000000..696d112cd7
--- /dev/null
+++ b/docs/nspr/reference/linked_lists.rst
@@ -0,0 +1,36 @@
+This chapter describes the NSPR API for managing linked lists. The API
+is a set of macros for initializing a circular (doubly linked) list,
+inserting and removing elements from the list. The macros are not thread
+safe. The caller must provide for mutually-exclusive access to the list,
+and for the nodes being added and removed from the list.
+
+- `Linked List Types <#Linked_List_Types>`__
+- `Linked List Macros <#Linked_List_Macros>`__
+
+.. _Linked_List_Types:
+
+Linked List Types
+-----------------
+
+The :ref:`PRCList` type represents a circular linked list.
+
+.. _Linked_List_Macros:
+
+Linked List Macros
+------------------
+
+Macros that create and operate on linked lists are:
+
+ - :ref:`PR_INIT_CLIST`
+ - :ref:`PR_INIT_STATIC_CLIST`
+ - :ref:`PR_APPEND_LINK`
+ - :ref:`PR_INSERT_LINK`
+ - :ref:`PR_NEXT_LINK`
+ - :ref:`PR_PREV_LINK`
+ - :ref:`PR_REMOVE_LINK`
+ - :ref:`PR_REMOVE_AND_INIT_LINK`
+ - :ref:`PR_INSERT_BEFORE`
+ - :ref:`PR_INSERT_AFTER`
+ - :ref:`PR_CLIST_IS_EMPTY`
+ - :ref:`PR_LIST_HEAD`
+ - :ref:`PR_LIST_TAIL`
diff --git a/docs/nspr/reference/locks.rst b/docs/nspr/reference/locks.rst
new file mode 100644
index 0000000000..5ab57102c1
--- /dev/null
+++ b/docs/nspr/reference/locks.rst
@@ -0,0 +1,42 @@
+This chapter describes the NSPR API for creation and manipulation of a
+mutex of type :ref:`PRLock`.
+
+- `Lock Type <#Lock_Type>`__
+- `Lock Functions <#Lock_Functions>`__
+
+In NSPR, a mutex of type :ref:`PRLock` controls locking, and associated
+condition variables communicate changes in state among threads. When a
+programmer associates a mutex with an arbitrary collection of data, the
+mutex provides a protective monitor around the data.
+
+In general, a monitor is a conceptual entity composed of a mutex, one or
+more condition variables, and the monitored data. Monitors in this
+generic sense should not be confused with monitors used in Java
+programming. In addition to :ref:`PRLock`, NSPR provides another mutex
+type, :ref:`PRMonitor`, which is reentrant and can have only one associated
+condition variable. :ref:`PRMonitor` is intended for use with Java and
+reflects the Java approach to thread synchronization.
+
+For an introduction to NSPR thread synchronization, including locks and
+condition variables, see `Introduction to
+NSPR <Introduction_to_NSPR>`__.
+
+For reference information on NSPR condition variables, see `Condition
+Variables <Condition_Variables>`__.
+
+.. _Lock_Type:
+
+Lock Type
+---------
+
+ - :ref:`PRLock`
+
+.. _Lock_Functions:
+
+Lock Functions
+--------------
+
+ - :ref:`PR_NewLock` creates a new lock object.
+ - :ref:`PR_DestroyLock` destroys a specified lock object.
+ - :ref:`PR_Lock` locks a specified lock object.
+ - :ref:`PR_Unlock` unlocks a specified lock object.
diff --git a/docs/nspr/reference/logging.rst b/docs/nspr/reference/logging.rst
new file mode 100644
index 0000000000..ba91ad39a6
--- /dev/null
+++ b/docs/nspr/reference/logging.rst
@@ -0,0 +1,116 @@
+NSPR Logging
+============
+
+This chapter describes the global functions you use to perform logging.
+NSPR provides a set of logging functions that conditionally write
+``printf()`` style strings to the console or to a log file. NSPR uses
+this facility itself for its own development debugging purposes.
+
+You can select events to be logged by module or level. A module is a
+user-defined class of log events. A level is a numeric value that
+indicates the seriousness of the event to be logged. You can combine
+module and level criteria to get highly selective logging.
+
+NSPR also provides "assert"-style macros and functions to aid in
+application debugging.
+
+- `Conditional Compilation and
+ Execution <#Conditional_Compilation_and_Execution>`__
+- `Log Types and Variables <#Log_Types_and_Variables>`__
+- `Logging Functions and Macros <#Logging_Functions_and_Macros>`__
+- `Use Example <#Use_Example>`__
+
+.. _Conditional_Compilation_and_Execution:
+
+Conditional Compilation and Execution
+-------------------------------------
+
+NSPR's logging facility is conditionally compiled in and enabled for
+applications using it. These controls are platform dependent. Logging is
+not compiled in for the Win16 platform. Logging is compiled into the
+NSPR debug builds; logging is not compiled into the NSPR optimized
+builds. The compile time ``#define`` values ``DEBUG`` or
+``FORCE_PR_LOG`` enable NSPR logging for application programs.
+
+To enable NSPR logging and/or the debugging aids in your application,
+compile using the NSPR debug build headers and runtime. Set one of the
+compile-time defines when you build your application.
+
+Execution-time control of NSPR's logging uses two environment variables.
+These variables control which modules and levels are logged as well as
+the file name of the log file. By default, no logging is enabled at
+execution time.
+
+.. _Log_Types_and_Variables:
+
+Log Types and Variables
+-----------------------
+
+Two types supporting NSPR logging are exposed in the API:
+
+ - :ref:`PRLogModuleInfo`
+ - :ref:`PRLogModuleLevel`
+
+Two environment variables control the behavior of logging at execution
+time:
+
+ - :ref:`NSPR_LOG_MODULES`
+ - :ref:`NSPR_LOG_FILE`
+
+.. _Logging_Functions_and_Macros:
+
+Logging Functions and Macros
+----------------------------
+
+The functions and macros for logging are:
+
+ - :ref:`PR_NewLogModule`
+ - :ref:`PR_SetLogFile`
+ - :ref:`PR_SetLogBuffering`
+ - :ref:`PR_LogPrint`
+ - :ref:`PR_LogFlush`
+ - :ref:`PR_LOG_TEST`
+ - :ref:`PR_LOG`
+ - :ref:`PR_Assert`
+ - :ref:`PR_STATIC_ASSERT` (new in NSPR 4.6.6XXX this hasn't been released
+ yet; the number is a logical guess)
+ - :ref:`PR_NOT_REACHED`
+
+.. note::
+
+ The above documentation has not been ported to MDN yet, see
+ http://www-archive.mozilla.org/projects/nspr/reference/html/prlog.html#25338.
+
+.. _Use_Example:
+
+Use Example
+-----------
+
+The following sample code fragment demonstrates use of the logging and
+debugging aids.
+
+- Compile the program with DEBUG defined.
+- Before running the compiled program, set the environment variable
+ NSPR_LOG_MODULES to userStuff:5
+
+.. code::
+
+ static void UserLogStuff( void )
+ {
+ PRLogModuleInfo *myLM;
+ PRIntn i;
+
+ PR_STATIC_ASSERT(5 > 4); /* NSPR 4.6.6 or newer */
+
+ myLM = PR_NewLogModule( "userStuff" );
+ PR_ASSERT( myLM );
+
+ PR_LOG( myLM, PR_LOG_NOTICE, ("Log a Notice %d\n", 999 ));
+ for (i = 0; i < 10 ; i++ )
+ {
+ PR_LOG( myLM, PR_LOG_DEBUG, ("Log Debug number: %d\n", i));
+ PR_Sleep( 500 );
+ }
+ PR_LOG( myLM, PR_LOG_NOTICE, "That's all folks\n");
+
+ } /* end UserLogStuff() */
diff --git a/docs/nspr/reference/long_long_(64-bit)_integers.rst b/docs/nspr/reference/long_long_(64-bit)_integers.rst
new file mode 100644
index 0000000000..d0a747e7ed
--- /dev/null
+++ b/docs/nspr/reference/long_long_(64-bit)_integers.rst
@@ -0,0 +1,32 @@
+Long Long integers
+==================
+
+This chapter describes the global functions you use to perform 64-bit
+integer operations. The functions define a portable API that can be used
+reliably in any environment. Where 64-bit integers are desired, use of
+NSPR's implementation is recommended to ensure cross-platform
+compatibility.
+
+Most of the 64-bit integer operations are implemented as macros. The
+specific implementation of each macro depends on whether the compiler
+for the target platform supports 64-bit integers. For a specific target
+platform, if 64-bit integers are supported for that platform, define
+``HAVE_LONG_LONG`` at compile time.
+
+.. _64-Bit_Integer_Types:
+
+64-Bit Integer Types
+~~~~~~~~~~~~~~~~~~~~
+
+NSPR provides two types to represent 64-bit integers:
+
+- :ref:`PRInt64`
+- :ref:`PRUint64`
+
+.. _64-Bit_Integer_Functions:
+
+64-Bit Integer Functions
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+The API defined for the 64-bit integer functions is consistent across
+all supported platforms.
diff --git a/docs/nspr/reference/memory_management_operations.rst b/docs/nspr/reference/memory_management_operations.rst
new file mode 100644
index 0000000000..1f61020685
--- /dev/null
+++ b/docs/nspr/reference/memory_management_operations.rst
@@ -0,0 +1,49 @@
+This chapter describes the global functions and macros you use to
+perform memory management. NSPR provides heap-based memory management
+functions that map to the familiar ``malloc()``, ``calloc()``,
+``realloc()``, and ``free()``.
+
+- `Memory Allocation Functions <#Memory_Allocation_Functions>`__
+- `Memory Allocation Macros <#Memory_Allocation_Macros>`__
+
+.. _Memory_Allocation_Functions:
+
+Memory Allocation Functions
+---------------------------
+
+NSPR has its own heap, and these functions act on that heap. Libraries
+built on top of NSPR, such as the Netscape security libraries, use these
+functions to allocate and free memory. If you are allocating memory for
+use by such libraries or freeing memory that was allocated by such
+libraries, you must use these NSPR functions rather than the libc
+equivalents.
+
+Memory allocation functions are:
+
+ - :ref:`PR_Malloc`
+ - :ref:`PR_Calloc`
+ - :ref:`PR_Realloc`
+ - :ref:`PR_Free`
+
+``PR_Malloc()``, ``PR_Calloc()``, ``PR_Realloc()``, and ``PR_Free()``
+have the same signatures as their libc equivalents ``malloc()``,
+``calloc()``, ``realloc()``, and ``free()``, and have the same
+semantics. (Note that the argument type ``size_t`` is replaced by
+:ref:`PRUint32`.) Memory allocated by ``PR_Malloc()``, ``PR_Calloc()``, or
+``PR_Realloc()`` must be freed by ``PR_Free()``.
+
+.. _Memory_Allocation_Macros:
+
+Memory Allocation Macros
+------------------------
+
+Macro versions of the memory allocation functions are available, as well
+as additional macros that provide programming convenience:
+
+ - :ref:`PR_MALLOC`
+ - :ref:`PR_NEW`
+ - :ref:`PR_REALLOC`
+ - :ref:`PR_CALLOC`
+ - :ref:`PR_NEWZAP`
+ - :ref:`PR_DELETE`
+ - :ref:`PR_FREEIF`
diff --git a/docs/nspr/reference/monitors.rst b/docs/nspr/reference/monitors.rst
new file mode 100644
index 0000000000..63d43d595b
--- /dev/null
+++ b/docs/nspr/reference/monitors.rst
@@ -0,0 +1,63 @@
+In addition to the mutex type :ref:`PRLock`, NSPR provides a special type,
+:ref:`PRMonitor`, for use in Java programming. This chapter describes the
+NSPR API for creation and manipulation of a mutex of type :ref:`PRMonitor`.
+
+- `Monitor Type <#Monitor_Type>`__
+- `Monitor Functions <#Monitor_Functions>`__
+
+With a mutex of type :ref:`PRLock`, a single thread may enter the monitor
+only once before it exits, and the mutex can have multiple associated
+condition variables.
+
+With a mutex of type :ref:`PRMonitor`, a single thread may re-enter a
+monitor as many times as it sees fit. The first time the thread enters a
+monitor, it acquires the monitor's lock and the thread's entry count is
+incremented to 1. Each subsequent time the thread successfully enters
+the same monitor, the thread's entry count is incremented again, and
+each time the thread exits the monitor, the thread's entry count is
+decremented. When the entry count for a thread reaches zero, the thread
+releases the monitor's lock, and other threads that were blocked while
+trying to enter the monitor will be rescheduled.
+
+A call to :ref:`PR_Wait` temporarily returns the entry count to zero. When
+the calling thread resumes, it has the same entry count it had before
+the wait operation.
+
+Unlike a mutex of type :ref:`PRLock`, a mutex of type :ref:`PRMonitor` has a
+single, implicitly associated condition variable that may be used to
+facilitate synchronization of threads with the change in state of
+monitored data.
+
+For an introduction to NSPR thread synchronization, including locks and
+condition variables, see `Introduction to
+NSPR <Introduction_to_NSPR>`__.
+
+.. _Monitor_Type:
+
+Monitor Type
+------------
+
+With the exception of :ref:`PR_NewMonitor`, which creates a new monitor
+object, all monitor functions require a pointer to an opaque object of
+type :ref:`PRMonitor`.
+
+.. _Monitor_Functions:
+
+Monitor Functions
+-----------------
+
+All monitor functions are thread-safe. However, this safety does not
+extend to protecting the monitor object from deletion.
+
+ - :ref:`PR_NewMonitor` creates a new monitor.
+ - :ref:`PR_DestroyMonitor` destroys a monitor object.
+ - :ref:`PR_EnterMonitor` enters the lock associated with a specified
+ monitor.
+ - :ref:`PR_ExitMonitor` decrements the entry count associated with a
+ specified monitor.
+ - :ref:`PR_Wait` waits for a notify on a specified monitor's condition
+ variable.
+ - :ref:`PR_Notify` notifies a thread waiting on a specified monitor's
+ condition variable.
+ - :ref:`PR_NotifyAll` notifies all threads waiting on a specified
+ monitor's condition variable.
diff --git a/docs/nspr/reference/named_shared_memory.rst b/docs/nspr/reference/named_shared_memory.rst
new file mode 100644
index 0000000000..dff1275cc4
--- /dev/null
+++ b/docs/nspr/reference/named_shared_memory.rst
@@ -0,0 +1,95 @@
+The chapter describes the NSPR API for named shared memory. Shared
+memory allows multiple processes to access one or more common shared
+memory regions, using it as an interprocess communication channel. The
+NSPR shared memory API provides a cross-platform named shared-memory
+interface that is modeled on similar constructs in the Unix and Windows
+operating systems.
+
+- `Shared Memory Protocol <#Shared_Memory_Protocol>`__
+- `Named Shared Memory Functions <#Named_Shared_Memory_Functions>`__
+
+.. _Shared_Memory_Protocol:
+
+Shared Memory Protocol
+----------------------
+
+.. _Using_Named_Shared_Memory_Functions:
+
+Using Named Shared Memory Functions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:ref:`PR_OpenSharedMemory` creates the shared memory segment, if it does
+not already exist, or opens a connection with the existing shared memory
+segment if it already exists.
+
+:ref:`PR_AttachSharedMemory` should be called following
+:ref:`PR_OpenSharedMemory` to map the memory segment to an address in the
+application's address space. :ref:`PR_AttachSharedMemory` may also be
+called to remap a shared memory segment after detaching the same
+``PRSharedMemory`` object. Be sure to detach it when you're finished.
+
+:ref:`PR_DetachSharedMemory` should be called to unmap the shared memory
+segment from the application's address space.
+
+:ref:`PR_CloseSharedMemory` should be called when no further use of the
+``PRSharedMemory`` object is required within a process. Following a call
+to :ref:`PR_CloseSharedMemory`, the ``PRSharedMemory`` object is invalid
+and cannot be reused.
+
+:ref:`PR_DeleteSharedMemory` should be called before process termination.
+After you call :ref:`PR_DeleteSharedMemory`, any further use of the shared
+memory associated with the name may cause unpredictable results.
+
+Filenames
+~~~~~~~~~
+
+The name passed to :ref:`PR_OpenSharedMemory` should be a valid filename
+for a Unix platform. :ref:`PR_OpenSharedMemory` creates file using the name
+passed in. Some platforms may mangle the name before creating the file
+and the shared memory. The Unix implementation may use SysV IPC shared
+memory, Posix shared memory, or memory mapped files; the filename may be
+used to define the namespace. On Windows, the name is significant, but
+there is no file associated with the name.
+
+No assumptions about the persistence of data in the named file should be
+made. Depending on platform, the shared memory may be mapped onto system
+paging space and be discarded at process termination.
+
+All names provided to :ref:`PR_OpenSharedMemory` should be valid filename
+syntax or name syntax for shared memory for the target platform.
+Referenced directories should have permissions appropriate for writing.
+
+.. _Limits_on_Shared_Memory_Resources:
+
+Limits on Shared Memory Resources
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Different platforms have limits on both the number and size of shared
+memory resources. The default system limits on some platforms may be
+smaller than your requirements. These limits may be adjusted on some
+platforms either via boot-time options or by setting the size of the
+system paging space to accommodate more and/or larger shared memory
+segment(s).
+
+.. _Security_Considerations:
+
+Security Considerations
+~~~~~~~~~~~~~~~~~~~~~~~
+
+On Unix platforms, depending on implementation, contents of the backing
+store for the shared memory can be exposed via the file system. Set
+permissions and or access controls at create and attach time to ensure
+you get the desired security.
+
+On Windows platforms, no special security measures are provided.
+
+.. _Named_Shared_Memory_Functions:
+
+Named Shared Memory Functions
+-----------------------------
+
+ - :ref:`PR_OpenSharedMemory`
+ - :ref:`PR_AttachSharedMemory`
+ - :ref:`PR_DetachSharedMemory`
+ - :ref:`PR_CloseSharedMemory`
+ - :ref:`PR_DeleteSharedMemory`
diff --git a/docs/nspr/reference/network_addresses.rst b/docs/nspr/reference/network_addresses.rst
new file mode 100644
index 0000000000..c6845e6efc
--- /dev/null
+++ b/docs/nspr/reference/network_addresses.rst
@@ -0,0 +1,82 @@
+This chapter describes the NSPR types and functions used to manipulate
+network addresses.
+
+- `Network Address Types and
+ Constants <#Network_Address_Types_and_Constants>`__
+- `Network Address Functions <#Network_Address_Functions>`__
+
+The API described in this chapter recognizes the emergence of Internet
+Protocol Version 6 (IPv6). To facilitate the transition to IPv6, it is
+recommended that clients treat all structures containing network
+addresses as transparent objects and use the functions documented here
+to manipulate the information.
+
+If used consistently, this API also eliminates the need to deal with the
+byte ordering of network addresses. Typically, the only numeric
+declarations required are the well-known port numbers that are part of
+the :ref:`PRNetAddr` structure.
+
+.. _Network_Address_Types_and_Constants:
+
+Network Address Types and Constants
+-----------------------------------
+
+ - :ref:`PRHostEnt`
+ - :ref:`PRProtoEnt`
+ - :ref:`PR_NETDB_BUF_SIZE`
+
+.. _Network_Address_Functions:
+
+Network address functions
+-------------------------
+
+.. _Initializing_a_Network_Address:
+
+Initializing a network address
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:ref:`PR_InitializeNetAddr` facilitates the use of :ref:`PRNetAddr`, the basic
+network address structure, in a polymorphic manner. By using these
+functions with other network address functions, clients can support
+either version 4 or version 6 of the Internet Protocol transparently.
+
+All NSPR functions that require `PRNetAddr <PRNetAddr>`__ as an argument
+accept either an IPv4 or IPv6 version of the address.
+
+ - :ref:`PR_InitializeNetAddr`
+
+.. _Converting_Between_a_String_and_a_Network_Address:
+
+Converting between a string and a network address
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ - :ref:`PR_StringToNetAddr`
+ - :ref:`PR_NetAddrToString`
+
+.. _Converting_address_formats:
+
+Converting address formats
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ - :ref:`PR_ConvertIPv4AddrToIPv6`
+
+.. _Getting_Host_Names_and_Addresses:
+
+Getting host names and addresses
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ - :ref:`PR_GetHostByName`
+ - :ref:`PR_GetHostByAddr`
+ - :ref:`PR_EnumerateHostEnt`
+ - :ref:`PR_GetAddrInfoByName`
+ - :ref:`PR_EnumerateAddrInfo`
+ - :ref:`PR_GetCanonNameFromAddrInfo`
+ - :ref:`PR_FreeAddrInfo`
+
+.. _Getting_Protocol_Entries:
+
+Getting protocol entries
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+ - :ref:`PR_GetProtoByName`
+ - :ref:`PR_GetProtoByNumber`
diff --git a/docs/nspr/reference/nspr_error_handling.rst b/docs/nspr/reference/nspr_error_handling.rst
new file mode 100644
index 0000000000..df40678607
--- /dev/null
+++ b/docs/nspr/reference/nspr_error_handling.rst
@@ -0,0 +1,206 @@
+This chapter describes the functions for retrieving and setting errors
+and the error codes set by NSPR.
+
+- `Error Type <#Error_Type>`__
+- `Error Functions <#Error_Functions>`__
+- `Error Codes <#Error_Codes>`__
+
+For information on naming conventions for NSPR types, functions, and
+macros, see `NSPR Naming
+Conventions <Introduction_to_NSPR#NSPR_Naming_Conventions>`__.
+
+.. _Error_Type:
+
+Error Type
+----------
+
+ - :ref:`PRErrorCode`
+
+.. _Error_Functions:
+
+Error Functions
+---------------
+
+ - :ref:`PR_SetError`
+ - :ref:`PR_SetErrorText`
+ - :ref:`PR_GetError`
+ - :ref:`PR_GetOSError`
+ - :ref:`PR_GetErrorTextLength`
+ - :ref:`PR_GetErrorText`
+
+.. _Error_Codes:
+
+Error Codes
+-----------
+
+Error codes defined in ``prerror.h``:
+
+``PR_OUT_OF_MEMORY_ERROR``
+ Insufficient memory to perform request.
+``PR_BAD_DESCRIPTOR_ERROR``
+ The file descriptor used as an argument in the preceding function is
+ invalid.
+``PR_WOULD_BLOCK_ERROR``
+ The operation would have blocked, which conflicts with the semantics
+ that have been established.
+``PR_ACCESS_FAULT_ERROR``
+ One of the arguments of the preceding function specified an invalid
+ memory address.
+``PR_INVALID_METHOD_ERROR``
+ The preceding function is invalid for the type of file descriptor
+ used.
+``PR_ILLEGAL_ACCESS_ERROR``
+ One of the arguments of the preceding function specified an invalid
+ memory address.
+``PR_UNKNOWN_ERROR``
+ Some unknown error has occurred.
+``PR_PENDING_INTERRUPT_ERROR``
+ The operation terminated because another thread has interrupted it
+ with :ref:`PR_Interrupt`.
+``PR_NOT_IMPLEMENTED_ERROR``
+ The preceding function has not been implemented.
+``PR_IO_ERROR``
+ The preceding I/O function encountered some sort of an error, perhaps
+ an invalid device.
+``PR_IO_TIMEOUT_ERROR``
+ The I/O operation has not completed in the time specified for the
+ preceding function.
+``PR_IO_PENDING_ERROR``
+ An I/O operation has been attempted on a file descriptor that is
+ currently busy with another operation.
+``PR_DIRECTORY_OPEN_ERROR``
+ The directory could not be opened.
+``PR_INVALID_ARGUMENT_ERROR``
+ One or more of the arguments to the function is invalid.
+``PR_ADDRESS_NOT_AVAILABLE_ERROR``
+ The network address (:ref:`PRNetAddr`) is not available (probably in
+ use).
+``PR_ADDRESS_NOT_SUPPORTED_ERROR``
+ The type of network address specified is not supported.
+``PR_IS_CONNECTED_ERROR``
+ An attempt to connect on an already connected network file
+ descriptor.
+``PR_BAD_ADDRESS_ERROR``
+ The network address specified is invalid (as reported by the
+ network).
+``PR_ADDRESS_IN_USE_ERROR``
+ Network address specified (:ref:`PRNetAddr`) is in use.
+``PR_CONNECT_REFUSED_ERROR``
+ The peer has refused to allow the connection to be established.
+``PR_NETWORK_UNREACHABLE_ERROR``
+ The network address specifies a host that is unreachable (perhaps
+ temporary).
+``PR_CONNECT_TIMEOUT_ERROR``
+ The connection attempt did not complete in a reasonable period of
+ time.
+``PR_NOT_CONNECTED_ERROR``
+ The preceding function attempted to use connected semantics on a
+ network file descriptor that was not connected.
+``PR_LOAD_LIBRARY_ERROR``
+ Failure to load a dynamic library.
+``PR_UNLOAD_LIBRARY_ERROR``
+ Failure to unload a dynamic library.
+``PR_FIND_SYMBOL_ERROR``
+ Symbol could not be found in the specified library.
+``PR_INSUFFICIENT_RESOURCES_ERROR``
+ There are insufficient system resources to process the request.
+``PR_DIRECTORY_LOOKUP_ERROR``
+ A directory lookup on a network address has failed.
+``PR_TPD_RANGE_ERROR``
+ Attempt to access a thread-private data index that is out of range of
+ any index that has been allocated to the process.
+``PR_PROC_DESC_TABLE_FULL_ERROR``
+ The process' table for holding open file descriptors is full.
+``PR_SYS_DESC_TABLE_FULL_ERROR``
+ The system's table for holding open file descriptors has been
+ exceeded.
+``PR_NOT_SOCKET_ERROR``
+ An attempt to use a non-network file descriptor on a network-only
+ operation.
+``PR_NOT_TCP_SOCKET_ERROR``
+ Attempt to perform a TCP specific function on a non-TCP file
+ descriptor.
+``PR_SOCKET_ADDRESS_IS_BOUND_ERRO``
+ Attempt to bind an address to a TCP file descriptor that is already
+ bound.
+``PR_NO_ACCESS_RIGHTS_ERROR``
+ Calling thread does not have privilege to perform the operation
+ requested.
+``PR_OPERATION_NOT_SUPPORTED_ERRO``
+ The requested operation is not supported by the platform.
+``PR_PROTOCOL_NOT_SUPPORTED_ERROR``
+ The host operating system does not support the protocol requested.
+``PR_REMOTE_FILE_ERROR``
+ Access to the remote file has been severed.
+``PR_BUFFER_OVERFLOW_ERROR``
+ The value retrieved is too large to be stored in the buffer provided.
+``PR_CONNECT_RESET_ERROR``
+ The (TCP) connection has been reset by the peer.
+``PR_RANGE_ERROR``
+ Unused.
+``PR_DEADLOCK_ERROR``
+ Performing the requested operation would have caused a deadlock. The
+ deadlock was avoided.
+``PR_FILE_IS_LOCKED_ERROR``
+ An attempt to acquire a lock on a file has failed because the file is
+ already locked.
+``PR_FILE_TOO_BIG_ERROR``
+ Completing the write or seek operation would have resulted in a file
+ larger than the system could handle.
+``PR_NO_DEVICE_SPACE_ERROR``
+ The device for storing the file is full.
+``PR_PIPE_ERROR``
+ Unused.
+``PR_NO_SEEK_DEVICE_ERROR``
+ Unused.
+``PR_IS_DIRECTORY_ERROR``
+ Attempt to perform a normal file operation on a directory.
+``PR_LOOP_ERROR``
+ Symbolic link loop.
+``PR_NAME_TOO_LONG_ERROR``
+ Filename is longer than allowed by the host operating system.
+``PR_FILE_NOT_FOUND_ERROR``
+ The requested file was not found.
+``PR_NOT_DIRECTORY_ERROR``
+ Attempt to perform directory specific operations on a normal file.
+``PR_READ_ONLY_FILESYSTEM_ERROR``
+ Attempt to write to a read-only file system.
+``PR_DIRECTORY_NOT_EMPTY_ERROR``
+ Attempt to delete a directory that is not empty.
+``PR_FILESYSTEM_MOUNTED_ERROR``
+ Attempt to delete or rename a file object while the file system is
+ busy.
+``PR_NOT_SAME_DEVICE_ERROR``
+ Request to rename a file to a file system on another device.
+``PR_DIRECTORY_CORRUPTED_ERROR``
+ The directory object in the file system is corrupted.
+``PR_FILE_EXISTS_ERROR``
+ Attempt to create or rename a file when the new name is already being
+ used.
+``PR_MAX_DIRECTORY_ENTRIES_ERROR``
+ Attempt to add new filename to directory would exceed the limit
+ allowed.
+``PR_INVALID_DEVICE_STATE_ERROR``
+ The device was in an invalid state to complete the desired operation.
+``PR_DEVICE_IS_LOCKED_ERROR``
+ The device needed to perform the desired request is locked.
+``PR_NO_MORE_FILES_ERROR``
+ There are no more entries in the directory.
+``PR_END_OF_FILE_ERROR``
+ Unexpectedly encountered end of file (Mac OS only).
+``PR_FILE_SEEK_ERROR``
+ An unexpected seek error (Mac OS only).
+``PR_FILE_IS_BUSY_ERROR``
+ The file is busy and the operation cannot be performed.
+``PR_IN_PROGRESS_ERROR``
+ The operation is still in progress (probably a nonblocking connect).
+``PR_ALREADY_INITIATED_ERROR``
+ The (retried) operation has already been initiated (probably a
+ nonblocking connect).
+``PR_GROUP_EMPTY_ERROR``
+ The wait group is empty.
+``PR_INVALID_STATE_ERROR``
+ The attempted operation is on an object that was in an improper state
+ to perform the request.
+``PR_MAX_ERROR``
+ Placeholder for the end of the list.
diff --git a/docs/nspr/reference/nspr_log_file.rst b/docs/nspr/reference/nspr_log_file.rst
new file mode 100644
index 0000000000..0208d4bac4
--- /dev/null
+++ b/docs/nspr/reference/nspr_log_file.rst
@@ -0,0 +1,31 @@
+NSPR_LOG_FILE
+=============
+
+This environment variable specifies the file to which log messages are
+directed.
+
+
+Syntax
+------
+
+::
+
+ filespec
+
+*filespec* is a filename. The exact syntax is platform specific.
+
+
+Description
+-----------
+
+Use this environment variable to specify a log file other than the
+default. If :ref:`NSPR_LOG_FILE` is not in the environment, then log output
+is written to ``stdout`` or ``stderr``, depending on the platform. Set
+:ref:`NSPR_LOG_FILE` to the name of the log file you want to use. NSPR
+logging, when enabled, writes to the file named in this environment
+variable.
+
+For MS Windows systems, you can set :ref:`NSPR_LOG_FILE` to the special
+(case-sensitive) value ``WinDebug``. This value causes logging output to
+be written using the Windows function ``OutputDebugString()``, which
+writes to the debugger window.
diff --git a/docs/nspr/reference/nspr_log_modules.rst b/docs/nspr/reference/nspr_log_modules.rst
new file mode 100644
index 0000000000..9f2986607f
--- /dev/null
+++ b/docs/nspr/reference/nspr_log_modules.rst
@@ -0,0 +1,88 @@
+NSPR_LOG_MODULES
+================
+
+This environment variable specifies which log modules have logging
+enabled.
+
+
+Syntax
+------
+
+::
+
+ moduleName:level[, moduleName:level]*
+
+*moduleName* is the name specified in a
+`:ref:`PR_NewLogModule` <http://www-archive.mozilla.org/projects/nspr/reference/html/prlog.html#25372>`__
+call or one of the handy magic names listed below.
+
+*level* is a numeric value between 0 and 5, with the values having the
+following meanings:
+
+- 0 = PR_LOG_NONE: nothing should be logged
+- 1 = PR_LOG_ALWAYS: important; intended to always be logged
+- 2 = PR_LOG_ERROR: errors
+- 3 = PR_LOG_WARNING: warnings
+- 4 = PR_LOG_DEBUG: debug messages, notices
+- 5: everything!
+
+
+Description
+-----------
+
+Specify a ``moduleName`` that is associated with the ``name`` argument
+in a call to
+`:ref:`PR_NewLogModule` <http://www-archive.mozilla.org/projects/nspr/reference/html/prlog.html#25372>`__
+and a non-zero ``level`` value to enable logging for the named
+``moduleName``.
+
+Special log module names are provided for controlling NSPR's log service
+at execution time. These controls should be set in the
+:ref:`NSPR_LOG_MODULES` environment variable at execution time to affect
+NSPR's log service for your application.
+
+- **all** The name ``all`` enables all log modules. To enable all log
+ module calls to
+ ```PR_LOG`` <http://www-archive.mozilla.org/projects/nspr/reference/html/prlog.html#25497>`__,
+ set the variable as follows:
+
+ ::
+
+ set NSPR_LOG_MODULES=all:5
+
+- **timestamp** Including ``timestamp`` results in a timestamp of the
+ form "2015-01-15 21:24:26.049906 UTC - " prefixing every logged line.
+
+- **append** Including ``append`` results in log entries being appended
+ to the existing contents of the file referenced by NSPR_LOG_FILE. If
+ not specified, the existing contents of NSPR_LOG_FILE will be lost as
+ a new file is created with the same filename.
+
+- **sync** The name ``sync`` enables unbuffered logging. This ensures
+ that all log messages are flushed to the operating system as they are
+ written, but may slow the program down.
+
+- **bufsize:size** The name ``bufsize:``\ *size* sets the log buffer to
+ *size*.
+
+Examples
+--------
+
+Log everything from the Toolkit::Storage component that happens,
+prefixing each line with the timestamp when it was logged to the file
+/tmp/foo.log (which will be replaced each time the executable is run).
+
+::
+
+ set NSPR_LOG_MODULES=timestamp,mozStorage:5
+ set NSPR_LOG_FILE=/tmp/foo.log
+
+.. _Logging_with_Try_Server:
+
+Logging with Try Server
+-----------------------
+
+- For **mochitest**, edit variable :ref:`NSPR_LOG_MODULES` in
+ ``testing/mochitest/runtests.py`` before pushing to try. You would be
+ able to download the log file as an artifact from the Log viewer.
+- (other tests?)
diff --git a/docs/nspr/reference/nspr_types.rst b/docs/nspr/reference/nspr_types.rst
new file mode 100644
index 0000000000..6e304f4eb5
--- /dev/null
+++ b/docs/nspr/reference/nspr_types.rst
@@ -0,0 +1,180 @@
+This chapter describes the most common NSPR types. Other chapters
+describe more specialized types when describing the functions that use
+them.
+
+- `Calling Convention Types <#Calling_Convention_Types>`__ are used for
+ externally visible functions and globals.
+- `Algebraic Types <#Algebraic_Types>`__ of various lengths are used
+ for integer algebra.
+- `Miscellaneous Types <#Miscellaneous_Types>`__ are used for
+ representing size, pointer difference, Boolean values, and return
+ values.
+
+For information on naming conventions for NSPR types, functions, and
+macros, see `NSPR Naming
+Conventions <Introduction_to_NSPR#NSPR_Naming_Conventions>`__.
+
+.. _Calling_Convention_Types:
+
+Calling Convention Types
+------------------------
+
+These types are used to support cross-platform declarations of
+prototypes and implementations:
+
+ - :ref:`PR_EXTERN` is used for declarations of external functions or
+ variables.
+ - :ref:`PR_IMPLEMENT` is used for definitions of external functions or
+ variables.
+ - :ref:`PR_CALLBACK` is used for definitions and declarations of functions
+ that are called via function pointers. A typical example is a
+ function implemented in an application but called from a shared
+ library.
+
+Here are some simple examples of the use of these types:
+
+.. container:: highlight
+
+ In dowhim.h:
+
+ .. code::
+
+ PR_EXTERN( void ) DoWhatIMean( void );
+
+ static void PR_CALLBACK RootFunction(void *arg);
+
+.. container:: highlight
+
+ In dowhim.c:
+
+ .. code::
+
+ PR_IMPLEMENT( void ) DoWhatIMean( void ) { return; };
+
+ PRThread *thread = PR_CreateThread(..., RootFunction, ...);
+
+.. _Algebraic_Types:
+
+Algebraic Types
+---------------
+
+NSPR provides the following type definitions with unambiguous bit widths
+for algebraic operations:
+
+- `8-, 16-, and 32-bit Integer
+ Types <#8-,_16-,_and_32-bit_Integer_Types>`__
+- `64-bit Integer Types <#64-bit_Integer_Types>`__
+- `Floating-Point Number Type <#Floating-Point_Number_Type>`__
+
+For convenience, NSPR also provides type definitions with
+platform-dependent bit widths:
+
+- `Native OS Integer Types <#Native_OS_Integer_Types>`__
+
+.. _8-.2C_16-.2C_and_32-bit_Integer_Types:
+
+8-, 16-, and 32-bit Integer Types
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. _Signed_Integers:
+
+Signed Integers
+^^^^^^^^^^^^^^^
+
+ - :ref:`PRInt8`
+ - :ref:`PRInt16`
+ - :ref:`PRInt32`
+
+.. _Unsigned_Integers:
+
+Unsigned Integers
+^^^^^^^^^^^^^^^^^
+
+ - :ref:`PRUint8`
+ - :ref:`PRUint16`
+ - :ref:`PRUint32`
+
+.. _64-bit_Integer_Types:
+
+64-bit Integer Types
+~~~~~~~~~~~~~~~~~~~~
+
+Different platforms treat 64-bit numeric fields in different ways. Some
+systems require emulation of 64-bit fields by using two 32-bit numeric
+fields bound in a structure. Since the types (``long long`` versus
+``struct LONGLONG``) are not type compatible, NSPR defines macros to
+manipulate 64-bit numeric fields. These macros are defined in
+``prlong.h``. Conscientious use of these macros ensures portability of
+code to all the platforms supported by NSPR and still provides optimal
+behavior on those systems that treat long long values directly.
+
+ - :ref:`PRInt64`
+ - :ref:`PRUint64`
+
+.. _Floating-Point_Number_Type:
+
+Floating-Point Number Type
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The NSPR floating-point type is always 64 bits.
+
+ - :ref:`PRFloat64`
+
+.. _Native_OS_Integer_Types:
+
+Native OS Integer Types
+~~~~~~~~~~~~~~~~~~~~~~~
+
+These types are most appropriate for automatic variables. They are
+guaranteed to be at least 16 bits, though various architectures may
+define them to be wider (for example, 32 or even 64 bits). These types
+are never valid for fields of a structure.
+
+ - :ref:`PRIntn`
+ - :ref:`PRUintn`
+
+.. _Miscellaneous_Types:
+
+Miscellaneous Types
+-------------------
+
+- `Size Type <#Size_Type>`__
+- `Pointer Difference Types <#Pointer_Difference_Types>`__
+- `Boolean Types <#Boolean_Types>`__
+- `Status Type for Return Values <#Status_Type_for_Return_Values>`__
+
+.. _Size_Type:
+
+Size Type
+~~~~~~~~~
+
+ - :ref:`PRSize`
+
+.. _Pointer_Difference_Types:
+
+Pointer Difference Types
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Types for pointer difference. Variables of these types are suitable for
+storing a pointer or pointer subtraction. These are the same as the
+corresponding types in ``libc``.
+
+ - :ref:`PRPtrdiff`
+ - :ref:`PRUptrdiff`
+
+.. _Boolean_Types:
+
+Boolean Types
+~~~~~~~~~~~~~
+
+Type and constants for Boolean values.
+
+ - :ref:`PRBool`
+ - :ref:`PRPackedBool`
+
+.. _Status_Type_for_Return_Values:
+
+Status Type for Return Values
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ - :ref:`PRStatus`
diff --git a/docs/nspr/reference/pl_comparestrings.rst b/docs/nspr/reference/pl_comparestrings.rst
new file mode 100644
index 0000000000..46050c74b2
--- /dev/null
+++ b/docs/nspr/reference/pl_comparestrings.rst
@@ -0,0 +1,27 @@
+PL_CompareStrings
+=================
+
+Compares two character strings.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <plhash.h>
+
+ PRIntn PL_CompareStrings(
+ const void *v1,
+ const void *v2);
+
+
+Description
+-----------
+
+:ref:`PL_CompareStrings` compares ``v1`` and ``v2`` as character strings
+using ``strcmp``. If the two strings are equal, it returns 1. If the two
+strings are not equal, it returns 0.
+
+:ref:`PL_CompareStrings` can be used as the comparator function for
+string-valued key or entry value.
diff --git a/docs/nspr/reference/pl_comparevalues.rst b/docs/nspr/reference/pl_comparevalues.rst
new file mode 100644
index 0000000000..9fef0b63f3
--- /dev/null
+++ b/docs/nspr/reference/pl_comparevalues.rst
@@ -0,0 +1,27 @@
+PL_CompareValues
+================
+
+Compares two ``void *`` values numerically.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <plhash.h>
+
+ PRIntn PL_CompareValues(const
+ void *v1,
+ const void *v2);
+
+
+Description
+-----------
+
+:ref:`PL_CompareValues` compares the two ``void *`` values ``v1`` and
+``v2`` numerically, i.e., it returns the value of the expression ``v1``
+== ``v2``.
+
+:ref:`PL_CompareValues` can be used as the comparator function for integer
+or pointer-valued key or entry value.
diff --git a/docs/nspr/reference/pl_hashstring.rst b/docs/nspr/reference/pl_hashstring.rst
new file mode 100644
index 0000000000..3c6b8cde5f
--- /dev/null
+++ b/docs/nspr/reference/pl_hashstring.rst
@@ -0,0 +1,36 @@
+PL_HashString
+=============
+
+A general-purpose hash function for character strings.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <plhash.h>
+
+ PLHashNumber PL_HashString(const void *key);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``key``
+ A pointer to a character string.
+
+
+Returns
+~~~~~~~
+
+The hash number for the specified key.
+
+
+Description
+-----------
+
+:ref:`PL_HashString` can be used as the key hash function for a hash table
+if the key is a character string.
diff --git a/docs/nspr/reference/pl_hashtableadd.rst b/docs/nspr/reference/pl_hashtableadd.rst
new file mode 100644
index 0000000000..a4827d5c05
--- /dev/null
+++ b/docs/nspr/reference/pl_hashtableadd.rst
@@ -0,0 +1,52 @@
+PL_HashTableAdd
+===============
+
+Add a new entry with the specified key and value to the hash table.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <plhash.h>
+
+ PLHashEntry *PL_HashTableAdd(
+ PLHashTable *ht,
+ const void *key,
+ void *value);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``ht``
+ A pointer to the the hash table to which to add the entry.
+``key``
+ A pointer to the key for the entry to be added.
+``value``
+ A pointer to the value for the entry to be added.
+
+
+Returns
+~~~~~~~
+
+A pointer to the new entry.
+
+
+Description
+-----------
+
+Add a new entry with the specified key and value to the hash table.
+
+If an entry with the same key already exists in the table, the
+``freeEntry`` function is invoked with the ``HT_FREE_VALUE`` flag. You
+can write your ``freeEntry`` function to free the value of the specified
+entry if the old value should be freed. The default ``freeEntry``
+function does not free the value of the entry.
+
+:ref:`PL_HashTableAdd` returns ``NULL`` if there is not enough memory to
+create a new entry. It doubles the number of buckets if the table is
+overloaded.
diff --git a/docs/nspr/reference/pl_hashtabledestroy.rst b/docs/nspr/reference/pl_hashtabledestroy.rst
new file mode 100644
index 0000000000..9b45e5a609
--- /dev/null
+++ b/docs/nspr/reference/pl_hashtabledestroy.rst
@@ -0,0 +1,32 @@
+PL_HashTableDestroy
+===================
+
+Frees the table and all the entries.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <plhash.h>
+
+ void PL_HashTableDestroy(PLHashTable *ht);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``ht``
+ A pointer to the hash table to be destroyed.
+
+
+Description
+-----------
+
+:ref:`PL_HashTableDestroy` frees all the entries in the table and the table
+itself. The entries are freed by the ``freeEntry`` function (with the
+``HT_FREE_ENTRY`` flag) in the ``allocOps`` structure supplied when the
+table was created.
diff --git a/docs/nspr/reference/pl_hashtableenumerateentries.rst b/docs/nspr/reference/pl_hashtableenumerateentries.rst
new file mode 100644
index 0000000000..ce77c8aad7
--- /dev/null
+++ b/docs/nspr/reference/pl_hashtableenumerateentries.rst
@@ -0,0 +1,46 @@
+PL_HashTableEnumerateEntries
+============================
+
+Enumerates all the entries in the hash table, invoking a specified
+function on each entry.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <plhash.h>
+
+ PRIntn PL_HashTableEnumerateEntries(
+ PLHashTable *ht,
+ PLHashEnumerator f,
+ void *arg);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``ht``
+ A pointer to the hash table whose entries are to be enumerated.
+``f``
+ Function to be applied to each entry.
+``arg``
+ Argument for function ``f``.
+
+
+Returns
+~~~~~~~
+
+The number of entries enumerated.
+
+
+Description
+-----------
+
+The entries are enumerated in an unspecified order. For each entry, the
+enumerator function is invoked with the entry, the index (in the
+sequence of enumeration, starting from 0) of the entry, and arg as
+arguments.
diff --git a/docs/nspr/reference/pl_hashtablelookup.rst b/docs/nspr/reference/pl_hashtablelookup.rst
new file mode 100644
index 0000000000..236c08a874
--- /dev/null
+++ b/docs/nspr/reference/pl_hashtablelookup.rst
@@ -0,0 +1,45 @@
+PL_HashTableLookup
+==================
+
+Looks up the entry with the specified key and return its value.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <plhash.h>
+
+ void *PL_HashTableLookup(
+ PLHashTable *ht,
+ const void *key);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``ht``
+ A pointer to the hash table in which to look up the entry specified
+ by ``key``.
+``key``
+ A pointer to the key for the entry to look up.
+
+
+Returns
+~~~~~~~
+
+The value of the entry with the specified key, or ``NULL`` if there is
+no such entry.
+
+
+Description
+-----------
+
+If there is no entry with the specified key, :ref:`PL_HashTableLookup`
+returns ``NULL``. This means that one cannot tell whether a ``NULL``
+return value means the entry does not exist or the value of the entry is
+``NULL``. Keep this ambiguity in mind if you want to store ``NULL``
+values in a hash table.
diff --git a/docs/nspr/reference/pl_hashtableremove.rst b/docs/nspr/reference/pl_hashtableremove.rst
new file mode 100644
index 0000000000..32c3662f63
--- /dev/null
+++ b/docs/nspr/reference/pl_hashtableremove.rst
@@ -0,0 +1,46 @@
+PL_HashTableRemove
+==================
+
+Removes the entry with the specified key from the hash table.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <plhash.h>
+
+ PRBool PL_HashTableRemove(
+ PLHashTable *ht,
+ const void *key);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``ht``
+ A pointer to the hash table from which to remove the entry.
+``key``
+ A pointer to the key for the entry to be removed.
+
+
+Description
+-----------
+
+If there is no entry in the table with the specified key,
+:ref:`PL_HashTableRemove` returns ``PR_FALSE``. If the entry exists,
+:ref:`PL_HashTableRemove` removes the entry from the table, invokes
+``freeEntry`` with the ``HT_FREE_ENTRY`` flag to frees the entry, and
+returns ``PR_TRUE``.
+
+If the table is underloaded, :ref:`PL_HashTableRemove` also shrinks the
+number of buckets by half.
+
+
+Remark
+------
+
+This function should return :ref:`PRStatus`.
diff --git a/docs/nspr/reference/pl_newhashtable.rst b/docs/nspr/reference/pl_newhashtable.rst
new file mode 100644
index 0000000000..d4479284c5
--- /dev/null
+++ b/docs/nspr/reference/pl_newhashtable.rst
@@ -0,0 +1,67 @@
+PL_NewHashTable
+===============
+
+Create a new hash table.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <plhash.h>
+
+ PLHashTable *PL_NewHashTable(
+ PRUint32 numBuckets,
+ PLHashFunction keyHash,
+ PLHashComparator keyCompare,
+ PLHashComparator valueCompare,
+ const PLHashAllocOps *allocOps,
+ void *allocPriv
+ );
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``numBuckets``
+ The number of buckets in the hash table.
+``keyHash``
+ Hash function.
+``keyCompare``
+ Function used to compare keys of entries.
+``valueCompare``
+ Function used to compare keys of entries.
+``allocOps``
+ A pointer to a ``PLHashAllocOps`` structure that must exist
+ throughout the lifetime of the new hash table.
+``allocPriv``
+ Passed as the first argument (pool).
+
+
+Returns
+~~~~~~~
+
+The new hash table.
+
+
+Description
+-----------
+
+:ref:`PL_NewHashTable` creates a new hash table. The table has at least 16
+buckets. You can pass a value of 0 as ``numBuckets`` to create the
+default number of buckets in the new table. The arguments ``keyCompare``
+and ``valueCompare`` are functions of type :ref:`PLHashComparator` that the
+hash table library functions use to compare the keys and the values of
+entries.
+
+The argument ``allocOps`` points to a ``PLHashAllocOps`` structure that
+must exist throughout the lifetime of the new hash table. The hash table
+library functions do not make a copy of this structure. When the
+allocation functions in ``allocOps`` are invoked, the allocation private
+data allocPriv is passed as the first argument (pool). You can specify a
+``NULL`` value for ``allocOps`` to use the default allocation functions.
+If ``allocOps`` is ``NULL``, ``allocPriv`` is ignored. Note that the
+default ``freeEntry`` function does not free the value of the entry.
diff --git a/docs/nspr/reference/pl_strcpy.rst b/docs/nspr/reference/pl_strcpy.rst
new file mode 100644
index 0000000000..addd05b824
--- /dev/null
+++ b/docs/nspr/reference/pl_strcpy.rst
@@ -0,0 +1,40 @@
+PL_strcpy
+=========
+
+
+Copies a string, up to and including the trailing ``'\0'``, into a
+destination buffer.
+
+
+Syntax
+~~~~~~
+
+.. code::
+
+ char * PL_strcpy(char *dest, const char *src);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has these parameters:
+
+``dest``
+ Pointer to a buffer. On output, the buffer contains a copy of the
+ string passed in src.
+``src``
+ Pointer to the string to be copied.
+
+
+Returns
+~~~~~~~
+
+The function returns a pointer to the buffer specified by the ``dest``
+parameter.
+
+
+Description
+~~~~~~~~~~~
+
+If the string specified by ``src`` is longer than the buffer specified
+by ``dest``, the buffer will not be null-terminated.
diff --git a/docs/nspr/reference/pl_strdup.rst b/docs/nspr/reference/pl_strdup.rst
new file mode 100644
index 0000000000..fcced90ec4
--- /dev/null
+++ b/docs/nspr/reference/pl_strdup.rst
@@ -0,0 +1,48 @@
+PL_strdup
+=========
+
+Returns a pointer to a new memory node in the NSPR heap containing a
+copy of a specified string.
+
+
+Syntax
+~~~~~~
+
+.. code::
+
+ #include <plstr.h>
+
+ char *PL_strdup(const char *s);
+
+
+Parameter
+~~~~~~~~~
+
+The function has a single parameter:
+
+``s``
+ The string to copy, may be ``NULL``.
+
+
+Returns
+~~~~~~~
+
+The function returns one of these values:
+
+- If successful, a pointer to a copy of the specified string.
+- If the memory allocation fails, ``NULL``.
+
+
+Description
+~~~~~~~~~~~
+
+To accommodate the terminator, the size of the allocated memory is one
+greater than the length of the string being copied. A ``NULL`` argument,
+like a zero-length argument, results in a pointer to a one-byte block of
+memory containing the null value.
+
+Notes
+~~~~~
+
+The memory allocated by :ref:`PL_strdup` should be freed with
+`PL_strfree </en/PL_strfree>`__.
diff --git a/docs/nspr/reference/pl_strfree.rst b/docs/nspr/reference/pl_strfree.rst
new file mode 100644
index 0000000000..e17c0c34cf
--- /dev/null
+++ b/docs/nspr/reference/pl_strfree.rst
@@ -0,0 +1,21 @@
+PL_strfree
+==========
+
+Frees memory allocated by :ref:`PL_strdup`
+
+
+Syntax
+~~~~~~
+
+.. code::
+
+ void PL_strfree(char *s);
+
+
+Parameter
+~~~~~~~~~
+
+The function has these parameter:
+
+``s``
+ Pointer to the string to be freed.
diff --git a/docs/nspr/reference/pl_strlen.rst b/docs/nspr/reference/pl_strlen.rst
new file mode 100644
index 0000000000..edd1ecd129
--- /dev/null
+++ b/docs/nspr/reference/pl_strlen.rst
@@ -0,0 +1,28 @@
+PL_strlen
+=========
+
+Returns the length of a specified string (not including the trailing
+``'\0'``)
+
+
+Syntax
+~~~~~~
+
+.. code::
+
+ PRUint32 PL_strlen(const char *str);
+
+
+Parameter
+~~~~~~~~~
+
+The function has these parameter:
+
+``str``
+ Size in bytes of item to be allocated.
+
+
+Returns
+~~~~~~~
+
+If successful, the function returns length of the specified string.
diff --git a/docs/nspr/reference/plhashallocops.rst b/docs/nspr/reference/plhashallocops.rst
new file mode 100644
index 0000000000..02cceb9c2b
--- /dev/null
+++ b/docs/nspr/reference/plhashallocops.rst
@@ -0,0 +1,40 @@
+
+Syntax
+------
+
+.. code::
+
+ #include <plhash.h>
+
+ typedef struct PLHashAllocOps {
+ void *(PR_CALLBACK *allocTable)(void *pool, PRSize size);
+ void (PR_CALLBACK *freeTable)(void *pool, void *item);
+ PLHashEntry *(PR_CALLBACK *allocEntry)(void *pool, const void *key);
+ void (PR_CALLBACK *freeEntry)(void *pool, PLHashEntry *he, PRUintn flag);
+ } PLHashAllocOps;
+
+ #define HT_FREE_VALUE 0 /* just free the entry's value */
+ #define HT_FREE_ENTRY 1 /* free value and entire entry */
+
+
+Description
+-----------
+
+Users of the hash table functions can provide their own memory
+allocation functions. A pair of functions is used to allocate and tree
+the table, and another pair of functions is used to allocate and free
+the table entries.
+
+The first argument, pool, for all four functions is a void \* pointer
+that is a piece of data for the memory allocator. Typically pool points
+to a memory pool used by the memory allocator.
+
+The ``freeEntry`` function does not need to free the value of the entry.
+If flag is ``HT_FREE_ENTRY``, the function frees the entry.
+
+
+Remark
+------
+
+The ``key`` argument for the ``allocEntry`` function does not seem to be
+useful. It is unused in the default ``allocEntry`` function.
diff --git a/docs/nspr/reference/plhashcomparator.rst b/docs/nspr/reference/plhashcomparator.rst
new file mode 100644
index 0000000000..1310a6f37d
--- /dev/null
+++ b/docs/nspr/reference/plhashcomparator.rst
@@ -0,0 +1,41 @@
+PLHashComparator
+================
+
+
+Syntax
+------
+
+.. code::
+
+ #include <plhash.h>
+
+ typedef PRIntn (PR_CALLBACK *PLHashComparator)(
+ const void *v1,
+ const void *v2);
+
+
+Description
+-----------
+
+:ref:`PLHashComparator` is a function type that compares two values of an
+unspecified type. It returns a nonzero value if the two values are
+equal, and 0 if the two values are not equal. :ref:`PLHashComparator`
+defines the meaning of equality for the unspecified type.
+
+For convenience, two comparator functions are provided.
+:ref:`PL_CompareStrings` compare two character strings using ``strcmp``.
+:ref:`PL_CompareValues` compares the values of the arguments v1 and v2
+numerically.
+
+
+Remark
+------
+
+The return value of :ref:`PLHashComparator` functions should be of type
+:ref:`PRBool`.
+
+
+See Also
+--------
+
+:ref:`PL_CompareStrings`, :ref:`PL_CompareValues`
diff --git a/docs/nspr/reference/plhashentry.rst b/docs/nspr/reference/plhashentry.rst
new file mode 100644
index 0000000000..ea11ac3439
--- /dev/null
+++ b/docs/nspr/reference/plhashentry.rst
@@ -0,0 +1,36 @@
+
+Syntax
+------
+
+.. code::
+
+ #include <plhash.h>
+
+ typedef struct PLHashEntry PLHashEntry;
+
+
+Description
+-----------
+
+``PLHashEntry`` is a structure that represents an entry in the hash
+table. An entry has a key and a value, represented by the following
+fields in the ``PLHashEntry`` structure.
+
+.. code::
+
+ const void *key;
+ void *value;
+
+The key field is a pointer to an opaque key. The value field is a
+pointer to an opaque value. If the key of an entry is an integral value
+that can fit into a ``void *`` pointer, you can just cast the key itself
+to ``void *`` and store it in the key field. Similarly, if the value of
+an entry is an integral value that can fit into a ``void *`` pointer,
+you can cast the value itself to ``void *`` and store it in the value
+field.
+
+.. warning::
+
+ **Warning**: There are other fields in the ``PLHashEntry`` structure
+ besides key and value. These fields are for use by the hash table
+ library functions and the user should not tamper with them.
diff --git a/docs/nspr/reference/plhashenumerator.rst b/docs/nspr/reference/plhashenumerator.rst
new file mode 100644
index 0000000000..eba3635c5e
--- /dev/null
+++ b/docs/nspr/reference/plhashenumerator.rst
@@ -0,0 +1,41 @@
+
+Syntax
+------
+
+.. code::
+
+ #include <plhash.h>
+
+ typedef PRIntn (PR_CALLBACK *PLHashEnumerator)(PLHashEntry *he, PRIntn index, void *arg);
+
+ /* Return value */
+ #define HT_ENUMERATE_NEXT 0 /* continue enumerating entries */
+ #define HT_ENUMERATE_STOP 1 /* stop enumerating entries */
+ #define HT_ENUMERATE_REMOVE 2 /* remove and free the current entry */
+ #define HT_ENUMERATE_UNHASH 4 /* just unhash the current entry */
+
+
+Description
+-----------
+
+``PLHashEnumerator`` is a function type used in the enumerating a hash
+table. When all the table entries are enumerated, each entry is passed
+to a user-specified function of type ``PLHashEnumerator`` with the hash
+table entry, an integer index, and an arbitrary piece of user data as
+argument.
+
+
+Remark
+------
+
+The meaning of ``HT_ENUMERATE_UNHASH`` is not clear. In the current
+implementation, it will leave the hash table in an inconsistent state.
+The entries are unlinked from the table, they are not freed, but the
+entry count (the ``nentries`` field of the ``PLHashTable`` structure) is
+not decremented.
+
+
+See Also
+--------
+
+:ref:`PL_HashTableEnumerateEntries`
diff --git a/docs/nspr/reference/plhashfunction.rst b/docs/nspr/reference/plhashfunction.rst
new file mode 100644
index 0000000000..e1767e7e94
--- /dev/null
+++ b/docs/nspr/reference/plhashfunction.rst
@@ -0,0 +1,22 @@
+
+Syntax
+------
+
+.. code::
+
+ #include <plhash.h>
+
+ typedef PLHashNumber (PR_CALLBACK *PLHashFunction)(const void *key);
+
+
+Description
+-----------
+
+``PLHashNumber`` is a function type that maps the key of a hash table
+entry to a hash number.
+
+
+See Also
+--------
+
+`PL_HashString <PL_HashString>`__
diff --git a/docs/nspr/reference/plhashnumber.rst b/docs/nspr/reference/plhashnumber.rst
new file mode 100644
index 0000000000..a84c147aa9
--- /dev/null
+++ b/docs/nspr/reference/plhashnumber.rst
@@ -0,0 +1,29 @@
+
+Syntax
+------
+
+.. code::
+
+ #include <plhash.h>
+
+ typedef PRUint32 PLHashNumber;
+
+ #define PL_HASH_BITS 32
+
+
+Description
+-----------
+
+``PLHashNumber`` is an unsigned 32-bit integer. ``PLHashNumber`` is the
+data type of the return value of a hash function. A hash function maps a
+key to a hash number, which is then used to compute the index of the
+bucket.
+
+The macro ``PL_HASH_BITS`` is the size (in bits) of the ``PLHashNumber``
+data type and has the value of 32.
+
+
+See Also
+--------
+
+``PLHashFunction``
diff --git a/docs/nspr/reference/plhashtable.rst b/docs/nspr/reference/plhashtable.rst
new file mode 100644
index 0000000000..70bad3aebe
--- /dev/null
+++ b/docs/nspr/reference/plhashtable.rst
@@ -0,0 +1,21 @@
+
+Syntax
+------
+
+.. code::
+
+ #include <plhash.h>
+
+ typedef struct PLHashTable PLHashTable;
+
+
+Description
+-----------
+
+The opaque ``PLHashTable`` structure represents a hash table. Entries in
+the table have the type ``PLHashEntry`` and are organized into buckets.
+The number of buckets in a hash table may be changed by the library
+functions during the lifetime of the table to optimize speed and space.
+
+A new hash table is created by the :ref:`PL_NewHashTable` function, and
+destroyed by the :ref:`PL_HashTableDestroy` function.
diff --git a/docs/nspr/reference/pr_abort.rst b/docs/nspr/reference/pr_abort.rst
new file mode 100644
index 0000000000..5c14ea2e2e
--- /dev/null
+++ b/docs/nspr/reference/pr_abort.rst
@@ -0,0 +1,21 @@
+PR_Abort
+========
+
+Aborts the process in a nongraceful manner.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prinit.h>
+
+ void PR_Abort(void);
+
+
+Description
+-----------
+
+:ref:`PR_Abort` results in a core file and a call to the debugger or
+equivalent, in addition to causing the entire process to stop.
diff --git a/docs/nspr/reference/pr_accept.rst b/docs/nspr/reference/pr_accept.rst
new file mode 100644
index 0000000000..eb1b1ad3ed
--- /dev/null
+++ b/docs/nspr/reference/pr_accept.rst
@@ -0,0 +1,65 @@
+PR_Accept
+=========
+
+Accepts a connection on a specified socket.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRFileDesc* PR_Accept(
+ PRFileDesc *fd,
+ PRNetAddr *addr,
+ PRIntervalTime timeout);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``fd``
+ A pointer to a :ref:`PRFileDesc` object representing the rendezvous
+ socket on which the caller is willing to accept new connections.
+``addr``
+ A pointer to a structure of type :ref:`PRNetAddr`. On output, this
+ structure contains the address of the connecting entity.
+``timeout``
+ A value of type :ref:`PRIntervalTime` specifying the time limit for
+ completion of the accept operation.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- Upon successful acceptance of a connection, a pointer to a new
+ :ref:`PRFileDesc` structure representing the newly accepted connection.
+- If unsuccessful, ``NULL``. Further information can be obtained by
+ calling :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+The socket ``fd`` is a rendezvous socket that has been bound to an
+address with :ref:`PR_Bind` and is listening for connections after a call
+to :ref:`PR_Listen`. :ref:`PR_Accept` accepts the first connection from the
+queue of pending connections and creates a new socket for the newly
+accepted connection. The rendezvous socket can still be used to accept
+more connections.
+
+If the ``addr`` parameter is not ``NULL``, :ref:`PR_Accept` stores the
+address of the connecting entity in the :ref:`PRNetAddr` object pointed to
+by ``addr``.
+
+:ref:`PR_Accept` blocks the calling thread until either a new connection is
+successfully accepted or an error occurs. If the timeout parameter is
+not ``PR_INTERVAL_NO_TIMEOUT`` and no pending connection can be accepted
+before the time limit, :ref:`PR_Accept` returns ``NULL`` with the error
+code ``PR_IO_TIMEOUT_ERROR``.
diff --git a/docs/nspr/reference/pr_acceptread.rst b/docs/nspr/reference/pr_acceptread.rst
new file mode 100644
index 0000000000..d0e8fca61b
--- /dev/null
+++ b/docs/nspr/reference/pr_acceptread.rst
@@ -0,0 +1,73 @@
+PR_AcceptRead
+=============
+
+Accepts a new connection and receives a block of data.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRInt32 PR_AcceptRead(
+ PRFileDesc *listenSock,
+ PRFileDesc **acceptedSock,
+ PRNetAddr **peerAddr,
+ void *buf,
+ PRInt32 amount,
+ PRIntervalTime timeout);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``listenSock``
+ A pointer to a :ref:`PRFileDesc` object representing a socket descriptor
+ that has been called with the :ref:`PR_Listen` function, also known as
+ the rendezvous socket.
+``acceptedSock``
+ A pointer to a pointer to a :ref:`PRFileDesc` object. On return,
+ ``*acceptedSock`` points to the :ref:`PRFileDesc` object for the newly
+ connected socket. This parameter is valid only if the function return
+ does not indicate failure.
+``peerAddr``
+ A pointer a pointer to a :ref:`PRNetAddr` object. On return,
+ ``peerAddr`` points to the address of the remote socket. The
+ :ref:`PRNetAddr` object that ``peerAddr`` points to will be in the
+ buffer pointed to by ``buf``. This parameter is valid only if the
+ function return does not indicate failure.
+``buf``
+ A pointer to a buffer to hold data sent by the peer and the peer's
+ address. This buffer must be large enough to receive ``amount`` bytes
+ of data and two :ref:`PRNetAddr` structures (thus allowing the runtime
+ to align the addresses as needed).
+``amount``
+ The number of bytes of data to receive. Does not include the size of
+ the :ref:`PRNetAddr` structures. If 0, no data will be read from the
+ peer.
+``timeout``
+ The timeout interval only applies to the read portion of the
+ operation. :ref:`PR_AcceptRead` blocks indefinitely until the connection
+ is accepted; the read will time out after the timeout interval
+ elapses.
+
+
+Returns
+~~~~~~~
+
+- A positive number indicates the number of bytes read from the peer.
+- The value -1 indicates a failure. The reason for the failure can be
+ obtained by calling :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+:ref:`PR_AcceptRead` accepts a new connection and retrieves the newly
+created socket's descriptor and the connecting peer's address. Also, as
+its name suggests, :ref:`PR_AcceptRead` receives the first block of data
+sent by the peer.
diff --git a/docs/nspr/reference/pr_access.rst b/docs/nspr/reference/pr_access.rst
new file mode 100644
index 0000000000..14fb5018b2
--- /dev/null
+++ b/docs/nspr/reference/pr_access.rst
@@ -0,0 +1,41 @@
+PR_Access
+=========
+
+Determines the accessibility of a file.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRStatus PR_Access(
+ const char *name,
+ PRAccessHow how);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``name``
+ The pathname of the file whose accessibility is to be determined.
+``how``
+ Specifies which access permission to check for. Use one of the
+ following values:
+
+ - :ref:`PR_ACCESS_READ_OK`. Test for read permission.
+ - :ref:`PR_ACCESS_WRITE_OK`. Test for write permission.
+ - :ref:`PR_ACCESS_EXISTS`. Check existence of file.
+
+
+Returns
+~~~~~~~
+
+One of the following values:
+
+- If the requested access is permitted, ``PR_SUCCESS``.
+- If the requested access is not permitted, ``PR_FAILURE``.
diff --git a/docs/nspr/reference/pr_append_link.rst b/docs/nspr/reference/pr_append_link.rst
new file mode 100644
index 0000000000..7525f348a5
--- /dev/null
+++ b/docs/nspr/reference/pr_append_link.rst
@@ -0,0 +1,32 @@
+PR_APPEND_LINK
+==============
+
+Appends an element to the end of a list.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prclist.h>
+
+ PR_APPEND_LINK (
+ PRCList *elemp,
+ PRCList *listp);
+
+
+Parameters
+~~~~~~~~~~
+
+``elemp``
+ A pointer to the element to be inserted.
+``listp``
+ A pointer to the list.
+
+
+Description
+-----------
+
+PR_APPEND_LINK adds the specified element to the end of the specified
+list.
diff --git a/docs/nspr/reference/pr_assert.rst b/docs/nspr/reference/pr_assert.rst
new file mode 100644
index 0000000000..669c8d346a
--- /dev/null
+++ b/docs/nspr/reference/pr_assert.rst
@@ -0,0 +1,43 @@
+PR_ASSERT
+=========
+
+Terminates execution when a given expression is ``FALSE``.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prlog.h>
+
+ void PR_ASSERT ( expression );
+
+
+Parameters
+~~~~~~~~~~
+
+The macro has this parameter:
+
+expression
+ Any valid C language expression that evaluates to ``TRUE`` or
+ ``FALSE``.
+
+
+Returns
+~~~~~~~
+
+Nothing
+
+
+Description
+-----------
+
+This macro evaluates the specified expression. When the result is zero
+(``FALSE``) the application terminates; otherwise the application
+continues. The macro converts the expression to a string and passes it
+to ``PR_Assert``, using file and line parameters from the compile-time
+environment.
+
+This macro compiles to nothing if compile-time options are not specified
+to enable logging.
diff --git a/docs/nspr/reference/pr_atomicadd.rst b/docs/nspr/reference/pr_atomicadd.rst
new file mode 100644
index 0000000000..3f2afeece0
--- /dev/null
+++ b/docs/nspr/reference/pr_atomicadd.rst
@@ -0,0 +1,37 @@
+PR_AtomicAdd
+============
+
+
+Syntax
+------
+
+.. code::
+
+ #include <pratom.h>
+
+ PRInt32 PR_AtomicAdd(
+ PRInt32 *ptr,
+ PRInt32 val);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameters:
+
+``ptr``
+ A pointer to the value to increment.
+``val``
+ A value to be added.
+
+
+Returns
+~~~~~~~
+
+The returned value is the result of the addition.
+
+
+Description
+-----------
+
+Atomically add a 32 bit value.
diff --git a/docs/nspr/reference/pr_atomicdecrement.rst b/docs/nspr/reference/pr_atomicdecrement.rst
new file mode 100644
index 0000000000..a27b8d8227
--- /dev/null
+++ b/docs/nspr/reference/pr_atomicdecrement.rst
@@ -0,0 +1,37 @@
+PR_AtomicDecrement
+==================
+
+Atomically decrements a 32-bit value.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <pratom.h>
+
+ PRInt32 PR_AtomicDecrement(PRInt32 *val);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``val``
+ A pointer to the value to decrement.
+
+
+Returns
+~~~~~~~
+
+The function returns the decremented value (i.e., the result).
+
+
+Description
+-----------
+
+:ref:`PR_AtomicDecrement` first decrements the referenced variable by one.
+The value returned is the referenced variable's final value. The
+modification to memory is unconditional.
diff --git a/docs/nspr/reference/pr_atomicincrement.rst b/docs/nspr/reference/pr_atomicincrement.rst
new file mode 100644
index 0000000000..126ed7989c
--- /dev/null
+++ b/docs/nspr/reference/pr_atomicincrement.rst
@@ -0,0 +1,37 @@
+PR_AtomicIncrement
+==================
+
+Atomically increments a 32-bit value.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <pratom.h>
+
+ PRInt32 PR_AtomicIncrement(PRInt32 *val);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``val``
+ A pointer to the value to increment.
+
+
+Returns
+~~~~~~~
+
+The function returns the incremented value (i.e., the result).
+
+
+Description
+-----------
+
+The referenced variable is incremented by one. The result of the
+function is the value of the memory after the operation. The writing of
+the memory is unconditional.
diff --git a/docs/nspr/reference/pr_atomicset.rst b/docs/nspr/reference/pr_atomicset.rst
new file mode 100644
index 0000000000..3c9df0b1b9
--- /dev/null
+++ b/docs/nspr/reference/pr_atomicset.rst
@@ -0,0 +1,42 @@
+PR_AtomicSet
+============
+
+Atomically sets a 32-bit value and return its previous contents.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <pratom.h>
+
+ PRInt32 PR_AtomicSet(
+ PRInt32 *val,
+ PRInt32 newval);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameter:
+
+``val``
+ A pointer to the value to be set.
+``newval``
+ The new value to assign to the ``val`` parameter.
+
+
+Returns
+~~~~~~~
+
+The function returns the prior value of the referenced variable.
+
+
+Description
+-----------
+
+:ref:`PR_AtomicSet` first reads the value of var, then updates it with the
+supplied value. The returned value is the value that was read\ *before*
+memory was updated. The memory modification is unconditional--that is,
+it isn't a test and set operation.
diff --git a/docs/nspr/reference/pr_attachsharedmemory.rst b/docs/nspr/reference/pr_attachsharedmemory.rst
new file mode 100644
index 0000000000..ed7c38d4a3
--- /dev/null
+++ b/docs/nspr/reference/pr_attachsharedmemory.rst
@@ -0,0 +1,44 @@
+PR_AttachSharedMemory
+=====================
+
+Attaches a memory segment previously opened with :ref:`PR_OpenSharedMemory`
+and maps it into the process memory space.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prshm.h>
+
+.. code::
+
+ NSPR_API( void * )
+ PR_AttachSharedMemory(
+ PRSharedMemory *shm,
+ PRIntn flags
+ );
+
+ /* Define values for PR_AttachSharedMemory(...,flags) */
+ #define PR_SHM_READONLY 0x01
+
+
+Parameters
+~~~~~~~~~~
+
+The function has these parameters:
+
+shm
+ The handle returned from :ref:`PR_OpenSharedMemory`.
+flags
+ Options for mapping the shared memory. ``PR_SHM_READONLY`` causes the
+ memory to be attached read-only.
+
+
+Returns
+~~~~~~~
+
+Address where shared memory is mapped, or ``NULL`` if an error occurs.
+Retrieve the reason for the failure by calling :ref:`PR_GetError` and
+:ref:`PR_GetOSError`.
diff --git a/docs/nspr/reference/pr_attachthread.rst b/docs/nspr/reference/pr_attachthread.rst
new file mode 100644
index 0000000000..78edfc19b0
--- /dev/null
+++ b/docs/nspr/reference/pr_attachthread.rst
@@ -0,0 +1,76 @@
+PR_AttachThread
+===============
+
+.. container:: blockIndicator obsolete obsoleteHeader
+
+ | **Obsolete**
+ | This feature is obsolete. Although it may still work in some
+ browsers, its use is discouraged since it could be removed at any
+ time. Try to avoid using it.
+
+Associates a :ref:`PRThread` object with an existing native thread.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <pprthread.h>
+
+ PRThread* PR_AttachThread(
+ PRThreadType type,
+ PRThreadPriority priority,
+ PRThreadStack *stack);
+
+
+Parameters
+~~~~~~~~~~
+
+:ref:`PR_AttachThread` has the following parameters:
+
+``type``
+ Specifies that the thread is either a user thread
+ (``PR_USER_THREAD``) or a system thread (``PR_SYSTEM_THREAD``).
+``priority``
+ The priority to assign to the thread being attached.
+``stack``
+ The stack for the thread being attached.
+
+
+Returns
+~~~~~~~
+
+The function returns one of these values:
+
+- If successful, a pointer to a :ref:`PRThread` object.
+- If unsuccessful, for example if system resources are not available,
+ ``NULL``.
+
+
+Description
+-----------
+
+You use :ref:`PR_AttachThread` when you want to use NSS functions on the
+native thread that was not created with NSPR. :ref:`PR_AttachThread`
+informs NSPR about the new thread by associating a :ref:`PRThread` object
+with the native thread.
+
+The thread object is automatically destroyed when it is no longer
+needed.
+
+You don't need to call :ref:`PR_AttachThread` unless you create your own
+native thread. :ref:`PR_Init` calls :ref:`PR_AttachThread` automatically for
+the primordial thread.
+
+.. note::
+
+ **Note**: As of NSPR release v3.0, :ref:`PR_AttachThread` and
+ :ref:`PR_DetachThread` are obsolete. A native thread not created by NSPR
+ is automatically attached the first time it calls an NSPR function,
+ and automatically detached when it exits.
+
+In NSPR release 19980529B and earlier, it is necessary for a native
+thread not created by NSPR to call :ref:`PR_AttachThread` before it calls
+any NSPR functions, and call :ref:`PR_DetachThread` when it is done calling
+NSPR functions.
diff --git a/docs/nspr/reference/pr_available.rst b/docs/nspr/reference/pr_available.rst
new file mode 100644
index 0000000000..8906bcace5
--- /dev/null
+++ b/docs/nspr/reference/pr_available.rst
@@ -0,0 +1,51 @@
+PR_Available
+============
+
+Determines the number of bytes (expressed as a 32-bit integer) that are
+available for reading beyond the current read-write pointer in a
+specified file or socket.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRInt32 PR_Available(PRFileDesc *fd);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``fd``
+ Pointer to a :ref:`PRFileDesc` object representing a file or socket.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If the function completes successfully, it returns the number of
+ bytes that are available for reading. For a normal file, these are
+ the bytes beyond the current file pointer.
+- If the function fails, it returns the value -1. The error code can
+ then be retrieved via :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+:ref:`PR_Available` works on normal files and sockets. :ref:`PR_Available`
+does not work with pipes on Win32 platforms.
+
+
+See Also
+--------
+
+If the number of bytes available for reading is out of the range of a
+32-bit integer, use :ref:`PR_Available64`.
diff --git a/docs/nspr/reference/pr_available64.rst b/docs/nspr/reference/pr_available64.rst
new file mode 100644
index 0000000000..7ee2dd07a7
--- /dev/null
+++ b/docs/nspr/reference/pr_available64.rst
@@ -0,0 +1,51 @@
+PR_Available64
+==============
+
+Determines the number of bytes (expressed as a 32-bit integer) that are
+available for reading beyond the current read-write pointer in a
+specified file or socket.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRInt64 PR_Available64(PRFileDesc *fd);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``fd``
+ Pointer to a :ref:`PRFileDesc` object representing a file or socket.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If the function completes successfully, it returns the number of
+ bytes that are available for reading. For a normal file, these are
+ the bytes beyond the current file pointer.
+- If the function fails, it returns the value -1. The error code can
+ then be retrieved via :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+:ref:`PR_Available64` works on normal files and sockets. :ref:`PR_Available`
+does not work with pipes on Win32 platforms.
+
+
+See Also
+--------
+
+If the number of bytes available for reading is within the range of a
+32-bit integer, use :ref:`PR_Available`.
diff --git a/docs/nspr/reference/pr_bind.rst b/docs/nspr/reference/pr_bind.rst
new file mode 100644
index 0000000000..25bba0bcd1
--- /dev/null
+++ b/docs/nspr/reference/pr_bind.rst
@@ -0,0 +1,54 @@
+PR_Bind
+=======
+
+Binds an address to a specified socket.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRStatus PR_Bind(
+ PRFileDesc *fd,
+ const PRNetAddr *addr);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``fd``
+ A pointer to a :ref:`PRFileDesc` object representing a socket.
+``addr``
+ A pointer to a :ref:`PRNetAddr` object representing the address to which
+ the socket will be bound.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- Upon successful binding of an address to a socket, ``PR_SUCCESS``.
+- If unsuccessful, ``PR_FAILURE``. Further information can be obtained
+ by calling :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+When a new socket is created, it has no address bound to it. :ref:`PR_Bind`
+assigns the specified address (also known as name) to the socket. If you
+do not care about the exact IP address assigned to the socket, set the
+``inet.ip`` field of :ref:`PRNetAddr` to :ref:`PR_htonl`\ (``PR_INADDR_ANY``).
+If you do not care about the TCP/UDP port assigned to the socket, set
+the ``inet.port`` field of :ref:`PRNetAddr` to 0.
+
+Note that if :ref:`PR_Connect` is invoked on a socket that is not bound, it
+implicitly binds an arbitrary address the socket.
+
+Call :ref:`PR_GetSockName` to obtain the address (name) bound to a socket.
diff --git a/docs/nspr/reference/pr_blockclockinterrupts.rst b/docs/nspr/reference/pr_blockclockinterrupts.rst
new file mode 100644
index 0000000000..936dedb877
--- /dev/null
+++ b/docs/nspr/reference/pr_blockclockinterrupts.rst
@@ -0,0 +1,14 @@
+PR_BlockClockInterrupts
+=======================
+
+Blocks the timer signal used for preemptive scheduling.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prinit.h>
+
+ void PR_BlockClockInterrupts(void);
diff --git a/docs/nspr/reference/pr_callback.rst b/docs/nspr/reference/pr_callback.rst
new file mode 100644
index 0000000000..47798a77b5
--- /dev/null
+++ b/docs/nspr/reference/pr_callback.rst
@@ -0,0 +1,24 @@
+PR_CALLBACKimplementation
+=========================
+
+Used to define pointers to functions that will be implemented by the
+client but called from a (different) shared library.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtypes.h>type PR_CALLBACKimplementation
+
+
+Description
+-----------
+
+Functions that are implemented in an application (or shared library)
+that are intended to be called from another shared library (such as
+NSPR) must be declared with the ``PR_CALLBACK`` attribute. Normally such
+functions are passed by reference (pointer to function). The
+``PR_CALLBACK`` attribute is included as part of the function's
+definition between its return value type and the function's name.
diff --git a/docs/nspr/reference/pr_calloc.rst b/docs/nspr/reference/pr_calloc.rst
new file mode 100644
index 0000000000..b36a89d9b4
--- /dev/null
+++ b/docs/nspr/reference/pr_calloc.rst
@@ -0,0 +1,42 @@
+PR_Calloc
+=========
+
+Allocates zeroed memory from the heap for a number of objects of a given
+size.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prmem.h>
+
+ void *PR_Calloc (
+ PRUint32 nelem,
+ PRUint32 elsize);
+
+
+Parameters
+~~~~~~~~~~
+
+``nelem``
+ The number of elements of size ``elsize`` to be allocated.
+``elsize``
+ The size of an individual element.
+
+
+Returns
+~~~~~~~
+
+An untyped pointer to the allocated memory, or if the allocation attempt
+fails, ``NULL``. Call ``PR_GetError()`` to retrieve the error returned
+by the libc function ``malloc()``.
+
+
+Description
+-----------
+
+This function allocates memory on the heap for the specified number of
+objects of the specified size. All bytes in the allocated memory are
+cleared.
diff --git a/docs/nspr/reference/pr_callonce.rst b/docs/nspr/reference/pr_callonce.rst
new file mode 100644
index 0000000000..912f1a3ac3
--- /dev/null
+++ b/docs/nspr/reference/pr_callonce.rst
@@ -0,0 +1,35 @@
+PR_CallOnce
+===========
+
+Ensures that subsystem initialization occurs only once.
+
+
+Syntax
+------
+
+.. code::
+
+ PRStatus PR_CallOnce(
+ PRCallOnceType *once,
+ PRCallOnceFN func);
+
+
+Parameters
+~~~~~~~~~~
+
+:ref:`PR_CallOnce` has these parameters:
+
+``once``
+ A pointer to an object of type :ref:`PRCallOnceType`. Initially (before
+ any threading issues exist), the object must be initialized to all
+ zeros. From that time on, the client should consider the object
+ read-only (or even opaque) and allow the runtime to manipulate its
+ content appropriately.
+``func``
+ A pointer to the function the calling client has designed to perform
+ the subsystem initialization. The function will be called once, at
+ most, for each subsystem to be initialized. It should return a
+ :ref:`PRStatus` indicating the result of the initialization process.
+ While the first thread executes this function, other threads
+ attempting the same initialization will be blocked until it has been
+ completed.
diff --git a/docs/nspr/reference/pr_canceljob.rst b/docs/nspr/reference/pr_canceljob.rst
new file mode 100644
index 0000000000..6d49445d75
--- /dev/null
+++ b/docs/nspr/reference/pr_canceljob.rst
@@ -0,0 +1,30 @@
+PR_CancelJob
+============
+
+Causes a previously queued job to be canceled.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtpool.h>
+
+ NSPR_API(PRStatus) PR_CancelJob(PRJob *job);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``job``
+ A pointer to a :ref:`PRJob` structure returned by a :ref:`PR_QueueJob`
+ function representing the job to be cancelled.
+
+
+Returns
+~~~~~~~
+
+:ref:`PRStatus`
diff --git a/docs/nspr/reference/pr_centermonitor.rst b/docs/nspr/reference/pr_centermonitor.rst
new file mode 100644
index 0000000000..8c135f844e
--- /dev/null
+++ b/docs/nspr/reference/pr_centermonitor.rst
@@ -0,0 +1,57 @@
+PR_CEnterMonitor
+================
+
+Enters the lock associated with a cached monitor.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prcmon.h>
+
+ PRMonitor* PR_CEnterMonitor(void *address);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``address``
+ A reference to the data that is to be protected by the monitor. This
+ reference must remain valid as long as there are monitoring
+ operations being performed.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, the function returns a pointer to the :ref:`PRMonitor`
+ associated with the value specified in the ``address`` parameter.
+- If unsuccessful (the monitor cache needs to be expanded and the
+ system is out of memory), the function returns ``NULL``.
+
+
+Description
+-----------
+
+:ref:`PR_CEnterMonitor` uses the value specified in the ``address``
+parameter to find a monitor in the monitor cache, then enters the lock
+associated with the monitor. If no match is found, an available monitor
+is associated with the address and the monitor's entry count is
+incremented (so it has a value of one). If a match is found, then either
+the calling thread is already in the monitor (and this is a reentrant
+call) or another thread is holding the monitor's mutex. In the former
+case, the entry count is simply incremented and the function returns. In
+the latter case, the calling thread is likely to find the monitor locked
+by another thread and waits for that thread to exit before continuing.
+
+.. note::
+
+ **Note**: :ref:`PR_CEnterMonitor` and :ref:`PR_CExitMonitor` must be
+ paired--that is, there must be an exit for every entry--or the object
+ will never become available for any other thread.
diff --git a/docs/nspr/reference/pr_cexitmonitor.rst b/docs/nspr/reference/pr_cexitmonitor.rst
new file mode 100644
index 0000000000..91e95f9bff
--- /dev/null
+++ b/docs/nspr/reference/pr_cexitmonitor.rst
@@ -0,0 +1,44 @@
+PR_CExitMonitor
+===============
+
+Decrement the entry count associated with a cached monitor.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prcmon.h>
+
+ PRStatus PR_CExitMonitor(void *address);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``address``
+ The address of the protected object--the same address previously
+ passed to :ref:`PR_CEnterMonitor`.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful, ``PR_FAILURE``. This may indicate that the address
+ parameter is invalid or that the calling thread is not in the
+ monitor.
+
+
+Description
+-----------
+
+Using the value specified in the address parameter to find a monitor in
+the monitor cache, :ref:`PR_CExitMonitor` decrements the entry count
+associated with the monitor. If the decremented entry count is zero, the
+monitor is exited.
diff --git a/docs/nspr/reference/pr_cleanup.rst b/docs/nspr/reference/pr_cleanup.rst
new file mode 100644
index 0000000000..6f4a93717e
--- /dev/null
+++ b/docs/nspr/reference/pr_cleanup.rst
@@ -0,0 +1,39 @@
+PR_Cleanup
+==========
+
+Coordinates a graceful shutdown of NSPR.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prinit.h>
+
+ PRStatus PR_Cleanup(void);
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If NSPR has been shut down successfully, ``PR_SUCCESS``.
+- If the calling thread of this function is not the primordial thread,
+ ``PR_FAILURE``.
+
+
+Description
+-----------
+
+:ref:`PR_Cleanup` must be called by the primordial thread near the end of
+the ``main`` function.
+
+:ref:`PR_Cleanup` attempts to synchronize the natural termination of the
+process. It does so by blocking the caller, if and only if it is the
+primordial thread, until all user threads have terminated. When the
+primordial thread returns from ``main``, the process immediately and
+silently exits. That is, the process (if necessary) forcibly terminates
+any existing threads and exits without significant blocking and without
+error messages or core files.
diff --git a/docs/nspr/reference/pr_clearinterrupt.rst b/docs/nspr/reference/pr_clearinterrupt.rst
new file mode 100644
index 0000000000..27900e40d9
--- /dev/null
+++ b/docs/nspr/reference/pr_clearinterrupt.rst
@@ -0,0 +1,29 @@
+PR_ClearInterrupt
+=================
+
+Clears the interrupt request for the calling thread.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prthread.h>
+
+ void PR_ClearInterrupt(void);
+
+
+Description
+-----------
+
+Interrupting is a cooperative process, so it's possible that the thread
+passed to :ref:`PR_Interrupt` may never respond to the interrupt request.
+For example, the target thread may reach the agreed-on control point
+without providing an opportunity for the runtime to notify the thread of
+the interrupt request. In this case, the request for interrupt is still
+pending with the thread and must be explicitly canceled. Therefore it is
+sometimes necessary to call :ref:`PR_ClearInterrupt` to clear a previous
+interrupt request.
+
+If no interrupt request is pending, :ref:`PR_ClearInterrupt` is a no-op.
diff --git a/docs/nspr/reference/pr_clist_is_empty.rst b/docs/nspr/reference/pr_clist_is_empty.rst
new file mode 100644
index 0000000000..1794a8b7b4
--- /dev/null
+++ b/docs/nspr/reference/pr_clist_is_empty.rst
@@ -0,0 +1,28 @@
+PR_CLIST_IS_EMPTY
+=================
+
+Checks for an empty circular list.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prclist.h>
+
+ PRIntn PR_CLIST_IS_EMPTY (PRCList *listp);
+
+
+Parameter
+~~~~~~~~~
+
+``listp``
+ A pointer to the linked list.
+
+
+Description
+-----------
+
+PR_CLIST_IS_EMPTY returns a non-zero value if the specified list is an
+empty list, otherwise returns zero.
diff --git a/docs/nspr/reference/pr_close.rst b/docs/nspr/reference/pr_close.rst
new file mode 100644
index 0000000000..1bf310c495
--- /dev/null
+++ b/docs/nspr/reference/pr_close.rst
@@ -0,0 +1,40 @@
+PR_Close
+========
+
+Closes a file descriptor.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRStatus PR_Close(PRFileDesc *fd);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``fd``
+ A pointer to a :ref:`PRFileDesc` object.
+
+
+Returns
+~~~~~~~
+
+One of the following values:
+
+- If file descriptor is closed successfully, ``PR_SUCCESS``.
+- If the file descriptor is not closed successfully, ``PR_FAILURE``.
+
+
+Description
+-----------
+
+The file descriptor may represent a normal file, a socket, or an end
+point of a pipe. On successful return, :ref:`PR_Close` frees the dynamic
+memory and other resources identified by the ``fd`` parameter.
diff --git a/docs/nspr/reference/pr_closedir.rst b/docs/nspr/reference/pr_closedir.rst
new file mode 100644
index 0000000000..e7a586282f
--- /dev/null
+++ b/docs/nspr/reference/pr_closedir.rst
@@ -0,0 +1,47 @@
+PR_CloseDir
+===========
+
+Closes the specified directory.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRStatus PR_CloseDir(PRDir *dir);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``dir``
+ A pointer to a :ref:`PRDir` structure representing the directory to be
+ closed.
+
+
+Returns
+~~~~~~~
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful, ``PR_FAILURE``. The reason for the failure can be
+ retrieved via :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+When a :ref:`PRDir` object is no longer needed, it must be closed and freed
+with a call to :ref:`PR_CloseDir` call. Note that after a :ref:`PR_CloseDir`
+call, any ``PRDirEntry`` object returned by a previous :ref:`PR_ReadDir`
+call on the same :ref:`PRDir` object becomes invalid.
+
+
+See Also
+--------
+
+:ref:`PR_OpenDir`
diff --git a/docs/nspr/reference/pr_closefilemap.rst b/docs/nspr/reference/pr_closefilemap.rst
new file mode 100644
index 0000000000..a6a5590729
--- /dev/null
+++ b/docs/nspr/reference/pr_closefilemap.rst
@@ -0,0 +1,40 @@
+PR_CloseFileMap
+===============
+
+Closes a file mapping.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRStatus PR_CloseFileMap(PRFileMap *fmap);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``fmap``
+ The file mapping to be closed.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If the memory region is successfully unmapped, ``PR_SUCCESS``.
+- If the memory region is not successfully unmapped, ``PR_FAILURE``.
+ The error code can be retrieved via :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+When a file mapping created with a call to :ref:`PR_CreateFileMap` is no
+longer needed, it should be closed with a call to :ref:`PR_CloseFileMap`.
diff --git a/docs/nspr/reference/pr_closesemaphore.rst b/docs/nspr/reference/pr_closesemaphore.rst
new file mode 100644
index 0000000000..07d1aca46e
--- /dev/null
+++ b/docs/nspr/reference/pr_closesemaphore.rst
@@ -0,0 +1,30 @@
+PR_CloseSemaphore
+=================
+
+Closes a specified semaphore.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <pripcsem.h>
+
+ NSPR_API(PRStatus) PR_CloseSemaphore(PRSem *sem);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``sem``
+ A pointer to a ``PRSem`` structure returned from a call to
+ :ref:`PR_OpenSemaphore`.
+
+
+Returns
+~~~~~~~
+
+:ref:`PRStatus`
diff --git a/docs/nspr/reference/pr_closesharedmemory.rst b/docs/nspr/reference/pr_closesharedmemory.rst
new file mode 100644
index 0000000000..221b07f2ba
--- /dev/null
+++ b/docs/nspr/reference/pr_closesharedmemory.rst
@@ -0,0 +1,32 @@
+PR_CloseSharedMemory
+====================
+
+Closes a shared memory segment identified by name.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prshm.h>
+
+ NSPR_API( PRStatus )
+ PR_CloseSharedMemory(
+ PRSharedMemory *shm
+ );
+
+
+Parameter
+~~~~~~~~~
+
+The function has these parameter:
+
+shm
+ The handle returned from :ref:`PR_OpenSharedMemory`.
+
+
+Returns
+~~~~~~~
+
+:ref:`PRStatus`.
diff --git a/docs/nspr/reference/pr_cnotify.rst b/docs/nspr/reference/pr_cnotify.rst
new file mode 100644
index 0000000000..d3c5163a81
--- /dev/null
+++ b/docs/nspr/reference/pr_cnotify.rst
@@ -0,0 +1,43 @@
+PR_CNotify
+==========
+
+Notify a thread waiting on a change in the state of monitored data.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prcmon.h>
+
+ PRStatus PR_CNotify(void *address);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``address``
+ The address of the monitored object. The calling thread must be in
+ the monitor defined by the value of the address.
+
+
+Returns
+~~~~~~~
+
+ - :ref:`PR_SUCCESS` indicates that the calling thread is the holder of the
+ mutex for the monitor referred to by the address parameter.
+ - :ref:`PR_FAILURE` indicates that the monitor has not been entered by the
+ calling thread.
+
+
+Description
+-----------
+
+Using the value specified in the ``address`` parameter to find a monitor
+in the monitor cache, :ref:`PR_CNotify` notifies single a thread waiting
+for the monitor's state to change. If a thread is waiting on the monitor
+(having called :ref:`PR_CWait`), then that thread is made ready. As soon as
+the thread is scheduled, it attempts to reenter the monitor.
diff --git a/docs/nspr/reference/pr_cnotifyall.rst b/docs/nspr/reference/pr_cnotifyall.rst
new file mode 100644
index 0000000000..80a7c2aeb9
--- /dev/null
+++ b/docs/nspr/reference/pr_cnotifyall.rst
@@ -0,0 +1,43 @@
+PR_CNotifyAll
+=============
+
+Notifies all the threads waiting for a change in the state of monitored
+data.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prcmon.h>
+
+ PRStatus PR_CNotifyAll(void *address);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``address``
+ The address of the monitored object. The calling thread must be in
+ the monitor at the time :ref:`PR_CNotifyAll` is called.
+
+
+Returns
+~~~~~~~
+
+ - :ref:`PR_SUCCESS` indicates that the referenced monitor was located and
+ the calling thread was in the monitor.
+ - :ref:`PR_FAILURE` indicates that the referenced monitor could not be
+ located or that the calling thread was not in the monitor
+
+
+Description
+-----------
+
+Using the value specified in the address parameter to find a monitor in
+the monitor cache, :ref:`PR_CNotifyAll` notifies all threads waiting for
+the monitor's state to change. All of the threads waiting on the state
+change are then scheduled to reenter the monitor.
diff --git a/docs/nspr/reference/pr_cnvtf.rst b/docs/nspr/reference/pr_cnvtf.rst
new file mode 100644
index 0000000000..f2b0fa88b5
--- /dev/null
+++ b/docs/nspr/reference/pr_cnvtf.rst
@@ -0,0 +1,45 @@
+PR_cnvtf
+========
+
+Converts a floating point number to a string.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prdtoa.h>
+
+ void PR_cnvtf (
+ char *buf,
+ PRIntn bufsz,
+ PRIntn prcsn,
+ PRFloat64 fval);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has these parameters:
+
+``buf``
+ The address of the buffer in which to store the result.
+``bufsz``
+ The size of the buffer provided to hold the result.
+``prcsn``
+ The number of digits of precision to which to generate the floating
+ point value.
+``fval``
+ The double-precision floating point number to be converted.
+
+
+Description
+-----------
+
+:ref:`PR_cnvtf` is a simpler interface to convert a floating point number
+to a string. It conforms to the ECMA standard of Javascript
+(ECMAScript).
+
+On return, the result is written to the buffer pointed to by ``buf`` of
+size ``bufsz``.
diff --git a/docs/nspr/reference/pr_connect.rst b/docs/nspr/reference/pr_connect.rst
new file mode 100644
index 0000000000..10978df792
--- /dev/null
+++ b/docs/nspr/reference/pr_connect.rst
@@ -0,0 +1,67 @@
+PR_Connect
+==========
+
+Initiates a connection on a specified socket.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRStatus PR_Connect(
+ PRFileDesc *fd,
+ const PRNetAddr *addr,
+ PRIntervalTime timeout);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``fd``
+ A pointer to a :ref:`PRFileDesc` object representing a socket.
+``addr``
+ A pointer to the address of the peer to which the socket is to be
+ connected.
+``timeout``
+ A value of type :ref:`PRIntervalTime` specifying the time limit for
+ completion of the connect operation.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- Upon successful completion of connection setup, ``PR_SUCCESS``.
+- If unsuccessful, ``PR_FAILURE``. Further information can be obtained
+ by calling :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+:ref:`PR_Connect` is usually invoked on a TCP socket, but it may also be
+invoked on a UDP socket. Both cases are discussed here.
+
+If the socket is a TCP socket, :ref:`PR_Connect` establishes a TCP
+connection to the peer. If the socket is not bound, it will be bound to
+an arbitrary local address.
+
+:ref:`PR_Connect` blocks until either the connection is successfully
+established or an error occurs. The function uses the lesser of the
+provided timeout and the OS's connect timeout. In particular, if you
+specify ``PR_INTERVAL_NO_TIMEOUT`` as the timeout, the OS's connection
+time limit will be used.
+
+If the socket is a UDP socket, there is no connection setup to speak of,
+since UDP is connectionless. If :ref:`PR_Connect` is invoked on a UDP
+socket, it has an overloaded meaning: :ref:`PR_Connect` merely saves the
+specified address as the default peer address for the socket, so that
+subsequently one can send and receive datagrams from the socket using
+:ref:`PR_Send` and :ref:`PR_Recv` instead of the usual :ref:`PR_SendTo` and
+:ref:`PR_RecvFrom`.
diff --git a/docs/nspr/reference/pr_connectcontinue.rst b/docs/nspr/reference/pr_connectcontinue.rst
new file mode 100644
index 0000000000..b4ba88bc20
--- /dev/null
+++ b/docs/nspr/reference/pr_connectcontinue.rst
@@ -0,0 +1,53 @@
+PR_ConnectContinue
+==================
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRStatus PR_ConnectContinue(
+ PRFileDesc *fd,
+ PRInt16 out_flags);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``fd``
+ A pointer to a :ref:`PRFileDesc` object representing a socket.
+
+``out_flags``
+ The out_flags field of the poll descriptor returned by
+ `PR_Poll() <PR_Poll>`__.
+
+
+Returns
+~~~~~~~
+
+- If the nonblocking connect has successfully completed,
+ PR_ConnectContinue returns PR_SUCCESS.
+- If PR_ConnectContinue() returns PR_FAILURE, call PR_GetError():
+- PR_IN_PROGRESS_ERROR: the nonblocking connect is still in
+ progress and has not completed yet. The caller should poll the file
+ descriptor for the in_flags PR_POLL_WRITE|PR_POLL_EXCEPT and retry
+ PR_ConnectContinue later when PR_Poll() returns.
+- Other errors: the nonblocking connect has failed with this
+ error code.
+
+
+Description
+-----------
+
+Continue a nonblocking connect. After a nonblocking connect is initiated
+with PR_Connect() (which fails with PR_IN_PROGRESS_ERROR), one should
+call PR_Poll() on the socket, with the in_flags PR_POLL_WRITE \|
+PR_POLL_EXCEPT. When PR_Poll() returns, one calls PR_ConnectContinue()
+on the socket to determine whether the nonblocking connect has completed
+or is still in progress. Repeat the PR_Poll(), PR_ConnectContinue()
+sequence until the nonblocking connect has completed.
diff --git a/docs/nspr/reference/pr_convertipv4addrtoipv6.rst b/docs/nspr/reference/pr_convertipv4addrtoipv6.rst
new file mode 100644
index 0000000000..c19535ccc2
--- /dev/null
+++ b/docs/nspr/reference/pr_convertipv4addrtoipv6.rst
@@ -0,0 +1,30 @@
+PR_ConvertIPv4AddrToIPv6
+========================
+
+Converts an IPv4 address into an (IPv4-mapped) IPv6 address.
+
+
+Syntax
+~~~~~~
+
+.. code::
+
+ #include <prnetdb.h>
+
+ void PR_ConvertIPv4AddrToIPv6(
+ PRUint32 v4addr,
+ PRIPv6Addr *v6addr
+ );
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``v4addr``
+ The IPv4 address to convert into an IPv4-mapped IPv6 address. This
+ must be specified in network byte order.
+``v6addr``
+ A pointer to a buffer, allocated by the caller, that is filled in
+ with the IPv6 address on return.
diff --git a/docs/nspr/reference/pr_createfilemap.rst b/docs/nspr/reference/pr_createfilemap.rst
new file mode 100644
index 0000000000..c347d94e5e
--- /dev/null
+++ b/docs/nspr/reference/pr_createfilemap.rst
@@ -0,0 +1,66 @@
+PR_CreateFileMap
+================
+
+Creates a file mapping object.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRFileMap* PR_CreateFileMap(
+ PRFileDesc *fd,
+ PRInt64 size,
+ PRFileMapProtect prot);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``fd``
+ A pointer to a :ref:`PRFileDesc` object representing the file that is to
+ be mapped to memory.
+``size``
+ Size of the file specified by ``fd``.
+``prot``
+ Protection option for read and write accesses of a file mapping. This
+ parameter consists of one of the following options:
+
+ - :ref:`PR_PROT_READONLY`. Read-only access.
+ - :ref:`PR_PROT_READWRITE`. Readable, and write is shared.
+ - :ref:`PR_PROT_WRITECOPY`. Readable, and write is private
+ (copy-on-write).
+
+
+Returns
+~~~~~~~
+
+- If successful, a file mapping of type :ref:`PRFileMap`.
+- If unsuccessful, ``NULL``.
+
+
+Description
+-----------
+
+The ``PRFileMapProtect`` enumeration used in the ``prot`` parameter is
+defined as follows:
+
+.. code::
+
+ typedef enum PRFileMapProtect {
+ PR_PROT_READONLY,
+ PR_PROT_READWRITE,
+ PR_PROT_WRITECOPY
+ } PRFileMapProtect;
+
+:ref:`PR_CreateFileMap` only prepares for the mapping a file to memory. The
+returned file-mapping object must be passed to :ref:`PR_MemMap` to actually
+map a section of the file to memory.
+
+The file-mapping object should be closed with a :ref:`PR_CloseFileMap` call
+when it is no longer needed.
diff --git a/docs/nspr/reference/pr_createiolayerstub.rst b/docs/nspr/reference/pr_createiolayerstub.rst
new file mode 100644
index 0000000000..3deb061db2
--- /dev/null
+++ b/docs/nspr/reference/pr_createiolayerstub.rst
@@ -0,0 +1,46 @@
+PR_CreateIOLayerStub
+====================
+
+Creates a new layer.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRFileDesc* PR_CreateIOLayerStub(
+ PRDescIdentity ident
+ PRIOMethods const *methods);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``ident``
+ The identity to be associated with the new layer.
+``methods``
+ A pointer to the :ref:`PRIOMethods` structure specifying the functions
+ for the new layer.
+
+
+Returns
+~~~~~~~
+
+A new file descriptor for the specified layer.
+
+
+Description
+-----------
+
+A new layer may be allocated by calling :ref:`PR_CreateIOLayerStub`. The
+file descriptor returned contains the pointer to the I/O methods table
+provided. The runtime neither modifies the table nor tests its
+correctness.
+
+The caller should override appropriate contents of the file descriptor
+returned before pushing it onto the protocol stack.
diff --git a/docs/nspr/reference/pr_createpipe.rst b/docs/nspr/reference/pr_createpipe.rst
new file mode 100644
index 0000000000..3409cb9912
--- /dev/null
+++ b/docs/nspr/reference/pr_createpipe.rst
@@ -0,0 +1,53 @@
+PR_CreatePipe
+=============
+
+Creates an anonymous pipe and retrieves file descriptors for the read
+and write ends of the pipe.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRStatus PR_CreatePipe(
+ PRFileDesc **readPipe,
+ PRFileDesc **writePipe);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``readPipe``
+ A pointer to a :ref:`PRFileDesc` pointer. On return, this parameter
+ contains the file descriptor for the read end of the pipe.
+``writePipe``
+ A pointer to a :ref:`PRFileDesc` pointer. On return, this parameter
+ contains the file descriptor for the write end of the pipe.
+
+
+Returns
+~~~~~~~
+
+The function returns one of these values:
+
+- If the pipe is successfully created, ``PR_SUCCESS``.
+- If the pipe is not successfully created, ``PR_FAILURE``. The error
+ code can be retrieved via :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+:ref:`PR_CreatePipe` creates an anonymous pipe. Data written into the write
+end of the pipe can be read from the read end of the pipe. Pipes are
+useful for interprocess communication between a parent and a child
+process. When the pipe is no longer needed, both ends should be closed
+with calls to :ref:`PR_Close`.
+
+:ref:`PR_CreatePipe` is currently implemented on Unix, Linux, Mac OS X, and
+Win32 only.
diff --git a/docs/nspr/reference/pr_createthread.rst b/docs/nspr/reference/pr_createthread.rst
new file mode 100644
index 0000000000..cf733e624d
--- /dev/null
+++ b/docs/nspr/reference/pr_createthread.rst
@@ -0,0 +1,79 @@
+PR_CreateThread
+===============
+
+Creates a new thread.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prthread.h>
+
+ PRThread* PR_CreateThread(
+ PRThreadType type,
+ void (*start)(void *arg),
+ void *arg,
+ PRThreadPriority priority,
+ PRThreadScope scope,
+ PRThreadState state,
+ PRUint32 stackSize);
+
+
+Parameters
+~~~~~~~~~~
+
+:ref:`PR_CreateThread` has the following parameters:
+
+``type``
+ Specifies that the thread is either a user thread
+ (``PR_USER_THREAD``) or a system thread (``PR_SYSTEM_THREAD``).
+``start``
+ A pointer to the thread's root function, which is called as the root
+ of the new thread. Returning from this function is the only way to
+ terminate a thread.
+``arg``
+ A pointer to the root function's only parameter. NSPR does not assess
+ the type or the validity of the value passed in this parameter.
+``priority``
+ The initial priority of the newly created thread.
+``scope``
+ Specifies your preference for making the thread local
+ (``PR_LOCAL_THREAD``), global (``PR_GLOBAL_THREAD``) or global bound
+ (``PR_GLOBAL_BOUND_THREAD``). However, NSPR may override this
+ preference if necessary.
+``state``
+ Specifies whether the thread is joinable (``PR_JOINABLE_THREAD``) or
+ unjoinable (``PR_UNJOINABLE_THREAD``).
+``stackSize``
+ Specifies your preference for the size of the stack, in bytes,
+ associated with the newly created thread. If you pass zero in this
+ parameter, :ref:`PR_CreateThread` chooses the most favorable
+ machine-specific stack size.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, a pointer to the new thread. This pointer remains
+ valid until the thread returns from its root function.
+- If unsuccessful, (for example, if system resources are unavailable),
+ ``NULL``.
+
+
+Description
+-----------
+
+If you want the thread to start up waiting for the creator to do
+something, enter a lock before creating the thread and then have the
+thread's root function enter and exit the same lock. When you are ready
+for the thread to run, exit the lock. For more information on locks and
+thread synchronization, see `Introduction to
+NSPR <Introduction_to_NSPR>`__.
+
+If you want to detect the completion of the created thread, make it
+joinable. You can then use :ref:`PR_JoinThread` to synchronize the
+termination of another thread.
diff --git a/docs/nspr/reference/pr_createthreadpool.rst b/docs/nspr/reference/pr_createthreadpool.rst
new file mode 100644
index 0000000000..a4e6021991
--- /dev/null
+++ b/docs/nspr/reference/pr_createthreadpool.rst
@@ -0,0 +1,43 @@
+PR_CreateThreadPool
+===================
+
+Create a new hash table.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtpool.h>
+
+ NSPR_API(PRThreadPool *)
+ PR_CreateThreadPool(
+ PRInt32 initial_threads,
+ PRInt32 max_threads,
+ PRUint32 stacksize
+ );
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``initial_threads``
+ The number of threads to be created within this thread pool.
+``max_threads``
+ The limit on the number of threads that will be created to server the
+ thread pool.
+``stacksize``
+ Size of the stack allocated to each thread in the thread.
+
+
+Returns
+~~~~~~~
+
+Pointer to a :ref:`PRThreadPool` structure or ``NULL`` on error.
+
+
+Description
+~~~~~~~~~~~
diff --git a/docs/nspr/reference/pr_cwait.rst b/docs/nspr/reference/pr_cwait.rst
new file mode 100644
index 0000000000..9af79c42c3
--- /dev/null
+++ b/docs/nspr/reference/pr_cwait.rst
@@ -0,0 +1,63 @@
+PR_CWait
+========
+
+Wait for a notification that a monitor's state has changed.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prcmon.h>
+
+ PRStatus PR_CWait(
+ void *address,
+ PRIntervalTime timeout);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``address``
+ The address of the protected object--the same address previously
+ passed to :ref:`PR_CEnterMonitor`.
+``timeout``
+ The amount of time (in :ref:`PRIntervalTime` units) that the thread is
+ willing to wait for an explicit notification before being
+ rescheduled. If you specify ``PR_INTERVAL_NO_TIMEOUT``, the function
+ returns if and only if the object is notified.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+ - :ref:`PR_SUCCESS` indicates either that the monitored object has been
+ notified or that the interval specified in the timeout parameter has
+ been exceeded.
+ - :ref:`PR_FAILURE` indicates either that the monitor could not be located
+ in the cache or that the monitor was located and the calling thread
+ was not the thread that held the monitor's mutex.
+
+
+Description
+-----------
+
+Using the value specified in the ``address`` parameter to find a monitor
+in the monitor cache, :ref:`PR_CWait` waits for a notification that the
+monitor's state has changed. While the thread is waiting, it exits the
+monitor (just as if it had called :ref:`PR_CExitMonitor` as many times as
+it had called :ref:`PR_CEnterMonitor`). When the wait has finished, the
+thread regains control of the monitor's lock with the same entry count
+as before the wait began.
+
+The thread waiting on the monitor resumes execution when the monitor is
+notified (assuming the thread is the next in line to receive the notify)
+or when the interval specified in the ``timeout`` parameter has been
+exceeded. When the thread resumes execution, it is the caller's
+responsibility to test the state of the monitored data to determine the
+appropriate action.
diff --git a/docs/nspr/reference/pr_delete.rst b/docs/nspr/reference/pr_delete.rst
new file mode 100644
index 0000000000..f3d5523836
--- /dev/null
+++ b/docs/nspr/reference/pr_delete.rst
@@ -0,0 +1,38 @@
+PR_Delete
+=========
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRStatus PR_Delete(const char *name);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameter:
+
+``name``
+ The pathname of the file to be deleted.
+
+
+Returns
+~~~~~~~
+
+One of the following values:
+
+- If file is deleted successfully, ``PR_SUCCESS``.
+- If the file is not deleted, ``PR_FAILURE``.
+
+
+Description
+-----------
+
+:ref:`PR_Delete` deletes a file with the specified pathname ``name``. If
+the function fails, the error code can then be retrieved via
+:ref:`PR_GetError`.
diff --git a/docs/nspr/reference/pr_delete_.rst b/docs/nspr/reference/pr_delete_.rst
new file mode 100644
index 0000000000..c441801d25
--- /dev/null
+++ b/docs/nspr/reference/pr_delete_.rst
@@ -0,0 +1,37 @@
+PR_DELETE
+=========
+
+
+Allocates memory of a specified size from the heap.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prmem.h>
+
+ void PR_DELETE(_ptr);
+
+
+Parameter
+~~~~~~~~~
+
+``_ptr``
+ The address of memory to be returned to the heap. Must be an lvalue
+ (an expression that can appear on the left side of an assignment
+ statement).
+
+
+Returns
+~~~~~~~
+
+Nothing.
+
+
+Description
+-----------
+
+This macro returns allocated memory to the heap from the specified
+location and sets ``_ptr`` to ``NULL``.
diff --git a/docs/nspr/reference/pr_deletesemaphore.rst b/docs/nspr/reference/pr_deletesemaphore.rst
new file mode 100644
index 0000000000..bfef8e89eb
--- /dev/null
+++ b/docs/nspr/reference/pr_deletesemaphore.rst
@@ -0,0 +1,30 @@
+PR_DeleteSemaphore
+==================
+
+Removes a semaphore specified by name from the system.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <pripcsem.h>
+
+ NSPR_API(PRStatus) PR_DeleteSemaphore(const char *name);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``name``
+ The name of a semaphore that was previously created via a call to
+ :ref:`PR_OpenSemaphore`.
+
+
+Returns
+~~~~~~~
+
+:ref:`PRStatus`
diff --git a/docs/nspr/reference/pr_deletesharedmemory.rst b/docs/nspr/reference/pr_deletesharedmemory.rst
new file mode 100644
index 0000000000..3e39bd6e04
--- /dev/null
+++ b/docs/nspr/reference/pr_deletesharedmemory.rst
@@ -0,0 +1,32 @@
+PR_DeleteSharedMemory
+=====================
+
+Deletes a shared memory segment identified by name.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prshm.h>
+
+ NSPR_API( PRStatus )
+ PR_DeleteSharedMemory(
+ const char *name
+ );
+
+
+Parameter
+~~~~~~~~~
+
+The function has these parameter:
+
+shm
+ The handle returned from :ref:`PR_OpenSharedMemory`.
+
+
+Returns
+~~~~~~~
+
+:ref:`PRStatus`.
diff --git a/docs/nspr/reference/pr_destroycondvar.rst b/docs/nspr/reference/pr_destroycondvar.rst
new file mode 100644
index 0000000000..2347a2d389
--- /dev/null
+++ b/docs/nspr/reference/pr_destroycondvar.rst
@@ -0,0 +1,30 @@
+PR_DestroyCondVar
+=================
+
+Destroys a condition variable.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prcvar.h>
+
+ void PR_DestroyCondVar(PRCondVar *cvar);
+
+
+Parameter
+~~~~~~~~~
+
+:ref:`PR_DestroyCondVar` has one parameter:
+
+``cvar``
+ A pointer to the condition variable object to be destroyed.
+
+
+Description
+-----------
+
+Before calling :ref:`PR_DestroyCondVar`, the caller is responsible for
+ensuring that the condition variable is no longer in use.
diff --git a/docs/nspr/reference/pr_destroylock.rst b/docs/nspr/reference/pr_destroylock.rst
new file mode 100644
index 0000000000..9ed566efc5
--- /dev/null
+++ b/docs/nspr/reference/pr_destroylock.rst
@@ -0,0 +1,30 @@
+PR_DestroyLock
+==============
+
+Destroys a specified lock object.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prlock.h>
+
+ void PR_DestroyLock(PRLock *lock);
+
+
+Parameter
+~~~~~~~~~
+
+:ref:`PR_DestroyLock` has one parameter:
+
+``lock``
+ A pointer to a lock object.
+
+Caution
+-------
+
+The caller must ensure that no thread is currently in a lock-specific
+function. Locks do not provide self-referential protection against
+deletion.
diff --git a/docs/nspr/reference/pr_destroymonitor.rst b/docs/nspr/reference/pr_destroymonitor.rst
new file mode 100644
index 0000000000..4d9d9e58ac
--- /dev/null
+++ b/docs/nspr/reference/pr_destroymonitor.rst
@@ -0,0 +1,31 @@
+PR_DestroyMonitor
+=================
+
+Destroys a monitor object.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prmon.h>
+
+ void PR_DestroyMonitor(PRMonitor *mon);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``mon``
+ A reference to an existing structure of type :ref:`PRMonitor`.
+
+
+Description
+-----------
+
+The caller is responsible for guaranteeing that the monitor is no longer
+in use before calling :ref:`PR_DestroyMonitor`. There must be no thread
+(including the calling thread) in the monitor or waiting on the monitor.
diff --git a/docs/nspr/reference/pr_destroypollableevent.rst b/docs/nspr/reference/pr_destroypollableevent.rst
new file mode 100644
index 0000000000..526a90258d
--- /dev/null
+++ b/docs/nspr/reference/pr_destroypollableevent.rst
@@ -0,0 +1,33 @@
+PR_DestroyPollableEvent
+=======================
+
+Close the file descriptor associated with a pollable event and release
+related resources.
+
+
+Syntax
+------
+
+.. code::
+
+ NSPR_API(PRStatus) PR_DestroyPollableEvent(PRFileDesc *event);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``event``
+ Pointer to a :ref:`PRFileDesc` structure previously created via a call
+ to :ref:`PR_NewPollableEvent`.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful, ``PR_FAILURE``. The reason for the failure can be
+ retrieved via :ref:`PR_GetError`.
diff --git a/docs/nspr/reference/pr_detachsharedmemory.rst b/docs/nspr/reference/pr_detachsharedmemory.rst
new file mode 100644
index 0000000000..1954762970
--- /dev/null
+++ b/docs/nspr/reference/pr_detachsharedmemory.rst
@@ -0,0 +1,35 @@
+PR_DetachSharedMemory
+=====================
+
+Unmaps a shared memory segment identified by name.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prshm.h>
+
+ NSPR_API( PRStatus )
+ PR_DetachSharedMemory(
+ PRSharedMemory *shm,
+ void *addr
+ );
+
+
+Parameters
+~~~~~~~~~~
+
+The function has these parameters:
+
+shm
+ The handle returned from :ref:`PR_OpenSharedMemory`.
+addr
+ The address to which the shared memory segment is mapped.
+
+
+Returns
+~~~~~~~
+
+:ref:`PRStatus`.
diff --git a/docs/nspr/reference/pr_detachthread.rst b/docs/nspr/reference/pr_detachthread.rst
new file mode 100644
index 0000000000..c140f60c37
--- /dev/null
+++ b/docs/nspr/reference/pr_detachthread.rst
@@ -0,0 +1,57 @@
+PR_DetachThread
+===============
+
+.. container:: blockIndicator obsolete obsoleteHeader
+
+ | **Obsolete**
+ | This feature is obsolete. Although it may still work in some
+ browsers, its use is discouraged since it could be removed at any
+ time. Try to avoid using it.
+
+Disassociates a PRThread object from a native thread.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <pprthread.h>
+
+ void PR_DetachThread(void);
+
+
+Parameters
+~~~~~~~~~~
+
+PR_DetachThread has no parameters.
+
+
+Returns
+~~~~~~~
+
+The function returns nothing.
+
+
+Description
+-----------
+
+This function detaches the NSPR thread from the currently executing
+native thread. The thread object and all related data attached to it are
+destroyed. The exit process is invoked. The call returns after the NSPR
+thread object is destroyed.
+
+This call is needed only if you attached the thread using
+:ref:`PR_AttachThread`.
+
+.. note::
+
+ **Note**: As of NSPR release v3.0, :ref:`PR_AttachThread` and
+ :ref:`PR_DetachThread` are obsolete. A native thread not created by NSPR
+ is automatically attached the first time it calls an NSPR function,
+ and automatically detached when it exits.
+
+In NSPR release 19980529B and earlier, it is necessary for a native
+thread not created by NSPR to call :ref:`PR_AttachThread` before it calls
+any NSPR functions, and call :ref:`PR_DetachThread` when it is done calling
+NSPR functions.
diff --git a/docs/nspr/reference/pr_disableclockinterrupts.rst b/docs/nspr/reference/pr_disableclockinterrupts.rst
new file mode 100644
index 0000000000..9296395873
--- /dev/null
+++ b/docs/nspr/reference/pr_disableclockinterrupts.rst
@@ -0,0 +1,14 @@
+PR_DisableClockInterrupts
+=========================
+
+Disables timer signals used for preemptive scheduling.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prinit.h>
+
+ void PR_DisableClockInterrupts(void);
diff --git a/docs/nspr/reference/pr_dtoa.rst b/docs/nspr/reference/pr_dtoa.rst
new file mode 100644
index 0000000000..d2f5d73bac
--- /dev/null
+++ b/docs/nspr/reference/pr_dtoa.rst
@@ -0,0 +1,93 @@
+PR_dtoa
+=======
+
+Converts a floating point number to a string.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prdtoa.h>
+
+ PRStatus PR_dtoa(
+ PRFloat64 d,
+ PRIntn mode,
+ PRIntn ndigits,
+ PRIntn *decpt,
+ PRIntn *sign,
+ char **rve,
+ char *buf,
+ PRSize bufsz);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has these parameters:
+
+``d``
+ The floating point number to be converted to a string.
+``mode``
+ The type of conversion to employ.
+``ndigits``
+ The number of digits desired in the output string.
+``decpt``
+ A pointer to a memory location where the runtime will store the
+ offset, relative to the beginning of the output string, of the
+ conversion's decimal point.
+``sign``
+ A location where the runtime can store an indication that the
+ conversion was of a negative value.
+``*rve``
+ If not ``NULL`` this location is set to the address of the end of the
+ result.
+``buf``
+ The address of the buffer in which to store the result.
+``bufsz``
+ The size of the buffer provided to hold the result.
+
+Results
+~~~~~~~
+
+The principle output is the null-terminated string stored in ``buf``. If
+``rve`` is not ``NULL``, ``*rve`` is set to point to the end of the
+returned value.
+
+
+Description
+-----------
+
+This function converts the specified floating point number to a string,
+using the method specified by ``mode``. Possible modes are:
+
+``0``
+ Shortest string that yields ``d`` when read in and rounded to
+ nearest.
+``1``
+ Like 0, but with Steele & White stopping rule. For example, with IEEE
+ 754 arithmetic, mode 0 gives 1e23 whereas mode 1 gives
+ 9.999999999999999e22.
+``2``
+ ``max(1, ndigits)`` significant digits. This gives a return value
+ similar to that of ``ecvt``, except that trailing zeros are
+ suppressed.
+``3``
+ Through ``ndigits`` past the decimal point. This gives a return value
+ similar to that from ``fcvt``, except that trailing zeros are
+ suppressed, and ``ndigits`` can be negative.
+``4,5,8,9``
+ Same as modes 2 and 3, but using\ *left to right* digit generation.
+``6-9``
+ Same as modes 2 and 3, but do not try fast floating-point estimate
+ (if applicable).
+``all others``
+ Treated as mode 2.
+
+Upon return, the buffer specified by ``buf`` and ``bufsz`` contains the
+converted string. Trailing zeros are suppressed. Sufficient space is
+allocated to the return value to hold the suppressed trailing zeros.
+
+If the input parameter ``d`` is\ *+Infinity*,\ *-Infinity* or\ *NAN*,
+``*decpt`` is set to 9999.
diff --git a/docs/nspr/reference/pr_entermonitor.rst b/docs/nspr/reference/pr_entermonitor.rst
new file mode 100644
index 0000000000..501104e082
--- /dev/null
+++ b/docs/nspr/reference/pr_entermonitor.rst
@@ -0,0 +1,41 @@
+PR_EnterMonitor
+===============
+
+Enters the lock associated with a specified monitor.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prmon.h>
+
+ void PR_EnterMonitor(PRMonitor *mon);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``mon``
+ A reference to an existing structure of type :ref:`PRMonitor`.
+
+
+Description
+-----------
+
+When the calling thread returns, it will have acquired the monitor's
+lock. Attempts to acquire the lock for a monitor that is held by some
+other thread will result in the caller blocking. The operation is
+neither timed nor interruptible.
+
+If the monitor's entry count is greater than zero and the calling thread
+is recognized as the holder of the lock, :ref:`PR_EnterMonitor` increments
+the entry count by one and returns. If the entry count is greater than
+zero and the calling thread is not recognized as the holder of the lock,
+the thread is blocked until the entry count reaches zero. When the entry
+count reaches zero (or if it is already zero), the entry count is
+incremented by one and the calling thread is recorded as the lock's
+holder.
diff --git a/docs/nspr/reference/pr_enumerateaddrinfo.rst b/docs/nspr/reference/pr_enumerateaddrinfo.rst
new file mode 100644
index 0000000000..d978766da6
--- /dev/null
+++ b/docs/nspr/reference/pr_enumerateaddrinfo.rst
@@ -0,0 +1,58 @@
+PR_EnumerateAddrInfo
+====================
+
+
+Enumerates each of the possible network addresses of a ``PRAddrInfo``
+structure, acquired from :ref:`PR_GetAddrInfoByName`.
+
+
+Syntax
+~~~~~~
+
+.. code::
+
+ #include <prnetdb.h>
+
+ void *PR_EnumerateAddrInfo(
+ void *enumPtr,
+ const PRAddrInfo *addrInfo,
+ PRUint16 port,
+ PRNetAddr *result);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``enumPtr``
+ The index pointer of the enumeration. To begin an enumeration, this
+ argument is set to ``NULL``. To continue an enumeration (thereby
+ getting successive addresses from the ``PRAddrInfo`` structure), the
+ value should be set to the function's last returned value. The
+ enumeration is complete when a value of ``NULL`` is returned.
+``addrInfo``
+ A pointer to a ``PRAddrInfo`` structure returned by
+ :ref:`PR_GetAddrInfoByName`.
+``port``
+ The port number to be assigned as part of the :ref:`PRNetAddr`
+ structure. This parameter is not checked for validity.
+``result``
+ On input, a pointer to a :ref:`PRNetAddr` structure. On output, this
+ structure is filled in by the runtime if the result of the call is
+ not ``NULL``.
+
+
+Returns
+~~~~~~~
+
+The function returns the value you should specify in the ``enumPtr``
+parameter for the next call of the enumerator. If the function returns
+``NULL``, the enumeration is ended.
+
+
+Description
+-----------
+
+:ref:`PR_EnumerateAddrInfo` is a stateless enumerator. The principle input,
+the ``PRAddrInfo`` structure, is not modified.
diff --git a/docs/nspr/reference/pr_enumeratehostent.rst b/docs/nspr/reference/pr_enumeratehostent.rst
new file mode 100644
index 0000000000..a808730330
--- /dev/null
+++ b/docs/nspr/reference/pr_enumeratehostent.rst
@@ -0,0 +1,61 @@
+PR_EnumerateHostEnt
+===================
+
+Evaluates each of the possible addresses of a :ref:`PRHostEnt` structure,
+acquired from :ref:`PR_GetHostByName` or :ref:`PR_GetHostByAddr`.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prnetdb.h>
+
+ PRIntn PR_EnumerateHostEnt(
+ PRIntn enumIndex,
+ const PRHostEnt *hostEnt,
+ PRUint16 port,
+ PRNetAddr *address);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``enumIndex``
+ The index of the enumeration. To begin an enumeration, this argument
+ is set to zero. To continue an enumeration (thereby getting
+ successive addresses from the host entry structure), the value should
+ be set to the function's last returned value. The enumeration is
+ complete when a value of zero is returned.
+``hostEnt``
+ A pointer to a :ref:`PRHostEnt` structure obtained from
+ :ref:`PR_GetHostByName` or :ref:`PR_GetHostByAddr`.
+``port``
+ The port number to be assigned as part of the :ref:`PRNetAddr`
+ structure. This parameter is not checked for validity.
+``address``
+ On input, a pointer to a :ref:`PRNetAddr` structure. On output, this
+ structure is filled in by the runtime if the result of the call is
+ greater than 0.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, the function returns the value you should specify in
+ the ``enumIndex`` parameter for the next call of the enumerator. If
+ the function returns 0, the enumeration is ended.
+- If unsuccessful, the function returns -1. You can retrieve the reason
+ for the failure by calling :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+:ref:`PR_EnumerateHostEnt` is a stateless enumerator. The principle input,
+the :ref:`PRHostEnt` structure, is not modified.
diff --git a/docs/nspr/reference/pr_exitmonitor.rst b/docs/nspr/reference/pr_exitmonitor.rst
new file mode 100644
index 0000000000..3cb8463e85
--- /dev/null
+++ b/docs/nspr/reference/pr_exitmonitor.rst
@@ -0,0 +1,44 @@
+PR_ExitMonitor
+==============
+
+Decrements the entry count associated with a specified monitor and, if
+the entry count reaches zero, releases the monitor's lock.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prmon.h>
+
+ PRStatus PR_ExitMonitor(PRMonitor *mon);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``mon``
+ A reference to an existing structure of type :ref:`PRMonitor`. The
+ monitor object referenced must be one for which the calling thread
+ currently holds the lock.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful (the calling thread has not entered the monitor),
+ ``PR_FAILURE``.
+
+
+Description
+-----------
+
+If the decremented entry count is zero, :ref:`PR_ExitMonitor` releases the
+monitor's lock. Threads that were blocked trying to enter the monitor
+will be rescheduled.
diff --git a/docs/nspr/reference/pr_explodetime.rst b/docs/nspr/reference/pr_explodetime.rst
new file mode 100644
index 0000000000..066ccfcd01
--- /dev/null
+++ b/docs/nspr/reference/pr_explodetime.rst
@@ -0,0 +1,46 @@
+PR_ExplodeTime
+==============
+
+Converts an absolute time to a clock/calendar time.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtime.h>
+
+ void PR_ExplodeTime(
+ PRTime usecs,
+ PRTimeParamFn params,
+ PRExplodedTime *exploded);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has these parameters:
+
+``usecs``
+ An absolute time in the :ref:`PRTime` format.
+``params``
+ A time parameter callback function.
+``exploded``
+ A pointer to a location where the converted time can be stored. This
+ location must be preallocated by the caller.
+
+
+Returns
+~~~~~~~
+
+Nothing; the buffer pointed to by ``exploded`` is filled with the
+exploded time.
+
+
+Description
+-----------
+
+This function converts the specified absolute time to a clock/calendar
+time in the specified time zone. Upon return, the location pointed to by
+the exploded parameter contains the converted time value.
diff --git a/docs/nspr/reference/pr_exportfilemapasstring.rst b/docs/nspr/reference/pr_exportfilemapasstring.rst
new file mode 100644
index 0000000000..649a373a34
--- /dev/null
+++ b/docs/nspr/reference/pr_exportfilemapasstring.rst
@@ -0,0 +1,47 @@
+PR_ExportFileMapAsString
+========================
+
+Creates a string identifying a :ref:`PRFileMap`.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prshma.h>
+
+ NSPR_API( PRStatus )
+ PR_ExportFileMapAsString(
+ PRFileMap *fm,
+ PRSize bufsize,
+ char *buf
+ );
+
+#. define PR_FILEMAP_STRING_BUFSIZE 128
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``fm``
+ A pointer to the :ref:`PRFileMap` to be represented as a string.
+``bufsize``
+ sizeof(buf)
+``buf``
+ A pointer to abuffer of length ``PR_FILEMAP_STRING_BUFSIZE``.
+
+
+Returns
+~~~~~~~
+
+:ref:`PRStatus`
+
+
+Description
+-----------
+
+Creates an identifier, as a string, from a :ref:`PRFileMap` object
+previously created with :ref:`PR_OpenAnonFileMap`.
diff --git a/docs/nspr/reference/pr_extern.rst b/docs/nspr/reference/pr_extern.rst
new file mode 100644
index 0000000000..64d38654b6
--- /dev/null
+++ b/docs/nspr/reference/pr_extern.rst
@@ -0,0 +1,30 @@
+PR_EXTERN
+=========
+
+Used to define the prototypes for functions or variables that are to be
+exported from a shared library.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtypes.h>
+
+ PR_EXTERN(type)prototype
+
+
+Description
+-----------
+
+:ref:`PR_EXTERN` is used to define externally visible routines and globals.
+For syntax details for each platform, see
+`prtypes.h <https://dxr.mozilla.org/mozilla-central/source/nsprpub/pr/include/prtypes.h>`__.
+The macro includes the proper specifications to declare the target
+``extern`` and set up other required linkages.
+
+.. warning::
+
+ **Warning**: Some platforms do not allow the use of the underscore
+ character (_) as the first character of an exported symbol.
diff --git a/docs/nspr/reference/pr_familyinet.rst b/docs/nspr/reference/pr_familyinet.rst
new file mode 100644
index 0000000000..251286257f
--- /dev/null
+++ b/docs/nspr/reference/pr_familyinet.rst
@@ -0,0 +1,23 @@
+PR_FamilyInet
+=============
+
+Gets the value of the address family for Internet Protocol.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prnetdb.h>
+
+ PRUint16 PR_FamilyInet(void);
+
+
+Returns
+~~~~~~~
+
+The value of the address family for Internet Protocol. This is usually
+``PR_AF_INET``, but can also be ``PR_AF_INET6`` if IPv6 is enabled. The
+returned value can be assigned to the ``inet.family`` field of a
+:ref:`PRNetAddr` object.
diff --git a/docs/nspr/reference/pr_findsymbol.rst b/docs/nspr/reference/pr_findsymbol.rst
new file mode 100644
index 0000000000..c30ccfabfe
--- /dev/null
+++ b/docs/nspr/reference/pr_findsymbol.rst
@@ -0,0 +1,52 @@
+PR_FindSymbol
+=============
+
+``PR_FindSymbol()`` will return an untyped reference to a symbol in a
+particular library given the identity of the library and a textual
+representation of the symbol in question.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prlink.h>
+
+ void* PR_FindSymbol (
+ PRLibrary *lib,
+ const char *name);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has these parameters:
+
+``lib``
+ A valid reference to a loaded library, as returned by
+ :ref:`PR_LoadLibrary`, or ``NULL``.
+``name``
+ A textual representation of the symbol to resolve.
+
+
+Returns
+~~~~~~~
+
+An untyped pointer.
+
+
+Description
+-----------
+
+This function finds and returns an untyped reference to the specified
+symbol in the specified library. If the lib parameter is ``NULL``, all
+libraries known to the runtime and the main program are searched in an
+unspecified order.
+
+Use this function to look up functions or data symbols in a shared
+library. Getting a pointer to a symbol in a library does indicate that
+the library is available when the search was made. The runtime does
+nothing to ensure the continued validity of the symbol. If the library
+is unloaded, for instance, the results of any :ref:`PR_FindSymbol` calls
+become invalid as well.
diff --git a/docs/nspr/reference/pr_findsymbolandlibrary.rst b/docs/nspr/reference/pr_findsymbolandlibrary.rst
new file mode 100644
index 0000000000..0df683a76c
--- /dev/null
+++ b/docs/nspr/reference/pr_findsymbolandlibrary.rst
@@ -0,0 +1,59 @@
+PR_FindSymbolAndLibrary
+=======================
+
+Finds a symbol in one of the currently loaded libraries, and returns
+both the symbol and the library in which it was found.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prlink.h>
+
+ void* PR_FindSymbolAndLibrary (
+ const char *name,
+ PRLibrary **lib);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has these parameters:
+
+``name``
+ The textual representation of the symbol to locate.
+``lib``
+ A reference to a location at which the runtime will store the library
+ in which the symbol was discovered. This location must be
+ pre-allocated by the caller.
+
+
+Returns
+~~~~~~~
+
+If successful, returns a non-``NULL`` pointer to the found symbol, and
+stores a pointer to the library in which it was found at the location
+pointed to by lib.
+
+If the symbol could not be found, returns ``NULL``.
+
+
+Description
+-----------
+
+This function finds the specified symbol in one of the currently loaded
+libraries. It returns the address of the symbol. Upon return, the
+location pointed to by the parameter lib contains a pointer to the
+library that contains that symbol. The location must be pre-allocated by
+the caller.
+
+The function returns ``NULL`` if no such function can be found. The
+order in which the known libraries are searched in not specified. This
+function is equivalent to calling first :ref:`PR_LoadLibrary`, then
+:ref:`PR_FindSymbol`.
+
+The identity returned from this function must be the target of a
+:ref:`PR_UnloadLibrary` in order to return the runtime to its original
+state.
diff --git a/docs/nspr/reference/pr_free.rst b/docs/nspr/reference/pr_free.rst
new file mode 100644
index 0000000000..577955d205
--- /dev/null
+++ b/docs/nspr/reference/pr_free.rst
@@ -0,0 +1,33 @@
+PR_Free
+=======
+
+Frees allocated memory in the heap.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prmem.h>
+
+ void PR_Free(void *ptr);
+
+
+Parameter
+~~~~~~~~~
+
+``ptr``
+ A pointer to the memory to be freed.
+
+
+Returns
+~~~~~~~
+
+Nothing.
+
+
+Description
+-----------
+
+This function frees the memory addressed by ``ptr`` in the heap.
diff --git a/docs/nspr/reference/pr_freeaddrinfo.rst b/docs/nspr/reference/pr_freeaddrinfo.rst
new file mode 100644
index 0000000000..bc004c037e
--- /dev/null
+++ b/docs/nspr/reference/pr_freeaddrinfo.rst
@@ -0,0 +1,32 @@
+PR_FreeAddrInfo
+===============
+
+
+Destroys the ``PRAddrInfo`` structure returned by
+:ref:`PR_GetAddrInfoByName`.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prnetdb.h>
+
+ void PR_EnumerateAddrInfo(PRAddrInfo *addrInfo);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``addrInfo``
+ A pointer to a ``PRAddrInfo`` structure returned by a successful call
+ to :ref:`PR_GetAddrInfoByName`.
+
+
+Returns
+~~~~~~~
+
+The function doesn't return anything.
diff --git a/docs/nspr/reference/pr_freeif.rst b/docs/nspr/reference/pr_freeif.rst
new file mode 100644
index 0000000000..3d8f9baba5
--- /dev/null
+++ b/docs/nspr/reference/pr_freeif.rst
@@ -0,0 +1,34 @@
+PR_FREEIF
+=========
+
+Conditionally frees allocated memory.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prmem.h>
+
+ void PR_FREEIF(_ptr);
+
+
+Parameter
+~~~~~~~~~
+
+``_ptr``
+ The address of memory to be returned to the heap.
+
+
+Returns
+~~~~~~~
+
+Nothing.
+
+
+Description
+-----------
+
+This macro returns memory to the heap when ``_ptr`` is not ``NULL``. If
+``_ptr`` is ``NULL``, the macro has no effect.
diff --git a/docs/nspr/reference/pr_freelibraryname.rst b/docs/nspr/reference/pr_freelibraryname.rst
new file mode 100644
index 0000000000..2091277c79
--- /dev/null
+++ b/docs/nspr/reference/pr_freelibraryname.rst
@@ -0,0 +1,39 @@
+PR_FreeLibraryName
+==================
+
+Frees memory allocated by NSPR for library names and path names.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prlink.h>
+
+ void PR_FreeLibraryName(char *mem);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has this parameter:
+
+``mem``
+ A reference to a character array that was previously allocated by the
+ dynamic library runtime.
+
+
+Returns
+~~~~~~~
+
+Nothing.
+
+
+Description
+-----------
+
+This function deletes the storage allocated by the runtime in the
+functions described previously. It is important to use this function to
+rather than calling directly into ``malloc`` in order to isolate the
+runtime's semantics regarding storage management.
diff --git a/docs/nspr/reference/pr_getaddrinfobyname.rst b/docs/nspr/reference/pr_getaddrinfobyname.rst
new file mode 100644
index 0000000000..fd428124da
--- /dev/null
+++ b/docs/nspr/reference/pr_getaddrinfobyname.rst
@@ -0,0 +1,48 @@
+PR_GetAddrInfoByName
+====================
+
+Looks up a host by name. Equivalent to ``getaddrinfo(host, NULL, ...)``
+of RFC 3493.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prnetdb.h>
+
+ PRAddrInfo *PR_GetAddrInfoByName(
+ const char *hostname,
+ PRUint16 af,
+ PRIntn flags);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``hostname``
+ The character string defining the host name of interest.
+``af``
+ The address family. May be ``PR_AF_UNSPEC`` or ``PR_AF_INET``.
+``flags``
+ May be either ``PR_AI_ADDRCONFIG`` or
+ ``PR_AI_ADDRCONFIG | PR_AI_NOCANONNAME``. Include
+ ``PR_AI_NOCANONNAME`` to suppress the determination of the canonical
+ name corresponding to ``hostname``
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, a pointer to the opaque ``PRAddrInfo`` structure
+ containing the results of the host lookup. Use
+ :ref:`PR_EnumerateAddrInfo` to inspect the :ref:`PRNetAddr` values stored
+ in this structure. When no longer needed, this pointer must be
+ destroyed with a call to :ref:`PR_FreeAddrInfo`.
+- If unsuccessful, ``NULL``. You can retrieve the reason for the
+ failure by calling :ref:`PR_GetError`.
diff --git a/docs/nspr/reference/pr_getcanonnamefromaddrinfo.rst b/docs/nspr/reference/pr_getcanonnamefromaddrinfo.rst
new file mode 100644
index 0000000000..b84fe84604
--- /dev/null
+++ b/docs/nspr/reference/pr_getcanonnamefromaddrinfo.rst
@@ -0,0 +1,33 @@
+PR_GetCanonNameFromAddrInfo
+===========================
+
+Extracts the canonical name of the hostname passed to
+:ref:`PR_GetAddrInfoByName`.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prnetdb.h>
+
+ const char *PR_GetCanonNameFromAddrInfo(const PRAddrInfo *addrInfo);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``addrInfo``
+ A pointer to a ``PRAddrInfo`` structure returned by a successful call
+ to :ref:`PR_GetAddrInfoByName`.
+
+
+Returns
+~~~~~~~
+
+The function returns a const pointer to the canonical hostname stored in
+the given ``PRAddrInfo`` structure. This pointer is invalidated once the
+``PRAddrInfo`` structure is destroyed by a call to :ref:`PR_FreeAddrInfo`.
diff --git a/docs/nspr/reference/pr_getconnectstatus.rst b/docs/nspr/reference/pr_getconnectstatus.rst
new file mode 100644
index 0000000000..a8753e546e
--- /dev/null
+++ b/docs/nspr/reference/pr_getconnectstatus.rst
@@ -0,0 +1,48 @@
+PR_GetConnectStatus
+===================
+
+Get the completion status of a nonblocking connection.
+
+
+Syntax
+------
+
+.. code::
+
+ PRStatus PR_GetConnectStatus(const PRPollDesc *pd);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``pd``
+ A pointer to a ``PRPollDesc`` satructure whose ``fd`` field is the
+ socket and whose ``in_flags`` field must contain ``PR_POLL_WRITE``
+ and ``PR_POLL_EXCEPT``.
+
+
+Returns
+~~~~~~~
+
+The function returns one of these values:
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful, ``PR_FAILURE``. The reason for the failure can be
+ retrieved via :ref:`PR_GetError`.
+
+If :ref:`PR_GetError` returns ``PR_IN_PROGRESS_ERROR``, the nonblocking
+connection is still in progress and has not completed yet.Other errors
+indicate that the connection has failed.
+
+
+Description
+-----------
+
+After :ref:`PR_Connect` on a nonblocking socket fails with
+``PR_IN_PROGRESS_ERROR``, you may wait for the connection to complete by
+calling :ref:`PR_Poll` on the socket with the ``in_flags``
+``PR_POLL_WRITE`` \| ``PR_POLL_EXCEPT``. When :ref:`PR_Poll` returns, call
+:ref:`PR_GetConnectStatus` on the socket to determine whether the
+nonblocking connect has succeeded or failed.
diff --git a/docs/nspr/reference/pr_getcurrentthread.rst b/docs/nspr/reference/pr_getcurrentthread.rst
new file mode 100644
index 0000000000..b198ce1a6a
--- /dev/null
+++ b/docs/nspr/reference/pr_getcurrentthread.rst
@@ -0,0 +1,32 @@
+PR_GetCurrentThread
+===================
+
+Returns the current thread object for the currently running code.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prthread.h>
+
+ PRThread* PR_GetCurrentThread(void);
+
+
+Returns
+~~~~~~~
+
+Always returns a valid reference to the calling thread--a self-identity.
+
+
+Description
+~~~~~~~~~~~
+
+The currently running thread may discover its own identity by calling
+:ref:`PR_GetCurrentThread`.
+
+.. note::
+
+ **Note**: This is the only safe way to establish the identity of a
+ thread. Creation and enumeration are both subject to race conditions.
diff --git a/docs/nspr/reference/pr_getdefaultiomethods.rst b/docs/nspr/reference/pr_getdefaultiomethods.rst
new file mode 100644
index 0000000000..674dea0366
--- /dev/null
+++ b/docs/nspr/reference/pr_getdefaultiomethods.rst
@@ -0,0 +1,31 @@
+PR_GetDefaultIOMethods
+======================
+
+Gets the default I/O methods table.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ const PRIOMethods* PR_GetDefaultIOMethods(void);
+
+
+Returns
+~~~~~~~
+
+If successful, the function returns a pointer to a :ref:`PRIOMethods`
+structure.
+
+
+Description
+-----------
+
+After using :ref:`PR_GetDefaultIOMethods` to identify the default I/O
+methods table, you can select elements from that table with which to
+build your own layer's methods table. You may not modify the default I/O
+methods table directly. You can pass your own layer's methods table to
+:ref:`PR_CreateIOLayerStub` to create your new layer.
diff --git a/docs/nspr/reference/pr_getdesctype.rst b/docs/nspr/reference/pr_getdesctype.rst
new file mode 100644
index 0000000000..66cbf58397
--- /dev/null
+++ b/docs/nspr/reference/pr_getdesctype.rst
@@ -0,0 +1,58 @@
+PR_GetDescType
+==============
+
+Describes what type of file is referenced by a specified file
+descriptor.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRDescType PR_GetDescType(PRFileDesc *file);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``file``
+ A pointer to a :ref:`PRFileDesc` object whose descriptor type is to be
+ returned.
+
+
+Returns
+~~~~~~~
+
+The function returns a ``PRDescType`` enumeration constant that
+describes the type of file.
+
+
+Description
+-----------
+
+The ``PRDescType`` enumeration is defined as follows:
+
+.. code::
+
+ typedef enum PRDescType {
+ PR_DESC_FILE = 1,
+ PR_DESC_SOCKET_TCP = 2,
+ PR_DESC_SOCKET_UDP = 3,
+ PR_DESC_LAYERED = 4
+ } PRDescType;
+
+The enumeration has the following enumerators:
+
+``PR_DESC_FILE``
+ The :ref:`PRFileDesc` object represents a normal file.
+``PR_DESC_SOCKET_TCP``
+ The :ref:`PRFileDesc` object represents a TCP socket.
+``PR_DESC_SOCKET_UDP``
+ The :ref:`PRFileDesc` object represents a UDP socket.
+``PR_DESC_LAYERED``
+ The :ref:`PRFileDesc` object is a layered file descriptor.
diff --git a/docs/nspr/reference/pr_geterror.rst b/docs/nspr/reference/pr_geterror.rst
new file mode 100644
index 0000000000..9a50d8aacc
--- /dev/null
+++ b/docs/nspr/reference/pr_geterror.rst
@@ -0,0 +1,23 @@
+PR_GetError
+===========
+
+Returns the current thread's last set platform-independent error code.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prerror.h>
+
+ PRErrorCode PR_GetError(void)
+
+
+Returns
+~~~~~~~
+
+The value returned is a 32-bit number. NSPR provides no direct
+interpretation of the number's value. NSPR does use :ref:`PR_SetError` to
+set error numbers defined in `Error
+Codes <NSPR_Error_Handling#Error_Code>`__.
diff --git a/docs/nspr/reference/pr_geterrortext.rst b/docs/nspr/reference/pr_geterrortext.rst
new file mode 100644
index 0000000000..e8a0508b8a
--- /dev/null
+++ b/docs/nspr/reference/pr_geterrortext.rst
@@ -0,0 +1,32 @@
+PR_GetErrorText
+===============
+
+Copies the current thread's current error text without altering the text
+as stored in the thread's context.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prerror.h>
+
+ PRInt32 PR_GetErrorText(char *text);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has one parameter:
+
+``text``
+ On output, the array pointed to contains the thread's current error
+ text.
+
+
+Returns
+-------
+
+The actual number of bytes copied. If the result is zero, ``text`` is
+unaffected.
diff --git a/docs/nspr/reference/pr_geterrortextlength.rst b/docs/nspr/reference/pr_geterrortextlength.rst
new file mode 100644
index 0000000000..c771e4c1ee
--- /dev/null
+++ b/docs/nspr/reference/pr_geterrortextlength.rst
@@ -0,0 +1,22 @@
+PR_GetErrorTextLength
+=====================
+
+Gets the length of the error text.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prerror.h>
+
+ PRInt32 PR_GetErrorTextLength(void)
+
+
+Returns
+~~~~~~~
+
+If a zero is returned, no error text is currently set. Otherwise, the
+value returned is sufficient to contain the error text currently
+available.
diff --git a/docs/nspr/reference/pr_getfileinfo.rst b/docs/nspr/reference/pr_getfileinfo.rst
new file mode 100644
index 0000000000..1d1daf5427
--- /dev/null
+++ b/docs/nspr/reference/pr_getfileinfo.rst
@@ -0,0 +1,55 @@
+PR_GetFileInfo
+==============
+
+Gets information about a file with a specified pathname. File size is
+expressed as a 32-bit integer.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRStatus PR_GetFileInfo(
+ const char *fn,
+ PRFileInfo *info);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``fn``
+ The pathname of the file to get information about.
+``info``
+ A pointer to a file information object (see :ref:`PRFileInfo`). On
+ output, :ref:`PR_GetFileInfo` writes information about the given file to
+ the file information object.
+
+
+Returns
+~~~~~~~
+
+One of the following values:
+
+- If the file information is successfully obtained, ``PR_SUCCESS``.
+- If the file information is not successfully obtained, ``PR_FAILURE``.
+
+
+Description
+-----------
+
+:ref:`PR_GetFileInfo` stores information about the file with the specified
+pathname in the :ref:`PRFileInfo` structure pointed to by ``info``. The
+file size is returned as an unsigned 32-bit integer.
+
+
+See Also
+--------
+
+For the 64-bit version of this function, see :ref:`PR_GetFileInfo64`. To
+get equivalent information on a file that's already open, use
+:ref:`PR_GetOpenFileInfo`.
diff --git a/docs/nspr/reference/pr_getfileinfo64.rst b/docs/nspr/reference/pr_getfileinfo64.rst
new file mode 100644
index 0000000000..f57217103a
--- /dev/null
+++ b/docs/nspr/reference/pr_getfileinfo64.rst
@@ -0,0 +1,55 @@
+PR_GetFileInfo64
+================
+
+Gets information about a file with a specified pathname. File size is
+expressed as a 64-bit integer.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRStatus PR_GetFileInfo64(
+ const char *fn,
+ PRFileInfo64 *info);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``fn``
+ The pathname of the file to get information about.
+``info``
+ A pointer to a 64-bit file information object (see :ref:`PRFileInfo64`).
+ On output, :ref:`PR_GetFileInfo64` writes information about the given
+ file to the file information object.
+
+
+Returns
+~~~~~~~
+
+One of the following values:
+
+- If the file information is successfully obtained, ``PR_SUCCESS``.
+- If the file information is not successfully obtained, ``PR_FAILURE``.
+
+
+Description
+-----------
+
+:ref:`PR_GetFileInfo64` stores information about the file with the
+specified pathname in the :ref:`PRFileInfo64` structure pointed to by
+``info``. The file size is returned as an unsigned 64-bit integer.
+
+
+See Also
+--------
+
+For the 32-bit version of this function, see :ref:`PR_GetFileInfo`. To get
+equivalent information on a file that's already open, use
+:ref:`PR_GetOpenFileInfo64`.
diff --git a/docs/nspr/reference/pr_gethostbyaddr.rst b/docs/nspr/reference/pr_gethostbyaddr.rst
new file mode 100644
index 0000000000..7140c5c5dd
--- /dev/null
+++ b/docs/nspr/reference/pr_gethostbyaddr.rst
@@ -0,0 +1,57 @@
+PR_GetHostByAddr
+================
+
+Looks up a host entry by its network address.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prnetdb.h>
+
+ PRStatus PR_GetHostByAddr(
+ const PRNetAddr *hostaddr,
+ char *buf,
+ PRIntn bufsize,
+ PRHostEnt *hostentry);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``hostaddr``
+ A pointer to the IP address of host in question.
+``buf``
+ A pointer to a buffer, allocated by the caller, that is filled in
+ with host data on output. All of the pointers in the ``hostentry``
+ structure point to data saved in this buffer. This buffer is
+ referenced by the runtime during a call to :ref:`PR_EnumerateHostEnt`.
+``bufsize``
+ Number of bytes in the ``buf`` parameter. The buffer must be at least
+ :ref:`PR_NETDB_BUF_SIZE` bytes.
+``hostentry``
+ This structure is allocated by the caller. On output, this structure
+ is filled in by the runtime if the function returns ``PR_SUCCESS``.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful, ``PR_FAILURE``. You can retrieve the reason for the
+ failure by calling :ref:`PR_GetError`.
+
+
+Description
+~~~~~~~~~~~
+
+:ref:`PR_GetHostByAddr` is used to perform reverse lookups of network
+addresses. That is, given a valid network address (of type
+:ref:`PRNetAddr`), :ref:`PR_GetHostByAddr` discovers the address' primary
+name, any aliases, and any other network addresses for the same host.
diff --git a/docs/nspr/reference/pr_gethostbyname.rst b/docs/nspr/reference/pr_gethostbyname.rst
new file mode 100644
index 0000000000..f1a1a4a108
--- /dev/null
+++ b/docs/nspr/reference/pr_gethostbyname.rst
@@ -0,0 +1,48 @@
+PR_GetHostByName
+================
+
+Looks up a host by name.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prnetdb.h>
+
+ PRStatus PR_GetHostByName(
+ const char *hostname,
+ char *buf,
+ PRIntn bufsize,
+ PRHostEnt *hostentry);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``hostname``
+ The character string defining the host name of interest.
+``buf``
+ A pointer to a buffer, allocated by the caller, that is filled in
+ with host data on output. All of the pointers in the ``hostentry``
+ structure point to data saved in this buffer. This buffer is
+ referenced by the runtime during a call to :ref:`PR_EnumerateHostEnt`.
+``bufsize``
+ Number of bytes in the ``buf`` parameter. The buffer must be at least
+ :ref:`PR_NETDB_BUF_SIZE` bytes.
+``hostentry``
+ This structure is allocated by the caller. On output, this structure
+ is filled in by the runtime if the function returns ``PR_SUCCESS``.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful, ``PR_FAILURE``. You can retrieve the reason for the
+ failure by calling :ref:`PR_GetError`.
diff --git a/docs/nspr/reference/pr_getidentitieslayer.rst b/docs/nspr/reference/pr_getidentitieslayer.rst
new file mode 100644
index 0000000000..9a15f9b60d
--- /dev/null
+++ b/docs/nspr/reference/pr_getidentitieslayer.rst
@@ -0,0 +1,48 @@
+PR_GetIdentitiesLayer
+=====================
+
+Finds the layer with the specified identity in the specified stack of
+layers.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRFileDesc* PR_GetIdentitiesLayer(
+ PRFileDesc* stack,
+ PRDescIdentity id);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``stack``
+ A pointer to a :ref:`PRFileDesc` object that is a layer in a stack of
+ layers.
+``id``
+ The identity of the specified layer.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, a pointer to a file descriptor of the layer with the
+ specified identity in the given stack of layers.
+- If not successful, ``NULL``.
+
+
+Description
+-----------
+
+The stack of layers to be searched is specified by the fd parameter,
+which is a layer in the stack. Both the layers underneath fd and the
+layers above fd are searched to find the layer with the specified
+identity.
diff --git a/docs/nspr/reference/pr_getinheritedfilemap.rst b/docs/nspr/reference/pr_getinheritedfilemap.rst
new file mode 100644
index 0000000000..62d93058f4
--- /dev/null
+++ b/docs/nspr/reference/pr_getinheritedfilemap.rst
@@ -0,0 +1,44 @@
+PR_GetInheritedFileMap
+======================
+
+Imports a :ref:`PRFileMap` previously exported by my parent process via
+``PR_CreateProcess``.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prshma.h>
+
+ NSPR_API( PRFileMap *)
+ PR_GetInheritedFileMap(
+ const char *shmname
+ );
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``shmname``
+ The name provided to :ref:`PR_ProcessAttrSetInheritableFileMap`.
+
+
+Returns
+~~~~~~~
+
+Pointer to :ref:`PRFileMap` or ``NULL`` on error.
+
+
+Description
+-----------
+
+:ref:`PR_GetInheritedFileMap` retrieves a PRFileMap object exported from
+its parent process via ``PR_CreateProcess``.
+
+.. note::
+
+ **Note:** This function is not implemented.
diff --git a/docs/nspr/reference/pr_getlayersidentity.rst b/docs/nspr/reference/pr_getlayersidentity.rst
new file mode 100644
index 0000000000..88a06602bc
--- /dev/null
+++ b/docs/nspr/reference/pr_getlayersidentity.rst
@@ -0,0 +1,30 @@
+PR_GetLayersIdentity
+====================
+
+Gets the unique identity for the layer of the specified file descriptor.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRDescIdentity PR_GetLayersIdentity(PRFileDesc* fd);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``fd``
+ A pointer to a file descriptor.
+
+
+Returns
+~~~~~~~
+
+If successful, the function returns the :ref:`PRDescIdentity` for the layer
+of the specified file descriptor.
diff --git a/docs/nspr/reference/pr_getlibraryname.rst b/docs/nspr/reference/pr_getlibraryname.rst
new file mode 100644
index 0000000000..7bbe3b05ef
--- /dev/null
+++ b/docs/nspr/reference/pr_getlibraryname.rst
@@ -0,0 +1,53 @@
+PR_GetLibraryName
+=================
+
+Constructs a full library path name.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prlink.h>
+
+ char* PR_GetLibraryName (
+ const char *dir,
+ const char *lib);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has these parameters:
+
+``dir``
+ A ``NULL``-terminated string representing the path name of the
+ library, as returned by :ref:`PR_GetLibraryPath`.
+``lib``
+ The leaf name of the library of interest.
+
+
+Returns
+~~~~~~~
+
+If successful, returns a new character string containing a constructed
+path name. In case of error, returns ``NULL``.
+
+
+Description
+-----------
+
+This function constructs a full path name from the specified directory
+name and library name. The constructed path name refers to the actual
+dynamically loaded library. It is suitable for use in the
+:ref:`PR_LoadLibrary` call.
+
+This function does not test for existence of the specified file, it just
+constructs the full filename. The way the name is constructed is system
+dependent.
+
+If sufficient storage cannot be allocated to contain the constructed
+path name, the function returns ``NULL``. Storage for the result is
+allocated by the runtime and becomes the responsibility of the caller.
+When it is no longer used, free it using :ref:`PR_FreeLibraryName`.
diff --git a/docs/nspr/reference/pr_getlibrarypath.rst b/docs/nspr/reference/pr_getlibrarypath.rst
new file mode 100644
index 0000000000..c604edc9cd
--- /dev/null
+++ b/docs/nspr/reference/pr_getlibrarypath.rst
@@ -0,0 +1,37 @@
+PR_GetLibraryPath
+=================
+
+Retrieves the current default library path.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prlink.h>
+
+ char* PR_GetLibraryPath(void);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has no parameters.
+
+
+Returns
+~~~~~~~
+
+A copy of the default library pathname string. In case of error, returns
+NULL.
+
+
+Description
+-----------
+
+This function retrieves the current default library pathname, copies it,
+and returns the copy. If sufficient storage cannot be allocated to
+contain the copy, the function returns ``NULL``. Storage for the result
+is allocated by the runtime and becomes the responsibility of the
+caller. When it is no longer used, free it using :ref:`PR_FreeLibraryName`.
diff --git a/docs/nspr/reference/pr_getnameforidentity.rst b/docs/nspr/reference/pr_getnameforidentity.rst
new file mode 100644
index 0000000000..4db060c81f
--- /dev/null
+++ b/docs/nspr/reference/pr_getnameforidentity.rst
@@ -0,0 +1,41 @@
+PR_GetNameForIdentity
+=====================
+
+Gets the string associated with a layer's unique identity.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ const char* PR_GetNameForIdentity(PRDescIdentity ident);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``ident``
+ A layer's identity.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, the function returns a pointer to the string
+ associated with the specified layer.
+- If unsuccessful, the function returns ``NULL``.
+
+
+Description
+-----------
+
+A string may be associated with a layer when the layer is created. The
+string is copied by the runtime, and :ref:`PR_GetNameForIdentity` returns a
+pointer to that copy.
diff --git a/docs/nspr/reference/pr_getopenfileinfo.rst b/docs/nspr/reference/pr_getopenfileinfo.rst
new file mode 100644
index 0000000000..7722606141
--- /dev/null
+++ b/docs/nspr/reference/pr_getopenfileinfo.rst
@@ -0,0 +1,54 @@
+PR_GetOpenFileInfo
+==================
+
+Gets an open file's information. File size is expressed as a 32-bit
+integer.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRStatus PR_GetOpenFileInfo(
+ PRFileDesc *fd,
+ PRFileInfo *info);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``fd``
+ A pointer to a :ref:`PRFileDesc` object for an open file.
+``info``
+ A pointer to a :ref:`PRFileInfo` object. On output, information about
+ the given file is written into the file information object.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If file information is successfully obtained, ``PR_SUCCESS``.
+- If file information is not successfully obtained, ``PR_FAILURE``.
+
+
+Description
+-----------
+
+:ref:`PR_GetOpenFileInfo` obtains the file type (normal file, directory, or
+other), file size (as a 32-bit integer), and the file creation and
+modification times of the open file represented by the file descriptor.
+
+
+See Also
+--------
+
+For the 64-bit version of this function, see :ref:`PR_GetOpenFileInfo64`.
+To get equivalent information on a file that's not already open, use
+:ref:`PR_GetFileInfo`.
diff --git a/docs/nspr/reference/pr_getopenfileinfo64.rst b/docs/nspr/reference/pr_getopenfileinfo64.rst
new file mode 100644
index 0000000000..aa0fa83fa2
--- /dev/null
+++ b/docs/nspr/reference/pr_getopenfileinfo64.rst
@@ -0,0 +1,56 @@
+PR_GetOpenFileInfo64
+====================
+
+Gets an open file's information. File size is expressed as a 64-bit
+integer.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRStatus PR_GetOpenFileInfo64(
+ PRFileDesc *fd,
+ PRFileInfo *info);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``fd``
+ A pointer to a :ref:`PRFileDesc` object for an open file.
+``info``
+ A pointer to a :ref:`PRFileInfo64` object. On output, information about
+ the given file is written into the file information object.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If file information is successfully obtained, ``PR_SUCCESS``.
+- If file information is not successfully obtained, ``PR_FAILURE``.
+
+
+Description
+-----------
+
+:ref:`PR_GetOpenFileInfo64` is the 64-bit version of
+:ref:`PR_GetOpenFileInfo`. It obtains the file type (normal file,
+directory, or other), file size (as a 64-bit integer), and the creation
+and modification times of the open file represented by the file
+descriptor.
+
+
+See Also
+--------
+
+For the 32-bit version of this function, see :ref:`PR_GetOpenFileInfo`. To
+get equivalent information on a file that's not already open, use
+:ref:`PR_GetFileInfo64`.
diff --git a/docs/nspr/reference/pr_getoserror.rst b/docs/nspr/reference/pr_getoserror.rst
new file mode 100644
index 0000000000..5068018a63
--- /dev/null
+++ b/docs/nspr/reference/pr_getoserror.rst
@@ -0,0 +1,31 @@
+PR_GetOSError
+=============
+
+Returns the current thread's last set OS-specific error code.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prerror.h>
+
+ PRInt32 PR_GetOSError(void)
+
+
+Returns
+~~~~~~~
+
+The value returned is a 32-bit signed number. Its interpretation is left
+to the caller.
+
+
+Description
+-----------
+
+Used for platform-specific code that requires the underlying OS error.
+For portability, clients should not create dependencies on the values of
+OS-specific error codes. However, this information is preserved, along
+with a platform neutral error code, on a per thread basis. It is most
+useful during development.
diff --git a/docs/nspr/reference/pr_getpeername.rst b/docs/nspr/reference/pr_getpeername.rst
new file mode 100644
index 0000000000..d45896444d
--- /dev/null
+++ b/docs/nspr/reference/pr_getpeername.rst
@@ -0,0 +1,35 @@
+PR_GetPeerName
+==============
+
+Gets the network address of the connected peer.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRStatus PR_GetPeerName(
+ PRFileDesc *fd,
+ PRNetAddr *addr);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``fd``
+ A pointer to a :ref:`PRFileDesc` object representing a socket.
+``addr``
+ On return, the address of the peer connected to the socket.
+
+
+Returns
+~~~~~~~
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful, ``PR_FAILURE``. The reason for the failure can be
+ obtained by calling :ref:`PR_GetError`.
diff --git a/docs/nspr/reference/pr_getprotobyname.rst b/docs/nspr/reference/pr_getprotobyname.rst
new file mode 100644
index 0000000000..d558b334dd
--- /dev/null
+++ b/docs/nspr/reference/pr_getprotobyname.rst
@@ -0,0 +1,47 @@
+PR_GetProtoByName
+=================
+
+Looks up a protocol entry based on the protocol's name.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prnetdb.h>
+
+ PRStatus PR_GetProtoByName(
+ const char* protocolname,
+ char* buffer,
+ PRInt32 bufsize,
+ PRProtoEnt* result);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``protocolname``
+ A pointer to the character string of the protocol's name.
+``buffer``
+ A pointer to a scratch buffer for the runtime to return result. This
+ buffer is allocated by the caller.
+``bufsize``
+ Number of bytes in the ``buffer`` parameter. The buffer must be at
+ least :ref:`PR_NETDB_BUF_SIZE` bytes.
+``result``
+ On input, a pointer to a :ref:`PRProtoEnt` structure. On output, this
+ structure is filled in by the runtime if the function returns
+ ``PR_SUCCESS``.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful, ``PR_FAILURE``. You can retrieve the reason for the
+ failure by calling :ref:`PR_GetError`.
diff --git a/docs/nspr/reference/pr_getprotobynumber.rst b/docs/nspr/reference/pr_getprotobynumber.rst
new file mode 100644
index 0000000000..423b1fa16a
--- /dev/null
+++ b/docs/nspr/reference/pr_getprotobynumber.rst
@@ -0,0 +1,47 @@
+PR_GetProtoByNumber
+===================
+
+Looks up a protocol entry based on protocol's number.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prnetdb.h>
+
+ PRStatus PR_GetProtoByNumber(
+ PRInt32 protocolnumber,
+ char* buffer,
+ PRInt32 bufsize,
+ PRProtoEnt* result);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``protocolnumber``
+ The number assigned to the protocol.
+``buffer``
+ A pointer to a scratch buffer for the runtime to return result. This
+ buffer is allocated by the caller.
+``bufsize``
+ Number of bytes in the ``buffer`` parameter. The buffer must be at
+ least :ref:`PR_NETDB_BUF_SIZE` bytes.
+``result``
+ On input, a pointer to a :ref:`PRNetAddr` structure. On output, this
+ structure is filled in by the runtime if the function returns
+ ``PR_SUCCESS``.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful, ``PR_FAILURE``. You can retrieve the reason for the
+ failure by calling :ref:`PR_GetError`.
diff --git a/docs/nspr/reference/pr_getrandomnoise.rst b/docs/nspr/reference/pr_getrandomnoise.rst
new file mode 100644
index 0000000000..1d482572f1
--- /dev/null
+++ b/docs/nspr/reference/pr_getrandomnoise.rst
@@ -0,0 +1,56 @@
+PR_GetRandomNoise
+=================
+
+Produces a random value for use as a seed value for another random
+number generator.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prrng.h>
+
+ NSPR_API(PRSize) PR_GetRandomNoise(
+ void *buf,
+ PRSize size
+ );
+
+
+Parameters
+~~~~~~~~~~
+
+The function has these parameters:
+
+``buf``
+ A pointer to a caller-supplied buffer to contain the generated random
+ number. ``buf`` must be at least as large as specified in ``size``.
+
+``size``
+ The size, in bytes, of the requested random number.
+
+
+Returns
+~~~~~~~
+
+:ref:`PRSize` value equal to the size of the random number actually
+generated, or zero. The generated size may be less than the size
+requested. A return value of zero means that :ref:`PR_GetRandomNoise` is
+not implemented on this platform, or there is no available noise to be
+returned at the time of the call.
+
+
+Description
+-----------
+
+:ref:`PR_GetRandomNoise` provides a random value, depending on platform.
+The length of the random value is dependent on the platform and its
+ability to provide a random value at that moment.
+
+:ref:`PR_GetRandomNoise` is intended to provide a "seed" value for a
+another random number generator that may be suitable for cryptographic
+operations. This implies that the random value provided may not be, by
+itself, cryptographically secure. The value generated by
+:ref:`PR_GetRandomNoise` is at best, extremely difficult to predict and is
+as nondeterministic as the underlying platform permits.
diff --git a/docs/nspr/reference/pr_getsocketoption.rst b/docs/nspr/reference/pr_getsocketoption.rst
new file mode 100644
index 0000000000..3e2112300c
--- /dev/null
+++ b/docs/nspr/reference/pr_getsocketoption.rst
@@ -0,0 +1,40 @@
+PR_GetSocketOption
+==================
+
+Retrieves the socket options set for a specified socket.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRStatus PR_GetSocketOption(
+ PRFileDesc *fd,
+ PRSocketOptionData *data);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``fd``
+ A pointer to a :ref:`PRFileDesc` object representing the socket whose
+ options are to be retrieved.
+``data``
+ A pointer to a structure of type :ref:`PRSocketOptionData`. On input,
+ the ``option`` field of this structure must be set to indicate which
+ socket option to retrieve for the socket represented by the ``fd``
+ parameter. On output, this structure contains the requested socket
+ option data.
+
+
+Returns
+~~~~~~~
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful, ``PR_FAILURE``. The reason for the failure can be
+ obtained by calling :ref:`PR_GetError`.
diff --git a/docs/nspr/reference/pr_getsockname.rst b/docs/nspr/reference/pr_getsockname.rst
new file mode 100644
index 0000000000..7cb6804a3c
--- /dev/null
+++ b/docs/nspr/reference/pr_getsockname.rst
@@ -0,0 +1,35 @@
+PR_GetSockName
+==============
+
+Gets network address for a specified socket.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRStatus PR_GetSockName(
+ PRFileDesc *fd,
+ PRNetAddr *addr);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``fd``
+ A pointer to a :ref:`PRFileDesc` object representing the socket.
+``addr``
+ On return, the address of the socket.
+
+
+Returns
+~~~~~~~
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful, ``PR_FAILURE``. The reason for the failure can be
+ obtained by calling :ref:`PR_GetError`.
diff --git a/docs/nspr/reference/pr_getspecialfd.rst b/docs/nspr/reference/pr_getspecialfd.rst
new file mode 100644
index 0000000000..4cb50fc3c1
--- /dev/null
+++ b/docs/nspr/reference/pr_getspecialfd.rst
@@ -0,0 +1,56 @@
+PR_GetSpecialFD
+===============
+
+Gets the file descriptor that represents the standard input, output, or
+error stream.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRFileDesc* PR_GetSpecialFD(PRSpecialFD id);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``id``
+ A pointer to an enumerator of type ``PRSpecialFD``, indicating the
+ type of I/O stream desired: ``PR_StandardInput``,
+ ``PR_StandardOutput``, or ``PR_StandardError``.
+
+
+Returns
+~~~~~~~
+
+If the ``id`` parameter is valid, :ref:`PR_GetSpecialFD` returns a file
+descriptor that represents the corresponding standard I/O stream.
+Otherwise, :ref:`PR_GetSpecialFD` returns ``NULL`` and sets the error to
+``PR_INVALID_ARGUMENT_ERROR``.
+
+
+Description
+-----------
+
+Type ``PRSpecialFD`` is defined as follows:
+
+.. code::
+
+ typedef enum PRSpecialFD{
+ PR_StandardInput,
+ PR_StandardOutput,
+ PR_StandardError
+ } PRSpecialFD;
+
+``#define PR_STDIN PR_GetSpecialFD(PR_StandardInput)``
+``#define PR_STDOUT PR_GetSpecialFD(PR_StandardOutput)``
+``#define PR_STDERR PR_GetSpecialFD(PR_StandardError)``
+
+File descriptors returned by :ref:`PR_GetSpecialFD` are owned by the
+runtime and should not be closed by the caller.
diff --git a/docs/nspr/reference/pr_getthreadpriority.rst b/docs/nspr/reference/pr_getthreadpriority.rst
new file mode 100644
index 0000000000..ff678c9938
--- /dev/null
+++ b/docs/nspr/reference/pr_getthreadpriority.rst
@@ -0,0 +1,23 @@
+PR_GetThreadPriority
+====================
+
+Returns the priority of a specified thread.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prthread.h>
+
+ PRThreadPriority PR_GetThreadPriority(PRThread *thread);
+
+
+Parameter
+~~~~~~~~~
+
+:ref:`PR_GetThreadPriority` has the following parameter:
+
+``thread``
+ A valid identifier for the thread whose priority you want to know.
diff --git a/docs/nspr/reference/pr_getthreadprivate.rst b/docs/nspr/reference/pr_getthreadprivate.rst
new file mode 100644
index 0000000000..7303eae93b
--- /dev/null
+++ b/docs/nspr/reference/pr_getthreadprivate.rst
@@ -0,0 +1,38 @@
+PR_GetThreadPrivate
+===================
+
+Recovers the per-thread private data for the current thread.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prthread.h>
+
+ void* PR_GetThreadPrivate(PRUintn index);
+
+
+Parameter
+~~~~~~~~~
+
+:ref:`PR_GetThreadPrivate` has the following parameters:
+
+``index``
+ The index into the per-thread private data table.
+
+
+Returns
+~~~~~~~
+
+``NULL`` if the data has not been set.
+
+
+Description
+-----------
+
+:ref:`PR_GetThreadPrivate` may be called at any time during a thread's
+execution. A thread can get access only to its own per-thread private
+data. Do not delete the object that the private data refers to without
+first clearing the thread's value.
diff --git a/docs/nspr/reference/pr_getthreadscope.rst b/docs/nspr/reference/pr_getthreadscope.rst
new file mode 100644
index 0000000000..32899683d9
--- /dev/null
+++ b/docs/nspr/reference/pr_getthreadscope.rst
@@ -0,0 +1,21 @@
+PR_GetThreadScope
+=================
+
+Gets the scoping of the current thread.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prthread.h>
+
+ PRThreadScope PR_GetThreadScope(void);
+
+
+Returns
+~~~~~~~
+
+A value of type :ref:`PRThreadScope` indicating whether the thread is local
+or global.
diff --git a/docs/nspr/reference/pr_getuniqueidentity.rst b/docs/nspr/reference/pr_getuniqueidentity.rst
new file mode 100644
index 0000000000..a0b49ac66f
--- /dev/null
+++ b/docs/nspr/reference/pr_getuniqueidentity.rst
@@ -0,0 +1,49 @@
+PR_GetUniqueIdentity
+====================
+
+Asks the runtime to allocate a unique identity for a layer identified by
+the layer's name.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRDescIdentity PR_GetUniqueIdentity(const char *layer_name);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``layer_name``
+ The string associated with the creation of a layer's identity.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, the :ref:`PRDescIdentity` for the layer associated with
+ the string specified in the layer named ``layer_name``.
+- If the function cannot allocate enough dynamic memory, it fails and
+ returns the value ``PR_INVALID_IO_LAYER`` with the error code
+ ``PR_OUT_OF_MEMORY_ERROR``.
+
+
+Description
+-----------
+
+A string may be associated with a layer when the layer is created.
+:ref:`PR_GetUniqueIdentity` allocates a unique layer identity and
+associates it with the string. The string can be subsequently passed to
+:ref:`PR_CreateIOLayerStub` to create a new file descriptor of that layer.
+
+Call :ref:`PR_GetUniqueIdentity` only once for any particular layer name.
+If you're creating a custom I/O layer, cache the result, and then use
+that cached result every time you call :ref:`PR_CreateIOLayerStub`.
diff --git a/docs/nspr/reference/pr_gmtparameters.rst b/docs/nspr/reference/pr_gmtparameters.rst
new file mode 100644
index 0000000000..d9abb9c0b6
--- /dev/null
+++ b/docs/nspr/reference/pr_gmtparameters.rst
@@ -0,0 +1,47 @@
+PR_GMTParameters
+================
+
+Returns the time zone offset information that maps the specified
+:ref:`PRExplodedTime` to GMT.
+
+.. note::
+
+ **Note:** Since this function requires GMT as input, its primary use
+ is as "filler" for cases in which you need a do-nothing callback.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtime.h>
+
+ PRTimeParameters PR_GMTParameters (
+ const PRExplodedTime *gmt);
+
+
+Parameter
+~~~~~~~~~
+
+``gmt``
+ A pointer to the clock/calendar time whose offsets are to be
+ determined. This time should be specified in GMT.
+
+
+Returns
+~~~~~~~
+
+A time parameters structure that expresses the time zone offsets at the
+specified time.
+
+
+Description
+-----------
+
+This is a frequently-used time parameter callback function. You don't
+normally call it directly; instead, you pass it as a parameter to
+``PR_ExplodeTime()`` or ``PR_NormalizeTime()``.
+
+This is a trivial function; for any input, it returns a
+:ref:`PRTimeParameters` structure with both fields set to zero.
diff --git a/docs/nspr/reference/pr_htonl.rst b/docs/nspr/reference/pr_htonl.rst
new file mode 100644
index 0000000000..094c59ced8
--- /dev/null
+++ b/docs/nspr/reference/pr_htonl.rst
@@ -0,0 +1,29 @@
+PR_htonl
+========
+
+Performs 32-bit conversion from host byte order to network byte order.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prnetdb.h>
+
+ PRUint32 PR_htonl(PRUint32 conversion);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``conversion``
+ The 32-bit unsigned integer, in host byte order, to be converted.
+
+
+Returns
+~~~~~~~
+
+The value of the ``conversion`` parameter in network byte order.
diff --git a/docs/nspr/reference/pr_htons.rst b/docs/nspr/reference/pr_htons.rst
new file mode 100644
index 0000000000..1e5b845ca9
--- /dev/null
+++ b/docs/nspr/reference/pr_htons.rst
@@ -0,0 +1,29 @@
+PR_htons
+========
+
+Performs 16-bit conversion from host byte order to network byte order.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prnetdb.h>
+
+ PRUint16 PR_htons(PRUint16 conversion);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``conversion``
+ The 16-bit unsigned integer, in host byte order, to be converted.
+
+
+Returns
+~~~~~~~
+
+The value of the ``conversion`` parameter in network byte order.
diff --git a/docs/nspr/reference/pr_implement.rst b/docs/nspr/reference/pr_implement.rst
new file mode 100644
index 0000000000..583716a15b
--- /dev/null
+++ b/docs/nspr/reference/pr_implement.rst
@@ -0,0 +1,28 @@
+PR_IMPLEMENT
+============
+
+Used to define implementations of symbols that are to be exported from a
+shared library.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtypes.h>
+
+ PR_IMPLEMENT(type)implementation
+
+
+Description
+-----------
+
+:ref:`PR_IMPLEMENT` is used to define implementations of externally visible
+routines and globals. For syntax details for each platform, see
+`prtypes.h <https://dxr.mozilla.org/mozilla-central/source/nsprpub/pr/include/prtypes.h>`__.
+
+.. warning::
+
+ **Warning**: Some platforms do not allow the use of the underscore
+ character (_) as the first character of an exported symbol.
diff --git a/docs/nspr/reference/pr_implodetime.rst b/docs/nspr/reference/pr_implodetime.rst
new file mode 100644
index 0000000000..1426d57a79
--- /dev/null
+++ b/docs/nspr/reference/pr_implodetime.rst
@@ -0,0 +1,36 @@
+PR_ImplodeTime
+==============
+
+Converts a clock/calendar time to an absolute time.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtime.h>
+
+ PRTime PR_ImplodeTime(const PRExplodedTime *exploded);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has these parameters:
+
+``exploded``
+ A pointer to the clock/calendar time to be converted.
+
+
+Returns
+~~~~~~~
+
+An absolute time value.
+
+
+Description
+-----------
+
+This function converts the specified clock/calendar time to an absolute
+time and returns the converted time value.
diff --git a/docs/nspr/reference/pr_importfilemapfromstring.rst b/docs/nspr/reference/pr_importfilemapfromstring.rst
new file mode 100644
index 0000000000..bc9694d557
--- /dev/null
+++ b/docs/nspr/reference/pr_importfilemapfromstring.rst
@@ -0,0 +1,39 @@
+PR_ImportFileMapFromString
+==========================
+
+Creates a :ref:`PRFileMap` from an identifying string.
+
+
+Syntax
+~~~~~~
+
+.. code::
+
+ #include <prshma.h>
+
+ NSPR_API( PRFileMap * )
+ PR_ImportFileMapFromString(
+ const char *fmstring
+ );
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+fmstring
+ A pointer to string created by :ref:`PR_ExportFileMapAsString`.
+
+
+Returns
+~~~~~~~
+
+:ref:`PRFileMap` pointer or ``NULL`` on error.
+
+
+Description
+-----------
+
+:ref:`PR_ImportFileMapFromString` creates a :ref:`PRFileMap` object from a
+string previously created by :ref:`PR_ExportFileMapAsString`.
diff --git a/docs/nspr/reference/pr_importtcpsocket.rst b/docs/nspr/reference/pr_importtcpsocket.rst
new file mode 100644
index 0000000000..4722744b3d
--- /dev/null
+++ b/docs/nspr/reference/pr_importtcpsocket.rst
@@ -0,0 +1,70 @@
+PR_ImportTCPSocket
+==================
+
+Imports a native TCP socket into NSPR.
+
+
+Syntax
+------
+
+.. code::
+
+ #include "private/pprio.h"
+
+ PRFileDesc* PR_ImportTCPSocket(PROsfd osfd);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``osfd``
+ The native file descriptor for the TCP socket to import. On POSIX
+ systems, this is an ``int``. On Windows, this is a ``SOCKET``.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- Upon successful completion, a pointer to the :ref:`PRFileDesc` object
+ created for the newly imported native TCP socket.
+- If the import of the native TCP socket failed, ``NULL``.
+
+
+Description
+-----------
+
+A native TCP socket ``osfd`` can be imported into NSPR with
+:ref:`PR_ImportTCPSocket`. The caller gives up control of the native TCP
+socket ``osfd`` and should use the ``PRFileDesc*`` returned by
+:ref:`PR_ImportTCPSocket` instead.
+
+Although :ref:`PR_ImportTCPSocket` is a supported function, it is declared
+in ``"private/pprio.h"`` to stress the fact that this function depends
+on the internals of the NSPR implementation. The caller needs to
+understand what NSPR will do to the native file descriptor and make sure
+that NSPR can use the native file descriptor successfully.
+
+For example, on POSIX systems, NSPR will put the native file descriptor
+(an ``int``) in non-blocking mode by calling ``fcntl`` to set the
+``O_NONBLOCK`` file status flag on the native file descriptor, and then
+NSPR will call socket functions such as ``recv``, ``send``, and ``poll``
+on the native file descriptor. The caller must not do anything to the
+native file descriptor before the :ref:`PR_ImportTCPSocket` call that will
+prevent the native file descriptor from working in non-blocking mode.
+
+Warning
+-------
+
+In theory, code that uses :ref:`PR_ImportTCPSocket` may break when NSPR's
+implementation changes. In practice, this is unlikely to happen because
+NSPR's implementation has been stable for years and because of NSPR's
+strong commitment to backward compatibility. Using
+:ref:`PR_ImportTCPSocket` is much more convenient than writing an NSPR I/O
+layer that wraps your native TCP sockets. Of course, it is best if you
+just use :ref:`PR_OpenTCPSocket` or :ref:`PR_NewTCPSocket`. If you are not
+sure whether :ref:`PR_ImportTCPSocket` is right for you, please ask in the
+mozilla.dev.tech.nspr newsgroup.
diff --git a/docs/nspr/reference/pr_init.rst b/docs/nspr/reference/pr_init.rst
new file mode 100644
index 0000000000..c047b2ed2d
--- /dev/null
+++ b/docs/nspr/reference/pr_init.rst
@@ -0,0 +1,44 @@
+PR_Init
+=======
+
+Initializes the runtime.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prinit.h>
+
+ void PR_Init(
+ PRThreadType type,
+ PRThreadPriority priority,
+ PRUintn maxPTDs);
+
+
+Parameters
+~~~~~~~~~~
+
+:ref:`PR_Init` has the following parameters:
+
+``type``
+ This parameter is ignored.
+``priority``
+ This parameter is ignored.
+``maxPTDs``
+ This parameter is ignored.
+
+
+Description
+-----------
+
+NSPR is now implicitly initialized, usually by the first NSPR function
+called by a program. :ref:`PR_Init` is necessary only if a program has
+specific initialization-sequencing requirements.
+
+Call :ref:`PR_Init` as follows:
+
+.. code::
+
+ PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
diff --git a/docs/nspr/reference/pr_init_clist.rst b/docs/nspr/reference/pr_init_clist.rst
new file mode 100644
index 0000000000..d6d57afade
--- /dev/null
+++ b/docs/nspr/reference/pr_init_clist.rst
@@ -0,0 +1,27 @@
+PR_INIT_CLIST
+=============
+
+Initializes a circular list.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prclist.h>
+
+ PR_INIT_CLIST (PRCList *listp);
+
+
+Parameter
+~~~~~~~~~
+
+``listp``
+ A pointer to the anchor of the linked list.
+
+
+Description
+-----------
+
+Initializes the specified list to be an empty list.
diff --git a/docs/nspr/reference/pr_init_static_clist.rst b/docs/nspr/reference/pr_init_static_clist.rst
new file mode 100644
index 0000000000..b837694efd
--- /dev/null
+++ b/docs/nspr/reference/pr_init_static_clist.rst
@@ -0,0 +1,32 @@
+PR_INIT_STATIC_CLIST
+====================
+
+Statically initializes a circular list.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prclist.h>
+
+ PR_INIT_STATIC_CLIST (PRCList *listp);
+
+
+Parameter
+~~~~~~~~~
+
+``listp``
+ A pointer to the anchor of the linked list.
+
+
+Description
+-----------
+
+PR_INIT_STATIC_CLIST statically initializes the specified list to be an
+empty list. For example,
+
+::
+
+ PRCList free_object_list = PR_INIT_STATIC_CLIST(&free_object_list);
diff --git a/docs/nspr/reference/pr_initialize.rst b/docs/nspr/reference/pr_initialize.rst
new file mode 100644
index 0000000000..027f040a79
--- /dev/null
+++ b/docs/nspr/reference/pr_initialize.rst
@@ -0,0 +1,63 @@
+PR_Initialize
+=============
+
+Provides an alternate form of explicit initialization. In addition to
+establishing the sequence of operations, :ref:`PR_Initialize` implicitly
+calls :ref:`PR_Cleanup` on exiting the primordial function.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prinit.h>
+
+ PRIntn PR_Initialize(
+ PRPrimordialFn prmain,
+ PRIntn argc,
+ char **argv,
+ PRUintn maxPTDs);
+
+
+Parameters
+~~~~~~~~~~
+
+:ref:`PR_Initialize` has the following parameters:
+
+``prmain``
+ The function that becomes the primordial thread's root function.
+ Returning from prmain leads to termination of the process.
+``argc``
+ The length of the argument vector, whether passed in from the host's
+ program-launching facility or fabricated by the actual main program.
+ This approach conforms to standard C programming practice.
+``argv``
+ The base address of an array of strings that compromise the program's
+ argument vector. This approach conforms to standard C programming
+ practice.
+``maxPTDs``
+ This parameter is ignored.
+
+
+Returns
+~~~~~~~
+
+The value returned from the root function, ``prmain``.
+
+
+Description
+-----------
+
+:ref:`PR_Initialize` initializes the NSPR runtime and places NSPR between
+the caller and the runtime library. This allows ``main`` to be treated
+like any other function, signaling its completion by returning and
+allowing the runtime to coordinate the completion of the other threads
+of the runtime.
+
+:ref:`PR_Initialize` does not return to its caller until all user threads
+have terminated.
+
+The priority of the main (or primordial) thread is
+``PR_PRIORITY_NORMAL``. The thread may adjust its own priority by using
+:ref:`PR_SetThreadPriority`.
diff --git a/docs/nspr/reference/pr_initialized.rst b/docs/nspr/reference/pr_initialized.rst
new file mode 100644
index 0000000000..c5323637b1
--- /dev/null
+++ b/docs/nspr/reference/pr_initialized.rst
@@ -0,0 +1,23 @@
+PR_Initialized
+==============
+
+Checks whether the runtime has been initialized.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prinit.h>
+
+ PRBool PR_Initialized(void);
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If :ref:`PR_Init` has already been called, ``PR_TRUE``.
+- If :ref:`PR_Init` has not already been called, ``PR_FALSE``.
diff --git a/docs/nspr/reference/pr_initializenetaddr.rst b/docs/nspr/reference/pr_initializenetaddr.rst
new file mode 100644
index 0000000000..c701334990
--- /dev/null
+++ b/docs/nspr/reference/pr_initializenetaddr.rst
@@ -0,0 +1,80 @@
+PR_InitializeNetAddr
+====================
+
+Initializes or reinitializes a network address. The storage for the
+network address structure is allocated by, and remains the
+responsibility of, the calling client.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prnetdb.h>
+
+ PRStatus PR_InitializeNetAddr(
+ PRNetAddrValue val,
+ PRUint16 port,
+ PRNetAddr *addr);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``val``
+ The value to be assigned to the IP Address portion of the network
+ address. This must be ``PR_IpAddrNull``, ``PR_IpAddrAny``, or
+ ``PR_IpAddrLoopback``.
+``port``
+ The port number to be assigned in the network address structure. The
+ value is specified in host byte order.
+``addr``
+ A pointer to the :ref:`PRNetAddr` structure to be manipulated.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful, ``PR_FAILURE``. This may occur, for example, if the
+ value of val is not within the ranges defined by ``PRNetAddrValue``.
+ You can retrieve the reason for the failure by calling
+ :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+:ref:`PR_InitializeNetAddr` allows the assignment of special network
+address values and the port number, while also setting the state that
+indicates the version of the address being used.
+
+The special network address values are identified by the enum
+``PRNetAddrValue``:
+
+.. code::
+
+ typedef enum PRNetAddrValue{
+ PR_IpAddrNull,
+ PR_IpAddrAny,
+ PR_IpAddrLoopback
+ } PRNetAddrValue;
+
+The enum has the following enumerators:
+
+``PR_IpAddrNull``
+ Do not overwrite the IP address. This allows the caller to change the
+ network address' port number assignment without affecting the host
+ address.
+``PR_IpAddrAny``
+ Assign logical ``PR_INADDR_ANY`` to IP address. This wildcard value
+ is typically used to establish a socket on which to listen for
+ incoming connection requests.
+``PR_IpAddrLoopback``
+ Assign logical ``PR_INADDR_LOOPBACK``. A client can use this value to
+ connect to itself without knowing the host's network address.
diff --git a/docs/nspr/reference/pr_insert_after.rst b/docs/nspr/reference/pr_insert_after.rst
new file mode 100644
index 0000000000..b51377e3dc
--- /dev/null
+++ b/docs/nspr/reference/pr_insert_after.rst
@@ -0,0 +1,32 @@
+PR_INSERT_AFTER
+===============
+
+Inserts an element after another element in a circular list.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prclist.h>
+
+ PR_INSERT_AFTER (
+ PRCList *elemp1
+ PRCList *elemp2);
+
+
+Parameters
+~~~~~~~~~~
+
+``elemp1``
+ A pointer to the element to be inserted.
+``elemp2``
+ A pointer to the element after which ``elemp1`` is to be inserted.
+
+
+Description
+-----------
+
+PR_INSERT_AFTER inserts the element specified by ``elemp1`` into the
+circular list, after the element specified by ``elemp2``.
diff --git a/docs/nspr/reference/pr_insert_before.rst b/docs/nspr/reference/pr_insert_before.rst
new file mode 100644
index 0000000000..6d6bcc8a3c
--- /dev/null
+++ b/docs/nspr/reference/pr_insert_before.rst
@@ -0,0 +1,32 @@
+PR_INSERT_BEFORE
+================
+
+Inserts an element before another element in a circular list.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prclist.h>
+
+ PR_INSERT_BEFORE (
+ PRCList *elemp1
+ PRCList *elemp2);
+
+
+Parameters
+~~~~~~~~~~
+
+``elemp1``
+ A pointer to the element to be inserted.
+``elemp2``
+ A pointer to the element before which ``elemp1`` is to be inserted.
+
+
+Description
+-----------
+
+PR_INSERT_BEFORE inserts the element specified by ``elemp1`` into the
+circular list, before the element specified by ``elemp2``.
diff --git a/docs/nspr/reference/pr_insert_link.rst b/docs/nspr/reference/pr_insert_link.rst
new file mode 100644
index 0000000000..24a6dd9cfd
--- /dev/null
+++ b/docs/nspr/reference/pr_insert_link.rst
@@ -0,0 +1,32 @@
+PR_INSERT_LINK
+==============
+
+Inserts an element at the head of the list.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prclist.h>
+
+ PR_INSERT_LINK (
+ PRCList *elemp,
+ PRCList *listp);
+
+
+Parameters
+~~~~~~~~~~
+
+``elemp``
+ A pointer to the element to be inserted.
+``listp``
+ A pointer to the list.
+
+
+Description
+-----------
+
+PR_INSERT_LINK inserts the specified element at the head of the
+specified list.
diff --git a/docs/nspr/reference/pr_interrupt.rst b/docs/nspr/reference/pr_interrupt.rst
new file mode 100644
index 0000000000..67bf324e96
--- /dev/null
+++ b/docs/nspr/reference/pr_interrupt.rst
@@ -0,0 +1,71 @@
+PR_Interrupt
+============
+
+Sets the interrupt request for a target thread.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prthread.h>
+
+ PRStatus PR_Interrupt(PRThread *thread);
+
+
+Parameter
+~~~~~~~~~
+
+:ref:`PR_Interrupt` has the following parameter:
+
+``thread``
+ The thread whose interrupt request you want to set.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If the specified thread is currently blocked, ``PR_SUCCESS``.
+- Otherwise, ``PR_FAILURE``.
+
+
+Description
+-----------
+
+The purpose of :ref:`PR_Interrupt` is to request that a thread performing
+some task stop what it is doing and return to some control point. It is
+assumed that a control point has been mutually arranged between the
+thread doing the interrupting and the thread being interrupted. When the
+interrupted thread reaches the prearranged point, it can communicate
+with its peer to discover the real reason behind the change in plans.
+
+The interrupt request remains in the thread's state until it is
+delivered exactly once or explicitly canceled. The interrupted thread
+returns ``PR_FAILURE`` (-1) with an error code (see :ref:`PR_GetError`) for
+blocking operations that return a :ref:`PRStatus` (such as I/O operations,
+monitor waits, or waiting on a condition). To check whether the thread
+was interrupted, compare the result of :ref:`PR_GetError` with
+``PR_PENDING_INTERRUPT_ERROR``.
+
+:ref:`PR_Interrupt` may itself fail if the target thread is invalid.
+
+Bugs
+----
+
+:ref:`PR_Interrupt` has the following limitations and known bugs:
+
+- There can be a delay for a thread to be interrupted from a blocking
+ I/O function. In all NSPR implementations, the maximum delay is at
+ most five seconds. In the pthreads-based implementation on Unix, the
+ maximum delay is 0.1 seconds.
+- File I/O is considered instantaneous, so file I/O functions cannot be
+ interrupted. Unfortunately the standard input, output, and error
+ streams are treated as files by NSPR, so a :ref:`PR_Read` call on
+ ``PR_STDIN`` cannot be interrupted even though it may block
+ indefinitely.
+- In the NT implementation, :ref:`PR_Connect` cannot be interrupted.
+- In the NT implementation, a file descriptor is not usable and must be
+ closed after an I/O function on the file descriptor is interrupted.
diff --git a/docs/nspr/reference/pr_intervalnow.rst b/docs/nspr/reference/pr_intervalnow.rst
new file mode 100644
index 0000000000..360dd6455a
--- /dev/null
+++ b/docs/nspr/reference/pr_intervalnow.rst
@@ -0,0 +1,50 @@
+PR_IntervalNow
+==============
+
+Returns the value of NSPR's free-running interval timer.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prinrval.h>
+
+ PRIntervalTime PR_IntervalNow(void);
+
+
+Returns
+~~~~~~~
+
+A :ref:`PRIntervalTime` object.
+
+
+Description
+-----------
+
+You can use the value returned by ``PR_IntervalNow()`` to establish
+epochs and to determine intervals (that is, compute the difference
+between two times). ``PR_IntervalNow()`` is both very efficient and
+nonblocking, so it is appropriate to use (for example) while holding a
+mutex.
+
+The most common use for ``PR_IntervalNow()`` is to establish an epoch
+and test for the expiration of intervals. In this case, you typically
+call ``PR_IntervalNow()`` in a sequence that looks like this:
+
+.. code::
+
+ PRUint32 interval = ... ; // milliseconds
+ // ...
+ PRStatus rv;
+ PRIntervalTime epoch = PR_IntervalNow();
+ PR_Lock(data->mutex);
+ while (!EvaluateData(data)) /* wait until condition is met */
+ {
+ PRUint32 delta = PR_IntervalToMilliseconds(PR_IntervalNow() - epoch);
+ if (delta > interval) break; /* timeout */
+ rv = PR_Wait(data->condition, PR_MillisecondsToInterval(interval - delta));
+ if (PR_FAILURE == rv) break; /* likely an interrupt */
+ }
+ PR_Unlock(data->mutex);
diff --git a/docs/nspr/reference/pr_intervaltomicroseconds.rst b/docs/nspr/reference/pr_intervaltomicroseconds.rst
new file mode 100644
index 0000000000..a8e3fec038
--- /dev/null
+++ b/docs/nspr/reference/pr_intervaltomicroseconds.rst
@@ -0,0 +1,34 @@
+PR_IntervalToMicroseconds
+=========================
+
+Converts platform-dependent intervals to standard clock microseconds.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prinrval.h>
+
+ PRUint32 PR_IntervalToMicroseconds(PRIntervalTime ticks);
+
+
+Parameter
+~~~~~~~~~
+
+``ticks``
+ The number of platform-dependent intervals to convert.
+
+
+Returns
+~~~~~~~
+
+Equivalent in microseconds of the value passed in the ``ticks``
+parameter.
+
+
+Description
+-----------
+
+Conversion may cause overflow, which is not reported.
diff --git a/docs/nspr/reference/pr_intervaltomilliseconds.rst b/docs/nspr/reference/pr_intervaltomilliseconds.rst
new file mode 100644
index 0000000000..a68c4534bb
--- /dev/null
+++ b/docs/nspr/reference/pr_intervaltomilliseconds.rst
@@ -0,0 +1,34 @@
+PR_IntervalToMilliseconds
+=========================
+
+Converts platform-dependent intervals to standard clock milliseconds.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prinrval.h>
+
+ PRUint32 PR_IntervalToMilliseconds(PRIntervalTime ticks);
+
+
+Parameter
+~~~~~~~~~
+
+``ticks``
+ The number of platform-dependent intervals to convert.
+
+
+Returns
+~~~~~~~
+
+Equivalent in milliseconds of the value passed in the ``ticks``
+parameter.
+
+
+Description
+-----------
+
+Conversion may cause overflow, which is not reported.
diff --git a/docs/nspr/reference/pr_intervaltoseconds.rst b/docs/nspr/reference/pr_intervaltoseconds.rst
new file mode 100644
index 0000000000..16d77f0ade
--- /dev/null
+++ b/docs/nspr/reference/pr_intervaltoseconds.rst
@@ -0,0 +1,33 @@
+PR_IntervalToSeconds
+====================
+
+Converts platform-dependent intervals to standard clock seconds.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prinrval.h>
+
+ PRUint32 PR_IntervalToSeconds(PRIntervalTime ticks);
+
+
+Parameter
+~~~~~~~~~
+
+``ticks``
+ The number of platform-dependent intervals to convert.
+
+
+Returns
+~~~~~~~
+
+Equivalent in seconds of the value passed in the ``ticks`` parameter.
+
+
+Description
+-----------
+
+Conversion may cause overflow, which is not reported.
diff --git a/docs/nspr/reference/pr_joinjob.rst b/docs/nspr/reference/pr_joinjob.rst
new file mode 100644
index 0000000000..1153cd9dcf
--- /dev/null
+++ b/docs/nspr/reference/pr_joinjob.rst
@@ -0,0 +1,30 @@
+PR_JoinJob
+==========
+
+Blocks the current thread until a job has completed.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtpool.h>
+
+ NSPR_API(PRStatus) PR_JoinJob(PRJob *job);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``job``
+ A pointer to a :ref:`PRJob` structure returned by a :ref:`PR_QueueJob`
+ function representing the job to be cancelled.
+
+
+Returns
+~~~~~~~
+
+:ref:`PRStatus`
diff --git a/docs/nspr/reference/pr_jointhread.rst b/docs/nspr/reference/pr_jointhread.rst
new file mode 100644
index 0000000000..a1bfddf81c
--- /dev/null
+++ b/docs/nspr/reference/pr_jointhread.rst
@@ -0,0 +1,57 @@
+PR_JoinThread
+=============
+
+Blocks the calling thread until a specified thread terminates.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prthread.h>
+
+ PRStatus PR_JoinThread(PRThread *thread);
+
+
+Parameter
+~~~~~~~~~
+
+:ref:`PR_JoinThread` has the following parameter:
+
+``thread``
+ A valid identifier for the thread that is to be joined.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, ``PR_SUCCESS``
+- If unsuccessful--for example, if no joinable thread can be found that
+ corresponds to the specified target thread, or if the target thread
+ is unjoinable--``PR_FAILURE``.
+
+
+Description
+-----------
+
+:ref:`PR_JoinThread` is used to synchronize the termination of a thread.
+The function is synchronous in that it blocks the calling thread until
+the target thread is in a joinable state. :ref:`PR_JoinThread` returns to
+the caller only after the target thread returns from its root function.
+
+:ref:`PR_JoinThread` must not be called until after :ref:`PR_CreateThread` has
+returned. If :ref:`PR_JoinThread` is not called on the same thread as
+:ref:`PR_CreateThread`, then it is the caller's responsibility to ensure
+that :ref:`PR_CreateThread` has completed.
+
+Several threads cannot wait for the same thread to complete. One of the
+calling threads operates successfully, and the others terminate with the
+error ``PR_FAILURE``.
+
+The calling thread is not blocked if the target thread has already
+terminated.
+
+:ref:`PR_JoinThread` is interruptible.
diff --git a/docs/nspr/reference/pr_jointhreadpool.rst b/docs/nspr/reference/pr_jointhreadpool.rst
new file mode 100644
index 0000000000..86d8ecf822
--- /dev/null
+++ b/docs/nspr/reference/pr_jointhreadpool.rst
@@ -0,0 +1,31 @@
+PR_JoinThreadPool
+=================
+
+Waits for all threads in a thread pool to complete, then releases
+resources allocated to the thread pool.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtpool.h>
+
+ NSPR_API(PRStatus) PR_JoinThreadPool( PRThreadPool *tpool );
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``tpool``
+ A pointer to a :ref:`PRThreadPool` structure previously created by a
+ call to :ref:`PR_CreateThreadPool`.
+
+
+Returns
+~~~~~~~
+
+:ref:`PRStatus`
diff --git a/docs/nspr/reference/pr_list_head.rst b/docs/nspr/reference/pr_list_head.rst
new file mode 100644
index 0000000000..e930c36295
--- /dev/null
+++ b/docs/nspr/reference/pr_list_head.rst
@@ -0,0 +1,33 @@
+PR_LIST_HEAD
+============
+
+Returns the head of a circular list.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prclist.h>
+
+ PRCList *PR_LIST_HEAD (PRCList *listp);
+
+
+Parameter
+~~~~~~~~~
+
+``listp``
+ A pointer to the linked list.
+
+
+Returns
+~~~~~~~
+
+A pointer to a list element.
+
+
+Description
+-----------
+
+:ref:`PR_LIST_HEAD` returns the head of the specified circular list.
diff --git a/docs/nspr/reference/pr_list_tail.rst b/docs/nspr/reference/pr_list_tail.rst
new file mode 100644
index 0000000000..87d9126f92
--- /dev/null
+++ b/docs/nspr/reference/pr_list_tail.rst
@@ -0,0 +1,33 @@
+PR_LIST_TAIL
+============
+
+Returns the tail of a circular list.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prclist.h>
+
+ PRCList *PR_LIST_TAIL (PRCList *listp);
+
+
+Parameter
+~~~~~~~~~
+
+``listp``
+ A pointer to the linked list.
+
+
+Returns
+~~~~~~~
+
+A pointer to a list element.
+
+
+Description
+-----------
+
+:ref:`PR_LIST_TAIL` returns the tail of the specified circular list.
diff --git a/docs/nspr/reference/pr_listen.rst b/docs/nspr/reference/pr_listen.rst
new file mode 100644
index 0000000000..1b1efefc21
--- /dev/null
+++ b/docs/nspr/reference/pr_listen.rst
@@ -0,0 +1,48 @@
+PR_Listen
+=========
+
+Listens for connections on a specified socket.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRStatus PR_Listen(
+ PRFileDesc *fd,
+ PRIntn backlog);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``fd``
+ A pointer to a :ref:`PRFileDesc` object representing a socket that will
+ be used to listen for new connections.
+``backlog``
+ The maximum length of the queue of pending connections.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- Upon successful completion of listen request, ``PR_SUCCESS``.
+- If unsuccessful, ``PR_FAILURE``. Further information can be obtained
+ by calling :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+:ref:`PR_Listen` turns the specified socket into a rendezvous socket. It
+creates a queue for pending connections and starts to listen for
+connection requests on the socket. The maximum size of the queue for
+pending connections is specified by the ``backlog`` parameter. Pending
+connections may be accepted by calling :ref:`PR_Accept`.
diff --git a/docs/nspr/reference/pr_loadlibrary.rst b/docs/nspr/reference/pr_loadlibrary.rst
new file mode 100644
index 0000000000..707973ec51
--- /dev/null
+++ b/docs/nspr/reference/pr_loadlibrary.rst
@@ -0,0 +1,46 @@
+PR_LoadLibrary
+==============
+
+Loads a referenced library.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prlink.h>
+
+ PRLibrary* PR_LoadLibrary(const char *name);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has this parameter:
+
+``name``
+ A platform-dependent character array that names the library to be
+ loaded, as returned by :ref:`PR_GetLibraryName`.
+
+
+Returns
+~~~~~~~
+
+If successful, returns a reference to an opaque :ref:`PRLibrary` object.
+
+If the operation fails, returns ``NULL``. Use :ref:`PR_GetError` to find
+the reason for the failure.
+
+
+Description
+-----------
+
+This function loads and returns a reference to the specified library.
+The returned reference becomes the library's identity. The function
+suppresses duplicate loading if the library is already known by the
+runtime.
+
+Each call to :ref:`PR_LoadLibrary` must be paired with a corresponding call
+to :ref:`PR_UnloadLibrary` in order to return the runtime to its original
+state.
diff --git a/docs/nspr/reference/pr_localtimeparameters.rst b/docs/nspr/reference/pr_localtimeparameters.rst
new file mode 100644
index 0000000000..976db05547
--- /dev/null
+++ b/docs/nspr/reference/pr_localtimeparameters.rst
@@ -0,0 +1,39 @@
+PR_LocalTimeParameters
+======================
+
+Returns the time zone offset information that maps the specified
+:ref:`PRExplodedTime` to local time.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtime.h>
+
+ PRTimeParameters PR_LocalTimeParameters (
+ const PRExplodedTime *gmt);
+
+
+Parameter
+~~~~~~~~~
+
+``gmt``
+ A pointer to the clock/calendar time whose offsets are to be
+ determined. This time should be specified in GMT.
+
+
+Returns
+~~~~~~~
+
+A time parameters structure that expresses the time zone offsets at the
+specified time.
+
+
+Description
+-----------
+
+This is a frequently-used time parameter callback function. You don't
+normally call it directly; instead, you pass it as a parameter to
+``PR_ExplodeTime()`` or ``PR_NormalizeTime()``.
diff --git a/docs/nspr/reference/pr_lock.rst b/docs/nspr/reference/pr_lock.rst
new file mode 100644
index 0000000000..53cb17deea
--- /dev/null
+++ b/docs/nspr/reference/pr_lock.rst
@@ -0,0 +1,42 @@
+PR_Lock
+=======
+
+Locks a specified lock object.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prlock.h>
+
+ void PR_Lock(PRLock *lock);
+
+
+Parameter
+~~~~~~~~~
+
+:ref:`PR_Lock` has one parameter:
+
+``lock``
+ A pointer to a lock object to be locked.
+
+
+Description
+-----------
+
+When :ref:`PR_Lock` returns, the calling thread is "in the monitor," also
+called "holding the monitor's lock." Any thread that attempts to acquire
+the same lock blocks until the holder of the lock exits the monitor.
+Acquiring the lock is not an interruptible operation, nor is there any
+timeout mechanism.
+
+:ref:`PR_Lock` is not reentrant. Calling it twice on the same thread
+results in undefined behavior.
+
+
+See Also
+--------
+
+ - :ref:`PR_Unlock`
diff --git a/docs/nspr/reference/pr_malloc.rst b/docs/nspr/reference/pr_malloc.rst
new file mode 100644
index 0000000000..d475ab6a4b
--- /dev/null
+++ b/docs/nspr/reference/pr_malloc.rst
@@ -0,0 +1,35 @@
+PR_MALLOC
+=========
+
+Allocates memory of a specified size from the heap.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prmem.h>
+
+ void * PR_MALLOC(_bytes);
+
+
+Parameter
+~~~~~~~~~
+
+``_bytes``
+ Size of the requested memory block.
+
+
+Returns
+~~~~~~~
+
+An untyped pointer to the allocated memory, or if the allocation attempt
+fails, ``NULL``. Call ``PR_GetError()`` to retrieve the error returned
+by the libc function ``malloc()``.
+
+
+Description
+-----------
+
+This macro allocates memory of the requested size from the heap.
diff --git a/docs/nspr/reference/pr_memmap.rst b/docs/nspr/reference/pr_memmap.rst
new file mode 100644
index 0000000000..6b37fe313a
--- /dev/null
+++ b/docs/nspr/reference/pr_memmap.rst
@@ -0,0 +1,50 @@
+PR_MemMap
+=========
+
+Maps a section of a file to memory.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ void* PR_MemMap(
+ PRFileMap *fmap,
+ PRInt64 offset,
+ PRUint32 len);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``fmap``
+ A pointer to the file-mapping object representing the file to be
+ memory-mapped.
+``offset``
+ The starting offset of the section of file to be mapped. The offset
+ must be aligned to whole pages.
+``len``
+ Length of the section of the file to be mapped.
+
+
+Returns
+~~~~~~~
+
+The starting address of the memory region to which the section of file
+is mapped. Returns ``NULL`` on error.
+
+
+Description
+-----------
+
+:ref:`PR_MemMap` maps a section of the file represented by the file mapping
+``fmap`` to memory. The section of the file starts at ``offset`` and has
+the length ``len``.
+
+When the file-mapping memory region is no longer needed, it should be
+unmapped with a call to :ref:`PR_MemUnmap`.
diff --git a/docs/nspr/reference/pr_microsecondstointerval.rst b/docs/nspr/reference/pr_microsecondstointerval.rst
new file mode 100644
index 0000000000..9771809bb8
--- /dev/null
+++ b/docs/nspr/reference/pr_microsecondstointerval.rst
@@ -0,0 +1,30 @@
+PR_MicrosecondsToInterval
+=========================
+
+Converts standard clock microseconds to platform-dependent intervals.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prinrval.h>
+
+ PRIntervalTime PR_MicrosecondsToInterval(PRUint32 milli);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``micro``
+ The number of microseconds to convert to interval form.
+
+
+Returns
+~~~~~~~
+
+Platform-dependent equivalent of the value passed in the ``micro``
+parameter.
diff --git a/docs/nspr/reference/pr_millisecondstointerval.rst b/docs/nspr/reference/pr_millisecondstointerval.rst
new file mode 100644
index 0000000000..b456b0096d
--- /dev/null
+++ b/docs/nspr/reference/pr_millisecondstointerval.rst
@@ -0,0 +1,30 @@
+PR_MillisecondsToInterval
+=========================
+
+Converts standard clock milliseconds to platform-dependent intervals.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prinrval.h>
+
+ PRIntervalTime PR_MillisecondsToInterval(PRUint32 milli);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``milli``
+ The number of milliseconds to convert to interval form.
+
+
+Returns
+~~~~~~~
+
+Platform-dependent equivalent of the value passed in the ``milli``
+parameter.
diff --git a/docs/nspr/reference/pr_mkdir.rst b/docs/nspr/reference/pr_mkdir.rst
new file mode 100644
index 0000000000..6d1a629f9a
--- /dev/null
+++ b/docs/nspr/reference/pr_mkdir.rst
@@ -0,0 +1,67 @@
+PR_MkDir
+========
+
+Creates a directory with a specified name and access mode.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRStatus PR_MkDir(
+ const char *name,
+ PRIntn mode);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``name``
+ The name of the directory to be created. All the path components up
+ to but not including the leaf component must already exist.
+``mode``
+ The access permission bits of the file mode of the new directory if
+ the file is created when ``PR_CREATE_FILE`` is on.
+
+Caveat: The mode parameter is currently applicable only on Unix
+platforms. It may be applicable to other platforms in the future.
+
+Possible values include the following:
+
+ - :ref:`00400`. Read by owner.
+ - :ref:`00200`. Write by owner.
+ - :ref:`00100`. Search by owner.
+ - :ref:`00040`. Read by group.
+ - :ref:`00020`. Write by group.
+ - :ref:`00010`. Search by group.
+ - :ref:`00004`. Read by others.
+ - :ref:`00002`. Write by others.
+ - :ref:`00001`. Search by others.
+
+
+Returns
+~~~~~~~
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful, ``PR_FAILURE``. The actual reason can be retrieved
+ via :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+:ref:`PR_MkDir` creates a new directory with the pathname ``name``. All the
+path components up to but not including the leaf component must already
+exist. For example, if the pathname of the directory to be created is
+``a/b/c/d``, the directory ``a/b/c`` must already exist.
+
+
+See Also
+--------
+
+:ref:`PR_RmDir`
diff --git a/docs/nspr/reference/pr_msec_per_sec.rst b/docs/nspr/reference/pr_msec_per_sec.rst
new file mode 100644
index 0000000000..86cabb124a
--- /dev/null
+++ b/docs/nspr/reference/pr_msec_per_sec.rst
@@ -0,0 +1,16 @@
+PR_MSEC_PER_SEC
+===============
+
+A convenience macro to improve code readability as well as to avoid
+mistakes in counting the number of zeros; represents the number of
+milliseconds in a second.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtime.h>
+
+ #define PR_MSEC_PER_SEC 1000UL
diff --git a/docs/nspr/reference/pr_name.rst b/docs/nspr/reference/pr_name.rst
new file mode 100644
index 0000000000..cddb792155
--- /dev/null
+++ b/docs/nspr/reference/pr_name.rst
@@ -0,0 +1,18 @@
+PR_NAME
+=======
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prinit.h>
+
+ #define PR_NAME "NSPR"
+
+
+Description
+-----------
+
+NSPR Name.
diff --git a/docs/nspr/reference/pr_netaddrtostring.rst b/docs/nspr/reference/pr_netaddrtostring.rst
new file mode 100644
index 0000000000..d21f4d5ecc
--- /dev/null
+++ b/docs/nspr/reference/pr_netaddrtostring.rst
@@ -0,0 +1,50 @@
+PR_NetAddrToString
+==================
+
+Converts a character string to a network address.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prnetdb.h>
+
+ PRStatus PR_NetAddrToString(
+ const PRNetAddr *addr,
+ char *string,
+ PRUint32 size);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``addr``
+ A pointer to the network address to be converted.
+``string``
+ A buffer that will hold the converted string on output.
+``size``
+ The size of the result buffer (``string``).
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful, ``PR_FAILURE``. You can retrieve the reason for the
+ failure by calling :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+The network address to be converted (``addr``) may be either an IPv4 or
+IPv6 address structure, assuming that the NSPR library and the host
+system are both configured to utilize IPv6 addressing. If ``addr`` is an
+IPv4 address, ``size`` needs to be at least 16. If ``addr`` is an IPv6
+address, ``size`` needs to be at least 46.
diff --git a/docs/nspr/reference/pr_netdb_buf_size.rst b/docs/nspr/reference/pr_netdb_buf_size.rst
new file mode 100644
index 0000000000..6c5c79d560
--- /dev/null
+++ b/docs/nspr/reference/pr_netdb_buf_size.rst
@@ -0,0 +1,20 @@
+PR_NETDB_BUF_SIZE
+=================
+
+Recommended size to use when specifying a scratch buffer for
+:ref:`PR_GetHostByName`, :ref:`PR_GetHostByAddr`, :ref:`PR_GetProtoByName`, or
+:ref:`PR_GetProtoByNumber`.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prnetdb.h>
+
+ #if defined(AIX) || defined(OSF1)
+ #define PR_NETDB_BUF_SIZE sizeof(struct protoent_data)
+ #else
+ #define PR_NETDB_BUF_SIZE 1024
+ #endif
diff --git a/docs/nspr/reference/pr_new.rst b/docs/nspr/reference/pr_new.rst
new file mode 100644
index 0000000000..0fd9973419
--- /dev/null
+++ b/docs/nspr/reference/pr_new.rst
@@ -0,0 +1,36 @@
+PR_NEW
+======
+
+Allocates memory of a specified size from the heap.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prmem.h>
+
+ _type * PR_NEW(_struct);
+
+
+Parameter
+~~~~~~~~~
+
+``_struct``
+ The name of a type.
+
+
+Returns
+~~~~~~~
+
+An pointer to a buffer sized to contain the type ``_struct``, or if the
+allocation attempt fails, ``NULL``. Call ``PR_GetError()`` to retrieve
+the error returned by the libc function.
+
+
+Description
+-----------
+
+This macro allocates memory whose size is ``sizeof(_struct)`` and
+returns a pointer to that memory.
diff --git a/docs/nspr/reference/pr_newcondvar.rst b/docs/nspr/reference/pr_newcondvar.rst
new file mode 100644
index 0000000000..1ce70e8fe5
--- /dev/null
+++ b/docs/nspr/reference/pr_newcondvar.rst
@@ -0,0 +1,34 @@
+PR_NewCondVar
+=============
+
+Creates a new condition variable.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prcvar.h>
+
+ PRCondVar* PR_NewCondVar(PRLock *lock);
+
+
+Parameter
+~~~~~~~~~
+
+:ref:`PR_NewCondVar` has one parameter:
+
+``lock``
+ The identity of the mutex that protects the monitored data, including
+ this condition variable.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, a pointer to the new condition variable object.
+- If unsuccessful (for example, if system resources are unavailable),
+ ``NULL``.
diff --git a/docs/nspr/reference/pr_newlock.rst b/docs/nspr/reference/pr_newlock.rst
new file mode 100644
index 0000000000..7fbc51348e
--- /dev/null
+++ b/docs/nspr/reference/pr_newlock.rst
@@ -0,0 +1,30 @@
+PR_NewLock
+==========
+
+Creates a new lock.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prlock.h>
+
+ PRLock* PR_NewLock(void);
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, a pointer to the new lock object.
+- If unsuccessful (for example, the lock cannot be created because of
+ resource constraints), ``NULL``.
+
+
+Description
+-----------
+
+:ref:`PR_NewLock` creates a new opaque lock.
diff --git a/docs/nspr/reference/pr_newmonitor.rst b/docs/nspr/reference/pr_newmonitor.rst
new file mode 100644
index 0000000000..3f80340c77
--- /dev/null
+++ b/docs/nspr/reference/pr_newmonitor.rst
@@ -0,0 +1,31 @@
+PR_NewMonitor
+=============
+
+Creates a new monitor object. The caller is responsible for the object
+and is expected to destroy it when appropriate.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prmon.h>
+
+ PRMonitor* PR_NewMonitor(void);
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, a pointer to a :ref:`PRMonitor` object.
+- If unsuccessful (for example, if some operating system resource is
+ unavailable), ``NULL``.
+
+
+Description
+-----------
+
+A newly created monitor has an entry count of zero.
diff --git a/docs/nspr/reference/pr_newpollableevent.rst b/docs/nspr/reference/pr_newpollableevent.rst
new file mode 100644
index 0000000000..be4e0f9bd8
--- /dev/null
+++ b/docs/nspr/reference/pr_newpollableevent.rst
@@ -0,0 +1,24 @@
+PR_NewPollableEvent
+===================
+
+Create a pollable event file descriptor.
+
+
+Syntax
+------
+
+.. code::
+
+ NSPR_API(PRFileDesc *) PR_NewPollableEvent( void);
+
+
+Parameter
+~~~~~~~~~
+
+None.
+
+
+Returns
+~~~~~~~
+
+Pointer to :ref:`PRFileDesc` or ``NULL``, on error.
diff --git a/docs/nspr/reference/pr_newprocessattr.rst b/docs/nspr/reference/pr_newprocessattr.rst
new file mode 100644
index 0000000000..a01e55498f
--- /dev/null
+++ b/docs/nspr/reference/pr_newprocessattr.rst
@@ -0,0 +1,39 @@
+PR_NewProcessAttr
+=================
+
+Creates a process attributes structure.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prlink.h>
+
+ PRProcessAttr *PR_NewProcessAttr(void);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has no parameters.
+
+
+Returns
+~~~~~~~
+
+A pointer to the new process attributes structure.
+
+
+Description
+-----------
+
+This function creates a new :ref:`PRProcessAttr`\ structure that specifies
+the attributes of a new process, then returns a pointer to the
+structure. The new :ref:`PRProcessAttr`\ structure is initialized with
+these default attributes:
+
+- The standard I/O streams (standard input, standard output, and
+ standard error) are not redirected.
+- No file descriptors are inherited by the new process.
diff --git a/docs/nspr/reference/pr_newtcpsocket.rst b/docs/nspr/reference/pr_newtcpsocket.rst
new file mode 100644
index 0000000000..4a6b285669
--- /dev/null
+++ b/docs/nspr/reference/pr_newtcpsocket.rst
@@ -0,0 +1,56 @@
+PR_NewTCPSocket
+===============
+
+Creates a new IPv4 TCP socket.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRFileDesc* PR_NewTCPSocket(void);
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- Upon successful completion, a pointer to the :ref:`PRFileDesc` object
+ created for the newly opened IPv4 TCP socket.
+- If the creation of a new TCP socket failed, ``NULL``.
+
+
+Description
+-----------
+
+TCP (Transmission Control Protocol) is a connection-oriented, reliable
+byte-stream protocol of the TCP/IP protocol suite. :ref:`PR_NewTCPSocket`
+creates a new IPv4 TCP socket. A TCP connection is established by a
+passive socket (the server) accepting a connection setup request from an
+active socket (the client). Typically, the server binds its socket to a
+well-known port with :ref:`PR_Bind`, calls :ref:`PR_Listen` to start listening
+for connection setup requests, and calls :ref:`PR_Accept` to accept a
+connection. The client makes a connection request using :ref:`PR_Connect`.
+
+After a connection is established, the client and server may send and
+receive data between each other. To receive data, one can call
+:ref:`PR_Read` or :ref:`PR_Recv`. To send data, one can call :ref:`PR_Write`,
+:ref:`PR_Writev`, :ref:`PR_Send`, or :ref:`PR_TransmitFile`. :ref:`PR_AcceptRead` is
+suitable for use by the server to accept a new client connection and
+read the client's first request in one function call.
+
+A TCP connection can be shut down by :ref:`PR_Shutdown`, and the sockets
+should be closed by :ref:`PR_Close`.
+
+
+See Also
+--------
+
+:ref:`PR_NewTCPSocket` is deprecated because it is hardcoded to create an
+IPv4 TCP socket. New code should use :ref:`PR_OpenTCPSocket` instead, which
+allows the address family (IPv4 or IPv6) of the new TCP socket to be
+specified.
diff --git a/docs/nspr/reference/pr_newthreadprivateindex.rst b/docs/nspr/reference/pr_newthreadprivateindex.rst
new file mode 100644
index 0000000000..8cb926bbee
--- /dev/null
+++ b/docs/nspr/reference/pr_newthreadprivateindex.rst
@@ -0,0 +1,65 @@
+PR_NewThreadPrivateIndex
+========================
+
+Returns a new index for a per-thread private data table and optionally
+associates a destructor with the data that will be assigned to the
+index.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prthread.h>
+
+ PRStatus PR_NewThreadPrivateIndex(
+ PRUintn *newIndex,
+ PRThreadPrivateDTOR destructor);
+
+
+Parameters
+~~~~~~~~~~
+
+:ref:`PR_NewThreadPrivateIndex` has the following parameters:
+
+``newIndex``
+ On output, an index that is valid for all threads in the process. You
+ use this index with :ref:`PR_SetThreadPrivate` and
+ :ref:`PR_GetThreadPrivate`.
+``destructor``
+ Specifies a destructor function :ref:`PRThreadPrivateDTOR` for the
+ private data associated with the index. This function can be
+ specified as ``NULL``.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, ``PR_SUCCESS``.
+- If the total number of indices exceeds 128, ``PR_FAILURE``.
+
+
+Description
+-----------
+
+If :ref:`PR_NewThreadPrivateIndex` is successful, every thread in the same
+process is capable of associating private data with the new index. Until
+the data for an index is actually set, the value of the private data at
+that index is ``NULL``. You pass this index to :ref:`PR_SetThreadPrivate`
+and :ref:`PR_GetThreadPrivate` to set and retrieve data associated with the
+index.
+
+When you allocate the index, you may also register a destructor function
+of type :ref:`PRThreadPrivateDTOR`. If a destructor function is registered
+with a new index, it will be called at one of two times, as long as the
+private data is not ``NULL``:
+
+- when replacement private data is set with :ref:`PR_SetThreadPrivate`
+- when a thread exits
+
+The index maintains independent data values for each binding thread. A
+thread can get access only to its own thread-specific data. There is no
+way to deallocate a private data index once it is allocated.
diff --git a/docs/nspr/reference/pr_newudpsocket.rst b/docs/nspr/reference/pr_newudpsocket.rst
new file mode 100644
index 0000000000..67aff3e5d4
--- /dev/null
+++ b/docs/nspr/reference/pr_newudpsocket.rst
@@ -0,0 +1,46 @@
+PR_NewUDPSocket
+===============
+
+Creates a new UDP socket.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRFileDesc* PR_NewUDPSocket(void);
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- Upon successful completion, a pointer to the :ref:`PRFileDesc` object
+ created for the newly opened UDP socket.
+- If the creation of a new UDP socket failed, ``NULL``.
+
+
+Description
+-----------
+
+UDP (User Datagram Protocol) is a connectionless, unreliable datagram
+protocol of the TCP/IP protocol suite. UDP datagrams may be lost or
+delivered in duplicates or out of sequence.
+
+:ref:`PR_NewUDPSocket` creates a new UDP socket. The socket may be bound to
+a well-known port number with :ref:`PR_Bind`. Datagrams can be sent with
+:ref:`PR_SendTo` and received with :ref:`PR_RecvFrom`. When the socket is no
+longer needed, it should be closed with a call to :ref:`PR_Close`.
+
+
+See Also
+--------
+
+:ref:`PR_NewUDPSocket` is deprecated because it is hardcoded to create an
+IPv4 UDP socket. New code should use :ref:`PR_OpenUDPSocket` instead, which
+allows the address family (IPv4 or IPv6) of the new UDP socket to be
+specified.
diff --git a/docs/nspr/reference/pr_newzap.rst b/docs/nspr/reference/pr_newzap.rst
new file mode 100644
index 0000000000..55d77885b1
--- /dev/null
+++ b/docs/nspr/reference/pr_newzap.rst
@@ -0,0 +1,38 @@
+PR_NEWZAP
+=========
+
+Allocates and clears memory from the heap for an instance of a given
+type.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prmem.h>
+
+ _type * PR_NEWZAP(_struct);
+
+
+Parameter
+~~~~~~~~~
+
+``_struct``
+ The name of a type.
+
+
+Returns
+~~~~~~~
+
+An pointer to a buffer sized to contain the type ``_struct``, or if the
+allocation attempt fails, ``NULL``. The bytes in the buffer are all
+initialized to 0. Call ``PR_GetError()`` to retrieve the error returned
+by the libc function.
+
+
+Description
+-----------
+
+This macro allocates an instance of the specified type from the heap and
+sets the content of that memory to zero.
diff --git a/docs/nspr/reference/pr_next_link.rst b/docs/nspr/reference/pr_next_link.rst
new file mode 100644
index 0000000000..1a48065e69
--- /dev/null
+++ b/docs/nspr/reference/pr_next_link.rst
@@ -0,0 +1,35 @@
+PR_NEXT_LINK
+============
+
+Returns the next element in a list.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prclist.h>
+
+ PRCList *PR_NEXT_LINK (PRCList *elemp);
+
+
+Parameter
+~~~~~~~~~
+
+``elemp``
+ A pointer to the element.
+
+
+Returns
+~~~~~~~
+
+A pointer to a list element.
+
+
+Description
+-----------
+
+PR_NEXT_LINK returns a pointer to the element following the specified
+element. It can be used to traverse a list. The following element is not
+removed from the list.
diff --git a/docs/nspr/reference/pr_normalizetime.rst b/docs/nspr/reference/pr_normalizetime.rst
new file mode 100644
index 0000000000..47a82ae78b
--- /dev/null
+++ b/docs/nspr/reference/pr_normalizetime.rst
@@ -0,0 +1,62 @@
+PR_NormalizeTime
+================
+
+Adjusts the fields of a clock/calendar time to their proper ranges,
+using a callback function.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtime.h>
+
+ void PR_NormalizeTime (
+ PRExplodedTime *time,
+ PRTimeParamFn params);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has these parameters:
+
+``time``
+ A pointer to a clock/calendar time in the :ref:`PRExplodedTime` format.
+``params``
+ A time parameter callback function.
+
+
+Returns
+~~~~~~~
+
+Nothing; the ``time`` parameter is altered by the callback function.
+
+
+Description
+-----------
+
+This function adjusts the fields of the specified time structure using
+the specified time parameter callback function, so that they are in the
+proper range.
+
+Call this function in these situations:
+
+- To normalize a time after performing arithmetic operations directly
+ on the field values of the calendar time object. For example, if you
+ have a ``]`` object that represents the date 3 March 1998 and you
+ want to say "forty days from 3 March 1998", you can simply add 40 to
+ the ``tm_mday`` field and then call ``PR_NormalizeTime()``.
+
+- To calculate the optional field values ``tm_wday`` and ``tm_yday``.
+ For example, suppose you want to compute the day of week for 3 March
+ 1998. You can set ``tm_mday`` to 3, ``tm_month`` to 2, and
+ ``tm_year`` to 1998, and all the other fields to 0, then call
+ ``PR_NormalizeTime()`` with :ref:`PR_GMTParameters`. On return,
+ ``tm_wday`` (and ``tm_yday``) are set for you.
+
+- To convert from one time zone to another. For example, if the input
+ argument time is in time zone A and the input argument ``params``
+ represents time zone B, when ``PR_NormalizeTime()`` returns, time
+ will be in time zone B.
diff --git a/docs/nspr/reference/pr_notify.rst b/docs/nspr/reference/pr_notify.rst
new file mode 100644
index 0000000000..c34d07f89b
--- /dev/null
+++ b/docs/nspr/reference/pr_notify.rst
@@ -0,0 +1,48 @@
+PR_Notify
+=========
+
+Notifies a monitor that a change in state of the monitored data has
+occurred.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prmon.h>
+
+ PRStatus PR_Notify(PRMonitor *mon);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameter:
+
+``mon``
+ A reference to an existing structure of type :ref:`PRMonitor`. The
+ monitor object referenced must be one for which the calling thread
+ currently holds the lock.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful, ``PR_FAILURE``.
+
+
+Description
+-----------
+
+Notification of a monitor signals the change of state of some monitored
+data. The changing of that data and the notification must all be
+performed while in the monitor. When the notification occurs, the
+runtime promotes a thread that is waiting on the monitor to a ready
+state. If more than one thread is waiting, the selection of which thread
+gets promoted cannot be determined in advance. This implies that all
+threads waiting on a single monitor must have the same semantics. If no
+thread is waiting on the monitor, the notify operation is a no-op.
diff --git a/docs/nspr/reference/pr_notifyall.rst b/docs/nspr/reference/pr_notifyall.rst
new file mode 100644
index 0000000000..0dd3a62b7e
--- /dev/null
+++ b/docs/nspr/reference/pr_notifyall.rst
@@ -0,0 +1,46 @@
+PR_NotifyAll
+============
+
+Promotes all threads waiting on a specified monitor to a ready state.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prmon.h>
+
+ PRStatus PR_NotifyAll(PRMonitor *mon);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameter:
+
+``mon``
+ A reference to an existing structure of type :ref:`PRMonitor`. The
+ monitor object referenced must be one for which the calling thread
+ currently holds the lock.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful, ``PR_FAILURE``.
+
+
+Description
+-----------
+
+A call to :ref:`PR_NotifyAll` causes all of the threads waiting on the
+monitor to be scheduled to be promoted to a ready state. If no threads
+are waiting, the operation is no-op.
+
+:ref:`PR_NotifyAll` should be used with some care. The expense of
+scheduling multiple threads increases dramatically as the number of
+threads increases.
diff --git a/docs/nspr/reference/pr_notifyallcondvar.rst b/docs/nspr/reference/pr_notifyallcondvar.rst
new file mode 100644
index 0000000000..aa73167c99
--- /dev/null
+++ b/docs/nspr/reference/pr_notifyallcondvar.rst
@@ -0,0 +1,35 @@
+PR_NotifyAllCondVar
+===================
+
+Notifies all of the threads waiting on a specified condition variable.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prcvar.h>
+
+ PRStatus PR_NotifyAllCondVar(PRCondVar *cvar);
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful (for example, if the caller has not locked the lock
+ associated with the condition variable), ``PR_FAILURE``.
+
+
+Description
+-----------
+
+The calling thread must hold the lock that protects the condition, as
+well as the invariants that are tightly bound to the condition.
+
+A call to :ref:`PR_NotifyAllCondVar` causes all of the threads waiting on
+the specified condition variable to be promoted to a ready state. If no
+threads are waiting, the operation is no-op.
diff --git a/docs/nspr/reference/pr_notifycondvar.rst b/docs/nspr/reference/pr_notifycondvar.rst
new file mode 100644
index 0000000000..e7289d9b06
--- /dev/null
+++ b/docs/nspr/reference/pr_notifycondvar.rst
@@ -0,0 +1,49 @@
+PR_NotifyCondVar
+================
+
+Notifies a condition variable of a change in its associated monitored
+data.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prcvar.h>
+
+ PRStatus PR_NotifyCondVar(PRCondVar *cvar);
+
+
+Parameter
+~~~~~~~~~
+
+:ref:`PR_NotifyCondVar` has one parameter:
+
+``cvar``
+ The condition variable to notify.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful (for example, if the caller has not locked the lock
+ associated with the condition variable), ``PR_FAILURE``.
+
+
+Description
+-----------
+
+The calling thread must hold the lock that protects the condition, as
+well as the invariants that are tightly bound to the condition.
+
+Notification of a condition variable signals a change of state in some
+monitored data. When the notification occurs, the runtime promotes a
+thread that is waiting on the condition variable to a ready state. If
+more than one thread is waiting, the selection of which thread gets
+promoted cannot be predicted. This implies that all threads waiting on a
+single condition variable must have the same semantics. If no thread is
+waiting on the condition variable, the notify operation is a no-op.
diff --git a/docs/nspr/reference/pr_now.rst b/docs/nspr/reference/pr_now.rst
new file mode 100644
index 0000000000..5f5efc0203
--- /dev/null
+++ b/docs/nspr/reference/pr_now.rst
@@ -0,0 +1,38 @@
+PR_Now
+======
+
+Returns the current time.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtime.h>
+
+ PRTime PR_Now(void);
+
+
+Parameters
+~~~~~~~~~~
+
+None.
+
+
+Returns
+~~~~~~~
+
+The current time as a :ref:`PRTime` value.
+
+
+Description
+-----------
+
+``PR_Now()`` returns the current time as number of microseconds since
+the NSPR epoch, which is midnight (00:00:00) 1 January 1970 UTC.
+
+You cannot assume that the values returned by ``PR_Now()`` are
+monotonically increasing because the system clock of the computer may be
+reset. To obtain monotonically increasing time stamps suitable for
+measuring elapsed time, use ``PR_IntervalNow()``.
diff --git a/docs/nspr/reference/pr_nsec_per_msec.rst b/docs/nspr/reference/pr_nsec_per_msec.rst
new file mode 100644
index 0000000000..8d471bd300
--- /dev/null
+++ b/docs/nspr/reference/pr_nsec_per_msec.rst
@@ -0,0 +1,16 @@
+PR_NSEC_PER_MSEC
+================
+
+A convenience macro to improve code readability as well as to avoid
+mistakes in counting the number of zeros; represents the number of
+nanoseconds in a millisecond.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtime.h>
+
+ #define PR_NSEC_PER_MSEC 1000000UL
diff --git a/docs/nspr/reference/pr_nsec_per_sec.rst b/docs/nspr/reference/pr_nsec_per_sec.rst
new file mode 100644
index 0000000000..78ceb7b326
--- /dev/null
+++ b/docs/nspr/reference/pr_nsec_per_sec.rst
@@ -0,0 +1,16 @@
+PR_NSEC_PER_SEC
+===============
+
+A convenience macro to improve code readability as well as to avoid
+mistakes in counting the number of zeros; represents the number of
+nanoseconds in a second.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtime.h>
+
+ #define PR_NSEC_PER_SEC 1000000000UL
diff --git a/docs/nspr/reference/pr_ntohl.rst b/docs/nspr/reference/pr_ntohl.rst
new file mode 100644
index 0000000000..8c48f88f40
--- /dev/null
+++ b/docs/nspr/reference/pr_ntohl.rst
@@ -0,0 +1,29 @@
+PR_ntohl
+========
+
+Performs 32-bit conversion from network byte order to host byte order.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prnetdb.h>
+
+ PRUint32 PR_ntohl(PRUint32 conversion);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``conversion``
+ The 32-bit unsigned integer, in network byte order, to be converted.
+
+
+Returns
+~~~~~~~
+
+The value of the ``conversion`` parameter in host byte order.
diff --git a/docs/nspr/reference/pr_ntohs.rst b/docs/nspr/reference/pr_ntohs.rst
new file mode 100644
index 0000000000..a20c9d2851
--- /dev/null
+++ b/docs/nspr/reference/pr_ntohs.rst
@@ -0,0 +1,29 @@
+PR_ntohs
+========
+
+Performs 16-bit conversion from network byte order to host byte order.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prnetdb.h>
+
+ PRUint16 PR_ntohs(PRUint16 conversion);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``conversion``
+ The 16-bit unsigned integer, in network byte order, to be converted.
+
+
+Returns
+~~~~~~~
+
+The value of the ``conversion`` parameter in host byte order.
diff --git a/docs/nspr/reference/pr_open.rst b/docs/nspr/reference/pr_open.rst
new file mode 100644
index 0000000000..1dba4d6a56
--- /dev/null
+++ b/docs/nspr/reference/pr_open.rst
@@ -0,0 +1,117 @@
+PR_Open
+=======
+
+Opens a file for reading, writing, or both. Also used to create a file.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRFileDesc* PR_Open(
+ const char *name,
+ PRIntn flags,
+ PRIntn mode);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``name``
+ The pathname of the file to be opened.
+``flags``
+ The file status flags define how the file is accessed. It is a
+ bitwise ``OR`` of the following bit flags. In most cases, only one of
+ the first three flags may be used. If the ``flags`` parameter does
+ not include any of the first three flags (``PR_RDONLY``,
+ ``PR_WRONLY``, or ``PR_RDWR``), the open file can't be read or
+ written, which is not useful.
+
+.. note::
+
+ **NOTE**: The constants PR_RDWR and friends are not in any interface
+ (`bug 433295 <https://bugzilla.mozilla.org/show_bug.cgi?id=433295>`__).
+ Thus they cannot be used in JavaScript, you have to use the octal
+ constants (see `File I/O Snippets </en/Code_snippets:File_I/O>`__).
+
++--------------------+-------+---------------------------------------+
+| Name | Value | Description |
++====================+=======+=======================================+
+| ``PR_RDONLY`` | 0x01 | Open for reading only. |
++--------------------+-------+---------------------------------------+
+| ``PR_WRONLY`` | 0x02 | Open for writing only. |
++--------------------+-------+---------------------------------------+
+| ``PR_RDWR`` | 0x04 | Open for reading and writing. |
++--------------------+-------+---------------------------------------+
+| ``PR_CREATE_FILE`` | 0x08 | If the file does not exist, the file |
+| | | is created. If the file exists, this |
+| | | flag has no effect. |
++--------------------+-------+---------------------------------------+
+| ``PR_APPEND`` | 0x10 | The file pointer is set to the end of |
+| | | the file prior to each write. |
++--------------------+-------+---------------------------------------+
+| ``PR_TRUNCATE`` | 0x20 | If the file exists, its length is |
+| | | truncated to 0. |
++--------------------+-------+---------------------------------------+
+| ``PR_SYNC`` | 0x40 | If set, each write will wait for both |
+| | | the file data and file status to be |
+| | | physically updated. |
++--------------------+-------+---------------------------------------+
+| ``PR_EXCL`` | 0x80 | With ``PR_CREATE_FILE``, if the file |
+| | | does not exist, the file is created. |
+| | | If the file already exists, no action |
+| | | and NULL is returned. |
++--------------------+-------+---------------------------------------+
+
+
+
+``mode``
+ When ``PR_CREATE_FILE`` flag is set and the file is created, these
+ flags define the access permission bits of the newly created file.
+ This feature is currently only applicable on Unix platforms. It is
+ ignored by any other platform but it may apply to other platforms in
+ the future. Possible values of the ``mode`` parameter are listed in
+ the table below.
+
+============ ===== =====================================
+Name Value Description
+============ ===== =====================================
+``PR_IRWXU`` 0700 read, write, execute/search by owner.
+``PR_IRUSR`` 0400 read permission, owner.
+``PR_IWUSR`` 0200 write permission, owner.
+``PR_IXUSR`` 0100 execute/search permission, owner.
+``PR_IRWXG`` 0070 read, write, execute/search by group
+``PR_IRGRP`` 0040 read permission, group
+``PR_IWGRP`` 0020 write permission, group
+``PR_IXGRP`` 0010 execute/search permission, group
+``PR_IRWXO`` 0007 read, write, execute/search by others
+``PR_IROTH`` 0004 read permission, others
+``PR_IWOTH`` 0002 write permission, others
+``PR_IXOTH`` 0001 execute/search permission, others
+============ ===== =====================================
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If the file is successfully opened, a pointer to a dynamically
+ allocated :ref:`PRFileDesc` for the newly opened file. The
+ :ref:`PRFileDesc` should be freed by calling :ref:`PR_Close`.
+- If the file was not opened successfully, a ``NULL`` pointer.
+
+
+Description
+-----------
+
+:ref:`PR_Open` creates a file descriptor (:ref:`PRFileDesc`) for the file with
+the pathname ``name`` and sets the file status flags of the file
+descriptor according to the value of ``flags``. If a new file is created
+as a result of the :ref:`PR_Open` call, its file mode bits are set
+according to the ``mode`` parameter.
diff --git a/docs/nspr/reference/pr_openanonfilemap.rst b/docs/nspr/reference/pr_openanonfilemap.rst
new file mode 100644
index 0000000000..30964c5dd7
--- /dev/null
+++ b/docs/nspr/reference/pr_openanonfilemap.rst
@@ -0,0 +1,51 @@
+PR_OpenAnonFileMap
+==================
+
+Creates or opens a named semaphore with the specified name
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prshma.h>
+
+ NSPR_API( PRFileMap *)
+ PR_OpenAnonFileMap(
+ const char *dirName,
+ PRSize size,
+ PRFileMapProtect prot
+ );
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``dirName``
+ A pointer to a directory name that will contain the anonymous file.
+``size``
+ The size of the shared memory.
+``prot``
+ How the shared memory is mapped.
+
+
+Returns
+~~~~~~~
+
+Pointer to :ref:`PRFileMap` or ``NULL`` on error.
+
+
+Description
+-----------
+
+If the shared memory already exists, a handle is returned to that shared
+memory object.
+
+On Unix platforms, :ref:`PR_OpenAnonFileMap` uses ``dirName`` as a
+directory name, without the trailing '/', to contain the anonymous file.
+A filename is generated for the name.
+
+On Windows platforms, ``dirName`` is ignored.
diff --git a/docs/nspr/reference/pr_opendir.rst b/docs/nspr/reference/pr_opendir.rst
new file mode 100644
index 0000000000..9bfe91a6e7
--- /dev/null
+++ b/docs/nspr/reference/pr_opendir.rst
@@ -0,0 +1,41 @@
+PR_OpenDir
+==========
+
+Opens the directory with the specified pathname.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRDir* PR_OpenDir(const char *name);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``name``
+ The pathname of the directory to be opened.
+
+
+Returns
+~~~~~~~
+
+- If the directory is successfully opened, a :ref:`PRDir` object is
+ dynamically allocated and the function returns a pointer to it.
+- If the directory cannot be opened, the function returns ``NULL``.
+
+
+Description
+-----------
+
+:ref:`PR_OpenDir` opens the directory specified by the pathname ``name``
+and returns a pointer to a directory stream (a :ref:`PRDir` object) that
+can be passed to subsequent :ref:`PR_ReadDir` calls to get the directory
+entries (files and subdirectories) in the directory. The :ref:`PRDir`
+pointer should eventually be closed by a call to :ref:`PR_CloseDir`.
diff --git a/docs/nspr/reference/pr_opensemaphore.rst b/docs/nspr/reference/pr_opensemaphore.rst
new file mode 100644
index 0000000000..6ac69cbe71
--- /dev/null
+++ b/docs/nspr/reference/pr_opensemaphore.rst
@@ -0,0 +1,58 @@
+PR_OpenSemaphore
+================
+
+Creates or opens a named semaphore with the specified name.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <pripcsem.h>
+
+ #define PR_SEM_CREATE 0x1 /* create if not exist */
+
+ #define PR_SEM_EXCL 0x2 /* fail if already exists */
+
+ NSPR_API(PRSem *) PR_OpenSemaphore(
+ const char *name,
+ PRIntn flags,
+ PRIntn mode,
+ PRUintn value
+ );
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``name``
+ The name to be given the semaphore.
+``flags``
+ How to create or open the semaphore.
+``mode``
+ Unix style file mode to be used when creating the semaphore.
+``value``
+ The initial value assigned to the semaphore.
+
+
+Returns
+~~~~~~~
+
+A pointer to a PRSem structure or ``NULL/code> on error.``
+
+
+Description
+~~~~~~~~~~~
+
+If the named semaphore doesn't exist and the ``PR_SEM_CREATE`` flag is
+specified, the named semaphore is created. The created semaphore needs
+to be removed from the system with a :ref:`PR_DeleteSemaphore` call.
+
+If ``PR_SEM_CREATE`` is specified, the third argument is the access
+permission bits of the new semaphore (same interpretation as the mode
+argument to :ref:`PR_Open`) and the fourth argument is the initial value of
+the new semaphore. ``If PR_SEM_CREATE`` is not specified, the third and
+fourth arguments are ignored.
diff --git a/docs/nspr/reference/pr_opensharedmemory.rst b/docs/nspr/reference/pr_opensharedmemory.rst
new file mode 100644
index 0000000000..759a81dbf8
--- /dev/null
+++ b/docs/nspr/reference/pr_opensharedmemory.rst
@@ -0,0 +1,68 @@
+PR_OpenSharedMemory
+===================
+
+Opens an existing shared memory segment or, if one with the specified
+name doesn't exist, creates a new one.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prshm.h>
+
+ NSPR_API( PRSharedMemory * )
+ PR_OpenSharedMemory(
+ const char *name,
+ PRSize size,
+ PRIntn flags,
+ PRIntn mode
+ );
+
+ /* Define values for PR_OpenShareMemory(...,create) */
+ #define PR_SHM_CREATE 0x1 /* create if not exist */
+ #define PR_SHM_EXCL 0x2 /* fail if already exists */
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+name
+ The name of the shared memory segment.
+size
+ The size of the shared memory segment.
+flags
+ Options for creating the shared memory.
+mode
+ Same as passed to :ref:`PR_Open`.
+
+
+Returns
+~~~~~~~
+
+Pointer to opaque structure ``PRSharedMemory``, or ``NULL`` if an error
+occurs. Retrieve the reason for the failure by calling :ref:`PR_GetError`
+and :ref:`PR_GetOSError`.
+
+
+Description
+-----------
+
+:ref:`PR_OpenSharedMemory` creates a new shared memory segment or
+associates a previously created memory segment with the specified name.
+When parameter ``create`` is (``PR_SHM_EXCL`` \| ``PR_SHM_CREATE``) and
+the shared memory already exists, the function returns ``NULL`` with the
+error set to ``PR_FILE_EXISTS_ERROR``.
+
+When parameter ``create`` is ``PR_SHM_CREATE`` and the shared memory
+already exists, a handle to that memory segment is returned. If the
+segment does not exist, it is created and a pointer to the related
+``PRSharedMemory`` structure is returned.
+
+When parameter ``create`` is 0, and the shared memory exists, a pointer
+to a ``PRSharedMemory`` structure is returned. If the shared memory does
+not exist, ``NULL`` is returned with the error set to
+``PR_FILE_NOT_FOUND_ERROR``.
diff --git a/docs/nspr/reference/pr_opentcpsocket.rst b/docs/nspr/reference/pr_opentcpsocket.rst
new file mode 100644
index 0000000000..c9c11799cf
--- /dev/null
+++ b/docs/nspr/reference/pr_opentcpsocket.rst
@@ -0,0 +1,59 @@
+PR_OpenTCPSocket
+================
+
+Creates a new TCP socket of the specified address family.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRFileDesc* PR_OpenTCPSocket(PRIntn af);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``af``
+ The address family of the new TCP socket. Can be ``PR_AF_INET``
+ (IPv4), ``PR_AF_INET6`` (IPv6), or ``PR_AF_LOCAL`` (Unix domain,
+ supported on POSIX systems only).
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- Upon successful completion, a pointer to the :ref:`PRFileDesc` object
+ created for the newly opened TCP socket.
+- If the creation of a new TCP socket failed, ``NULL``.
+
+
+Description
+-----------
+
+TCP (Transmission Control Protocol) is a connection-oriented, reliable
+byte-stream protocol of the TCP/IP protocol suite. :ref:`PR_OpenTCPSocket`
+creates a new TCP socket of the address family ``af``. A TCP connection
+is established by a passive socket (the server) accepting a connection
+setup request from an active socket (the client). Typically, the server
+binds its socket to a well-known port with :ref:`PR_Bind`, calls
+:ref:`PR_Listen` to start listening for connection setup requests, and
+calls :ref:`PR_Accept` to accept a connection. The client makes a
+connection request using :ref:`PR_Connect`.
+
+After a connection is established, the client and server may send and
+receive data between each other. To receive data, one can call
+:ref:`PR_Read` or :ref:`PR_Recv`. To send data, one can call :ref:`PR_Write`,
+:ref:`PR_Writev`, :ref:`PR_Send`, or :ref:`PR_TransmitFile`. :ref:`PR_AcceptRead` is
+suitable for use by the server to accept a new client connection and
+read the client's first request in one function call.
+
+A TCP connection can be shut down by :ref:`PR_Shutdown`, and the sockets
+should be closed by :ref:`PR_Close`.
diff --git a/docs/nspr/reference/pr_openudpsocket.rst b/docs/nspr/reference/pr_openudpsocket.rst
new file mode 100644
index 0000000000..f25373d7d8
--- /dev/null
+++ b/docs/nspr/reference/pr_openudpsocket.rst
@@ -0,0 +1,49 @@
+PR_OpenUDPSocket
+================
+
+Creates a new UDP socket of the specified address family.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRFileDesc* PR_OpenUDPSocket(PRIntn af);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``af``
+ The address family of the new UDP socket. Can be ``PR_AF_INET``
+ (IPv4), ``PR_AF_INET6`` (IPv6), or ``PR_AF_LOCAL`` (Unix domain,
+ supported on POSIX systems only).
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- Upon successful completion, a pointer to the :ref:`PRFileDesc` object
+ created for the newly opened UDP socket.
+- If the creation of a new UDP socket failed, ``NULL``.
+
+
+Description
+-----------
+
+UDP (User Datagram Protocol) is a connectionless, unreliable datagram
+protocol of the TCP/IP protocol suite. UDP datagrams may be lost or
+delivered in duplicates or out of sequence.
+
+:ref:`PR_OpenUDPSocket` creates a new UDP socket of the address family
+``af``. The socket may be bound to a well-known port number with
+:ref:`PR_Bind`. Datagrams can be sent with :ref:`PR_SendTo` and received with
+:ref:`PR_RecvFrom`. When the socket is no longer needed, it should be
+closed with a call to :ref:`PR_Close`.
diff --git a/docs/nspr/reference/pr_poll.rst b/docs/nspr/reference/pr_poll.rst
new file mode 100644
index 0000000000..bc40620a2a
--- /dev/null
+++ b/docs/nspr/reference/pr_poll.rst
@@ -0,0 +1,110 @@
+PR_Poll
+=======
+
+Detects when I/O is ready for a set of socket file descriptors.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRInt32 PR_Poll(
+ PRPollDesc *pds,
+ PRIntn npds,
+ PRIntervalTime timeout);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``pds``
+ A pointer to the first element of an array of ``PRPollDesc``
+ structures.
+``npds``
+ The number of elements in the ``pds`` array. If this parameter is
+ zero, :ref:`PR_Poll` is equivalent to :ref:`PR_Sleep` with a timeout.
+``timeout``
+ Amount of time the call will block waiting for I/O to become ready.
+ If this time expires without any I/O becoming ready, :ref:`PR_Poll`
+ returns zero.
+
+
+Returns
+~~~~~~~
+
+The function returns one of these values:
+
+- If successful, the function returns a positive number indicating the
+ number of ``PRPollDesc`` structures in ``pds`` that have events.
+- The value 0 indicates the function timed out.
+- The value -1 indicates the function failed. The reason for the
+ failure can be obtained by calling :ref:`PR_GetError`.
+
+
+Description
+~~~~~~~~~~~
+
+This function returns as soon as I/O is ready on one or more of the
+underlying socket objects. A count of the number of ready descriptors is
+returned unless a timeout occurs, in which case zero is returned.
+
+The ``in_flags`` field of the ``PRPollDesc`` data structure should be
+set to the I/O events (readable, writable, exception, or some
+combination) that the caller is interested in. On successful return, the
+``out_flags`` field of the ``PRPollDesc`` data structure is set to
+indicate what kind of I/O is ready on the respective descriptor.
+:ref:`PR_Poll` uses the ``out_flags`` fields as scratch variables during
+the call. If :ref:`PR_Poll` returns 0 or -1, the ``out_flags`` fields do
+not contain meaningful values and must not be used.
+
+The ``PRPollDesc`` structure is defined as follows:
+
+.. code::
+
+ struct PRPollDesc {
+ PRFileDesc* fd;
+ PRInt16 in_flags;
+ PRInt16 out_flags;
+ };
+
+ typedef struct PRPollDesc PRPollDesc;
+
+The structure has the following fields:
+
+``fd``
+ A pointer to a :ref:`PRFileDesc` object representing a socket or a
+ pollable event. This field can be set to ``NULL`` to indicate to
+ :ref:`PR_Poll` that this ``PRFileDesc object`` should be ignored.
+
+ .. note::
+
+ On Unix, the ``fd`` field can be set to a pointer to any
+ :ref:`PRFileDesc` object, including one representing a file or a
+ pipe. Cross-platform applications should only set the ``fd`` field
+ to a pointer to a :ref:`PRFileDesc` object representing a socket or a
+ pollable event because on Windows the ``select`` function can only
+ be used with sockets.
+``in_flags``
+ A bitwise ``OR`` of the following bit flags:
+
+ - :ref:`PR_POLL_READ`: ``fd`` is readable.
+ - :ref:`PR_POLL_WRITE`: ``fd`` is writable.
+ - :ref:`PR_POLL_EXCEPT`: ``fd`` has an exception condition.
+
+``out_flags``
+ A bitwise ``OR`` of the following bit flags:
+
+ - :ref:`PR_POLL_READ`
+ - :ref:`PR_POLL_WRITE`
+ - :ref:`PR_POLL_EXCEPT`
+ - :ref:`PR_POLL_ERR`: ``fd`` has an error.
+ - :ref:`PR_POLL_NVAL`: ``fd`` is bad.
+
+Note that the ``PR_POLL_ERR`` and ``PR_POLL_NVAL`` flags are used only
+in ``out_flags``. The ``PR_POLL_ERR`` and ``PR_POLL_NVAL`` events are
+always reported by :ref:`PR_Poll`.
diff --git a/docs/nspr/reference/pr_popiolayer.rst b/docs/nspr/reference/pr_popiolayer.rst
new file mode 100644
index 0000000000..42fae4c074
--- /dev/null
+++ b/docs/nspr/reference/pr_popiolayer.rst
@@ -0,0 +1,53 @@
+PR_PopIOLayer
+=============
+
+Removes a layer from the stack.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRFileDesc *PR_PopIOLayer(
+ PRFileDesc *stack,
+ PRDescIdentity id);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``stack``
+ A pointer to a :ref:`PRFileDesc` object representing the stack from
+ which the specified layer is to be removed.
+``id``
+ Identity of the layer to be removed from the stack.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If the layer is successfully removed from the stack, a pointer to the
+ removed layer.
+- If the layer is not found in the stack or cannot be popped (for
+ example, the bottommost layer), the function returns ``NULL`` with
+ the error code ``PR_INVALID_ARGUMENT_ERROR``.
+
+
+Description
+-----------
+
+:ref:`PR_PopIOLayer` pops the specified layer from the stack. If the object
+to be removed is found, :ref:`PR_PopIOLayer` returns a pointer to the
+removed object The object then becomes the responsibility of the caller.
+
+Even if the identity indicates the top layer of the stack, the reference
+returned is not the file descriptor for the stack and that file
+descriptor remains valid. In other words, ``stack`` continues to point
+to the top of the stack after the function returns.
diff --git a/docs/nspr/reference/pr_postsemaphore.rst b/docs/nspr/reference/pr_postsemaphore.rst
new file mode 100644
index 0000000000..e7145a60fe
--- /dev/null
+++ b/docs/nspr/reference/pr_postsemaphore.rst
@@ -0,0 +1,30 @@
+PR_PostSemaphore
+================
+
+Increments the value of a specified semaphore.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <pripcsem.h>
+
+ NSPR_API(PRStatus) PR_PostSemaphore(PRSem *sem);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``sem``
+ A pointer to a ``PRSem`` structure returned from a call to
+ :ref:`PR_OpenSemaphore`.
+
+
+Returns
+~~~~~~~
+
+:ref:`PRStatus`
diff --git a/docs/nspr/reference/pr_prev_link.rst b/docs/nspr/reference/pr_prev_link.rst
new file mode 100644
index 0000000000..2b887db657
--- /dev/null
+++ b/docs/nspr/reference/pr_prev_link.rst
@@ -0,0 +1,35 @@
+PR_PREV_LINK
+============
+
+Returns the preceding element in a list.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prclist.h>
+
+ PRCList *PR_PREV_LINK (PRCList *elemp);
+
+
+Parameter
+~~~~~~~~~
+
+``elemp``
+ A pointer to the element.
+
+
+Returns
+~~~~~~~
+
+A pointer to a list element.
+
+
+Description
+-----------
+
+:ref:`PR_PREV_LINK` returns a pointer to the element preceding the
+specified element. It can be used to traverse a list. The preceding
+element is not removed from the list.
diff --git a/docs/nspr/reference/pr_processattrsetinheritablefilemap.rst b/docs/nspr/reference/pr_processattrsetinheritablefilemap.rst
new file mode 100644
index 0000000000..c89f2740eb
--- /dev/null
+++ b/docs/nspr/reference/pr_processattrsetinheritablefilemap.rst
@@ -0,0 +1,53 @@
+PR_ProcessAttrSetInheritableFileMap
+===================================
+
+Prepare filemap for export to my children processes via
+``PR_CreateProcess``.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prshma.h>
+
+ NSPR_API(PRStatus)
+ PR_ProcessAttrSetInheritableFileMap(
+ PRProcessAttr *attr,
+ PRFileMap *fm,
+ const char *shmname
+ );
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``attr``
+ Pointer to a PRProcessAttr structure used to pass data to
+ PR_CreateProcess.
+``fm``
+ Pointer to a PRFileMap structure to be passed to the child process.
+``shmname``
+ Pointer to the name for the PRFileMap; used by child.
+
+
+Returns
+~~~~~~~
+
+:ref:`PRStatus`
+
+
+Description
+~~~~~~~~~~~
+
+:ref:`PR_ProcessAttrSetInheritableFileMap` connects the :ref:`PRFileMap` to
+:ref:`PRProcessAttr` with ``shmname``. A subsequent call to
+``PR_CreateProcess`` makes the :ref:`PRFileMap` importable by the child
+process.
+
+.. note::
+
+ **Note:** This function is not implemented.
diff --git a/docs/nspr/reference/pr_processexit.rst b/docs/nspr/reference/pr_processexit.rst
new file mode 100644
index 0000000000..862e48bb5e
--- /dev/null
+++ b/docs/nspr/reference/pr_processexit.rst
@@ -0,0 +1,23 @@
+PR_ProcessExit
+==============
+
+Causes an immediate, nongraceful, forced termination of the process.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prinit.h>
+
+ void PR_ProcessExit(PRIntn status);
+
+
+Parameter
+~~~~~~~~~
+
+:ref:`PR_ProcessExit` has one parameter:
+
+status
+ The exit status code of the process.
diff --git a/docs/nspr/reference/pr_pushiolayer.rst b/docs/nspr/reference/pr_pushiolayer.rst
new file mode 100644
index 0000000000..067370108f
--- /dev/null
+++ b/docs/nspr/reference/pr_pushiolayer.rst
@@ -0,0 +1,87 @@
+PR_PushIOLayer
+==============
+
+Adds a layer onto the stack.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRStatus PR_PushIOLayer(
+ PRFileDesc *stack,
+ PRDescIdentity id,
+ PRFileDesc *layer);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``stack``
+ A pointer to a :ref:`PRFileDesc` object representing the stack.
+``id``
+ A :ref:`PRDescIdentity` object for the layer on the stack above which
+ the new layer is to be added.
+``layer``
+ A pointer to a :ref:`PRFileDesc` object representing the new layer to be
+ added to the stack.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If the layer is successfully pushed onto the stack, ``PR_SUCCESS``.
+- If the layer is not successfully pushed onto the stack,
+ ``PR_FAILURE``. Use :ref:`PR_GetError` to get additional information
+ regarding the reason for the failure.
+
+
+Description
+-----------
+
+A file descriptor for a layer (possibly allocated using
+:ref:`PR_CreateIOLayerStub`) may be pushed onto an existing stack of file
+descriptors at any time. The new layer is inserted into the stack just
+above the layer with the identity specified by ``id``.
+
+Even if the ``id`` parameter indicates the topmost layer of the stack,
+the value of the file descriptor describing the original stack will not
+change. In other words, ``stack`` continues to point to the top of the
+stack after the function returns.
+
+Caution
+-------
+
+Keeping the pointer to the stack even as layers are pushed onto the top
+of the stack is accomplished by swapping the contents of the file
+descriptor being pushed and the stack's current top layer file
+descriptor.
+
+The intent is that the pointer to the stack remain the stack's identity
+even if someone (perhaps covertly) has pushed other layers. Some subtle
+ramifications:
+
+- The ownership of the storage pointed to by the caller's layer
+ argument is relinquished to the runtime. Accessing the object via the
+ pointer is not permitted while the runtime has ownership. The correct
+ mechanism to access the object is to get a pointer to it by calling
+ :ref:`PR_GetIdentitiesLayer`.
+
+- The contents of the caller's object are swapped into another
+ container, including the reference to the object's destructor. If the
+ original container was allocated using a different mechanism than
+ used by the runtime, the default calling of the layer's destructor by
+ the runtime will fail :ref:`PR_CreateIOLayerStub` is provided to
+ allocate layer objects and template implementations). The destructor
+ will be called on all layers when the stack is closed (see
+ :ref:`PR_Close`). If the containers are allocated by some method other
+ than :ref:`PR_CreateIOLayerStub`, it may be required that the stack have
+ the layers popped off (in reverse order that they were pushed) before
+ calling :ref:`PR_Close`.
diff --git a/docs/nspr/reference/pr_queuejob.rst b/docs/nspr/reference/pr_queuejob.rst
new file mode 100644
index 0000000000..0ad0eb3b8b
--- /dev/null
+++ b/docs/nspr/reference/pr_queuejob.rst
@@ -0,0 +1,43 @@
+PR_QueueJob
+===========
+
+Queues a job to a thread pool for execution.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtpool.h>
+
+ NSPR_API(PRJob *)
+ PR_QueueJob(
+ PRThreadPool *tpool,
+ PRJobFn fn,
+ void *arg,
+ PRBool joinable
+ );
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``tpool``
+ A pointer to a :ref:`PRThreadPool` structure previously created by a
+ call to :ref:`PR_CreateThreadPool`.
+``fn``
+ The function to be executed when the job is executed.
+``arg``
+ A pointer to an argument passed to ``fn``.
+``joinable``
+ If ``PR_TRUE``, the job is joinable. If ``PR_FALSE``, the job is not
+ joinable. See :ref:`PR_JoinJob`.
+
+
+Returns
+~~~~~~~
+
+Pointer to a :ref:`PRJob` structure or ``NULL`` on error.
diff --git a/docs/nspr/reference/pr_queuejob_connect.rst b/docs/nspr/reference/pr_queuejob_connect.rst
new file mode 100644
index 0000000000..b5aeaf899e
--- /dev/null
+++ b/docs/nspr/reference/pr_queuejob_connect.rst
@@ -0,0 +1,49 @@
+PR_QueueJob_Connect
+===================
+
+Causes a job to be queued when a socket can be connected.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtpool.h>
+
+ NSPR_API(PRJob *)
+ PR_QueueJob_Connect(
+ PRThreadPool *tpool,
+ PRJobIoDesc *iod,
+ const PRNetAddr *addr,
+ PRJobFn fn,
+ void * arg,
+ PRBool joinable
+ );
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``tpool``
+ A pointer to a :ref:`PRThreadPool` structure previously created by a
+ call to :ref:`PR_CreateThreadPool`.
+``iod``
+ A pointer to a :ref:`PRJobIoDesc` structure.
+``addr``
+ Pointer to a :ref:`PRNetAddr` structure for the socket being connected.
+``fn``
+ The function to be executed when the job is executed.
+``arg``
+ A pointer to an argument passed to ``fn``.
+``joinable``
+ If ``PR_TRUE``, the job is joinable. If ``PR_FALSE``, the job is not
+ joinable. See :ref:`PR_JoinJob`.
+
+
+Returns
+~~~~~~~
+
+Pointer to a :ref:`PRJob` structure or ``NULL`` on error.
diff --git a/docs/nspr/reference/pr_queuejob_read.rst b/docs/nspr/reference/pr_queuejob_read.rst
new file mode 100644
index 0000000000..0b715a7fa5
--- /dev/null
+++ b/docs/nspr/reference/pr_queuejob_read.rst
@@ -0,0 +1,46 @@
+PR_QueueJob_Read
+================
+
+Causes a job to be queued when a socket becomes readable.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtpool.h>
+
+ NSPR_API(PRJob *)
+ PR_QueueJob_Read(
+ PRThreadPool *tpool,
+ PRJobIoDesc *iod,
+ PRJobFn fn,
+ void *arg,
+ PRBool joinable
+ );
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``tpool``
+ A pointer to a :ref:`PRThreadPool` structure previously created by a
+ call to :ref:`PR_CreateThreadPool`.
+``iod``
+ A pointer to a :ref:`PRJobIoDesc` structure.
+``fn``
+ The function to be executed when the job is executed.
+``arg``
+ A pointer to an argument passed to ``fn``.
+``joinable``
+ If ``PR_TRUE``, the job is joinable. If ``PR_FALSE``, the job is not
+ joinable. See :ref:`PR_JoinJob`.
+
+
+Returns
+~~~~~~~
+
+Pointer to a :ref:`PRJob` structure or ``NULL`` on error.
diff --git a/docs/nspr/reference/pr_queuejob_timer.rst b/docs/nspr/reference/pr_queuejob_timer.rst
new file mode 100644
index 0000000000..4460433fa5
--- /dev/null
+++ b/docs/nspr/reference/pr_queuejob_timer.rst
@@ -0,0 +1,49 @@
+PR_QueueJob_Timer
+=================
+
+Causes a job to be queued when a timer expires.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtpool.h>
+
+ NSPR_API(PRJob *)
+ PR_QueueJob_Timer(
+ PRThreadPool *tpool,
+ PRIntervalTime timeout,
+ PRJobFn fn,
+ void * arg,
+ PRBool joinable
+ );
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``tpool``
+ A pointer to a :ref:`PRThreadPool` structure previously created by a
+ call to :ref:`PR_CreateThreadPool`.
+``iod``
+ A pointer to a :ref:`PRJobIoDesc` structure.
+``timeout``
+ A value, expressed as a :ref:`PRIntervalTime`, to wait before queuing
+ the job.
+``fn``
+ The function to be executed when the job is executed.
+``arg``
+ A pointer to an argument passed to ``fn``.
+``joinable``
+ If ``PR_TRUE``, the job is joinable. If ``PR_FALSE``, the job is not
+ joinable. See :ref:`PR_JoinJob`.
+
+
+Returns
+~~~~~~~
+
+Pointer to a :ref:`PRJob` structure or ``NULL`` on error.
diff --git a/docs/nspr/reference/pr_queuejob_write.rst b/docs/nspr/reference/pr_queuejob_write.rst
new file mode 100644
index 0000000000..9551b3bc4b
--- /dev/null
+++ b/docs/nspr/reference/pr_queuejob_write.rst
@@ -0,0 +1,46 @@
+PR_QueueJob_Write
+=================
+
+Causes a job to be queued when a socket becomes writable.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtpool.h>
+
+ NSPR_API(PRJob *)
+ PR_QueueJob_Write(
+ PRThreadPool *tpool,
+ PRJobIoDesc *iod,
+ PRJobFn fn,
+ void *arg,
+ PRBool joinable
+ );
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``tpool``
+ A pointer to a :ref:`PRThreadPool` structure previously created by a
+ call to :ref:`PR_CreateThreadPool`.
+``iod``
+ A pointer to a :ref:`PRJobIoDesc` structure.
+``fn``
+ The function to be executed when the job is executed.
+``arg``
+ A pointer to an argument passed to ``fn``.
+``joinable``
+ If ``PR_TRUE``, the job is joinable. If ``PR_FALSE``, the job is not
+ joinable. See :ref:`PR_JoinJob`.
+
+
+Returns
+~~~~~~~
+
+Pointer to a :ref:`PRJob` structure or ``NULL`` on error.
diff --git a/docs/nspr/reference/pr_queuejobaccept.rst b/docs/nspr/reference/pr_queuejobaccept.rst
new file mode 100644
index 0000000000..234a2148f4
--- /dev/null
+++ b/docs/nspr/reference/pr_queuejobaccept.rst
@@ -0,0 +1,46 @@
+PR_QueueJob_Accept
+==================
+
+Causes a job to be queued when a socket has a pending connection.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtpool.h>
+
+ NSPR_API(PRJob *)
+ PR_QueueJob_Accept(
+ PRThreadPool *tpool,
+ PRJobIoDesc *iod,
+ PRJobFn fn,
+ void *arg,
+ PRBool joinable
+ );
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``tpool``
+ A pointer to a :ref:`PRThreadPool` structure previously created by a
+ call to :ref:`PR_CreateThreadPool`.
+``iod``
+ A pointer to a :ref:`PRJobIoDesc` structure.
+``fn``
+ The function to be executed when the job is executed.
+``arg``
+ A pointer to an argument passed to ``fn``.
+``joinable``
+ If ``PR_TRUE``, the job is joinable. If ``PR_FALSE``, the job is not
+ joinable. See :ref:`PR_JoinJob`.
+
+
+Returns
+~~~~~~~
+
+Pointer to a :ref:`PRJob` structure or ``NULL`` on error.
diff --git a/docs/nspr/reference/pr_read.rst b/docs/nspr/reference/pr_read.rst
new file mode 100644
index 0000000000..a6d7c03efa
--- /dev/null
+++ b/docs/nspr/reference/pr_read.rst
@@ -0,0 +1,50 @@
+PR_Read
+=======
+
+Reads bytes from a file or socket.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRInt32 PR_Read(PRFileDesc *fd,
+ void *buf,
+ PRInt32 amount);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``fd``
+ A pointer to a :ref:`PRFileDesc` object for the file or socket.
+``buf``
+ A pointer to a buffer to hold the data read in. On output, the buffer
+ contains the data.
+``amount``
+ The size of ``buf`` (in bytes).
+
+
+Returns
+~~~~~~~
+
+One of the following values:
+
+- A positive number indicates the number of bytes actually read in.
+- The value 0 means end of file is reached or the network connection is
+ closed.
+- The value -1 indicates a failure. To get the reason for the failure,
+ call :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+The thread invoking :ref:`PR_Read` blocks until it encounters an
+end-of-stream indication, some positive number of bytes (but no more
+than ``amount`` bytes) are read in, or an error occurs.
diff --git a/docs/nspr/reference/pr_readdir.rst b/docs/nspr/reference/pr_readdir.rst
new file mode 100644
index 0000000000..1379b694c9
--- /dev/null
+++ b/docs/nspr/reference/pr_readdir.rst
@@ -0,0 +1,93 @@
+PR_ReadDir
+==========
+
+Gets a pointer to the next entry in the directory.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRDirEntry* PR_ReadDir(
+ PRDir *dir,
+ PRDirFlags flags);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``dir``
+ A pointer to a :ref:`PRDir` object that designates an open directory.
+``flags``
+ Specifies which directory entries, if any, to skip. Values can
+ include the following:
+
+ - :ref:`PR_SKIP_NONE`. Do not skip any files.
+ - :ref:`PR_SKIP_DOT`. Skip the directory entry "." representing the
+ current directory.
+ - :ref:`PR_SKIP_DOT_DOT`. Skip the directory entry ".." representing
+ the parent directory.
+ - :ref:`PR_SKIP_BOTH`. Skip both "." and ".."
+ - :ref:`PR_SKIP_HIDDEN`. Skip hidden files. On Windows platforms and
+ the Mac OS, this value identifies files with the "hidden"
+ attribute set. On Unix platform, this value identifies files whose
+ names begin with a period (".").
+
+
+Returns
+~~~~~~~
+
+- A pointer to the next entry in the directory.
+- If the end of the directory is reached or an error occurs, ``NULL``.
+ The reason can be retrieved via :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+:ref:`PR_ReadDir` returns a pointer to a directory entry structure:
+
+.. code::
+
+ struct PRDirEntry {
+ const char *name;
+ };
+
+ typedef struct PRDirEntry PRDirEntry;
+
+The structure has the following field:
+
+``name``
+ Name of entry, relative to directory name.
+
+The ``flags`` parameter is an enum of type ``PRDirFlags``:
+
+.. code::
+
+ typedef enum PRDirFlags {
+ PR_SKIP_NONE = 0x0,
+ PR_SKIP_DOT = 0x1,
+ PR_SKIP_DOT_DOT = 0x2,
+ PR_SKIP_BOTH = 0x3,
+ PR_SKIP_HIDDEN = 0x4
+ } PRDirFlags;
+
+The memory associated with the returned PRDirEntry structure is managed
+by NSPR. The caller must not free the ``PRDirEntry`` structure.
+Moreover, the ``PRDirEntry`` structure returned by each :ref:`PR_ReadDir`
+call is valid only until the next :ref:`PR_ReadDir` or :ref:`PR_CloseDir` call
+on the same :ref:`PRDir` object.
+
+If the end of the directory is reached, :ref:`PR_ReadDir` returns ``NULL``,
+and :ref:`PR_GetError` returns ``PR_NO_MORE_FILES_ERROR``.
+
+
+See Also
+--------
+
+:ref:`PR_OpenDir`
diff --git a/docs/nspr/reference/pr_realloc.rst b/docs/nspr/reference/pr_realloc.rst
new file mode 100644
index 0000000000..011b389086
--- /dev/null
+++ b/docs/nspr/reference/pr_realloc.rst
@@ -0,0 +1,43 @@
+
+PR_Realloc
+==========
+
+Resizes allocated memory on the heap.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prmem.h>
+
+ void *PR_Realloc (
+ void *ptr,
+ PRUint32 size);
+
+
+Parameters
+~~~~~~~~~~
+
+``ptr``
+ A pointer to the existing memory block being resized.
+``size``
+ The size of the new memory block.
+
+
+Returns
+~~~~~~~
+
+An untyped pointer to the allocated memory, or if the allocation attempt
+fails, ``NULL``. Call ``PR_GetError()`` to retrieve the error returned
+by the libc function ``realloc()``.
+
+
+Description
+~~~~~~~~~~~
+
+This function attempts to enlarge or shrink the memory block addressed
+by ptr to a new size. The contents of the specified memory remains the
+same up to the smaller of its old size and new size, although the new
+memory block's address can be different from the original address.
diff --git a/docs/nspr/reference/pr_recv.rst b/docs/nspr/reference/pr_recv.rst
new file mode 100644
index 0000000000..bfc1b7e5f9
--- /dev/null
+++ b/docs/nspr/reference/pr_recv.rst
@@ -0,0 +1,56 @@
+PR_Recv
+=======
+
+Receives bytes from a connected socket.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRInt32 PR_Recv(
+ PRFileDesc *fd,
+ void *buf,
+ PRInt32 amount,
+ PRIntn flags,
+ PRIntervalTime timeout);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``fd``
+ A pointer to a :ref:`PRFileDesc` object representing a socket.
+``buf``
+ A pointer to a buffer to hold the data received.
+``amount``
+ The size of ``buf`` (in bytes).
+``flags``
+ Must be zero or ``PR_MSG_PEEK``.
+``timeout``
+ A value of type :ref:`PRIntervalTime` specifying the time limit for
+ completion of the receive operation.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- A positive number indicates the number of bytes actually received.
+- The value 0 means the network connection is closed.
+- The value -1 indicates a failure. The reason for the failure can be
+ obtained by calling :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+:ref:`PR_Recv` blocks until some positive number of bytes are transferred,
+a timeout occurs, or an error occurs. No more than ``amount`` bytes will
+be transferred.
diff --git a/docs/nspr/reference/pr_recvfrom.rst b/docs/nspr/reference/pr_recvfrom.rst
new file mode 100644
index 0000000000..4d2cee79f6
--- /dev/null
+++ b/docs/nspr/reference/pr_recvfrom.rst
@@ -0,0 +1,62 @@
+PR_RecvFrom
+===========
+
+Receives bytes from a socket and stores the sending peer's address.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRInt32 PR_RecvFrom(
+ PRFileDesc *fd,
+ void *buf,
+ PRInt32 amount,
+ PRIntn flags,
+ PRNetAddr *addr,
+ PRIntervalTime timeout);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``fd``
+ A pointer to a :ref:`PRFileDesc` object representing a socket.
+``buf``
+ A pointer to a buffer containing the data received.
+``amount``
+ The size of ``buf`` (in bytes).
+``flags``
+ This obsolete parameter must always be zero.
+``addr``
+ A pointer to the :ref:`PRNetAddr` object that will be filled in with the
+ address of the sending peer on return.
+``timeout``
+ A value of type :ref:`PRIntervalTime` specifying the time limit for
+ completion of the receive operation.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- A positive number indicates the number of bytes actually received.
+- The value 0 means the network connection is closed.
+- The value -1 indicates a failure. The reason for the failure can be
+ obtained by calling :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+:ref:`PR_RecvFrom` receives up to a specified number of bytes from socket,
+which may or may not be connected. The operation blocks until one or
+more bytes are transferred, a timeout has occurred, or there is an
+error. No more than ``amount`` bytes will be transferred.
+:ref:`PR_RecvFrom` is usually used with a UDP socket.
diff --git a/docs/nspr/reference/pr_remove_and_init_link.rst b/docs/nspr/reference/pr_remove_and_init_link.rst
new file mode 100644
index 0000000000..667a072b21
--- /dev/null
+++ b/docs/nspr/reference/pr_remove_and_init_link.rst
@@ -0,0 +1,29 @@
+PR_REMOVE_AND_INIT_LINK
+=======================
+
+Removes an element from a circular list and initializes the linkage.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prclist.h>
+
+ PR_REMOVE_AND_INIT_LINK (PRCList *elemp);
+
+
+Parameter
+~~~~~~~~~
+
+``elemp``
+ A pointer to the element.
+
+
+Description
+-----------
+
+:ref:`PR_REMOVE_AND_INIT_LINK` removes the specified element from its
+circular list and initializes the links of the element to point to
+itself.
diff --git a/docs/nspr/reference/pr_remove_link.rst b/docs/nspr/reference/pr_remove_link.rst
new file mode 100644
index 0000000000..ab5492f810
--- /dev/null
+++ b/docs/nspr/reference/pr_remove_link.rst
@@ -0,0 +1,27 @@
+PR_REMOVE_LINK
+==============
+
+Removes an element from a circular list.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prclist.h>
+
+ PR_REMOVE_LINK (PRCList *elemp);
+
+
+Parameter
+~~~~~~~~~
+
+``elemp``
+ A pointer to the element.
+
+
+Description
+-----------
+
+:ref:`PR_REMOVE_LINK` removes the specified element from its circular list.
diff --git a/docs/nspr/reference/pr_rename.rst b/docs/nspr/reference/pr_rename.rst
new file mode 100644
index 0000000000..67bb1b2673
--- /dev/null
+++ b/docs/nspr/reference/pr_rename.rst
@@ -0,0 +1,45 @@
+PR_Rename
+=========
+
+Renames a file.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRStatus PR_Rename(
+ const char *from,
+ const char *to);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``from``
+ The old name of the file to be renamed.
+``to``
+ The new name of the file.
+
+
+Returns
+~~~~~~~
+
+One of the following values:
+
+- If file is successfully renamed, ``PR_SUCCESS``.
+- If file is not successfully renamed, ``PR_FAILURE``.
+
+
+Description
+-----------
+
+:ref:`PR_Rename` renames a file from its old name (``from``) to a new name
+(``to``). If a file with the new name already exists, :ref:`PR_Rename`
+fails with the error code ``PR_FILE_EXISTS_ERROR``. In this case,
+:ref:`PR_Rename` does not overwrite the existing filename.
diff --git a/docs/nspr/reference/pr_rmdir.rst b/docs/nspr/reference/pr_rmdir.rst
new file mode 100644
index 0000000000..7aa4aeb8c7
--- /dev/null
+++ b/docs/nspr/reference/pr_rmdir.rst
@@ -0,0 +1,46 @@
+PR_RmDir
+========
+
+Removes a directory with a specified name.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRStatus PR_RmDir(const char *name);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``name``
+ The name of the directory to be removed.
+
+
+Returns
+~~~~~~~
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful, ``PR_FAILURE``. The actual reason can be retrieved
+ via :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+:ref:`PR_RmDir` removes the directory specified by the pathname ``name``.
+The directory must be empty. If the directory is not empty, :ref:`PR_RmDir`
+fails and :ref:`PR_GetError` returns the error code
+``PR_DIRECTORY_NOT_EMPTY_ERROR``.
+
+
+See Also
+--------
+
+:ref:`PR_MkDir`
diff --git a/docs/nspr/reference/pr_secondstointerval.rst b/docs/nspr/reference/pr_secondstointerval.rst
new file mode 100644
index 0000000000..18c8947a36
--- /dev/null
+++ b/docs/nspr/reference/pr_secondstointerval.rst
@@ -0,0 +1,30 @@
+PR_SecondsToInterval
+====================
+
+Converts standard clock seconds to platform-dependent intervals.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prinrval.h>
+
+ PRIntervalTime PR_SecondsToInterval(PRUint32 seconds);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``seconds``
+ The number of seconds to convert to interval form.
+
+
+Returns
+~~~~~~~
+
+Platform-dependent equivalent of the value passed in the ``seconds``
+parameter.
diff --git a/docs/nspr/reference/pr_seek.rst b/docs/nspr/reference/pr_seek.rst
new file mode 100644
index 0000000000..b06351d059
--- /dev/null
+++ b/docs/nspr/reference/pr_seek.rst
@@ -0,0 +1,85 @@
+PR_Seek
+=======
+
+Moves the current read-write file pointer by an offset expressed as a
+32-bit integer.
+
+.. container:: blockIndicator deprecated deprecatedHeader
+
+ | **Deprecated**
+ | This feature is no longer recommended. Though some browsers might
+ still support it, it may have already been removed from the
+ relevant web standards, may be in the process of being dropped, or
+ may only be kept for compatibility purposes. Avoid using it, and
+ update existing code if possible; see the `compatibility
+ table <#Browser_compatibility>`__ at the bottom of this page to
+ guide your decision. Be aware that this feature may cease to work
+ at any time.
+
+Deprecated in favor of :ref:`PR_Seek64`.
+
+
+Syntax
+~~~~~~
+
+.. code::
+
+ #include <prio.h>
+
+ PRInt32 PR_Seek(
+ PRFileDesc *fd,
+ PRInt32 offset,
+ PRSeekWhence whence);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``fd``
+ A pointer to a :ref:`PRFileDesc` object.
+``offset``
+ A value, in bytes, used with the whence parameter to set the file
+ pointer. A negative value causes seeking in the reverse direction.
+``whence``
+ A value of type :ref:`PRSeekWhence` that specifies how to interpret the
+ ``offset`` parameter in setting the file pointer associated with the
+ fd parameter. The value for the ``whence`` parameter can be one of
+ the following:
+
+ - :ref:`PR_SEEK_SET`. Sets the file pointer to the value of the
+ ``offset`` parameter.
+ - :ref:`PR_SEEK_CUR`. Sets the file pointer to its current location
+ plus the value of the ``offset`` parameter.
+ - :ref:`PR_SEEK_END`. Sets the file pointer to the size of the file
+ plus the value of the ``offset`` parameter.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If the function completes successfully, it returns the resulting file
+ pointer location, measured in bytes from the beginning of the file.
+- If the function fails, the file pointer remains unchanged and the
+ function returns -1. The error code can then be retrieved with
+ :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+Here's an idiom for obtaining the current location of the file pointer
+for the file descriptor ``fd``:
+
+``PR_Seek(fd, 0, PR_SEEK_CUR)``
+
+
+See Also
+--------
+
+If you need to move the file pointer by a large offset that's out of the
+range of a 32-bit integer, use :ref:`PR_Seek64`. New code should use
+:ref:`PR_Seek64` so that it can handle files larger than 2 GB.
diff --git a/docs/nspr/reference/pr_seek64.rst b/docs/nspr/reference/pr_seek64.rst
new file mode 100644
index 0000000000..3c144575dc
--- /dev/null
+++ b/docs/nspr/reference/pr_seek64.rst
@@ -0,0 +1,73 @@
+PR_Seek64
+=========
+
+Moves the current read-write file pointer by an offset expressed as a
+64-bit integer.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRInt64 PR_Seek64(
+ PRFileDesc *fd,
+ PRInt64 offset,
+ PRSeekWhence whence);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``fd``
+ A pointer to a :ref:`PRFileDesc` object.
+``offset``
+ A value, in bytes, used with the whence parameter to set the file
+ pointer. A negative value causes seeking in the reverse direction.
+``whence``
+ A value of type :ref:`PRSeekWhence` that specifies how to interpret the
+ ``offset`` parameter in setting the file pointer associated with the
+ fd parameter. The value for the ``whence`` parameter can be one of
+ the following:
+
+ - :ref:`PR_SEEK_SET`. Sets the file pointer to the value of the
+ ``offset`` parameter.
+ - :ref:`PR_SEEK_CUR`. Sets the file pointer to its current location
+ plus the value of the ``offset`` parameter.
+ - :ref:`PR_SEEK_END`. Sets the file pointer to the size of the file
+ plus the value of the ``offset`` parameter.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If the function completes successfully, it returns the resulting file
+ pointer location, measured in bytes from the beginning of the file.
+- If the function fails, the file pointer remains unchanged and the
+ function returns -1. The error code can then be retrieved with
+ :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+This is the idiom for obtaining the current location (expressed as a
+64-bit integer) of the file pointer for the file descriptor ``fd``:
+
+``PR_Seek64(fd, 0, PR_SEEK_CUR)``
+
+If the operating system can handle only a 32-bit file offset,
+:ref:`PR_Seek64` may fail with the error code ``PR_FILE_TOO_BIG_ERROR`` if
+the ``offset`` parameter is out of the range of a 32-bit integer.
+
+
+See Also
+--------
+
+:ref:`PR_Seek`
diff --git a/docs/nspr/reference/pr_send.rst b/docs/nspr/reference/pr_send.rst
new file mode 100644
index 0000000000..1ffdad63e4
--- /dev/null
+++ b/docs/nspr/reference/pr_send.rst
@@ -0,0 +1,56 @@
+PR_Send
+=======
+
+Sends bytes from a connected socket.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRInt32 PR_Send(
+ PRFileDesc *fd,
+ const void *buf,
+ PRInt32 amount,
+ PRIntn flags,
+ PRIntervalTime timeout);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``fd``
+ A pointer to a :ref:`PRFileDesc` object representing a socket.
+``buf``
+ A pointer to a buffer containing the data to be sent.
+``amount``
+ The size of ``buf`` (in bytes).
+``flags``
+ This obsolete parameter must always be zero.
+``timeout``
+ A value of type :ref:`PRIntervalTime` specifying the time limit for
+ completion of the receive operation.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- A positive number indicates the number of bytes successfully sent. If
+ the parameter fd is a blocking socket, this number must always equal
+ amount.
+- The value -1 indicates a failure. The reason for the failure can be
+ obtained by calling :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+:ref:`PR_Send` blocks until all bytes are sent, a timeout occurs, or an
+error occurs.
diff --git a/docs/nspr/reference/pr_sendto.rst b/docs/nspr/reference/pr_sendto.rst
new file mode 100644
index 0000000000..ba3ce59868
--- /dev/null
+++ b/docs/nspr/reference/pr_sendto.rst
@@ -0,0 +1,58 @@
+PR_SendTo
+=========
+
+Sends bytes a socket to a specified destination.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRInt32 PR_SendTo(
+ PRFileDesc *fd,
+ const void *buf,
+ PRInt32 amount,
+ PRIntn flags,
+ const PRNetAddr *addr,
+ PRIntervalTime timeout);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``fd``
+ A pointer to a :ref:`PRFileDesc` object representing a socket.
+``buf``
+ A pointer to a buffer containing the data to be sent.
+``amount``
+ The size of ``buf`` (in bytes).
+``flags``
+ This obsolete parameter must always be zero.
+``addr``
+ A pointer to the address of the destination.
+``timeout``
+ A value of type :ref:`PRIntervalTime` specifying the time limit for
+ completion of the receive operation.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- A positive number indicates the number of bytes successfully sent.
+- The value -1 indicates a failure. The reason for the failure can be
+ obtained by calling :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+:ref:`PR_SendTo` sends a specified number of bytes from a socket to the
+specified destination address. The calling thread blocks until all bytes
+are sent, a timeout has occurred, or there is an error.
diff --git a/docs/nspr/reference/pr_setconcurrency.rst b/docs/nspr/reference/pr_setconcurrency.rst
new file mode 100644
index 0000000000..309a0312bc
--- /dev/null
+++ b/docs/nspr/reference/pr_setconcurrency.rst
@@ -0,0 +1,42 @@
+PR_SetConcurrency
+=================
+
+Creates extra virtual processor threads. Generally used with MP systems.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prinit.h>
+
+ void PR_SetConcurrency(PRUintn numCPUs);
+
+
+Parameter
+~~~~~~~~~
+
+:ref:`PR_SetConcurrency` has one parameter:
+
+``numCPUs``
+ The number of extra virtual processor threads to be created.
+
+
+Description
+-----------
+
+Setting concurrency controls the number of virtual processors that NSPR
+uses to implement its ``M x N`` threading model. The ``M x N`` model is
+not available on all host systems. On those where it is not available,
+:ref:`PR_SetConcurrency` is ignored.
+
+Virtual processors are actually\ *global* threads, each of which is
+designed to support an arbitrary number of\ *local* threads. Since
+global threads are scheduled by the host operating system, this model is
+particularly applicable to multiprocessor architectures, where true
+parallelism is possible. However, it may also prove advantageous on
+uniprocessor systems to reduce the impact of having a locally scheduled
+thread calling incidental blocking functions. In such cases, all the
+threads being supported by the virtual processor will block, but those
+assigned to another virtual processor will be unaffected.
diff --git a/docs/nspr/reference/pr_seterror.rst b/docs/nspr/reference/pr_seterror.rst
new file mode 100644
index 0000000000..87d256139a
--- /dev/null
+++ b/docs/nspr/reference/pr_seterror.rst
@@ -0,0 +1,35 @@
+PR_SetError
+===========
+
+Sets error information within a thread context.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prerror.h>
+
+ void PR_SetError(PRErrorCode errorCode, PRInt32 oserr)
+
+
+Parameters
+~~~~~~~~~~
+
+The function has these parameters:
+
+``errorCode``
+ The NSPR (platform-independent) translation of the error.
+
+``oserr``
+ The platform-specific error. If there is no appropriate OS error
+ number, a zero may be supplied.
+
+
+Description
+-----------
+
+NSPR does not validate the value of the error number or OS error number
+being specified. The runtime merely stores the value and returns it when
+requested.
diff --git a/docs/nspr/reference/pr_seterrortext.rst b/docs/nspr/reference/pr_seterrortext.rst
new file mode 100644
index 0000000000..5fa385cc52
--- /dev/null
+++ b/docs/nspr/reference/pr_seterrortext.rst
@@ -0,0 +1,43 @@
+PR_SetErrorText
+===============
+
+Sets the text associated with an error.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prerror.h>
+
+ void PR_SetErrorText(PRIntn textLength, const char *text)
+
+
+Parameters
+~~~~~~~~~~
+
+The function has these parameters:
+
+``textLength``
+ The length of the text in the ``text``. May be ``NULL``. If not
+ ``NULL``, and if ``text`` is zero, the string is assumed to be a
+ null-terminated C string. Otherwise the text is assumed to be the
+ length specified and to possibly include ``NULL`` characters (as
+ might occur in a multilingual string).
+
+``text``
+ The text to associate with the error.
+
+
+Description
+-----------
+
+The text is copied into the thread structure and remains there until the
+next call to :ref:`PR_SetError`. If there is error text already present in
+the thread, the previous value is first deleted. The new value is copied
+into storage allocated and owned by NSPR and remains there until the
+next call to :ref:`PR_SetError` or another call to :ref:`PR_SetErrorText`.
+
+NSPR makes no use of this function. Clients may use it for their own
+purposes.
diff --git a/docs/nspr/reference/pr_setlibrarypath.rst b/docs/nspr/reference/pr_setlibrarypath.rst
new file mode 100644
index 0000000000..52336e62d9
--- /dev/null
+++ b/docs/nspr/reference/pr_setlibrarypath.rst
@@ -0,0 +1,45 @@
+PR_SetLibraryPath
+=================
+
+Registers a default library pathname with a runtime.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prlink.h>
+
+ PRStatus PR_SetLibraryPath(const char *path);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has this parameter:
+
+``path``
+ A pointer to a character array that contains the directory path that
+ the application should use as a default. The syntax of the pathname
+ is not defined, nor whether that pathname should be absolute or
+ relative.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful, ``PR_FAILURE``. This may indicate that the function
+ cannot allocate sufficient storage to make a copy of the path string
+
+
+Description
+-----------
+
+This function registers a default library pathname with the runtime.
+This allows an environment to express policy decisions globally and
+lazily, rather than hardcoding and distributing the decisions throughout
+the code.
diff --git a/docs/nspr/reference/pr_setpollableevent.rst b/docs/nspr/reference/pr_setpollableevent.rst
new file mode 100644
index 0000000000..e9de4eb19e
--- /dev/null
+++ b/docs/nspr/reference/pr_setpollableevent.rst
@@ -0,0 +1,32 @@
+PR_SetPollableEvent
+===================
+
+Set a pollable event.
+
+
+Syntax
+------
+
+.. code::
+
+ NSPR_API(PRStatus) PR_SetPollableEvent(PRFileDesc *event);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``event``
+ Pointer to a :ref:`PRFileDesc` structure previously created via a call
+ to :ref:`PR_NewPollableEvent`.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful, ``PR_FAILURE``. The reason for the failure can be
+ retrieved via :ref:`PR_GetError`.
diff --git a/docs/nspr/reference/pr_setsocketoption.rst b/docs/nspr/reference/pr_setsocketoption.rst
new file mode 100644
index 0000000000..8484a4b727
--- /dev/null
+++ b/docs/nspr/reference/pr_setsocketoption.rst
@@ -0,0 +1,45 @@
+PR_SetSocketOption
+==================
+
+Retrieves the socket options set for a specified socket.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRStatus PR_SetSocketOption(
+ PRFileDesc *fd,
+ PRSocketOptionData *data);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``fd``
+ A pointer to a :ref:`PRFileDesc` object representing the socket whose
+ options are to be set.
+``data``
+ A pointer to a structure of type :ref:`PRSocketOptionData` specifying
+ the options to set.
+
+
+Returns
+~~~~~~~
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful, ``PR_FAILURE``. The reason for the failure can be
+ obtained by calling :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+On input, the caller must set both the ``option`` and ``value`` fields
+of the :ref:`PRSocketOptionData` object pointed to by the ``data``
+parameter.
diff --git a/docs/nspr/reference/pr_setthreadpriority.rst b/docs/nspr/reference/pr_setthreadpriority.rst
new file mode 100644
index 0000000000..32ca051656
--- /dev/null
+++ b/docs/nspr/reference/pr_setthreadpriority.rst
@@ -0,0 +1,37 @@
+PR_SetThreadPriority
+====================
+
+Sets the priority of a specified thread.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prthread.h>
+
+ void PR_SetThreadPriority(
+ PRThread *thread,
+ PRThreadPriority priority);
+
+
+Parameters
+~~~~~~~~~~
+
+:ref:`PR_SetThreadPriority` has the following parameters:
+
+``thread``
+ A valid identifier for the thread whose priority you want to set.
+``priority``
+ The priority you want to set.
+
+
+Description
+-----------
+
+Modifying the priority of a thread other than the calling thread is
+risky. It is difficult to ensure that the state of the target thread
+permits a priority adjustment without ill effects. It is preferable for
+a thread to specify itself in the thread parameter when it calls
+:ref:`PR_SetThreadPriority`.
diff --git a/docs/nspr/reference/pr_setthreadprivate.rst b/docs/nspr/reference/pr_setthreadprivate.rst
new file mode 100644
index 0000000000..0f24c5b386
--- /dev/null
+++ b/docs/nspr/reference/pr_setthreadprivate.rst
@@ -0,0 +1,56 @@
+PR_SetThreadPrivate
+===================
+
+Sets per-thread private data.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prthread.h>
+
+ PRStatus PR_SetThreadPrivate(PRUintn index, void *priv);
+
+
+Parameters
+~~~~~~~~~~
+
+:ref:`PR_SetThreadPrivate` has the following parameters:
+
+``index``
+ An index into the per-thread private data table.
+``priv``
+ The per-thread private data, or more likely, a pointer to the data.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, ``PR_SUCCESS``.
+- If the index is invalid, ``PR_FAILURE``.
+
+
+Description
+-----------
+
+If the thread already has non-``NULL`` private data associated with it,
+and if the destructor function for the index is known (not ``NULL``),
+NSPR calls the destructor function associated with the index before
+setting the new data value. The pointer at the index is swapped with
+``NULL``. If the swapped out value is not ``NULL``, the destructor
+function is called. On return, the private data associated with the
+index is reassigned the new private data's value, even if it is
+``NULL``. The runtime provides no protection for the private data. The
+destructor is called with the runtime holding no locks. Synchronization
+is the client's responsibility.
+
+The only way to eliminate thread private data at an index prior to the
+thread's termination is to call :ref:`PR_SetThreadPrivate` with a ``NULL``
+argument. This causes the index's destructor function to be called, and
+afterwards assigns a ``NULL`` in the table. A client must not delete the
+referent object of a non-``NULL`` private data without first eliminating
+it from the table.
diff --git a/docs/nspr/reference/pr_shutdown.rst b/docs/nspr/reference/pr_shutdown.rst
new file mode 100644
index 0000000000..faee7dfe15
--- /dev/null
+++ b/docs/nspr/reference/pr_shutdown.rst
@@ -0,0 +1,57 @@
+PR_Shutdown
+===========
+
+Shuts down part of a full-duplex connection on a specified socket.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRStatus PR_Shutdown(
+ PRFileDesc *fd,
+ PRShutdownHow how);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``fd``
+ A pointer to a :ref:`PRFileDesc` object representing a connected socket.
+``how``
+ The kind of disallowed operations on the socket. Possible values
+ include the following:
+
+ - :ref:`PR_SHUTDOWN_RCV`. Further receives will be disallowed.
+ - :ref:`PR_SHUTDOWN_SEND`. Further sends will be disallowed.
+ - :ref:`PR_SHUTDOWN_BOTH`. Further sends and receives will be
+ disallowed.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- Upon successful completion of shutdown request, ``PR_SUCCESS``.
+- If unsuccessful, ``PR_FAILURE``. Further information can be obtained
+ by calling :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+The ``PRShutdownHow`` enumeration is defined as follows:
+
+.. code::
+
+ typedef enum PRShutdownHow{
+ PR_SHUTDOWN_RCV = 0,
+ PR_SHUTDOWN_SEND = 1,
+ PR_SHUTDOWN_BOTH = 2
+ } PRShutdownHow;
diff --git a/docs/nspr/reference/pr_shutdownthreadpool.rst b/docs/nspr/reference/pr_shutdownthreadpool.rst
new file mode 100644
index 0000000000..3fb411af64
--- /dev/null
+++ b/docs/nspr/reference/pr_shutdownthreadpool.rst
@@ -0,0 +1,30 @@
+PR_ShutdownThreadPool
+=====================
+
+Notifies all threads in a thread pool to terminate.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtpool.h>
+
+ NSPR_API(PRStatus) PR_ShutdownThreadPool( PRThreadPool *tpool );
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``tpool``
+ A pointer to a :ref:`PRThreadPool` structure previously created by a
+ call to :ref:`PR_CreateThreadPool`.
+
+
+Returns
+~~~~~~~
+
+:ref:`PRStatus`
diff --git a/docs/nspr/reference/pr_sleep.rst b/docs/nspr/reference/pr_sleep.rst
new file mode 100644
index 0000000000..b3ad0ec2cd
--- /dev/null
+++ b/docs/nspr/reference/pr_sleep.rst
@@ -0,0 +1,52 @@
+PR_Sleep
+========
+
+Causes the current thread to yield for a specified amount of time.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prthread.h>
+
+ PRStatus PR_Sleep(PRIntervalTime ticks);
+
+
+Parameter
+~~~~~~~~~
+
+:ref:`PR_Sleep` has the following parameter:
+
+``ticks``
+ The number of ticks you want the thread to sleep for (see
+ :ref:`PRIntervalTime`).
+
+
+Returns
+~~~~~~~
+
+Calling :ref:`PR_Sleep` with a parameter equivalent to
+``PR_INTERVAL_NO_TIMEOUT`` is an error and results in a ``PR_FAILURE``
+error.
+
+
+Description
+-----------
+
+:ref:`PR_Sleep` simply waits on a condition for the amount of time
+specified. If you set ticks to ``PR_INTERVAL_NO_WAIT``, the thread
+yields.
+
+If ticks is not ``PR_INTERVAL_NO_WAIT``, :ref:`PR_Sleep` uses an existing
+lock, but has to create a new condition for this purpose. If you have
+already created such structures, it is more efficient to use them
+directly.
+
+Calling :ref:`PR_Sleep` with the value of ticks set to
+``PR_INTERVAL_NO_WAIT`` simply surrenders the processor to ready threads
+of the same priority. All other values of ticks cause :ref:`PR_Sleep` to
+block the calling thread for the specified interval.
+
+Threads blocked in :ref:`PR_Sleep` are interruptible.
diff --git a/docs/nspr/reference/pr_static_assert.rst b/docs/nspr/reference/pr_static_assert.rst
new file mode 100644
index 0000000000..4aa2e31c58
--- /dev/null
+++ b/docs/nspr/reference/pr_static_assert.rst
@@ -0,0 +1,46 @@
+PR_STATIC_ASSERT
+================
+
+Prevents code from compiling when an expression has the value ``FALSE``
+at compile time.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prlog.h>
+
+ PR_STATIC_ASSERT ( expression );
+
+
+Parameters
+~~~~~~~~~~
+
+The macro has this parameter:
+
+expression
+ Any valid expression which evaluates at compile-time to ``TRUE`` or
+ ``FALSE``. An expression which cannot be evaluated at compile time
+ will cause a compiler error; see :ref:`PR_ASSERT` for a runtime
+ alternative.
+
+
+Returns
+~~~~~~~
+
+Nothing
+
+
+Description
+-----------
+
+This macro evaluates the specified expression. When the result is zero
+(``FALSE``) program compilation will fail with a compiler error;
+otherwise compilation completes successfully. The compiler error will
+include the number of the line for which the compile-time assertion
+failed.
+
+This macro may only be used in locations where an ``extern`` function
+declaration may be used.
diff --git a/docs/nspr/reference/pr_stringtonetaddr.rst b/docs/nspr/reference/pr_stringtonetaddr.rst
new file mode 100644
index 0000000000..af6e3b3e4f
--- /dev/null
+++ b/docs/nspr/reference/pr_stringtonetaddr.rst
@@ -0,0 +1,48 @@
+PR_StringToNetAddr
+==================
+
+Converts a character string to a network address.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prnetdb.h>
+
+ PRStatus PR_StringToNetAddr(
+ const char *string,
+ PRNetAddr *addr);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``string``
+ The string to be converted.
+``addr``
+ On output, the equivalent network address.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful, ``PR_FAILURE``. You can retrieve the reason for the
+ failure by calling :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+For IPv4 addresses, the input string represents numbers in the Internet
+standard "." notation. IPv6 addresses are indicated as strings using ":"
+characters separating octets, with numerous caveats for shortcutting
+(see RFC #1884). If the NSPR library and the host are configured to
+support IPv6, both formats are supported. Otherwise, use of anything
+other than IPv4 dotted notation results in an error.
diff --git a/docs/nspr/reference/pr_strtod.rst b/docs/nspr/reference/pr_strtod.rst
new file mode 100644
index 0000000000..af870ce03b
--- /dev/null
+++ b/docs/nspr/reference/pr_strtod.rst
@@ -0,0 +1,50 @@
+PR_strtod
+=========
+
+Converts the prefix of a decimal string to the nearest double-precision
+floating point number.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prdtoa.h>
+
+ PRFloat64 PR_strtod(const char *s00, char **se);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has these parameters:
+
+``s00``
+ The input string to be scanned.
+``se``
+ A pointer that, if not ``NULL``, will be assigned the address of the
+ last character scanned in the input string.
+
+
+Returns
+~~~~~~~
+
+The result of the conversion is a ``PRFloat64`` value equivalent to the
+input string. If the parameter ``se`` is not ``NULL`` the location it
+references is also set.
+
+
+Description
+-----------
+
+:ref:`PR_strtod` converts the prefix of the input decimal string pointed to
+by ``s00`` to a nearest double-precision floating point number. Ties are
+broken by the IEEE round-even rule. The string is scanned up to the
+first unrecognized character. If the value of ``se`` is not
+(``char **``) ``NULL``, :ref:`PR_strtod` stores a pointer to the character
+terminating the scan in ``*se``. If the answer would overflow, a
+properly signed ``HUGE_VAL`` (infinity) is returned. If the answer would
+underflow, a properly signed 0 is returned. In both cases,
+``PR_GetError()`` returns the error code ``PR_RANGE_ERROR``. If no
+number can be formed, ``se`` is set to ``s00``, and 0 is returned.
diff --git a/docs/nspr/reference/pr_sync.rst b/docs/nspr/reference/pr_sync.rst
new file mode 100644
index 0000000000..5d6df28689
--- /dev/null
+++ b/docs/nspr/reference/pr_sync.rst
@@ -0,0 +1,40 @@
+PR_Sync
+=======
+
+Synchronizes any buffered data for a file descriptor to its backing
+device (disk).
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRStatus PR_Sync(PRFileDesc *fd);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``fd``
+ Pointer to a :ref:`PRFileDesc` object representing a file.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- On successful completion, ``PR_SUCCESS``.
+- If the function fails, ``PR_FAILURE``.
+
+
+Description
+-----------
+
+:ref:`PR_Sync` writes all the in-memory buffered data of the specified file
+to the disk.
diff --git a/docs/nspr/reference/pr_tickspersecond.rst b/docs/nspr/reference/pr_tickspersecond.rst
new file mode 100644
index 0000000000..362a817d94
--- /dev/null
+++ b/docs/nspr/reference/pr_tickspersecond.rst
@@ -0,0 +1,36 @@
+PR_TicksPerSecond
+=================
+
+Returns the number of ticks per second currently used to determine the
+value of :ref:`PRIntervalTime`.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prinrval.h>
+
+ PRUint32 PR_TicksPerSecond(void);
+
+
+Returns
+~~~~~~~
+
+An integer between 1000 and 100000 indicating the number of ticks per
+second counted by :ref:`PRIntervalTime` on the current platform. This value
+is platform-dependent and does not change after NSPR is initialized.
+
+
+Description
+-----------
+
+The value returned by ``PR_TicksPerSecond()`` lies between
+``PR_INTERVAL_MIN`` and ``PR_INTERVAL_MAX``.
+
+The relationship between a :ref:`PRIntervalTime` tick and standard clock
+units is platform-dependent. PR\_\ ``PR_TicksPerSecond()`` allows you to
+discover exactly what that relationship is. Seconds per tick (the
+inverse of PR\_\ ``PR_TicksPerSecond()``) is always between 10
+microseconds and 1 millisecond.
diff --git a/docs/nspr/reference/pr_transmitfile.rst b/docs/nspr/reference/pr_transmitfile.rst
new file mode 100644
index 0000000000..c9812b072e
--- /dev/null
+++ b/docs/nspr/reference/pr_transmitfile.rst
@@ -0,0 +1,76 @@
+PR_TransmitFile
+===============
+
+Sends a complete file across a connected socket.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRInt32 PR_TransmitFile(
+ PRFileDesc *networkSocket,
+ PRFileDesc *sourceFile,
+ const void *headers,
+ PRInt32 hlen,
+ PRTransmitFileFlags flags,
+ PRIntervalTime timeout);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``networkSocket``
+ A pointer to a :ref:`PRFileDesc` object representing the connected
+ socket to send data over.
+``sourceFile``
+ A pointer to a :ref:`PRFileDesc` object representing the file to send.
+``headers``
+ A pointer to the buffer holding the headers to be sent before sending
+ data.
+``hlen``
+ Length of the ``headers`` buffer in bytes.
+``flags``
+ One of the following flags:
+
+ - :ref:`PR_TRANSMITFILE_KEEP_OPEN` indicates that the socket will be kept
+ open after the data is sent.
+ - :ref:`PR_TRANSMITFILE_CLOSE_SOCKET` indicates that the connection should
+ be closed immediately after successful transfer of the file.
+
+``timeout``
+ Time limit for completion of the transmit operation.
+
+
+Returns
+~~~~~~~
+
+- A positive number indicates the number of bytes successfully written,
+ including both the headers and the file.
+- The value -1 indicates a failure. If an error occurs while sending
+ the file, the ``PR_TRANSMITFILE_CLOSE_SOCKET`` flag is ignored. The
+ reason for the failure can be obtained by calling :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+The :ref:`PR_TransmitFile` function sends a complete file (``sourceFile``)
+across a connected socket (``networkSocket``). If ``headers`` is
+non-``NULL``, :ref:`PR_TransmitFile` sends the headers across the socket
+before sending the file.
+
+The enumeration ``PRTransmitFileFlags``, used in the ``flags``
+parameter, is defined as follows:
+
+.. code::
+
+ typedef enum PRTransmitFileFlags {
+ PR_TRANSMITFILE_KEEP_OPEN = 0,
+ PR_TRANSMITFILE_CLOSE_SOCKET = 1
+ } PRTransmitFileFlags;
diff --git a/docs/nspr/reference/pr_unblockclockinterrupts.rst b/docs/nspr/reference/pr_unblockclockinterrupts.rst
new file mode 100644
index 0000000000..79bf984272
--- /dev/null
+++ b/docs/nspr/reference/pr_unblockclockinterrupts.rst
@@ -0,0 +1,14 @@
+PR_UnblockClockInterrupts
+=========================
+
+Unblocks the timer signal used for preemptive scheduling.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prinit.h>
+
+ void PR_UnblockClockInterrupts(void);
diff --git a/docs/nspr/reference/pr_unloadlibrary.rst b/docs/nspr/reference/pr_unloadlibrary.rst
new file mode 100644
index 0000000000..fc9659c4e4
--- /dev/null
+++ b/docs/nspr/reference/pr_unloadlibrary.rst
@@ -0,0 +1,41 @@
+PR_UnloadLibrary
+================
+
+Unloads a library loaded with :ref:`PR_LoadLibrary`.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prlink.h>
+
+ PRStatus PR_UnloadLibrary(PRLibrary *lib);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has this parameter:
+
+``lib``
+ A reference previously returned from :ref:`PR_LoadLibrary`.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful, ``PR_FAILURE``. Use :ref:`PR_GetError` to find the
+ reason for the failure.
+
+
+Description
+-----------
+
+This function undoes the effect of a :ref:`PR_LoadLibrary`. After calling
+this function, future references to the library using its identity as
+returned by :ref:`PR_LoadLibrary` will be invalid.
diff --git a/docs/nspr/reference/pr_unlock.rst b/docs/nspr/reference/pr_unlock.rst
new file mode 100644
index 0000000000..4243a7510f
--- /dev/null
+++ b/docs/nspr/reference/pr_unlock.rst
@@ -0,0 +1,43 @@
+PR_Unlock
+=========
+
+Releases a specified lock object. Releasing an unlocked lock results in
+an error.
+
+Attempting to release a lock that was locked by a different thread
+causes undefined behavior.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prlock.h>
+
+ PRStatus PR_Unlock(PRLock *lock);
+
+
+Parameter
+~~~~~~~~~
+
+:ref:`PR_Unlock` has one parameter:
+
+``lock``
+ A pointer to a lock object to be released.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful (for example, if the caller does not own the lock),
+ ``PR_FAILURE``.
+
+
+See Also
+--------
+
+- `PR_Lock <PR_Lock>`__
diff --git a/docs/nspr/reference/pr_unmap.rst b/docs/nspr/reference/pr_unmap.rst
new file mode 100644
index 0000000000..85182b6e02
--- /dev/null
+++ b/docs/nspr/reference/pr_unmap.rst
@@ -0,0 +1,45 @@
+PR_MemUnmap
+===========
+
+Unmap a memory region that is backed by a memory-mapped file.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRStatus PR_MemUnmap(
+ void *addr,
+ PRUint32 len);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``addr``
+ The starting address of the memory region to be unmapped.
+``len``
+ The length, in bytes, of the memory region.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If the memory region is successfully unmapped, ``PR_SUCCESS``.
+- If the memory region is not successfully unmapped, ``PR_FAILURE``.
+ The error code can be retrieved via :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+:ref:`PR_MemUnmap` removes the file mapping for the memory region
+(``addr``, ``addr + len``). The parameter ``addr`` is the return value
+of an earlier call to :ref:`PR_MemMap`.
diff --git a/docs/nspr/reference/pr_usec_per_msec.rst b/docs/nspr/reference/pr_usec_per_msec.rst
new file mode 100644
index 0000000000..afed88bd57
--- /dev/null
+++ b/docs/nspr/reference/pr_usec_per_msec.rst
@@ -0,0 +1,16 @@
+PR_USEC_PER_MSEC
+================
+
+A convenience macro to improve code readability as well as to avoid
+mistakes in counting the number of zeros; represents the number of
+microseconds in a millisecond.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtime.h>
+
+ #define PR_USEC_PER_MSEC 1000UL
diff --git a/docs/nspr/reference/pr_usec_per_sec.rst b/docs/nspr/reference/pr_usec_per_sec.rst
new file mode 100644
index 0000000000..8389f7db6b
--- /dev/null
+++ b/docs/nspr/reference/pr_usec_per_sec.rst
@@ -0,0 +1,16 @@
+PR_USEC_PER_SEC
+===============
+
+A convenience macro to improve code readability as well as to avoid
+mistakes in counting the number of zeros; represents the number of
+microseconds in a second.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtime.h>
+
+ #define PR_USEC_PER_SEC 1000000UL
diff --git a/docs/nspr/reference/pr_version.rst b/docs/nspr/reference/pr_version.rst
new file mode 100644
index 0000000000..bb838b14ff
--- /dev/null
+++ b/docs/nspr/reference/pr_version.rst
@@ -0,0 +1,19 @@
+PR_VERSION
+==========
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prinit.h>
+
+ #define PR_VERSION "2.1 yyyymmdd"
+
+
+Description
+-----------
+
+The format of the version string is\ *MajorVersion.MinorVersion
+BuildDate*.
diff --git a/docs/nspr/reference/pr_versioncheck.rst b/docs/nspr/reference/pr_versioncheck.rst
new file mode 100644
index 0000000000..5d42827ad3
--- /dev/null
+++ b/docs/nspr/reference/pr_versioncheck.rst
@@ -0,0 +1,50 @@
+PR_VersionCheck
+===============
+
+Compares the version of NSPR assumed by the caller (the imported
+version) with the version being offered by the runtime (the exported
+version).
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prinit.h>
+
+ PRBool PR_VersionCheck(const char *importedVersion);
+
+
+Parameter
+~~~~~~~~~
+
+:ref:`PR_VersionCheck` has one parameter:
+
+``importedVersion``
+ The version of the shared library being imported.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If the version of the shared library is compatible with that expected
+ by the caller, ``PR_TRUE``.
+- If the versions are not compatible, ``PR_FALSE``.
+
+
+Description
+-----------
+
+:ref:`PR_VersionCheck` tests whether the version of the library being
+imported (``importedVersion``) is compatible with the running version of
+the shared library. This is a string comparison of sorts, though the
+details of the comparison will vary over time.
+
+
+See Also
+--------
+
+- `PR_VERSION <PR_VERSION>`__
diff --git a/docs/nspr/reference/pr_wait.rst b/docs/nspr/reference/pr_wait.rst
new file mode 100644
index 0000000000..3eb6e16f30
--- /dev/null
+++ b/docs/nspr/reference/pr_wait.rst
@@ -0,0 +1,82 @@
+PR_Wait
+=======
+
+Waits for an application-defined state of the monitored data to exist.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prmon.h>
+
+ PRStatus PR_Wait(
+ PRMonitor *mon,
+ PRIntervalTime ticks);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameter:
+
+``mon``
+ A reference to an existing structure of type :ref:`PRMonitor`. The
+ monitor object referenced must be one for which the calling thread
+ currently holds the lock.
+``ticks``
+ The amount of time (in :ref:`PRIntervalTime` units) that the thread is
+ willing to wait for an explicit notification before being
+ rescheduled.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+ - :ref:`PR_SUCCESS`` means the thread is being resumed from the ``PR_Wait`
+ call either because it was explicitly notified or because the time
+ specified by the parameter ``ticks`` has expired.
+ - :ref:`PR_FAILURE` means ``PR_Wait`` encountered a system error (such as
+ an invalid monitor reference) or the thread was interrupted by
+ another thread.
+
+
+Description
+-----------
+
+A call to :ref:`PR_Wait` causes the thread to release the monitor's lock,
+just as if it had called :ref:`PR_ExitMonitor` as many times as it had
+called :ref:`PR_EnterMonitor`. This has the effect of making the monitor
+available to other threads. When the wait is over, the thread regains
+control of the monitor's lock with the same entry count it had before
+the wait began.
+
+A thread waiting on the monitor resumes when the monitor is notified or
+when the timeout specified by the ``ticks`` parameter elapses. The
+resumption from the wait is merely a hint that a change of state has
+occurred. It is the responsibility of the programmer to evaluate the
+data and act accordingly. This is usually done by evaluating a Boolean
+expression involving the monitored data. While the Boolean expression is
+false, the thread should wait. The thread should act on the data only
+when the expression is true. The boolean expression must be evaluated
+while in the monitor and within a loop.
+
+In pseudo-code, the sequence is as follows:
+
+| ``PR_EnterMonitor(&ml);``
+| ``while (!expression) wait;``
+| ``... act on the state change ...``
+| ``PR_ExitMonitor(&ml);``
+
+A thread can be resumed from a wait for a variety of reasons. The most
+obvious is that it was notified by another thread. If the value of
+timeout is not ``PR_INTERVAL_NO_TIMEOUT``, :ref:`PR_Wait` resumes execution
+after the specified interval has expired. If a timeout value is used,
+the Boolean expression must include elapsed time as part of the
+monitored data.
+
+Resuming from the wait is merely an opportunity to evaluate the
+expression, not an assertion that the expression is true.
diff --git a/docs/nspr/reference/pr_waitcondvar.rst b/docs/nspr/reference/pr_waitcondvar.rst
new file mode 100644
index 0000000000..f915464f7b
--- /dev/null
+++ b/docs/nspr/reference/pr_waitcondvar.rst
@@ -0,0 +1,68 @@
+PR_WaitCondVar
+==============
+
+Waits on a condition.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prcvar.h>
+
+ PRStatus PR_WaitCondVar(
+ PRCondVar *cvar,
+ PRIntervalTime timeout);
+
+
+Parameters
+~~~~~~~~~~
+
+:ref:`PR_WaitCondVar` has the following parameters:
+
+``cvar``
+ The condition variable on which to wait.
+``timeout``
+ The value ``PR_INTERVAL_NO_TIMEOUT`` requires that a condition be
+ notified (or the thread interrupted) before it will resume from the
+ wait. The value ``PR_INTERVAL_NO_WAIT`` causes the thread to release
+ the lock, possibly causing a rescheduling within the runtime, then
+ immediately attempt to reacquire the lock and resume.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful (for example, if the caller has not locked the lock
+ associated with the condition variable or the thread was interrupted
+ with :ref:`PR_Interrupt`), ``PR_FAILURE``. The details can be determined
+ with :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+Before the call to :ref:`PR_WaitCondVar`, the lock associated with the
+condition variable must be held by the calling thread. After a call to
+:ref:`PR_WaitCondVar`, the lock is released and the thread is blocked in a
+"waiting on condition" state until another thread notifies the condition
+or a caller-specified amount of time expires.
+
+When the condition variable is notified, a thread waiting on that
+condition moves from the "waiting on condition" state to the "ready"
+state. When scheduled, the thread attempts to reacquire the lock that it
+held when :ref:`PR_WaitCondVar` was called.
+
+Any value other than ``PR_INTERVAL_NO_TIMEOUT`` or
+``PR_INTERVAL_NO_WAIT`` for the timeout parameter will cause the thread
+to be rescheduled due to either explicit notification or the expiration
+of the specified interval. The latter must be determined by treating
+time as one part of the monitored data being protected by the lock and
+tested explicitly for an expired interval. To detect the expiration of
+the specified interval, call :ref:`PR_IntervalNow` before and after the
+call to :ref:`PR_WaitCondVar` and compare the elapsed time with the
+specified interval.
diff --git a/docs/nspr/reference/pr_waitforpollableevent.rst b/docs/nspr/reference/pr_waitforpollableevent.rst
new file mode 100644
index 0000000000..5996dd4872
--- /dev/null
+++ b/docs/nspr/reference/pr_waitforpollableevent.rst
@@ -0,0 +1,33 @@
+PR_WaitForPollableEvent
+=======================
+
+Blocks the calling thread until the pollable event is set, and then
+atomically unsetting the event before returning.
+
+
+Syntax
+------
+
+.. code::
+
+ NSPR_API(PRStatus) PR_WaitForPollableEvent(PRFileDesc *event);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``event``
+ Pointer to a :ref:`PRFileDesc` structure previously created via a call
+ to :ref:`PR_NewPollableEvent`.
+
+
+Returns
+~~~~~~~
+
+The function returns one of the following values:
+
+- If successful, ``PR_SUCCESS``.
+- If unsuccessful, ``PR_FAILURE``. The reason for the failure can be
+ retrieved via :ref:`PR_GetError`.
diff --git a/docs/nspr/reference/pr_waitsemaphore.rst b/docs/nspr/reference/pr_waitsemaphore.rst
new file mode 100644
index 0000000000..1a0c7b3d1c
--- /dev/null
+++ b/docs/nspr/reference/pr_waitsemaphore.rst
@@ -0,0 +1,42 @@
+PR_WaitSemaphore
+================
+
+Returns the value of the environment variable.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <pripcsem.h>
+
+ NSPR_API(PRStatus) PR_WaitSemaphore(PRSem *sem);
+
+
+Parameter
+~~~~~~~~~
+
+The function has the following parameter:
+
+``sem``
+ A pointer to a ``PRSem`` structure returned from a call to
+ :ref:`PR_OpenSemaphore`.
+
+
+Returns
+~~~~~~~
+
+:ref:`PRStatus`
+
+
+Description
+-----------
+
+:ref:`PR_WaitSemaphore` tests the value of the semaphore. If the value of
+the semaphore is > 0, the value of the semaphore is decremented and the
+function returns. If the value of the semaphore is 0, the function
+blocks until the value becomes > 0, then the semaphore is decremented
+and the function returns.
+
+The "test and decrement" operation is performed atomically.
diff --git a/docs/nspr/reference/pr_write.rst b/docs/nspr/reference/pr_write.rst
new file mode 100644
index 0000000000..2ec2f1e5c6
--- /dev/null
+++ b/docs/nspr/reference/pr_write.rst
@@ -0,0 +1,50 @@
+PR_Write
+========
+
+Writes a buffer of data to a file or socket.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRInt32 PR_Write(
+ PRFileDesc *fd,
+ const void *buf,
+ PRInt32 amount);
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``fd``
+ A pointer to the :ref:`PRFileDesc` object for a file or socket.
+``buf``
+ A pointer to the buffer holding the data to be written.
+``amount``
+ The amount of data, in bytes, to be written from the buffer.
+
+
+Returns
+~~~~~~~
+
+One of the following values:
+
+- A positive number indicates the number of bytes successfully written.
+- The value -1 indicates that the operation failed. The reason for the
+ failure is obtained by calling :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+The thread invoking :ref:`PR_Write` blocks until all the data is written or
+the write operation fails. Therefore, the return value is equal to
+either ``amount`` (success) or -1 (failure). Note that if :ref:`PR_Write`
+returns -1, some data (less than ``amount`` bytes) may have been written
+before an error occurred.
diff --git a/docs/nspr/reference/pr_writev.rst b/docs/nspr/reference/pr_writev.rst
new file mode 100644
index 0000000000..0100b72afc
--- /dev/null
+++ b/docs/nspr/reference/pr_writev.rst
@@ -0,0 +1,79 @@
+PR_Writev
+=========
+
+Writes data to a socket from multiple buffers.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ PRInt32 PR_Writev(
+ PRFileDesc *fd,
+ PRIOVec *iov,
+ PRInt32 size,
+ PRIntervalTime timeout);
+
+ #define PR_MAX_IOVECTOR_SIZE 16
+
+
+Parameters
+~~~~~~~~~~
+
+The function has the following parameters:
+
+``fd``
+ A pointer to a :ref:`PRFileDesc` object for a socket.
+``iov``
+ An array of ``PRIOVec`` structures that describe the buffers to write
+ from.
+``size``
+ Number of ``PRIOVec`` structures in the ``iov`` array. The value of
+ this parameter must not be greater than ``PR_MAX_IOVECTOR_SIZE``. If
+ it is, the function will fail and the error will be set to
+ ``PR_BUFFER_OVERFLOW_ERROR``.
+``timeout``
+ A value of type :ref:`PRIntervalTime` describing the time limit for
+ completion of the entire write operation.
+
+
+Returns
+~~~~~~~
+
+One of the following values:
+
+- A positive number indicates the number of bytes successfully written.
+- The value -1 indicates that the operation failed. The reason for the
+ failure can be obtained by calling :ref:`PR_GetError`.
+
+
+Description
+-----------
+
+The thread calling :ref:`PR_Writev` blocks until all the data is written or
+the write operation fails. Therefore, the return value is equal to
+either the sum of all the buffer lengths (on success) or -1 (on
+failure). Note that if :ref:`PR_Writev` returns -1, part of the data may
+have been written before an error occurred. If the timeout parameter is
+not ``PR_INTERVAL_NO_TIMEOUT`` and all the data cannot be written in the
+specified interval, :ref:`PR_Writev` returns -1 with the error code
+``PR_IO_TIMEOUT_ERROR``.
+
+This is the type definition for ``PRIOVec``:
+
+.. code::
+
+ typedef struct PRIOVec {
+ char *iov_base;
+ int iov_len;
+ } PRIOVec;
+
+The ``PRIOVec`` structure has the following fields:
+
+``iov_base``
+ A pointer to the beginning of the buffer.
+``iov_len``
+ The size of the buffer.
diff --git a/docs/nspr/reference/praccesshow.rst b/docs/nspr/reference/praccesshow.rst
new file mode 100644
index 0000000000..b269dd93a1
--- /dev/null
+++ b/docs/nspr/reference/praccesshow.rst
@@ -0,0 +1,17 @@
+PRAccessHow
+===========
+
+This is the declaration for the enumeration :ref:`PRAccessHow`, used in the
+``how`` parameter of :ref:`PR_Access`:
+
+.. code::
+
+ #include <prio.h>
+
+ typedef enum PRAccessHow {
+ PR_ACCESS_EXISTS = 1,
+ PR_ACCESS_WRITE_OK = 2,
+ PR_ACCESS_READ_OK = 3
+ } PRAccessHow;
+
+See `PR_Access <en/PR_Access>`__ for what each of these values mean.
diff --git a/docs/nspr/reference/prbool.rst b/docs/nspr/reference/prbool.rst
new file mode 100644
index 0000000000..098cbae81b
--- /dev/null
+++ b/docs/nspr/reference/prbool.rst
@@ -0,0 +1,27 @@
+PRBool
+======
+
+Boolean value.
+
+
+Syntax
+~~~~~~
+
+.. code::
+
+ #include <prtypes.h>
+
+ typedef enum { PR_FALSE = 0, PR_TRUE = 1 } PRBool;
+
+
+Description
+~~~~~~~~~~~
+
+Wherever possible, do not use PRBool in Mozilla C++ code. Use standard
+C++ ``bool`` instead.
+
+Otherwise, use :ref:`PRBool` for variables and parameter types. Use
+``PR_FALSE`` and ``PR_TRUE`` for clarity of target type in assignments
+and actual arguments. Use ``if (bool)``, ``while (!bool)``,
+``(bool) ? x : y``, and so on to test Boolean values, just as you would
+C ``int``-valued conditions.
diff --git a/docs/nspr/reference/prcalloncefn.rst b/docs/nspr/reference/prcalloncefn.rst
new file mode 100644
index 0000000000..da37a9c86e
--- /dev/null
+++ b/docs/nspr/reference/prcalloncefn.rst
@@ -0,0 +1,22 @@
+PRCallOnceFN
+============
+
+Defines the signature of the function a client must implement.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prinit.h>
+
+ typedef PRStatus (PR_CALLBACK *PRCallOnceFN)(void);
+
+
+Description
+-----------
+
+The function is called to perform the initialization desired. The
+function is expected to return a :ref:`PRStatus` indicating the outcome of
+the process.
diff --git a/docs/nspr/reference/prcalloncetype.rst b/docs/nspr/reference/prcalloncetype.rst
new file mode 100644
index 0000000000..6d306d0222
--- /dev/null
+++ b/docs/nspr/reference/prcalloncetype.rst
@@ -0,0 +1,41 @@
+PRCallOnceType
+==============
+
+Structure for tracking initialization.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prinit.h>
+
+ typedef struct PRCallOnceType {
+ PRIntn initialized;
+ PRInt32 inProgress;
+ PRStatus status;
+ } PRCallOnceType;
+
+
+Fields
+~~~~~~
+
+The structure has these fields:
+
+``initialized``
+ If not zero, the initialization process has been completed.
+``inProgress``
+ If not zero, the initialization process is currently being executed.
+ Calling threads that observe this status block until inProgress is
+ zero.
+``status``
+ An indication of the outcome of the initialization process.
+
+
+Description
+-----------
+
+The client is responsible for initializing the :ref:`PRCallOnceType`
+structure to all zeros. This initialization must be accomplished before
+any threading issues exist.
diff --git a/docs/nspr/reference/prclist.rst b/docs/nspr/reference/prclist.rst
new file mode 100644
index 0000000000..05dfdd5255
--- /dev/null
+++ b/docs/nspr/reference/prclist.rst
@@ -0,0 +1,27 @@
+PRCList
+=======
+
+A circular linked list.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prclist.h>
+
+ typedef struct PRCListStr PRCList;
+
+ typedef struct PRCListStr {
+ PRCList *next;
+ PRCList *previous;
+ };
+
+
+Description
+-----------
+
+PRClist defines a node in a circular linked list. It can be used as the
+anchor of a list and can be embedded in data structures that are
+maintained in a linked list.
diff --git a/docs/nspr/reference/prcondvar.rst b/docs/nspr/reference/prcondvar.rst
new file mode 100644
index 0000000000..6d40fe224d
--- /dev/null
+++ b/docs/nspr/reference/prcondvar.rst
@@ -0,0 +1,20 @@
+PRCondVar
+=========
+
+Structure for a condition variable.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prcvar.h>
+
+ typedef struct PRCondVar PRCondVar;
+
+
+Description
+-----------
+
+An NSPR condition variable is an opaque object identified by a pointer.
diff --git a/docs/nspr/reference/prdescidentity.rst b/docs/nspr/reference/prdescidentity.rst
new file mode 100644
index 0000000000..9f9ad2767f
--- /dev/null
+++ b/docs/nspr/reference/prdescidentity.rst
@@ -0,0 +1,36 @@
+PRDescIdentity
+==============
+
+The identity of a file descriptor's layer.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ typedef PRUintn PRDescIdentity;
+
+
+Description
+-----------
+
+File descriptors may be layered. Each layer has it own identity.
+Identities are allocated by the runtime and are to be associated (by the
+layer implementor) with all file descriptors of that layer. It is then
+possible to scan the chain of layers and find a layer that one
+recognizes, then predict that it will implement a desired protocol.
+
+There are three well-known identities:
+
+ - :ref:`PR_INVALID_IO_LAYER`, an invalid layer identity, for error return
+ - :ref:`PR_TOP_IO_LAYER`, the identity of the top of the stack
+ - :ref:`PR_NSPR_IO_LAYER`, the identity used by NSPR proper
+
+Layers are created by :ref:`PR_GetUniqueIdentity`. A string may be
+associated with a layer when the layer is created. The string is copied
+by the runtime, and :ref:`PR_GetNameForIdentity` returns a reference to
+that copy. There is no way to delete a layer's identity after the layer
+is created.
diff --git a/docs/nspr/reference/prdir.rst b/docs/nspr/reference/prdir.rst
new file mode 100644
index 0000000000..feabd3eafc
--- /dev/null
+++ b/docs/nspr/reference/prdir.rst
@@ -0,0 +1,26 @@
+PRDir
+=====
+
+Directory structure used with `Directory I/O
+Functions <I_O_Functions#Directory_I.2FO_Functions>`__.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ typedef struct PRDir PRDir;
+
+
+Description
+-----------
+
+The opaque structure :ref:`PRDir` represents an open directory in the file
+system. The function :ref:`PR_OpenDir` opens a specified directory and
+returns a pointer to a :ref:`PRDir` structure, which can be passed to
+:ref:`PR_ReadDir` repeatedly to obtain successive entries (files or
+subdirectories in the open directory). To close the directory, pass the
+:ref:`PRDir` pointer to :ref:`PR_CloseDir`.
diff --git a/docs/nspr/reference/prerrorcode.rst b/docs/nspr/reference/prerrorcode.rst
new file mode 100644
index 0000000000..9207ab80f7
--- /dev/null
+++ b/docs/nspr/reference/prerrorcode.rst
@@ -0,0 +1,30 @@
+PRErrorCode
+===========
+
+
+Type for error codes that can be retrieved with :ref:`PR_GetError`. You can
+also set your own errors using :ref:`PR_SetError`.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prerror.h>
+
+ typedef PRInt32 PRErrorCode
+
+
+Description
+-----------
+
+The service NSPR offers in this area is the ability to associate a
+thread-specific condition with an error number. The error number
+namespace is not well managed. NSPR assumes error numbers starting at
+-6000 (decimal) and progressing towards zero. At present less than 100
+error codes have been defined. If NSPR's error handling is adopted by
+calling clients, then some sort of partitioning of the namespace will
+have to be employed. NSPR does not attempt to address this issue.
+
+For NSPR errors, see `Error Codes <NSPR_Error_Handling#Error_Code>`__.
diff --git a/docs/nspr/reference/prexplodedtime.rst b/docs/nspr/reference/prexplodedtime.rst
new file mode 100644
index 0000000000..c970394709
--- /dev/null
+++ b/docs/nspr/reference/prexplodedtime.rst
@@ -0,0 +1,70 @@
+PRExplodedTime
+==============
+
+A clock/calendar representation of times.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtime.h>
+
+ typedef struct PRExplodedTime {
+ PRInt32 tm_usec;
+ PRInt32 tm_sec;
+ PRInt32 tm_min;
+ PRInt32 tm_hour;
+ PRInt32 tm_mday;
+ PRInt32 tm_month;
+ PRInt16 tm_year;
+ PRInt8 tm_wday;
+ PRInt16 tm_yday;
+ PRTimeParameters tm_params;
+ } PRExplodedTime;
+
+
+Description
+-----------
+
+The :ref:`PRExplodedTime` structure represents clock/calendar time.
+:ref:`PRExplodedTime` has the familiar time components: year, month, day of
+month, hour, minute, second. It also has a microsecond component, as
+well as the day of week and the day of year. In addition,
+:ref:`PRExplodedTime` includes a :ref:`PRTimeParameters` structure
+representing the local time zone information, so that the time point is
+non-ambiguously specified.
+
+The essential members of :ref:`PRExplodedTime` are:
+
+ - :ref:`tm_year`: absolute year, AD (by "absolute," we mean if the year is
+ 2000, this field's value is 2000).
+ - :ref:`tm_month`: number of months past tm_year. The range is [0, 11]. 0
+ is January and 11 is December.
+ - :ref:`tm_mday`: the day of month. The range is [1, 31]. Note that it
+ starts from 1 as opposed to 0.
+ - :ref:`tm_hour`: number of hours past tm_mday. The range is [0, 23].
+ - :ref:`tm_min`: number of minutes past tm_hour. The range is [0, 59].
+ - :ref:`tm_sec`: number of seconds past tm_min. The range is [0, 61]. The
+ values 60 and 61 are for accommodating up to two leap seconds.
+ - :ref:`tm_usec`: number of microseconds past tm_sec. The range is [0,
+ 999999].
+ - :ref:`tm_params`: a `PRTimeParameters` structure representing the
+ local time zone information.
+
+The nonessential members of :ref:`PRExplodedTime` are:
+
+ - :ref:`tm_wday`: day of week. The range is [0, 6]. 0 is Sunday, 1 is
+ Monday, and 6 is Saturday.
+ - :ref:`tm_yday`: day of year. The range is [0, 365]. 0 is the 1st of
+ January.
+
+On input to NSPR functions, only the essential members of
+:ref:`PRExplodedTime` must be specified. The two nonessential members (day
+of week and day of year) are ignored by NSPR functions as input. When an
+NSPR function returns a :ref:`PRExplodedTime` object or sets a
+:ref:`PRExplodedTime` object as output, all of the :ref:`PRExplodedTime`
+members are set, including the nonessential members. You can also use
+``PR_NormalizeTime()`` to calculate the values of the nonessential
+members.
diff --git a/docs/nspr/reference/prfiledesc.rst b/docs/nspr/reference/prfiledesc.rst
new file mode 100644
index 0000000000..50df48f83f
--- /dev/null
+++ b/docs/nspr/reference/prfiledesc.rst
@@ -0,0 +1,53 @@
+PRFileDesc
+==========
+
+A file descriptor used to represent any open file, such as a normal
+file, an end point of a pipe, or a socket (end point of network
+communication).
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ struct PRFileDesc {
+ PRIOMethods *methods;
+ PRFilePrivate *secret;
+ PRFileDesc *lower, *higher;
+ void (*dtor)(PRFileDesc *fd);
+ PRDescIdentity identity;
+ };
+
+ typedef struct PRFileDesc PRFileDesc;
+
+
+Parameters
+~~~~~~~~~~
+
+``methods``
+ The I/O methods table. See :ref:`PRIOMethods`.
+``secret``
+ Layer-dependent implementation data. See :ref:`PRFilePrivate`.
+``lower``
+ Pointer to lower layer.
+``higher``
+ Pointer to higher layer.
+``dtor``
+ A destructor function for the layer.
+``identity``
+ Identity of this particular layer. See :ref:`PRDescIdentity`.
+
+
+Description
+-----------
+
+The fields of this structure are significant only if you are
+implementing a layer on top of NSPR, such as SSL. Otherwise, you use
+functions such as :ref:`PR_Open` and :ref:`PR_NewTCPSocket` to obtain a file
+descriptor, which you should treat as an opaque structure.
+
+For more details about the use of :ref:`PRFileDesc` and related structures,
+see `File Descriptor Types <I_O_Types#File_Descriptor_Types>`__.
diff --git a/docs/nspr/reference/prfileinfo.rst b/docs/nspr/reference/prfileinfo.rst
new file mode 100644
index 0000000000..2fe487ac2f
--- /dev/null
+++ b/docs/nspr/reference/prfileinfo.rst
@@ -0,0 +1,49 @@
+PRFileInfo
+==========
+
+File information structure used with :ref:`PR_GetFileInfo` and
+:ref:`PR_GetOpenFileInfo`.
+
+
+Syntax
+~~~~~~
+
+.. code::
+
+ #include <prio.h>
+
+ struct PRFileInfo {
+ PRFileType type;
+ PRUint32 size;
+ PRTime creationTime;
+ PRTime modifyTime;
+ };
+
+ typedef struct PRFileInfo PRFileInfo;
+
+
+
+
+Fields
+~~~~~~
+
+The structure has the following fields:
+
+``type``
+ Type of file. See :ref:`PRFileType`.
+``size``
+ Size, in bytes, of file's contents.
+``creationTime``
+ Creation time per definition of :ref:`PRTime`. See
+ `prtime.h <https://dxr.mozilla.org/mozilla-central/source/nsprpub/pr/include/prtime.h>`__.
+``modifyTime``
+ Last modification time per definition of :ref:`PRTime`. See
+ `prtime.h <https://dxr.mozilla.org/mozilla-central/source/nsprpub/pr/include/prtime.h>`__.
+
+
+Description
+-----------
+
+The :ref:`PRFileInfo` structure provides information about a file, a
+directory, or some other kind of file system object, as specified by the
+``type`` field.
diff --git a/docs/nspr/reference/prfileinfo64.rst b/docs/nspr/reference/prfileinfo64.rst
new file mode 100644
index 0000000000..329a98bccc
--- /dev/null
+++ b/docs/nspr/reference/prfileinfo64.rst
@@ -0,0 +1,47 @@
+PRFileInfo64
+============
+
+File information structure used with :ref:`PR_GetFileInfo64` and
+:ref:`PR_GetOpenFileInfo64`.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ struct PRFileInfo64 {
+ PRFileType type;
+ PRUint64 size;
+ PRTime creationTime;
+ PRTime modifyTime;
+ };
+
+ typedef struct PRFileInfo64 PRFileInfo64;
+
+
+Fields
+~~~~~~
+
+The structure has the following fields:
+
+``type``
+ Type of file. See :ref:`PRFileType`.
+``size``
+ 64-bit size, in bytes, of file's contents.
+``creationTime``
+ Creation time per definition of :ref:`PRTime`. See
+ `prtime.h <https://dxr.mozilla.org/mozilla-central/source/nsprpub/pr/include/prtime.h>`__.
+``modifyTime``
+ Last modification time per definition of :ref:`PRTime`. See
+ `prtime.h <https://dxr.mozilla.org/mozilla-central/source/nsprpub/pr/include/prtime.h>`__.
+
+
+Description
+-----------
+
+The :ref:`PRFileInfo64` structure provides information about a file, a
+directory, or some other kind of file system object, as specified by the
+``type`` field.
diff --git a/docs/nspr/reference/prfilemap.rst b/docs/nspr/reference/prfilemap.rst
new file mode 100644
index 0000000000..3002ee034d
--- /dev/null
+++ b/docs/nspr/reference/prfilemap.rst
@@ -0,0 +1,27 @@
+PRFileMap
+=========
+
+Type returned by :ref:`PR_CreateFileMap` and passed to :ref:`PR_MemMap` and
+:ref:`PR_CloseFileMap`.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ typedef struct PRFileMap PRFileMap;
+
+
+Description
+-----------
+
+The opaque structure :ref:`PRFileMap` represents a memory-mapped file
+object. Before actually mapping a file to memory, you must create a
+memory-mapped file object by calling :ref:`PR_CreateFileMap`, which returns
+a pointer to :ref:`PRFileMap`. Then sections of the file can be mapped into
+memory by passing the :ref:`PRFileMap` pointer to :ref:`PR_MemMap`. The
+memory-mapped file object is closed by passing the :ref:`PRFileMap` pointer
+to :ref:`PR_CloseFileMap`.
diff --git a/docs/nspr/reference/prfileprivate.rst b/docs/nspr/reference/prfileprivate.rst
new file mode 100644
index 0000000000..dc264d7d85
--- /dev/null
+++ b/docs/nspr/reference/prfileprivate.rst
@@ -0,0 +1,24 @@
+PRFilePrivate
+=============
+
+
+Layer-dependent implementation data.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ typedef struct PRFilePrivate PRFilePrivate;
+
+
+Description
+-----------
+
+A layer implementor should collect all the private data of the layer in
+the :ref:`PRFilePrivate` structure. Each layer has its own definition of
+:ref:`PRFilePrivate`, which is hidden from other layers as well as from the
+users of the layer.
diff --git a/docs/nspr/reference/prfiletype.rst b/docs/nspr/reference/prfiletype.rst
new file mode 100644
index 0000000000..2382f50cca
--- /dev/null
+++ b/docs/nspr/reference/prfiletype.rst
@@ -0,0 +1,34 @@
+PRFileType
+==========
+
+
+Type for enumerators used in the type field of the :ref:`PRFileInfo` and
+:ref:`PRFileInfo64` structures.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ typedef enum PRFileType{
+ PR_FILE_FILE = 1,
+ PR_FILE_DIRECTORY = 2,
+ PR_FILE_OTHER = 3
+ } PRFileType;
+
+
+Enumerators
+~~~~~~~~~~~
+
+The enumeration has the following enumerators:
+
+``PR_FILE_FILE``
+ The information in the structure describes a file.
+``PR_FILE_DIRECTORY``
+ The information in the structure describes a directory.
+``PR_FILE_OTHER``
+ The information in the structure describes some other kind of file
+ system object.
diff --git a/docs/nspr/reference/prfloat64.rst b/docs/nspr/reference/prfloat64.rst
new file mode 100644
index 0000000000..704eabec96
--- /dev/null
+++ b/docs/nspr/reference/prfloat64.rst
@@ -0,0 +1,15 @@
+
+PRFloat64
+=========
+
+The NSPR floating-point type is always 64 bits.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtypes.h>
+
+ typedef double PRFloat64;
diff --git a/docs/nspr/reference/prhostent.rst b/docs/nspr/reference/prhostent.rst
new file mode 100644
index 0000000000..daf2d89d40
--- /dev/null
+++ b/docs/nspr/reference/prhostent.rst
@@ -0,0 +1,69 @@
+PRHostEnt
+=========
+
+A structure that defines a list of network addresses. This structure is
+output from :ref:`PR_GetHostByName` and :ref:`PR_GetHostByAddr` and passed to
+:ref:`PR_EnumerateHostEnt`. Clients should avoid directly accessing any of
+the structure's fields.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prnetdb.h>
+
+ typedef struct PRHostEnt {
+ char *h_name;
+ char **h_aliases;
+ #if defined(_WIN32)
+ PRInt16 h_addrtype;
+ PRInt16 h_length;
+ #else
+ PRInt32 h_addrtype;
+ PRInt32 h_length;
+ #endif
+ char **h_addr_list;
+ } PRHostEnt;
+
+
+Fields
+~~~~~~
+
+The structure has the following fields:
+
+``h_name``
+ Pointer to the official name of host.
+``h_aliases``
+ Pointer to a pointer to list of aliases. The list is terminated with
+ a ``NULL`` entry.
+``h_addrtype``
+ Host address type. For valid NSPR usage, this field must have a value
+ indicating either an IPv4 or an IPv6 address.
+``h_length``
+ Length of internal representation of the address in bytes. All of the
+ addresses in the list are of the same type and therefore of the same
+ length.
+``h_addr_list``
+ Pointer to a pointer to a list of addresses from name server (in
+ network byte order). The list is terminated with a ``NULL`` entry.
+
+
+Description
+-----------
+
+This structure is used by many of the network address functions. All
+addresses are passed in host order and returned in network order
+(suitable for use in system calls).
+
+Use the network address functions to manipulate the :ref:`PRHostEnt`
+structure. To make the transition to IP version 6 easier, it's best to
+treat :ref:`PRHostEnt` as an opaque structure.
+
+Note
+----
+
+``WINSOCK.H`` defines ``h_addrtype`` and ``h_length`` as a 16-bit field,
+whereas other platforms treat it as a 32-bit field. The ``#ifdef`` in
+the structure allows direct assignment of the :ref:`PRHostEnt` structure.
diff --git a/docs/nspr/reference/print16.rst b/docs/nspr/reference/print16.rst
new file mode 100644
index 0000000000..8701a0ea5d
--- /dev/null
+++ b/docs/nspr/reference/print16.rst
@@ -0,0 +1,14 @@
+PRInt16
+=======
+
+Guaranteed to be a signed 16-bit integer on all platforms.
+
+
+Syntax
+------
+
+::
+
+ #include <prtypes.h>
+
+ typedefdefinition PRInt16;
diff --git a/docs/nspr/reference/print32.rst b/docs/nspr/reference/print32.rst
new file mode 100644
index 0000000000..8b179c6f37
--- /dev/null
+++ b/docs/nspr/reference/print32.rst
@@ -0,0 +1,22 @@
+PRInt32
+=======
+
+Guaranteed to be a signed 32-bit integer on all platforms.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtypes.h>
+
+ typedefdefinition PRInt32;
+
+
+Description
+-----------
+
+May be defined as an ``int`` or a ``long``, depending on the platform.
+For syntax details for each platform, see
+`prtypes.h <https://dxr.mozilla.org/mozilla-central/source/nsprpub/pr/include/prtypes.h>`__.
diff --git a/docs/nspr/reference/print64.rst b/docs/nspr/reference/print64.rst
new file mode 100644
index 0000000000..3ef8c31d3f
--- /dev/null
+++ b/docs/nspr/reference/print64.rst
@@ -0,0 +1,22 @@
+PRInt64
+=======
+
+Guaranteed to be a signed 64-bit integer on all platforms.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtypes.h>
+
+ typedef definition PRInt64;
+
+
+Description
+-----------
+
+May be defined in several different ways, depending on the platform. For
+syntax details for each platform, see
+`prtypes.h <https://dxr.mozilla.org/mozilla-central/source/nsprpub/pr/include/prtypes.h>`__.
diff --git a/docs/nspr/reference/print8.rst b/docs/nspr/reference/print8.rst
new file mode 100644
index 0000000000..6dd21a8efe
--- /dev/null
+++ b/docs/nspr/reference/print8.rst
@@ -0,0 +1,14 @@
+PRInt8
+======
+
+Guaranteed to be a signed 8-bit integer on all platforms.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtypes.h>
+
+ typedef definition PRInt8;
diff --git a/docs/nspr/reference/printervaltime.rst b/docs/nspr/reference/printervaltime.rst
new file mode 100644
index 0000000000..9367d6ad7f
--- /dev/null
+++ b/docs/nspr/reference/printervaltime.rst
@@ -0,0 +1,72 @@
+PRIntervalTime
+==============
+
+A platform-dependent type that represents a monotonically increasing
+integer--the NSPR runtime clock.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prinrval.h>
+
+ typedef PRUint32 PRIntervalTime;
+
+ #define PR_INTERVAL_MIN 1000UL
+ #define PR_INTERVAL_MAX 100000UL
+
+ #define PR_INTERVAL_NO_WAIT 0UL
+ #define PR_INTERVAL_NO_TIMEOUT 0xffffffffUL
+
+
+Description
+-----------
+
+The units of :ref:`PRIntervalTime` are platform-dependent. They are chosen
+to be appropriate for the host OS, yet provide sufficient resolution and
+period to be useful to clients.
+
+The increasing interval value represented by :ref:`PRIntervalTime` wraps.
+It should therefore never be used for intervals greater than
+approximately 6 hours. Interval times are accurate regardless of host
+processing requirements and are very cheap to acquire.
+
+The constants ``PR_INTERVAL_MIN`` and ``PR_INTERVAL_MAX`` define a range
+in ticks per second. These constants bound both the period and the
+resolution of a :ref:`PRIntervalTime` object.
+
+The reserved constants ``PR_INTERVAL_NO_WAIT`` and
+``PR_INTERVAL_NO_TIMEOUT`` have special meaning for NSPR. They indicate
+that the process should wait no time (return immediately) or wait
+forever (never time out), respectively.
+
+.. _Important_Note:
+
+Important Note
+~~~~~~~~~~~~~~
+
+The counters used for interval times are allowed to overflow. Since the
+sampling of the counter used to define an arbitrary epoch may have any
+32-bit value, some care must be taken in the use of interval times. The
+proper coding style to test the expiration of an interval is as follows:
+
+.. code::
+
+ if ((PRIntervalTime)(now - epoch) > interval)
+ <... interval has expired ...>
+
+As long as the interval and the elapsed time (now - epoch) do not exceed
+half the namespace allowed by a :ref:`PRIntervalTime` (2\ :sup:`31`-1), the
+expression shown above provides the expected result even if the signs of
+now and epoch differ.
+
+The resolution of a :ref:`PRIntervalTime` object is defined by the API.
+NSPR guarantees that there will be at least 1000 ticks per second and
+not more than 100000. At the maximum resolution of 10000 ticks per
+second, each tick represents 1/100000 of a second. At that rate, a
+32-bit register will overflow in approximately 28 hours, making the
+maximum useful interval approximately 6 hours. Waiting on events more
+than half a day in the future must therefore be based on a calendar
+time.
diff --git a/docs/nspr/reference/printn.rst b/docs/nspr/reference/printn.rst
new file mode 100644
index 0000000000..0d7a465da8
--- /dev/null
+++ b/docs/nspr/reference/printn.rst
@@ -0,0 +1,17 @@
+PRIntn
+======
+
+This type is one of the most appropriate for automatic variables. It is
+guaranteed to be at least 16 bits, though various architectures may
+define it to be wider (for example, 32 or even 64 bits). This types is
+never valid for fields of a structure.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtypes.h>
+
+ typedef int PRIntn;
diff --git a/docs/nspr/reference/priomethods.rst b/docs/nspr/reference/priomethods.rst
new file mode 100644
index 0000000000..8e50ae1781
--- /dev/null
+++ b/docs/nspr/reference/priomethods.rst
@@ -0,0 +1,132 @@
+PRIOMethods
+===========
+
+The table of I/O methods used in a file descriptor.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ struct PRIOMethods {
+ PRDescType file_type;
+ PRCloseFN close;
+ PRReadFN read;
+ PRWriteFN write;
+ PRAvailableFN available;
+ PRAvailable64FN available64;
+ PRFsyncFN fsync;
+ PRSeekFN seek;
+ PRSeek64FN seek64;
+ PRFileInfoFN fileInfo;
+ PRFileInfo64FN fileInfo64;
+ PRWritevFN writev;
+ PRConnectFN connect;
+ PRAcceptFN accept;
+ PRBindFN bind;
+ PRListenFN listen;
+ PRShutdownFN shutdown;
+ PRRecvFN recv;
+ PRSendFN send;
+ PRRecvfromFN recvfrom;
+ PRSendtoFN sendto;
+ PRPollFN poll;
+ PRAcceptreadFN acceptread;
+ PRTransmitfileFN transmitfile;
+ PRGetsocknameFN getsockname;
+ PRGetpeernameFN getpeername;
+ PRGetsockoptFN getsockopt;
+ PRSetsockoptFN setsockopt;
+ };
+
+ typedef struct PRIOMethods PRIOMethods;
+
+
+Parameters
+~~~~~~~~~~
+
+``file_type``
+ Type of file represented (tos).
+``close``
+ Close file and destroy descriptor.
+``read``
+ Read up to the specified number of bytes into buffer.
+``write``
+ Write specified number of bytes from buffer.
+``available``
+ Determine number of bytes available for reading.
+``available64``
+ Same as previous field, except 64-bit.
+``fsync``
+ Flush all in-memory buffers of file to permanent store.
+``seek``
+ Position the file pointer to the desired place.
+``seek64``
+ Same as previous field, except 64-bit.
+``fileInfo``
+ Get information about an open file.
+``fileInfo64``
+ Same as previous field, except 64-bit.
+``writev``
+ Write from a vector of buffers.
+``connect``
+ Connect to the specified network address.
+``accept``
+ Accept a connection from a network peer.
+``bind``
+ Associate a network address with the file descriptor.
+``listen``
+ Prepare to listen for network connections.
+``shutdown``
+ Shut down a network connection.
+``recv``
+ Receive up to the specified number of bytes.
+``send``
+ Send all the bytes specified.
+``recvfrom``
+ Receive up to the specified number of bytes and report network
+ source.
+``sendto``
+ Send bytes to specified network address.
+``poll``
+ Test the file descriptor to see if it is ready for I/O.
+``acceptread``
+ Accept and read from a new network file descriptor.
+``transmitfile``
+ Transmit an entire file to the specified socket.
+``getsockname``
+ Get network address associated with a file descriptor.
+``getpeername``
+ Get peer's network address.
+``getsockopt``
+ Get current setting of specified socket option.
+``setsockopt``
+ Set value of specified socket option.
+
+
+Description
+-----------
+
+You don't need to know the type declaration for each function listed in
+the method table unless you are implementing a layer. For information
+about each function, see the corresponding function description in this
+document. For example, the ``write`` method in :ref:`PRIOMethods`
+implements the :ref:`PR_Write` function. For type definition details, see
+``prio.h``.
+
+The I/O methods table provides procedural access to the functions of the
+file descriptor. It is the responsibility of a layer implementor to
+provide suitable functions at every entry point (that is, for every
+function in the I/O methods table). If a layer provides no
+functionality, it should call the next lower (higher) function of the
+same name (for example, the "close" method would return
+``fd->lower->method->close(fd->lower)``).
+
+Not all functions in the methods table are implemented for all types of
+files. For example, the seek method is implemented for normal files but
+not for sockets. In cases where this partial implementation occurs, the
+function returns an error indication with an error code of
+``PR_INVALID_METHOD_ERROR``.
diff --git a/docs/nspr/reference/pripv6addr.rst b/docs/nspr/reference/pripv6addr.rst
new file mode 100644
index 0000000000..94250f5ad1
--- /dev/null
+++ b/docs/nspr/reference/pripv6addr.rst
@@ -0,0 +1,26 @@
+PRIPv6Addr
+==========
+
+Type used in the ``ipv6.ip`` field of the :ref:`PRNetAddr` structure.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ #if defined(_PR_INET6)
+ typedef struct in6_addr PRIPv6Addr;
+ #endif /* defined(_PR_INET6) */
+
+
+Description
+-----------
+
+PRIPv6Addr represents a 128-bit IPv6 address. It is equivalent to struct
+``in6_addr`` in the Berkeley socket interface. :ref:`PRIPv6Addr` is always
+manipulated as a byte array. Unlike the IPv4 address (a 4-byte unsigned
+integer) or the port number (a 2-byte unsigned integer), it has no
+network or host byte order.
diff --git a/docs/nspr/reference/prjob.rst b/docs/nspr/reference/prjob.rst
new file mode 100644
index 0000000000..7407f43cf1
--- /dev/null
+++ b/docs/nspr/reference/prjob.rst
@@ -0,0 +1,10 @@
+PRJob
+=====
+
+
+Syntax
+------
+
+::
+
+ #include <prtpool.h>
diff --git a/docs/nspr/reference/prjobfn.rst b/docs/nspr/reference/prjobfn.rst
new file mode 100644
index 0000000000..46e6bbc3f0
--- /dev/null
+++ b/docs/nspr/reference/prjobfn.rst
@@ -0,0 +1,12 @@
+PRJobFn
+=======
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtpool.h>
+
+ typedef void (PR_CALLBACK *PRJobFn)(void *arg);
diff --git a/docs/nspr/reference/prjobiodesc.rst b/docs/nspr/reference/prjobiodesc.rst
new file mode 100644
index 0000000000..60d10a6dc2
--- /dev/null
+++ b/docs/nspr/reference/prjobiodesc.rst
@@ -0,0 +1,16 @@
+PRJobIoDesc
+===========
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtpool.h>
+
+ typedef struct PRJobIoDesc {
+ PRFileDesc *socket;
+ PRErrorCode error;
+ PRIntervalTime timeout;
+ } PRJobIoDesc;
diff --git a/docs/nspr/reference/prlibrary.rst b/docs/nspr/reference/prlibrary.rst
new file mode 100644
index 0000000000..d0c8ba1ad7
--- /dev/null
+++ b/docs/nspr/reference/prlibrary.rst
@@ -0,0 +1,22 @@
+PRLibrary
+=========
+
+An opaque structure identifying a library.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prlink.h>
+
+ typedef struct PRLibrary PRLibrary;
+
+
+Description
+-----------
+
+A PRLibrary is an opaque structure. A reference to such a structure can
+be returned by some of the functions in the runtime and serve to
+identify a particular instance of a library.
diff --git a/docs/nspr/reference/prlinger.rst b/docs/nspr/reference/prlinger.rst
new file mode 100644
index 0000000000..9506dc0240
--- /dev/null
+++ b/docs/nspr/reference/prlinger.rst
@@ -0,0 +1,56 @@
+PRLinger
+========
+
+Structure used with the ``PR_SockOpt_Linger`` socket option to specify
+the time interval (in :ref:`PRIntervalTime` units) to linger on closing a
+socket if any data remain in the socket send buffer.
+
+
+Syntax
+~~~~~~
+
+.. code::
+
+ #include <prio.h>
+
+ typedef struct PRLinger {
+ PRBool polarity;
+ PRIntervalTime linger;
+ } PRLinger;
+
+
+Fields
+~~~~~~
+
+The structure has the following fields:
+
+``polarity``
+ Polarity of the option's setting: ``PR_FALSE`` means the option is
+ off, in which case the value of ``linger`` is ignored. ``PR_TRUE``
+ means the option is on, and the value of ``linger`` will be used to
+ determine how long :ref:`PR_Close` waits before returning.
+``linger``
+ Time (in :ref:`PRIntervalTime` units) to linger before closing if any
+ data remain in the socket send buffer.
+
+
+Description
+~~~~~~~~~~~
+
+By default, :ref:`PR_Close` returns immediately, but if there are any data
+remaining in the socket send buffer, the system attempts to deliver the
+data to the peer. The ``PR_SockOpt_Linger`` socket option, with a value
+represented by a structure of type :ref:`PRLinger`, makes it possible to
+change this default as follows:
+
+- If ``polarity`` is set to ``PR_FALSE``, :ref:`PR_Close` returns
+ immediately, but if there are any data remaining in the socket send
+ buffer, the runtime attempts to deliver the data to the peer.
+- If ``polarity`` is set to ``PR_TRUE`` and ``linger`` is set to 0
+ (``PR_INTERVAL_NO_WAIT``), the runtime aborts the connection when it
+ is closed and discards any data remaining in the socket send buffer.
+- If ``polarity`` is set to ``PR_TRUE`` and ``linger`` is nonzero, the
+ runtime *lingers* when the socket is closed. That is, if any data
+ remains in the socket send buffer, :ref:`PR_Close` blocks until either
+ all the data is sent and acknowledged by the peer or the interval
+ specified by ``linger`` expires.
diff --git a/docs/nspr/reference/prlock.rst b/docs/nspr/reference/prlock.rst
new file mode 100644
index 0000000000..a7a736d340
--- /dev/null
+++ b/docs/nspr/reference/prlock.rst
@@ -0,0 +1,22 @@
+PRLock
+======
+
+A mutual exclusion lock.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prlock.h>
+
+ typedef struct PRLock PRLock;
+
+
+Description
+-----------
+
+NSPR represents a lock as an opaque entity to clients of the functions
+described in `"Locks" <en/NSPR_API_Reference/Locks>`__. Functions that
+operate on locks do not have timeouts and are not interruptible.
diff --git a/docs/nspr/reference/prlogmoduleinfo.rst b/docs/nspr/reference/prlogmoduleinfo.rst
new file mode 100644
index 0000000000..ea616e435a
--- /dev/null
+++ b/docs/nspr/reference/prlogmoduleinfo.rst
@@ -0,0 +1,23 @@
+PR_NewLogModule
+===============
+
+
+The ``PRLogModuleInfo`` structure controls logging from within your
+application. To log your program's activity, create a
+``PRLogModuleInfo`` structure using
+`:ref:`PR_NewLogModule` <http://www-archive.mozilla.org/projects/nspr/reference/html/prlog.html#25372>`__
+.
+
+
+Syntax
+------
+
+::
+
+ #include <prlog.h>
+
+ typedef struct PRLogModuleInfo {
+ const char *name;
+ PRLogModuleLevel level;
+ struct PRLogModuleInfo *next;
+ } PRLogModuleInfo;
diff --git a/docs/nspr/reference/prlogmodulelevel.rst b/docs/nspr/reference/prlogmodulelevel.rst
new file mode 100644
index 0000000000..d9ed3f78ca
--- /dev/null
+++ b/docs/nspr/reference/prlogmodulelevel.rst
@@ -0,0 +1,26 @@
+PRLogModuleLevel
+================
+
+The enumerated type :ref:`PRLogModuleLevel` defines levels of logging
+available to application programs.
+
+
+Syntax
+------
+
+::
+
+ #include <prlog.h>
+
+ typedef enum PRLogModuleLevel {
+ PR_LOG_NONE = 0,
+ PR_LOG_ALWAYS = 1,
+ PR_LOG_ERROR = 2,
+ PR_LOG_WARNING = 3,
+ PR_LOG_DEBUG = 4,
+
+ PR_LOG_NOTICE = PR_LOG_DEBUG,
+ PR_LOG_WARN = PR_LOG_WARNING,
+ PR_LOG_MIN = PR_LOG_DEBUG,
+ PR_LOG_MAX = PR_LOG_DEBUG
+ } PRLogModuleLevel;
diff --git a/docs/nspr/reference/prmcastrequest.rst b/docs/nspr/reference/prmcastrequest.rst
new file mode 100644
index 0000000000..9c7c63ecef
--- /dev/null
+++ b/docs/nspr/reference/prmcastrequest.rst
@@ -0,0 +1,40 @@
+PRMcastRequest
+==============
+
+Structure used to specify values for the ``PR_SockOpt_AddMember`` and
+``PR_SockOpt_DropMember`` socket options that define a request to join
+or leave a multicast group.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ struct PRMcastRequest {
+ PRNetAddr mcaddr;
+ PRNetAddr ifaddr;
+ };
+
+ typedef struct PRMcastRequest PRMcastRequest;
+
+
+Fields
+~~~~~~
+
+The structure has the following fields:
+
+``mcaddr``
+ IP multicast address of group.
+``ifaddr``
+ Local IP address of interface.
+
+
+Description
+-----------
+
+The ``mcaddr`` and ``ifaddr`` fields are of the type :ref:`PRNetAddr`, but
+their ``port`` fields are ignored. Only the IP address (``inet.ip``)
+fields are used.
diff --git a/docs/nspr/reference/prmonitor.rst b/docs/nspr/reference/prmonitor.rst
new file mode 100644
index 0000000000..5a420f9452
--- /dev/null
+++ b/docs/nspr/reference/prmonitor.rst
@@ -0,0 +1,15 @@
+PRMonitor
+=========
+
+An opaque structure managed entirely by the client. Clients create them
+when needed and must destroy them when no longer needed.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prmon.h>
+
+ typedef struct PRMonitor PRMonitor;
diff --git a/docs/nspr/reference/prnetaddr.rst b/docs/nspr/reference/prnetaddr.rst
new file mode 100644
index 0000000000..71fe1a65d4
--- /dev/null
+++ b/docs/nspr/reference/prnetaddr.rst
@@ -0,0 +1,85 @@
+PRNetAddr
+=========
+
+Type used with `Socket Manipulation
+Functions <Socket_Manipulation_Functions>`__ to specify a network
+address.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ union PRNetAddr {
+ struct {
+ PRUint16 family;
+ char data[14];
+ } raw;
+ struct {
+ PRUint16 family;
+ PRUint16 port;
+ PRUint32 ip;
+ char pad[8];
+ } inet;
+ #if defined(_PR_INET6)
+ struct {
+ PRUint16 family;
+ PRUint16 port;
+ PRUint32 flowinfo;
+ PRIPv6Addr ip;
+ } ipv6;
+ #endif /* defined(_PR_INET6) */
+ };
+
+ typedef union PRNetAddr PRNetAddr;
+
+
+Fields
+~~~~~~
+
+The structure has the following fields:
+
+``family``
+ Address family: ``PR_AF_INET|PR_AF_INET6`` for ``raw.family``,
+ ``PR_AF_INET`` for ``inet.family``, ``PR_AF_INET6`` for
+ ``ipv6.family``.
+``data``
+ Raw address data.
+``port``
+ Port number of TCP or UDP, in network byte order.
+``ip``
+ The actual 32 (for ``inet.ip``) or 128 (for ``ipv6.ip``) bits of IP
+ address. The ``inet.ip`` field is in network byte order.
+``pad``
+ Unused.
+``flowinfo``
+ Routing information.
+
+
+Description
+-----------
+
+The union :ref:`PRNetAddr` represents a network address. NSPR supports only
+the Internet address family. By default, NSPR is built to support only
+IPv4, but it's possible to build the NSPR library to support both IPv4
+and IPv6. Therefore, the ``family`` field can be ``PR_AF_INET`` only for
+default NSPR, and can also be ``PR_AF_INET6`` if the binary supports
+``IPv6``.
+
+:ref:`PRNetAddr` is binary-compatible with the socket address structures in
+the familiar Berkeley socket interface, although this fact should not be
+relied upon. The raw member of the union is equivalent to
+``struct sockaddr``, the ``inet`` member is equivalent to
+``struct sockaddr_in``, and if the binary is built with ``IPv6``
+support, the ``ipv6`` member is equivalent to ``struct sockaddr_in6``.
+(Note that :ref:`PRNetAddr` does not have the ``length`` field that is
+present in ``struct sockaddr_in`` on some Unix platforms.)
+
+The macros ``PR_AF_INET``, ``PR_AF_INET6``, ``PR_INADDR_ANY``,
+``PR_INADDR_LOOPBACK`` are defined if ``prio.h`` is included.
+``PR_INADDR_ANY`` and ``PR_INADDR_LOOPBACK`` are special ``IPv4``
+addresses in host byte order, so they must be converted to network byte
+order before being assigned to the ``inet.ip`` field.
diff --git a/docs/nspr/reference/process_initialization.rst b/docs/nspr/reference/process_initialization.rst
new file mode 100644
index 0000000000..c5b0fd1763
--- /dev/null
+++ b/docs/nspr/reference/process_initialization.rst
@@ -0,0 +1,63 @@
+Process Initialization
+======================
+
+This chapter describes the NSPR API for versioning, process
+initialization, and shutdown of NSPR.
+
+- `Identity and Versioning <#Identity_and_Versioning>`__
+- `Initialization and Cleanup <#Initialization_and_Cleanup>`__
+- `Module Initialization <#Module_Initialization>`__
+
+.. _Identity_and_Versioning:
+
+Identity and Versioning
+-----------------------
+
+.. _Name_and_Version_Constants:
+
+Name and Version Constants
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ - :ref:`PR_NAME`
+ - :ref:`PR_VERSION`
+ - :ref:`PR_VersionCheck`
+
+.. _Initialization_and_Cleanup:
+
+Initialization and Cleanup
+--------------------------
+
+NSPR detects whether the library has been initialized and performs
+implicit initialization if it hasn't. Implicit initialization should
+suffice unless a program has specific sequencing requirements or needs
+to characterize the primordial thread. Explicit initialization is rarely
+necessary.
+
+Implicit initialization assumes that the initiator is the primordial
+thread and that the thread is a user thread of normal priority.
+
+ - :ref:`PR_Init`
+ - :ref:`PR_Initialize`
+ - :ref:`PR_Initialized`
+ - :ref:`PR_Cleanup`
+ - :ref:`PR_DisableClockInterrupts`
+ - :ref:`PR_BlockClockInterrupts`
+ - :ref:`PR_UnblockClockInterrupts`
+ - :ref:`PR_SetConcurrency`
+ - :ref:`PR_ProcessExit`
+ - :ref:`PR_Abort`
+
+.. _Module_Initialization:
+
+Module Initialization
+~~~~~~~~~~~~~~~~~~~~~
+
+Initialization can be tricky in a threaded environment, especially
+initialization that must happen exactly once. :ref:`PR_CallOnce` ensures
+that such initialization code is called only once. This facility is
+recommended in situations where complicated global initialization is
+required.
+
+ - :ref:`PRCallOnceType`
+ - :ref:`PRCallOnceFN`
+ - :ref:`PR_CallOnce`
diff --git a/docs/nspr/reference/process_management_and_interprocess_communication.rst b/docs/nspr/reference/process_management_and_interprocess_communication.rst
new file mode 100644
index 0000000000..c508c7362c
--- /dev/null
+++ b/docs/nspr/reference/process_management_and_interprocess_communication.rst
@@ -0,0 +1,64 @@
+Process Management And Interprocess Communication
+=================================================
+
+This chapter describes the NSPR routines that deal with processes. A
+process is an instance of a program. NSPR provides routines to create a
+new process and to wait for the termination of another process.
+
+NSPR does not provide an equivalent of the Unix ``fork()``. The
+newly-created process executes its program from the beginning. A new
+process can inherit specified file descriptors from its parent, and the
+parent can redirect the standard I/O streams of the child process to
+specified file descriptors.
+
+Note that the functions described in this chapter are not available for
+MacOS or Win16 operating systems.
+
+.. _Process_Management_Types_and_Constants:
+
+Process Management Types and Constants
+--------------------------------------
+
+The types defined for process management are:
+
+ - :ref:`PRProcess`
+ - :ref:`PRProcessAttr`
+
+.. _Process_Management_Functions:
+
+Process Management Functions
+----------------------------
+
+The process manipulation function fall into these categories:
+
+- `Setting the Attributes of a New
+ Process <#Setting_the_Attributes_of_a_New_Process>`__
+- `Creating and Managing
+ Processes <#Creating_and_Managing_Processes>`__
+
+.. _Setting_the_Attributes_of_a_New_Process:
+
+Setting the Attributes of a New Process
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The functions that create and manipulate attribute sets of new processes
+are:
+
+ - :ref:`PR_NewProcessAttr`
+ - :ref:`PR_ResetProcessAttr`
+ - :ref:`PR_DestroyProcessAttr`
+ - :ref:`PR_ProcessAttrSetStdioRedirect`
+ - :ref:`PR_ProcessAttrSetCurrentDirectory`
+ - :ref:`PR_ProcessAttrSetInheritableFD`
+
+.. _Creating_and_Managing_Processes:
+
+Creating and Managing Processes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The functions that create and manage processes are:
+
+ - :ref:`PR_CreateProcess`
+ - :ref:`PR_DetachProcess`
+ - :ref:`PR_WaitProcess`
+ - :ref:`PR_KillProcess`
diff --git a/docs/nspr/reference/prpackedbool.rst b/docs/nspr/reference/prpackedbool.rst
new file mode 100644
index 0000000000..c32888de84
--- /dev/null
+++ b/docs/nspr/reference/prpackedbool.rst
@@ -0,0 +1,20 @@
+PRPackedBool
+============
+
+Packed Boolean value.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtypes.h>
+
+ typedef PRUint8 PRPackedBool;
+
+
+Description
+-----------
+
+Use :ref:`PRPackedBool` within structures where bit fields are not desirable but minimum and consistent overhead matters.
diff --git a/docs/nspr/reference/prprimordialfn.rst b/docs/nspr/reference/prprimordialfn.rst
new file mode 100644
index 0000000000..5bb4266ff1
--- /dev/null
+++ b/docs/nspr/reference/prprimordialfn.rst
@@ -0,0 +1,19 @@
+PRPrimordialFn
+==============
+
+The type for the root function used by :ref:`PR_Initialize` is specified as
+follows:
+
+
+Syntax
+------
+
+.. code::
+
+ typedef PRIntn (PR_CALLBACK *PRPrimordialFn)(PRIntn argc, char **argv);
+
+
+See Also
+--------
+
+ - :ref:`PR_Initialize`
diff --git a/docs/nspr/reference/prprocess.rst b/docs/nspr/reference/prprocess.rst
new file mode 100644
index 0000000000..b17db0b3da
--- /dev/null
+++ b/docs/nspr/reference/prprocess.rst
@@ -0,0 +1,20 @@
+PRProcess
+=========
+
+Represents a process.
+
+
+Syntax
+------
+
+::
+
+ #include <prproces.h>
+
+ typedef struct PRProcess PRProcess;
+
+
+Description
+-----------
+
+A pointer to the opaque :ref:`PRProcess` structure identifies a process.
diff --git a/docs/nspr/reference/prprocessattr.rst b/docs/nspr/reference/prprocessattr.rst
new file mode 100644
index 0000000000..65e48e1201
--- /dev/null
+++ b/docs/nspr/reference/prprocessattr.rst
@@ -0,0 +1,23 @@
+PRProcessAttr
+=============
+
+Represents the attributes of a new process.
+
+
+Syntax
+------
+
+::
+
+ #include <prproces.h>
+
+ typedef struct PRProcessAttr PRProcessAttr;
+
+
+Description
+-----------
+
+This opaque structure describes the attributes of a process to be
+created. Pass a pointer to a :ref:`PRProcessAttr` into ``PR_CreateProcess``
+when you create a new process, specifying information such as standard
+input/output redirection and file descriptor inheritance.
diff --git a/docs/nspr/reference/prprotoent.rst b/docs/nspr/reference/prprotoent.rst
new file mode 100644
index 0000000000..6d913a1cc8
--- /dev/null
+++ b/docs/nspr/reference/prprotoent.rst
@@ -0,0 +1,37 @@
+PRProtoEnt
+==========
+
+Protocol entry returned by :ref:`PR_GetProtoByName` and
+:ref:`PR_GetProtoByNumber`.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prnetdb.h>
+
+ typedef struct PRProtoEnt {
+ char *p_name;
+ char **p_aliases;
+ #if defined(_WIN32)
+ PRInt16 p_num;
+ #else
+ PRInt32 p_num;
+ #endif
+ } PRProtoEnt;
+
+
+Fields
+~~~~~~
+
+The structure has the following fields:
+
+``p_name``
+ Pointer to official protocol name.
+``p_aliases``
+ Pointer to a pointer to a list of aliases. The list is terminated
+ with a ``NULL`` entry.
+``p_num``
+ Protocol number.
diff --git a/docs/nspr/reference/prptrdiff.rst b/docs/nspr/reference/prptrdiff.rst
new file mode 100644
index 0000000000..c069649215
--- /dev/null
+++ b/docs/nspr/reference/prptrdiff.rst
@@ -0,0 +1,14 @@
+PRPtrdiff
+=========
+
+Signed pointer difference type.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtypes.h>
+
+ typedef ptrdiff_t PRPtrdiff;
diff --git a/docs/nspr/reference/prseekwhence.rst b/docs/nspr/reference/prseekwhence.rst
new file mode 100644
index 0000000000..4d6ae71371
--- /dev/null
+++ b/docs/nspr/reference/prseekwhence.rst
@@ -0,0 +1,35 @@
+PRSeekWhence
+============
+
+Specifies how to interpret the ``offset`` parameter in setting the file
+pointer associated with the ``fd`` parameter for the :ref:`PR_Seek` and
+:ref:`PR_Seek64` functions.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ typedef PRSeekWhence {
+ PR_SEEK_SET = 0,
+ PR_SEEK_CUR = 1,
+ PR_SEEK_END = 2
+ } PRSeekWhence;
+
+
+Enumerators
+~~~~~~~~~~~
+
+The enumeration has the following enumerators:
+
+``PR_SEEK_SET``
+ Sets the file pointer to the value of the ``offset`` parameter.
+``PR_SEEK_CUR``
+ Sets the file pointer to its current location plus the value of the
+ ``offset`` parameter.
+``PR_SEEK_END``
+ Sets the file pointer to the size of the file plus the value of the
+ ``offset`` parameter.
diff --git a/docs/nspr/reference/prsize.rst b/docs/nspr/reference/prsize.rst
new file mode 100644
index 0000000000..8d0810574e
--- /dev/null
+++ b/docs/nspr/reference/prsize.rst
@@ -0,0 +1,15 @@
+PRSize
+======
+
+A type for representing the size of an object (not the size of a
+pointer). This is the same as the corresponding type in ``libc``.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtypes.h>
+
+ typedef size_t PRSize;
diff --git a/docs/nspr/reference/prsocketoptiondata.rst b/docs/nspr/reference/prsocketoptiondata.rst
new file mode 100644
index 0000000000..442d1559e9
--- /dev/null
+++ b/docs/nspr/reference/prsocketoptiondata.rst
@@ -0,0 +1,83 @@
+PRSocketOptionData
+==================
+
+Type for structure used with :ref:`PR_GetSocketOption` and
+:ref:`PR_SetSocketOption` to specify options for file descriptors that
+represent sockets.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ typedef struct PRSocketOptionData
+ {
+ PRSockOption option;
+ union
+ {
+ PRUintn ip_ttl;
+ PRUintn mcast_ttl;
+ PRUintn tos;
+ PRBool non_blocking;
+ PRBool reuse_addr;
+ PRBool keep_alive;
+ PRBool mcast_loopback;
+ PRBool no_delay;
+ PRSize max_segment;
+ PRSize recv_buffer_size;
+ PRSize send_buffer_size;
+ PRLinger linger;
+ PRMcastRequest add_member;
+ PRMcastRequest drop_member;
+ PRNetAddr mcast_if;
+ } value;
+ } PRSocketOptionData;
+
+
+Fields
+~~~~~~
+
+The structure has the following fields:
+
+``ip_ttl``
+ IP time-to-live.
+``mcast_ttl``
+ IP multicast time-to-live.
+``tos``
+ IP type-of-service and precedence.
+``non_blocking``
+ Nonblocking (network) I/O.
+``reuse_addr``
+ Allow local address reuse.
+``keep_alive``
+ Periodically test whether connection is still alive.
+``mcast_loopback``
+ IP multicast loopback.
+``no_delay``
+ Disable Nagle algorithm. Don't delay send to coalesce packets.
+``max_segment``
+ TCP maximum segment size.
+``recv_buffer_size``
+ Receive buffer size.
+``send_buffer_size``
+ Send buffer size.
+``linger``
+ Time to linger on close if data are present in socket send buffer.
+``add_member``
+ Join an IP multicast group.
+``drop_member``
+ Leave an IP multicast group.
+``mcast_if``
+ IP multicast interface address.
+
+
+Description
+~~~~~~~~~~~
+
+:ref:`PRSocketOptionData` is a name-value pair for a socket option. The
+``option`` field (of enumeration type :ref:`PRSockOption`) specifies the
+name of the socket option, and the ``value`` field (a union of all
+possible values) specifies the value of the option.
diff --git a/docs/nspr/reference/prsockoption.rst b/docs/nspr/reference/prsockoption.rst
new file mode 100644
index 0000000000..daaa20cb21
--- /dev/null
+++ b/docs/nspr/reference/prsockoption.rst
@@ -0,0 +1,79 @@
+PRSockOption
+============
+
+Enumeration type used in the ``option`` field of :ref:`PRSocketOptionData`
+to form the name portion of a name-value pair.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prio.h>
+
+ typedef enum PRSockOption {
+ PR_SockOpt_Nonblocking,
+ PR_SockOpt_Linger,
+ PR_SockOpt_Reuseaddr,
+ PR_SockOpt_Keepalive,
+ PR_SockOpt_RecvBufferSize,
+ PR_SockOpt_SendBufferSize,
+ PR_SockOpt_IpTimeToLive,
+ PR_SockOpt_IpTypeOfService,
+ PR_SockOpt_AddMember,
+ PR_SockOpt_DropMember,
+ PR_SockOpt_McastInterface,
+ PR_SockOpt_McastTimeToLive,
+ PR_SockOpt_McastLoopback,
+ PR_SockOpt_NoDelay,
+ PR_SockOpt_MaxSegment,
+ PR_SockOpt_Last
+ } PRSockOption;
+
+
+Enumerators
+~~~~~~~~~~~
+
+The enumeration has the following enumerators:
+
+``PR_SockOpt_Nonblocking``
+ Nonblocking I/O.
+``PR_SockOpt_Linger``
+ Time to linger on close if data is present in the socket send buffer.
+``PR_SockOpt_Reuseaddr``
+ Allow local address reuse.
+``PR_SockOpt_Keepalive``
+ Periodically test whether connection is still alive.
+``PR_SockOpt_RecvBufferSize``
+ Receive buffer size.
+``PR_SockOpt_SendBufferSize``
+ Send buffer size.
+``PR_SockOpt_IpTimeToLive``
+ IP time-to-live.
+``PR_SockOpt_IpTypeOfService``
+ IP type-of-service and precedence.
+``PR_SockOpt_AddMember``
+ Join an IP multicast group.
+``PR_SockOpt_DropMember``
+ Leave an IP multicast group.
+``PR_SockOpt_McastInterface``
+ IP multicast interface address.
+``PR_SockOpt_McastTimeToLive``
+ IP multicast time-to-live.
+``PR_SockOpt_McastLoopback``
+ IP multicast loopback.
+``PR_SockOpt_NoDelay``
+ Disable Nagle algorithm. Don't delay send to coalesce packets.
+``PR_SockOpt_MaxSegment``
+ Maximum segment size.
+``PR_SockOpt_Last``
+ Always one greater than the maximum valid socket option numerator.
+
+
+Description
+-----------
+
+The :ref:`PRSockOption` enumeration consists of all the socket options
+supported by NSPR. The ``option`` field of :ref:`PRSocketOptionData` should
+be set to an enumerator of type :ref:`PRSockOption`.
diff --git a/docs/nspr/reference/prstaticlinktable.rst b/docs/nspr/reference/prstaticlinktable.rst
new file mode 100644
index 0000000000..ce76ab10a3
--- /dev/null
+++ b/docs/nspr/reference/prstaticlinktable.rst
@@ -0,0 +1,22 @@
+PRStaticLinkTable
+=================
+
+A static link table entry can be created by a client of the runtime so
+that other clients can access static or dynamic libraries transparently.
+The basic function on a dynamic library is to acquire a pointer to a
+function that the library exports. If, during initialization, such
+entries are manually created, then future attempts to link to the
+symbols can be treated in a consistent fashion.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prlink.h>
+
+ typedef struct PRStaticLinkTable {
+ const char *name;
+ void (*fp)();
+ } PRStaticLinkTable;
diff --git a/docs/nspr/reference/prstatus.rst b/docs/nspr/reference/prstatus.rst
new file mode 100644
index 0000000000..15106b276b
--- /dev/null
+++ b/docs/nspr/reference/prstatus.rst
@@ -0,0 +1,14 @@
+PRStatus
+========
+
+Type for status code returned by some functions.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtypes.h>
+
+ typedef enum { PR_FAILURE = -1, PR_SUCCESS = 0 } PRStatus;
diff --git a/docs/nspr/reference/prthread.rst b/docs/nspr/reference/prthread.rst
new file mode 100644
index 0000000000..7f479735f4
--- /dev/null
+++ b/docs/nspr/reference/prthread.rst
@@ -0,0 +1,26 @@
+PRThread
+========
+
+An NSPR thread.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prthread.h>
+
+ typedef struct PRThread PRThread;
+
+
+Description
+~~~~~~~~~~~
+
+In NSPR, a thread is represented by a pointer to an opaque structure of
+type :ref:`PRThread`. This pointer is a required parameter for most of the
+functions that operate on threads.
+
+A ``PRThread*`` is the successful result of creating a new thread. The
+identifier remains valid until it returns from its root function and, if
+the thread was created joinable, is joined.
diff --git a/docs/nspr/reference/prthreadpool.rst b/docs/nspr/reference/prthreadpool.rst
new file mode 100644
index 0000000000..3659f189c7
--- /dev/null
+++ b/docs/nspr/reference/prthreadpool.rst
@@ -0,0 +1,10 @@
+PRThreadPool
+============
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtpool.h>
diff --git a/docs/nspr/reference/prthreadpriority.rst b/docs/nspr/reference/prthreadpriority.rst
new file mode 100644
index 0000000000..97c5c358da
--- /dev/null
+++ b/docs/nspr/reference/prthreadpriority.rst
@@ -0,0 +1,62 @@
+PRThreadPriority
+================
+
+A thread's priority setting.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prthread.h>
+
+ typedef enum PRThreadPriority
+ {
+ PR_PRIORITY_FIRST = 0,
+ PR_PRIORITY_LOW = 0,
+ PR_PRIORITY_NORMAL = 1,
+ PR_PRIORITY_HIGH = 2,
+ PR_PRIORITY_URGENT = 3,
+ PR_PRIORITY_LAST = 3
+ } PRThreadPriority;
+
+
+Enumerators
+~~~~~~~~~~~
+
+``PR_PRIORITY_FIRST``
+ Placeholder.
+``PR_PRIORITY_LOW``
+ The lowest possible priority. This priority is appropriate for
+ threads that are expected to perform intensive computation.
+``PR_PRIORITY_NORMAL``
+ The most commonly expected priority.
+``PR_PRIORITY_HIGH``
+ Slightly higher priority than ``PR_PRIORITY_NORMAL``. This priority
+ is for threads performing work of high urgency but short duration.
+``PR_PRIORITY_URGENT``
+ Highest priority. Only one thread at a time typically has this
+ priority.
+``PR_PRIORITY_LAST``
+ Placeholder
+
+
+Description
+-----------
+
+In general, an NSPR thread of higher priority has a statistically better
+chance of running relative to threads of lower priority. However,
+because of the multiple strategies NSPR uses to implement threading on
+various host platforms, NSPR priorities are not precisely defined. At
+best they are intended to specify a preference in the amount of CPU time
+that a higher-priority thread might expect relative to a lower-priority
+thread. This preference is still subject to resource availability and
+must not be used in place of proper synchronization.
+
+
+See Also
+--------
+
+`Setting Thread
+Priorities <Introduction_to_NSPR#Setting_Thread_Priorities>`__.
diff --git a/docs/nspr/reference/prthreadprivatedtor.rst b/docs/nspr/reference/prthreadprivatedtor.rst
new file mode 100644
index 0000000000..6a9339d07d
--- /dev/null
+++ b/docs/nspr/reference/prthreadprivatedtor.rst
@@ -0,0 +1,24 @@
+PRThreadPrivateDTOR
+===================
+
+The destructor function passed to PR_NewThreadPrivateIndex that is
+associated with the resulting thread private index.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prthread.h>
+
+ typedef void (PR_CALLBACK *PRThreadPrivateDTOR)(void *priv);
+
+
+Description
+~~~~~~~~~~~
+
+Until the data associated with an index is actually set with a call to
+:ref:`PR_SetThreadPrivate`, the value of the data is ``NULL``. If the data
+associated with the index is not ``NULL``, NSPR passes a reference to
+the data to the destructor function when the thread terminates.
diff --git a/docs/nspr/reference/prthreadscope.rst b/docs/nspr/reference/prthreadscope.rst
new file mode 100644
index 0000000000..c468704295
--- /dev/null
+++ b/docs/nspr/reference/prthreadscope.rst
@@ -0,0 +1,56 @@
+PRThreadScope
+=============
+
+The scope of an NSPR thread, specified as a parameter to
+:ref:`PR_CreateThread` or returned by :ref:`PR_GetThreadScope`.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prthread.h>
+
+ typedef enum PRThreadScope {
+ PR_LOCAL_THREAD,
+ PR_GLOBAL_THREAD
+ PR_GLOBAL_BOUND_THREAD
+ } PRThreadScope;
+
+
+Enumerators
+~~~~~~~~~~~
+
+``PR_LOCAL_THREAD``
+ A local thread, scheduled locally by NSPR within the process.
+``PR_GLOBAL_THREAD``
+ A global thread, scheduled by the host OS.
+``PR_GLOBAL_BOUND_THREAD``
+ A global bound (kernel) thread, scheduled by the host OS
+
+
+Description
+-----------
+
+An enumerator of type :ref:`PRThreadScope` specifies how a thread is
+scheduled: either locally by NSPR within the process (a local thread) or
+globally by the host (a global thread).
+
+Global threads are scheduled by the host OS and compete with all other
+threads on the host OS for resources. They are subject to fairly
+sophisticated scheduling techniques.
+
+Local threads are scheduled by NSPR within the process. The process is
+assumed to be globally scheduled, but NSPR can manipulate local threads
+without system intervention. In most cases, this leads to a significant
+performance benefit.
+
+However, on systems that require NSPR to make a distinction between
+global and local threads, global threads are invariably required to do
+any form of I/O. If a thread is likely to do a lot of I/O, making it a
+global thread early is probably warranted.
+
+On systems that don't make a distinction between local and global
+threads, NSPR silently ignores the scheduling request. To find the scope
+of the thread, call :ref:`PR_GetThreadScope`.
diff --git a/docs/nspr/reference/prthreadstack.rst b/docs/nspr/reference/prthreadstack.rst
new file mode 100644
index 0000000000..374ac3ac07
--- /dev/null
+++ b/docs/nspr/reference/prthreadstack.rst
@@ -0,0 +1,31 @@
+PRThreadStack
+=============
+
+.. container:: blockIndicator obsolete obsoleteHeader
+
+ | **Obsolete**
+ | This feature is obsolete. Although it may still work in some
+ browsers, its use is discouraged since it could be removed at any
+ time. Try to avoid using it.
+
+The opaque :ref:`PRThreadStack` structure is only used in the third
+argument "``PRThreadStack *stack``" to the :ref:`PR_AttachThread` function.
+The '``stack``' argument is now obsolete and ignored by
+:ref:`PR_AttachThread`. You should pass ``NULL`` as the 'stack' argument to
+:ref:`PR_AttachThread`.
+
+.. _Definition:
+
+Syntax
+------
+
+.. code::
+
+ #include <prthread.h>
+
+ typedef struct PRThreadStack PRThreadStack;
+
+.. _Definition_2:
+
+
+-
diff --git a/docs/nspr/reference/prthreadstate.rst b/docs/nspr/reference/prthreadstate.rst
new file mode 100644
index 0000000000..7285779f39
--- /dev/null
+++ b/docs/nspr/reference/prthreadstate.rst
@@ -0,0 +1,54 @@
+PRThreadState
+=============
+
+A thread's thread state is either joinable or unjoinable.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prthread.h>
+
+ typedef enum PRThreadState {
+ PR_JOINABLE_THREAD,
+ PR_UNJOINABLE_THREAD
+ } PRThreadState;
+
+
+Enumerators
+~~~~~~~~~~~
+
+``PR_UNJOINABLE_THREAD``
+ Thread termination happens implicitly when the thread returns from
+ the root function. The time of release of the resources assigned to
+ the thread cannot be determined in advance. Threads created with a
+ ``PR_UNJOINABLE_THREAD`` state cannot be used as arguments to
+ :ref:`PR_JoinThread`.
+``PR_JOINABLE_THREAD``
+ Joinable thread references remain valid after they have returned from
+ their root function until :ref:`PR_JoinThread` is called. This approach
+ facilitates management of the process' critical resources.
+
+
+Description
+-----------
+
+A thread is a critical resource and must be managed.
+
+The lifetime of a thread extends from the time it is created to the time
+it returns from its root function. What happens when it returns from its
+root function depends on the thread state passed to :ref:`PR_CreateThread`
+when the thread was created.
+
+If a thread is created as a joinable thread, it continues to exist after
+returning from its root function until another thread joins it. The join
+process permits strict synchronization of thread termination and
+therefore promotes effective resource management.
+
+If a thread is created as an unjoinable (also called detached) thread,
+it terminates and cleans up after itself after returning from its root
+function. This results in some ambiguity after the thread's root
+function has returned and before the thread has finished terminating
+itself.
diff --git a/docs/nspr/reference/prthreadtype.rst b/docs/nspr/reference/prthreadtype.rst
new file mode 100644
index 0000000000..4934af7a2f
--- /dev/null
+++ b/docs/nspr/reference/prthreadtype.rst
@@ -0,0 +1,40 @@
+PRThreadType
+============
+
+The type of an NSPR thread, specified as a parameter to
+:ref:`PR_CreateThread`.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prthread.h>
+
+ typedef enum PRThreadType {
+ PR_USER_THREAD,
+ PR_SYSTEM_THREAD
+ } PRThreadType;
+
+
+Enumerators
+~~~~~~~~~~~
+
+``PR_USER_THREAD``
+ :ref:`PR_Cleanup` blocks until the last thread of type
+ ``PR_USER_THREAD`` terminates.
+``PR_SYSTEM_THREAD``
+ NSPR ignores threads of type ``PR_SYSTEM_THREAD`` when determining
+ when a call to :ref:`PR_Cleanup` should return.
+
+
+Description
+-----------
+
+Threads can be either user threads or system threads. NSPR allows the
+client to synchronize the termination of all user threads and ignores
+those created as system threads. This arrangement implies that a system
+thread should not have volatile data that needs to be safely stored
+away. The applicability of system threads is somewhat dubious;
+therefore, they should be used with caution.
diff --git a/docs/nspr/reference/prtime.rst b/docs/nspr/reference/prtime.rst
new file mode 100644
index 0000000000..8498e942ff
--- /dev/null
+++ b/docs/nspr/reference/prtime.rst
@@ -0,0 +1,33 @@
+PRTime
+======
+
+A representation of absolute times.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtime.h>
+
+ typedef PRInt64 PRTime;
+
+
+Description
+-----------
+
+This type is a 64-bit integer representing the number of microseconds
+since the NSPR epoch, midnight (00:00:00) 1 January 1970 Coordinated
+Universal Time (UTC). A time after the epoch has a positive value, and a
+time before the epoch has a negative value.
+
+In NSPR, we use the more familiar term Greenwich Mean Time (GMT) in
+place of UTC. Although UTC and GMT are not exactly the same in their
+precise definitions, they can generally be treated as if they were.
+
+.. note::
+
+ **Note:** Keep in mind that while :ref:`PRTime` stores times in
+ microseconds since epoch, JavaScript date objects store times in
+ milliseconds since epoch.
diff --git a/docs/nspr/reference/prtimeparameters.rst b/docs/nspr/reference/prtimeparameters.rst
new file mode 100644
index 0000000000..4ddd01c7cd
--- /dev/null
+++ b/docs/nspr/reference/prtimeparameters.rst
@@ -0,0 +1,54 @@
+PRTimeParameters
+================
+
+A representation of time zone information.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtime.h>
+
+ typedef struct PRTimeParameters {
+ PRInt32 tp_gmt_offset;
+ PRInt32 tp_dst_offset;
+ } PRTimeParameters;
+
+
+Description
+-----------
+
+Each geographic location has a standard time zone, and if Daylight
+Saving Time (DST) is practiced, a daylight time zone. The
+:ref:`PRTimeParameters` structure represents the local time zone
+information in terms of the offset (in seconds) from GMT. The overall
+offset is broken into two components:
+
+``tp_gmt_offset``
+ The offset of the local standard time from GMT.
+
+``tp_dst_offset``
+ If daylight savings time (DST) is in effect, the DST adjustment from
+ the local standard time. This is most commonly 1 hour, but may also
+ be 30 minutes or some other amount. If DST is not in effect, the
+ tp_dst_offset component is 0.
+
+For example, the US Pacific Time Zone has both a standard time zone
+(Pacific Standard Time, or PST) and a daylight time zone (Pacific
+Daylight Time, or PDT).
+
+- In PST, the local time is 8 hours behind GMT, so ``tp_gmt_offset`` is
+ -28800 seconds. ``tp_dst_offset`` is 0, indicating that daylight
+ saving time is not in effect.
+
+- In PDT, the clock is turned forward by one hour, so the local time is
+ 7 hours behind GMT. This is broken down as -8 + 1 hours, so
+ ``tp_gmt_offset`` is -28800 seconds, and ``tp_dst_offset`` is 3600
+ seconds.
+
+A second example is Japan, which is 9 hours ahead of GMT. Japan does not
+use daylight saving time, so the only time zone is Japan Standard Time
+(JST). In JST ``tp_gmt_offset`` is 32400 seconds, and ``tp_dst_offset``
+is 0.
diff --git a/docs/nspr/reference/prtimeparamfn.rst b/docs/nspr/reference/prtimeparamfn.rst
new file mode 100644
index 0000000000..1efd6a5895
--- /dev/null
+++ b/docs/nspr/reference/prtimeparamfn.rst
@@ -0,0 +1,24 @@
+PRTimeParamFn
+=============
+
+This type defines a callback function to calculate and return the time
+parameter offsets from a calendar time object in GMT.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtime.h>
+
+ typedef PRTimeParameters (PR_CALLBACK_DECL *PRTimeParamFn)
+ (const PRExplodedTime *gmt);
+
+
+Description
+-----------
+
+The type :ref:`PRTimeParamFn` represents a callback function that, when
+given a time instant in GMT, returns the time zone information (offset
+from GMT and DST offset) at that time instant.
diff --git a/docs/nspr/reference/pruint16.rst b/docs/nspr/reference/pruint16.rst
new file mode 100644
index 0000000000..f4e699caed
--- /dev/null
+++ b/docs/nspr/reference/pruint16.rst
@@ -0,0 +1,14 @@
+PRUint16
+========
+
+Guaranteed to be an unsigned 16-bit integer on all platforms.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtypes.h>
+
+ typedefdefinition PRUint16;
diff --git a/docs/nspr/reference/pruint32.rst b/docs/nspr/reference/pruint32.rst
new file mode 100644
index 0000000000..5ea00f9b54
--- /dev/null
+++ b/docs/nspr/reference/pruint32.rst
@@ -0,0 +1,22 @@
+PRUint32
+========
+
+Guaranteed to be an unsigned 32-bit integer on all platforms.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtypes.h>
+
+ typedefdefinition PRUint32;
+
+
+Description
+-----------
+
+May be defined as an unsigned ``int`` or an unsigned ``long``, depending
+on the platform. For syntax details for each platform, see
+`prtypes.h <https://dxr.mozilla.org/mozilla-central/source/nsprpub/pr/include/prtypes.h>`__.
diff --git a/docs/nspr/reference/pruint64.rst b/docs/nspr/reference/pruint64.rst
new file mode 100644
index 0000000000..0bc74917e5
--- /dev/null
+++ b/docs/nspr/reference/pruint64.rst
@@ -0,0 +1,22 @@
+PRUint64
+========
+
+Guaranteed to be an unsigned 64-bit integer on all platforms.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtypes.h>
+
+ typedef definition PRUint64;
+
+
+Description
+-----------
+
+May be defined in several different ways, depending on the platform. For
+syntax details for each platform, see
+`prtypes.h <https://dxr.mozilla.org/mozilla-central/source/nsprpub/pr/include/prtypes.h>`__.
diff --git a/docs/nspr/reference/pruint8.rst b/docs/nspr/reference/pruint8.rst
new file mode 100644
index 0000000000..2f4224d179
--- /dev/null
+++ b/docs/nspr/reference/pruint8.rst
@@ -0,0 +1,15 @@
+PRUint8
+=======
+
+Guaranteed to be an unsigned 8-bit integer on all platforms. There is no
+type equivalent to a plain ``char``.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtypes.h>
+
+ typedefdefinition PRUint8;
diff --git a/docs/nspr/reference/pruintn.rst b/docs/nspr/reference/pruintn.rst
new file mode 100644
index 0000000000..d3aa4a9bb8
--- /dev/null
+++ b/docs/nspr/reference/pruintn.rst
@@ -0,0 +1,17 @@
+PRUintn
+=======
+
+This (unsigned) type is one of the most appropriate for automatic
+variables. It is guaranteed to be at least 16 bits, though various
+architectures may define it to be wider (for example, 32 or even 64
+bits). This types is never valid for fields of a structure.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtypes.h>
+
+ typedef unsigned int PRUintn;
diff --git a/docs/nspr/reference/prunichar.rst b/docs/nspr/reference/prunichar.rst
new file mode 100644
index 0000000000..35ebf41562
--- /dev/null
+++ b/docs/nspr/reference/prunichar.rst
@@ -0,0 +1,18 @@
+PRUnichar
+=========
+
+An unsigned 16-bit type, like ``char`` in Java or the "characters" of a
+JavaScript string defined in
+`/mozilla/xpcom/base/nscore.h <http://hg.mozilla.org/mozilla-central/file/d35b4d003e9e/xpcom/base/nscore.h>`__.
+
+
+Syntax
+------
+
+.. code::
+
+ #if defined(NS_WIN32)
+ typedef wchar_t PRUnichar;
+ #else
+ typedef PRUInt16 PRUnichar;
+ #endif
diff --git a/docs/nspr/reference/pruptrdiff.rst b/docs/nspr/reference/pruptrdiff.rst
new file mode 100644
index 0000000000..f58f0a5eb8
--- /dev/null
+++ b/docs/nspr/reference/pruptrdiff.rst
@@ -0,0 +1,14 @@
+PRUptrdiff
+==========
+
+Unsigned pointer difference type.
+
+
+Syntax
+------
+
+.. code::
+
+ #include <prtypes.h>
+
+ typedef unsigned long PRUptrdiff;
diff --git a/docs/nspr/reference/random_number_generator.rst b/docs/nspr/reference/random_number_generator.rst
new file mode 100644
index 0000000000..0caaf3bbf8
--- /dev/null
+++ b/docs/nspr/reference/random_number_generator.rst
@@ -0,0 +1,12 @@
+Random Number Generator
+=======================
+
+This chapter describes the NSPR random number generator.
+
+.. _Random_Number_Generator_Function:
+
+Random Number Generator Function
+--------------------------------
+
+ - :ref:`PR_GetRandomNoise` - Produces a random value for use as a seed
+ value for another random number generator.
diff --git a/docs/nspr/reference/string_operations.rst b/docs/nspr/reference/string_operations.rst
new file mode 100644
index 0000000000..b160f093d2
--- /dev/null
+++ b/docs/nspr/reference/string_operations.rst
@@ -0,0 +1,11 @@
+This chapter describes some of the key NSPR functions for manipulating
+strings. Libraries built on top of NSPR, such as the Netscape security
+libraries, use these functions to manipulate strings. If you are copying
+or examining strings for use by such libraries or freeing strings that
+were allocated by such libraries, you must use these NSPR functions
+rather than the libc equivalents.
+
+ - :ref:`PL_strlen`
+ - :ref:`PL_strcpy`
+ - :ref:`PL_strdup`
+ - :ref:`PL_strfree`
diff --git a/docs/nspr/reference/thread_pools.rst b/docs/nspr/reference/thread_pools.rst
new file mode 100644
index 0000000000..75fc9f75a2
--- /dev/null
+++ b/docs/nspr/reference/thread_pools.rst
@@ -0,0 +1,41 @@
+This chapter describes the NSPR API Thread Pools.
+
+.. note::
+
+ **Note:** This API is a preliminary version in NSPR 4.0 and is
+ subject to change.
+
+Thread pools create and manage threads to provide support for scheduling
+work (jobs) onto one or more threads. NSPR's thread pool is modeled on
+the thread pools described by David R. Butenhof in\ *Programming with
+POSIX Threads* (Addison-Wesley, 1997).
+
+- `Thread Pool Types <#Thread_Pool_Types>`__
+- `Thread Pool Functions <#Thread_Pool_Functions>`__
+
+.. _Thread_Pool_Types:
+
+Thread Pool Types
+-----------------
+
+ - :ref:`PRJobIoDesc`
+ - :ref:`PRJobFn`
+ - :ref:`PRThreadPool`
+ - :ref:`PRJob`
+
+.. _Thread_Pool_Functions:
+
+Thread Pool Functions
+---------------------
+
+ - :ref:`PR_CreateThreadPool`
+ - :ref:`PR_QueueJob`
+ - :ref:`PR_QueueJob_Read`
+ - :ref:`PR_QueueJob_Write`
+ - :ref:`PR_QueueJob_Accept`
+ - :ref:`PR_QueueJob_Connect`
+ - :ref:`PR_QueueJob_Timer`
+ - :ref:`PR_CancelJob`
+ - :ref:`PR_JoinJob`
+ - :ref:`PR_ShutdownThreadPool`
+ - :ref:`PR_JoinThreadPool`
diff --git a/docs/nspr/reference/thread_synchronization_sample.rst b/docs/nspr/reference/thread_synchronization_sample.rst
new file mode 100644
index 0000000000..e1417b130c
--- /dev/null
+++ b/docs/nspr/reference/thread_synchronization_sample.rst
@@ -0,0 +1,2 @@
+This page has no content. Enrich Mozilla Developer Center by
+contributing.
diff --git a/docs/nspr/reference/threads.rst b/docs/nspr/reference/threads.rst
new file mode 100644
index 0000000000..fdcdcc271e
--- /dev/null
+++ b/docs/nspr/reference/threads.rst
@@ -0,0 +1,129 @@
+NSPR provides an execution environment that promotes the use of
+lightweight threads. Each thread is an execution entity that is
+scheduled independently from other threads in the same process. This
+chapter describes the basic NSPR threading API.
+
+- `Threading Types and Constants <#Threading_Types_and_Constants>`__
+- `Threading Functions <#Threading_Functions>`__
+
+A thread has a limited number of resources that it truly owns. These
+resources include a stack and the CPU registers (including PC). To an
+NSPR client, a thread is represented by a pointer to an opaque structure
+of type :ref:`PRThread`. A thread is created by an explicit client request
+and remains a valid, independent execution entity until it returns from
+its root function or the process abnormally terminates. Threads are
+critical resources and therefore require some management. To synchronize
+the termination of a thread, you can **join** it with another thread
+(see :ref:`PR_JoinThread`). Joining a thread provides definitive proof that
+the target thread has terminated and has finished with both the
+resources to which the thread has access and the resources of the thread
+itself.
+
+For an overview of the NSPR threading model and sample code that
+illustrates its use, see `Introduction to
+NSPR <Introduction_to_NSPR>`__.
+
+For API reference information related to thread synchronization, see
+`Locks <Locks>`__ and `Condition Variables <Condition_Variables>`__.
+
+.. _Threading_Types_and_Constants:
+
+Threading Types and Constants
+-----------------------------
+
+ - :ref:`PRThread`
+ - :ref:`PRThreadType`
+ - :ref:`PRThreadScope`
+ - :ref:`PRThreadState`
+ - :ref:`PRThreadPriority`
+ - :ref:`PRThreadPrivateDTOR`
+
+.. _Threading_Functions:
+
+Threading Functions
+-------------------
+
+Most of the functions described here accept a pointer to the thread as
+an argument. NSPR does not check for the validity of the thread. It is
+the caller's responsibility to ensure that the thread is valid. The
+effects of these functions on invalid threads are undefined.
+
+- `Creating, Joining, and Identifying
+ Threads <#Creating,_Joining,_and_Identifying_Threads>`__
+- `Controlling Thread Priorities <#Controlling_Thread_Priorities>`__
+- `Interrupting and Yielding <#Interrupting_and_Yielding>`__
+- `Setting Global Thread
+ Concurrency <#Setting_Global_Thread_Concurrency>`__
+- `Getting a Thread's Scope <#Getting_a_Thread's_Scope>`__
+
+.. _Creating.2C_Joining.2C_and_Identifying_Threads:
+
+Creating, Joining, and Identifying Threads
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ - :ref:`PR_CreateThread` creates a new thread.
+ - :ref:`PR_JoinThread` blocks the calling thread until a specified thread
+ terminates.
+ - :ref:`PR_GetCurrentThread` returns the current thread object for the
+ currently running code.
+ - :ref:`PR_AttachThread`` associates a :ref:`PRThread` object with an existing
+ native thread.
+ - :ref:`PR_DetachThread`` disassociates a :ref:`PRThread` object from a native
+ thread.
+
+.. _Controlling_Thread_Priorities:
+
+Controlling Thread Priorities
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+For an overview of the way NSPR controls thread priorities, see `Setting
+Thread Priorities <Introduction_to_NSPR#Setting_Thread_Priorities.>`__.
+
+You set a thread's NSPR priority when you create it with
+:ref:`PR_CreateThread`. After a thread has been created, you can get and
+set its priority with these functions:
+
+ - :ref:`PR_GetThreadPriority`
+ - :ref:`PR_SetThreadPriority`
+
+.. _Controlling_Per-Thread_Private_Data:
+
+Controlling Per-Thread Private Data
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can use these functions to associate private data with each of the
+threads in a process:
+
+ - :ref:`PR_NewThreadPrivateIndex` allocates a unique index. If the call is
+ successful, every thread in the same process is capable of
+ associating private data with the new index.
+ - :ref:`PR_SetThreadPrivate` associates private thread data with an index.
+ - :ref:`PR_GetThreadPrivate` retrieves data associated with an index.
+
+.. _Interrupting_and_Yielding:
+
+Interrupting and Yielding
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ - :ref:`PR_Interrupt` requests an interrupt of another thread. Once the
+ target thread has been notified of the request, the request stays
+ with the thread until the notification either has been delivered
+ exactly once or is cleared.
+ - :ref:`PR_ClearInterrupt` clears a previous interrupt request.
+ - :ref:`PR_Sleep` causes a thread to yield to other threads for a
+ specified number of ticks.
+
+.. _Setting_Global_Thread_Concurrency:
+
+Setting Global Thread Concurrency
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ - :ref:`PR_SetConcurrency` sets the number of global threads used by NSPR
+ to create local threads.
+
+.. _Getting_a_Thread.27s_Scope:
+
+Getting a Thread's Scope
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+ - :ref:`PR_GetThreadScope` gets the scoping of the current thread.
diff --git a/docs/nspr/running_nspr_tests.rst b/docs/nspr/running_nspr_tests.rst
new file mode 100644
index 0000000000..d877aecf2d
--- /dev/null
+++ b/docs/nspr/running_nspr_tests.rst
@@ -0,0 +1,95 @@
+Running NSPR tests
+==================
+
+NSPR has a test suite in the ``mozilla/nsprpub/pr/tests`` directory.
+
+By default, we don't build the test programs. Running ``gmake`` in the
+top-level directory (``mozilla/nsprpub``) only builds the NSPR
+libraries. To build the test programs, you need to change directory to
+``mozilla/nsprpub/pr/tests`` and run ``gmake``. Refer to :ref:`NSPR build
+instructions` for details.
+
+To run the test suite, run the shell script
+``mozilla/nsprpub/pr/tests/runtests.sh`` in the directory where the test
+program binaries reside, for example,
+
+.. code::
+
+ cvs -q co -r NSPR_4_6_6_RTM mozilla/nsprpub
+ mkdir linux.debug
+ cd linux.debug
+ ../mozilla/nsprpub/configure
+ gmake
+ cd pr/tests
+ gmake
+ ../../../mozilla/nsprpub/pr/tests/runtests.sh
+
+The output of the test suite looks like this:
+
+.. code::
+
+ NSPR Test Results - tests
+
+ BEGIN Mon Mar 12 11:44:41 PDT 2007
+ NSPR_TEST_LOGFILE /dev/null
+
+ Test Result
+
+ accept Passed
+ acceptread Passed
+ acceptreademu Passed
+ affinity Passed
+ alarm Passed
+ anonfm Passed
+ atomic Passed
+ attach Passed
+ bigfile Passed
+ cleanup Passed
+ cltsrv Passed
+ concur Passed
+ cvar Passed
+ cvar2 Passed
+ ...
+ sprintf FAILED
+ ...
+ timetest Passed
+ tpd Passed
+ udpsrv Passed
+ vercheck Passed
+ version Passed
+ writev Passed
+ xnotify Passed
+ zerolen Passed
+ END Mon Mar 12 11:55:47 PDT 2007
+
+.. _How_to_determine_if_the_test_suite_passed:
+
+How to determine if the test suite passed
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If all the tests reported **Passed** as the results, the test suite
+passed.
+
+What if some of the tests crashed or reported **FAILED** as the results?
+It doesn't necessarily mean the test suite failed because some of the
+test programs are known to fail. Until the test failures are fixed, you
+should run NSPR tests against **a known good version of NSPR on the same
+platform**, and save the test results as the benchmark. Then you can
+detect regressions of the new version by comparing its test results with
+the benchmark.
+
+.. _Known_issues:
+
+Known issues
+~~~~~~~~~~~~
+
+Other issues with the NSPR test suite are:
+
+#. Some of the test programs test the accuracy of the timeout of NSPR
+ functions. Since none of our operating systems is a real-time OS,
+ such test programs may fail when the test machine is heavily loaded.
+#. Some tests, such as ``pipepong`` and ``sockpong``, should not be run
+ directly. They will be invoked by their companion test programs
+ (e.g., ``pipeping`` and ``sockping``). This is not an issue if you
+ run ``runtests.sh`` because ``runtests.sh`` knows not to run such
+ test programs directly.
diff --git a/docs/nspr/using_io_timeouts_and_interrupts_on_nt.rst b/docs/nspr/using_io_timeouts_and_interrupts_on_nt.rst
new file mode 100644
index 0000000000..9aaca06a1b
--- /dev/null
+++ b/docs/nspr/using_io_timeouts_and_interrupts_on_nt.rst
@@ -0,0 +1,131 @@
+This technical memo is a cautionary note on using NetScape Portable
+Runtime's (NSPR) IO timeout and interrupt on Windows NT 3.51 and 4.0.
+Due to a limitation of the present implementation of NSPR IO on NT,
+programs must follow the following guideline:
+
+If a thread calls an NSPR IO function on a file descriptor and the IO
+function fails with <tt>PR_IO_TIMEOUT_ERROR</tt> or
+<tt>PR_PENDING_INTERRUPT_ERROR</tt>, the file descriptor must be closed
+before the thread exits.
+
+In this memo we explain the problem this guideline is trying to work
+around and discuss its limitations.
+
+.. _NSPR_IO_on_NT:
+
+NSPR IO on NT
+-------------
+
+The IO model of NSPR 2.0 is synchronous and blocking. A thread calling
+an IO function is blocked until the IO operation finishes, either due to
+a successful IO completion or an error. If the IO operation cannot
+complete before the specified timeout, the IO function returns with
+<tt>PR_IO_TIMEOUT_ERROR</tt>. If the thread gets interrupted by another
+thread's <tt>PR_Interrupt()</tt> call, the IO function returns with
+<tt>PR_PENDING_INTERRUPT_ERROR</tt>.
+
+On Windows NT, NSPR IO is implemented using NT's *overlapped* (also
+called *asynchronous*) *IO*. When a thread calls an IO function, the
+thread issues an overlapped IO request using the overlapped buffer in
+its <tt>PRThread</tt> structure. Then the thread is put to sleep. In the
+meantime, there are dedicated internal threads (called the *idle
+threads*) monitoring the IO completion port for completed IO requests.
+If a completed IO request appears at the IO completion port, an idle
+thread fetches it and wakes up the thread that issued the IO request
+earlier. This is the normal way the thread is awakened.
+
+.. _IO_Timeout_and_Interrupt:
+
+IO Timeout and Interrupt
+------------------------
+
+However, NSPR may wake up the thread in two other situations:
+
+- if the overlapped IO request is not completed before the specified
+ timeout. (Note that we can't specify timeout on overlapped IO
+ requests, so the timeouts are all handled at the NSPR level.) In this
+ case, the error is <tt>PR_IO_TIMEOUT_ERROR</tt>.
+- if the thread gets interrupted by another thread's
+ <tt>PR_Interrupt()</tt> call. In this case, the error is
+ <tt>PR_PENDING_INTERRUPT_ERROR</tt>.
+
+These two errors are generated by the NSPR layer, so the OS is oblivious
+of what is going on and the overlapped IO request is still in progress.
+The OS still has a pointer to the overlapped buffer in the thread's
+<tt>PRThread</tt> structure. If the thread subsequently exists and its
+<tt>PRThread</tt> structure gets deleted, the pointer to the overlapped
+buffer will be pointing to freed memory. This is problematic.
+
+.. _Canceling_Overlapped_IO_by_Closing_the_File_Descriptor:
+
+Canceling Overlapped IO by Closing the File Descriptor
+------------------------------------------------------
+
+Therefore, we need to cancel the outstanding overlapped IO request
+before the thread exits. NT's <tt>CancelIo()</tt> function would be
+ideal for this purpose. Unfortunately, <tt>CancelIo()</tt> is not
+available on NT 3.51. So we can't go this route as long as we are
+supporting NT 3.51. The only reliable way to cancel outstanding
+overlapped IO request that works on both NT 3.51 and 4.0 is to close the
+file descriptor, hence the rule of thumb stated at the beginning of this
+memo.
+
+.. _Limitations:
+
+Limitations
+-----------
+
+This seemingly harsh way to force the completion of outstanding
+overlapped IO request has the following limitations:
+
+- It is difficult for threads to shared a file descriptor. For example,
+ suppose thread A and thread B call <tt>PR_Accept()</tt> on the same
+ socket, and they time out at the same time. Following the rule of
+ thumb, both threads would close the socket. The first
+ <tt>PR_Close()</tt> would succeed, but the second <tt>PR_Close()</tt>
+ would be freeing freed memory. A solution that may work is to use a
+ lock to ensure only one thread can be using that socket at all times.
+- Once there is a timeout or interrupt error, the file descriptor is no
+ longer usable. Suppose the file descriptor is intended to be used for
+ the life time of the process, for example, the logging file, this is
+ really not acceptable. A possible solution is to add a
+ <tt>PR_DisableInterrupt()</tt> function to turn off interrupts when
+ accessing such file descriptors.
+
+..
+
+ *A related known bug is that timeout and interrupt don't work for
+ <tt>PR_Connect()</tt> on NT. This bug is due to a different
+ limitation in our NT implementation.*
+
+.. _Conclusions:
+
+Conclusions
+-----------
+
+As long as we need to support NT 3.51, we need to program under the
+guideline that after an IO timeout or interrupt error, the thread must
+make sure the file descriptor is closed before it exits. Programs should
+also take care in sharing file descriptors and using IO timeout or
+interrupt on files that need to stay open throughout the process.
+
+When we stop supporting NT 3.51, we can look into using NT 4's
+<tt>CancelIo()</tt> function to cancel outstanding overlapped IO
+requests when we get IO timeout or interrupt errors. If
+<tt>CancelIo()</tt> really works as advertised, that should
+fundamentally solve this problem.
+
+If these limitations with IO timeout and interrupt are not acceptable to
+the needs of your programs, you can consider using the Win95 version of
+NSPR. The Win95 version runs without trouble on NT, but you would lose
+the better performance provided by NT fibers and asynchronous IO.
+
+|
+
+.. _Original_Document_Information:
+
+Original Document Information
+-----------------------------
+
+- Author: larryh@netscape.com
+- Last Updated Date: December 1, 2004
diff --git a/docs/performance/Benchmarking.md b/docs/performance/Benchmarking.md
new file mode 100644
index 0000000000..3b429463f7
--- /dev/null
+++ b/docs/performance/Benchmarking.md
@@ -0,0 +1,98 @@
+# Benchmarking
+
+## Debug Builds
+
+Debug builds (\--enable-debug) and non-optimized builds
+(\--disable-optimize) are *much* slower. Any performance metrics
+gathered by such builds are largely unrelated to what would be found in
+a release browser.
+
+## Rust optimization level
+
+Local optimized builds are [compiled with rust optimization level 1 by
+default](https://groups.google.com/forum/#!topic/mozilla.dev.platform/pN9O5EB_1q4),
+unlike Nightly builds, which use rust optimization level 2. This setting
+reduces build times significantly but comes with a serious hit to
+runtime performance for any rust code ([for example stylo and
+webrender](https://groups.google.com/d/msg/mozilla.dev.platform/pN9O5EB_1q4/ooXNuqMECAAJ)).
+Add the following to your [mozconfig] in order to build with level 2:
+
+```
+ac_add_options RUSTC_OPT_LEVEL=2
+```
+
+## Profile Guided Optimization (PGO)
+[Profile Guided
+Optimization](/build/buildsystem/pgo.rst#profile-guided-optimization) is
+disabled by default and may improve runtime by up to 20%. However, it takes a
+long time to build. To enable, add the following to your [mozconfig]:
+```
+ac_add_options MOZ_PGO=1
+```
+
+## GC Poisoning
+
+Many Firefox builds have a diagnostic tool that causes crashes to happen
+sooner and produce much more actionable information, but also slow down
+regular usage substantially. In particular, \"GC poisoning\" is used in
+all debug builds, and in optimized Nightly builds (but not opt Developer
+Edition or Beta builds). The poisoning can be disabled by setting the
+environment variable
+
+```
+ JSGC_DISABLE_POISONING=1
+```
+
+before starting the browser.
+
+## Async Stacks
+
+Async stacks no longer impact performance since **Firefox 78**, as
+{{bug(1601179)}} limits async stack capturing to when DevTools is
+opened.
+
+Another option that is on by default in non-release builds is the
+preference javascript.options.asyncstack, which provides better
+debugging information to developers. Set it to false to match a release
+build. (This may be disabled for many situations in the future. See
+{{bug(1280819)}}.
+
+## Accelerated Graphics
+
+Especially on Linux, accelerated graphics can sometimes lead to severe
+performance problems even if things look ok visually. Normally you would
+want to leave acceleration enabled while profiling, but on Linux you may
+wish to disable accelerated graphics (Preferences -\> Advanced -\>
+General -\> Use hardware acceleration when available).
+
+## Flash Plugin
+
+If you are profiling real websites, you should disable the Adobe Flash
+plugin so you are testing Firefox code and not Flash jank problems. In
+about:addons \> Plugins, set Shockwave Flash to \"Never Activate\".
+
+## Timer Precision
+
+Firefox reduces the precision of the Performance APIs and other clock
+and timer APIs accessible to Web Content. They are currently reduce to a
+multiple of 2ms; which is controlled by the privacy.reduceTimerPrecision
+about:config flag.
+
+The exact value of the precision is controlled by the
+privacy.resistFingerprinting.reduceTimerPrecision.microseconds
+about:config flag.
+
+## Profiling tools
+
+Currently the Gecko Profiler has limitations in the UI for inverted call
+stack top function analysis which is very useful for finding heavy
+functions that call into a whole bunch of code. Currently such functions
+may be easy to miss looking at a profile, so feel free to *also* use
+your favorite native profiler. It also lacks features such as
+instruction level profiling which can be helpful in low level profiling,
+or finding the hot loop inside a large function, etc. Some example tools
+include Instruments on OSX (part of XCode), [RotateRight
+Zoom](http://www.rotateright.com/) on Linux (uses perf underneath), and
+Intel VTune on Windows or Linux.
+
+[mozconfig]: /setup/configuring_build_options.rst#using-a-mozconfig-configuration-file
diff --git a/docs/performance/GPU_performance.md b/docs/performance/GPU_performance.md
new file mode 100644
index 0000000000..25085f41c3
--- /dev/null
+++ b/docs/performance/GPU_performance.md
@@ -0,0 +1,42 @@
+# GPU Performance
+
+Doing performance work with GPUs is harder than with CPUs because of the
+asynchronous and massively parallel architecture.
+
+## Tools
+
+[PIX](https://devblogs.microsoft.com/pix/introduction/) - Can do
+timing of Direct3D calls. Works reasonably well with Firefox.
+
+NVIDIA PerfHUD - Last I checked required a special build to be used.
+
+NVIDIA Parallel Nsight - Haven\'t tried.
+
+AMD GPU ShaderAnalyzer - Will compile a shader and show the machine code
+and give static pipeline estimations. Not that useful for Firefox
+because all of our shaders are pretty simple.
+
+AMD GPU PerfStudio - I had trouble getting this to work, and can\'t
+remember whether I actually did or not.
+
+[Intel Graphics Performance Analyzers](http://software.intel.com/en-us/articles/intel-gpa/ "http://software.intel.com/en-us/articles/intel-gpa/")
+- Haven\'t tried.
+
+[APITrace](https://github.com/apitrace/apitrace "https://github.com/apitrace/apitrace")
+- Open source, works OK.
+
+[PVRTrace](http://www.imgtec.com/powervr/insider/pvrtrace.asp "http://www.imgtec.com/powervr/insider/pvrtrace.asp")
+- Doesn\'t seem to emit traces on android/Nexus S. Looks like it\'s
+designed for X11-based linux-ARM devices, OMAP3 is mentioned a lot in
+the docs \...
+
+## Guides
+
+[Accurately Profiling Direct3D API Calls (Direct3D
+9)](http://msdn.microsoft.com/en-us/library/bb172234%28v=vs.85%29.aspx "http://msdn.microsoft.com/en-us/library/bb172234(v=vs.85).aspx")
+Suggests avoiding normal profilers like xperf and instead measuring the
+time to flush the command buffer.
+
+[OS X - Best Practices for Working with Texture
+Data](http://developer.apple.com/library/mac/#documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_texturedata/opengl_texturedata.html "http://developer.apple.com/library/mac/#documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_texturedata/opengl_texturedata.html")
+- Sort of old, but still useful.
diff --git a/docs/performance/activity_monitor_and_top.md b/docs/performance/activity_monitor_and_top.md
new file mode 100644
index 0000000000..4e687c4dfe
--- /dev/null
+++ b/docs/performance/activity_monitor_and_top.md
@@ -0,0 +1,165 @@
+# Activity Monitor, Battery Status Menu and top
+
+This article describes the Activity Monitor, Battery Status Menu, and
+`top` --- three related tools available on Mac OS X.
+
+**Note**: The [power profiling overview](power_profiling_overview.md) is
+worth reading at this point if you haven't already. It may make parts
+of this document easier to understand.
+
+## Activity Monitor
+
+This is a [built-in OS X tool](https://support.apple.com/en-au/HT201464)
+that shows real-time process measurements. It is well-known and its
+"Energy Impact" measure is likely to be consulted by users to compare
+the power consumption of different programs. ([Apple support
+documentation](https://support.apple.com/en-au/HT202776) specifically
+recommends it for troubleshooting battery life problems.)
+***Unfortunately "Energy Impact" is not a good measure for either
+users or software developers and it should be avoided.*** Activity
+Monitor can still be useful, however.
+
+### Power-related measurements
+
+Activity Monitor has several tabs. They can all be customized to show
+any of the available measurements (by right-clicking on the column
+strip) but only the "Energy" tab groups child processes with parent
+processes, which is useful, so it's the best one to use. The following
+screenshot shows a customized "Energy" tab.
+
+![](img/ActMon-Energy.png)
+
+The power-related columns are as follows.
+
+- **Energy Impact** / **Avg Energy Impact**: See the separate section
+ below.
+- **% CPU**: CPU usage percentage. This can exceed 100% if multiple
+ cores are being used.
+- **Idle wake Ups**:
+ - In Mac OS 10.9 this measured "package idle exit" wakeups. This
+ is the same value as
+ [powermetrics](./powermetrics.md)'
+ "Pkg idle" measurement (i.e.
+ `task_power_info::task_platform_idle_wakeups` obtained from the
+ `task_info` function.)
+ - In Mac OS 10.10 it appears to have been changed to measure
+ interrupt-level wakeups (a superset of idle wakeups), which are
+ less interesting. This is the same value as
+ [powermetrics](./powermetrics.md)'
+ "Intr" measurement (i.e.
+ `task_power_info::task_interrupt_wakeups` obtained from the
+ `task_info` function.)
+- **Requires High Perf GPU**: Many Macs have two GPUs: a low-power,
+ low-performance integrated GPU, and a high-power, high-performance
+ external GPU. Using the high-performance GPU can greatly increase
+ power consumption, and should be avoided whenever possible. This
+ column indicates which GPU is being used.
+
+Activity Monitor can be useful for cursory measurements, but for more
+precise and detailed measurements other tools such as
+[powermetrics](./powermetrics.md) are better.
+
+### What does "Energy Impact" measure?
+
+"Energy Impact" is a hybrid proxy measure of power consumption.
+[Careful
+investigation](https://blog.mozilla.org/nnethercote/2015/08/26/what-does-the-os-x-activity-monitors-energy-impact-actually-measure/)
+indicates that on Mac OS 10.10 and 10.11 it is computed with a formula
+that is machine model-specific, and includes the following factors: CPU
+usage, wakeup frequency, [quality of service
+class](https://developer.apple.com/library/prerelease/mac/documentation/Performance/Conceptual/power_efficiency_guidelines_osx/PrioritizeWorkAtTheTaskLevel.html)
+usage, and disk, GPU, and network activity. The weightings of each
+factor can be found in one of the the files in
+`/usr/share/pmenergy/Mac-<id>.plist`, where `<id>` can be determined
+with the following command.
+
+ ioreg -l | grep board-id
+
+In contrast, on Mac OS 10.9 it is computed via a simpler machine
+model-independent formula that only factors in CPU usage and wakeup
+frequency.
+
+In both cases "Energy Impact" often correlates poorly with actual
+power consumption and should be avoided in favour of direct measurements
+that have clear physical meanings.
+
+### What does "Average Energy Impact" measure?
+
+When the Energy tab of Activity Monitor is first opened, the "Average
+Energy Impact" column is empty and the title bar says "Activity
+Monitor (Processing\...)". After 5--10 seconds, the "Average Energy
+Impact" column is populated with values and the title bar changes to
+"Activity Monitor (Applications in last 8 hours)". If you have `top`
+open during those 5--10 seconds you'll see that `systemstats` is
+running and using a lot of CPU, and so presumably the measurements are
+obtained from it.
+
+`systemstats` is a program that runs continuously and periodically
+measures, among other things, CPU usage and idle wakeups for each
+running process. Tests indicate that it is almost certainly using the
+same "Energy Impact" formula to compute the "Average Energy Impact",
+using measurements from the past 8 hours of wake time (i.e. if a laptop
+is closed for several hours and then reopened, those hours are not
+included in the calculation.)
+
+## Battery status menu
+
+When you click on the battery icon in the OS X menu bar you get a
+drop-down menu that includes a list of "Apps Using Significant Energy".
+This is crude but prominent, and therefore worth understanding --- even
+though it's not much use for profiling applications.
+
+![Screenshot of the OS X battery statusmenu](img/battery-status-menu.png)
+
+When you open this menu for the first time in a while it says
+"Collecting Power Usage Information" for a few seconds, and if you have
+`top` open during that time you'll see that, once again, `systemstats`
+is running and using a lot of CPU. Furthermore, if you click on an
+application name in the menu Activity Monitor will be opened and that
+application's entry will be highlighted. Based on these facts it seems
+reasonable to assume that "Energy Impact" is again being used to
+determine which applications are "using significant energy".
+
+Testing shows that once an energy-intensive application is started it
+takes less than a minute for it to show up in the battery status menu.
+And once the application stops using high amounts of energy it takes a
+few minutes to disappear. The exception is when the application is
+closed, in which case it disappears immediately. And it appears that a
+program with an "Energy Impact" of roughly 20 or more will eventually
+show up as significant, and programs that have much higher "Energy
+Impact" values tend to show up more quickly.
+
+All of these battery status menu observations are difficult to make
+reliably and so should be treated with caution. It is clear, however,
+that the window used by the battery status menu is measured in seconds
+or minutes, which is much less than the 8 hour window used for "Average
+Energy Impact" in Activity Monitor.
+
+## `top`
+
+`top` is similar to Activity Monitor, but is a command-line utility. To
+see power-related measurements, invoke it as follows.
+
+```
+top -stats pid,command,cpu,idlew,power -o power -d
+```
+
+**Note**: `-a` and `-e` can be used instead of `-d` to get different
+counting modes. See the man page for details.
+
+It will show periodically-updating data like the following.
+
+ PID COMMAND %CPU IDLEW POWER
+ 50300 firefox 12.9 278 26.6
+ 76256 plugin-container 3.4 159 11.3
+ 151 coreaudiod 0.9 68 4.3
+ 76505 top 1.5 1 1.6
+ 76354 Activity Monitor 1.0 0 1.0
+
+- The **PID**, **COMMAND** and **%CPU** columns are self-explanatory.
+- The **IDLEW** column is the number of "package idle exit" wakeups.
+- The **POWER** column's value is computed by the same formula as the
+ one used for "Energy Impact" by Activity Monitor in Mac OS 10.9,
+ and should also be avoided.
+
+`top` is unlikely to be much use for power profiling.
diff --git a/docs/performance/automated_performance_testing_and_sheriffing.md b/docs/performance/automated_performance_testing_and_sheriffing.md
new file mode 100644
index 0000000000..02469c65de
--- /dev/null
+++ b/docs/performance/automated_performance_testing_and_sheriffing.md
@@ -0,0 +1,24 @@
+# Automated performance testing and sheriffing
+
+We have several test harnesses that test Firefox for various performance
+characteristics (page load time, startup time, etc.). We also generate
+some metrics as part of the build process (like installer size) that are
+interesting to track over time. Currently we aggregate this information
+in the [Perfherder web
+application](https://wiki.mozilla.org/Auto-tools/Projects/Perfherder)
+where performance sheriffs watch for significant regressions, filing
+bugs as appropriate.
+
+Current list of automated systems we are tracking (at least to some
+degree):
+
+- [Talos](https://wiki.mozilla.org/TestEngineering/Performance/Talos): The main
+ performance system, run on virtually every check-in to an
+ integration branch
+- [build_metrics](/setup/configuring_build_options.rst):
+ A grab bag of performance metrics generated by the build system
+- [AreWeFastYet](https://arewefastyet.com/): A generic JavaScript and
+ Web benchmarking system
+ tool
+- [Platform microbenchmarks](platform_microbenchmarks/platform_microbenchmarks.md)
+- [Build Metrics](build_metrics/build_metrics.md)
diff --git a/docs/performance/bestpractices.md b/docs/performance/bestpractices.md
new file mode 100644
index 0000000000..ee176de294
--- /dev/null
+++ b/docs/performance/bestpractices.md
@@ -0,0 +1,578 @@
+# Performance best practices for Firefox front-end engineers
+
+This guide will help Firefox developers working on front-end code
+produce code which is as performant as possible—not just on its own, but
+in terms of its impact on other parts of Firefox. Always keep in mind
+the side effects your changes may have, from blocking other tasks, to
+interfering with other user interface elements.
+
+## Avoid the main thread where possible
+
+The main thread is where we process user events and do painting. It's
+also important to note that most of our JavaScript runs on the main
+thread, so it's easy for script to cause delays in event processing or
+painting. That means that the more code we can get off of the main
+thread, the more that thread can respond to user events, paint, and
+generally be responsive to the user.
+
+You might want to consider using a Worker if you need to do some
+computation that can be done off of the main thread. If you need more
+elevated privileges than a standard worker allows, consider using a
+ChromeWorker, which is a Firefox-only API which lets you create
+workers with more elevated privileges.
+
+## Use requestIdleCallback()
+
+If you simply cannot avoid doing some kind of long job on the main
+thread, try to break it up into smaller pieces that you can run when the
+browser has a free moment to spare, and the user isn't doing anything.
+You can do that using **requestIdleCallback()** and the [Cooperative
+Scheduling of Background Tasks API](https://developer.mozilla.org/en-US/docs/Web/API/Background_Tasks_API),
+and doing it only when we have a free second where presumably the user
+isn’t doing something.
+
+See also the blog post [Collective scheduling with requestIdleCallback](https://hacks.mozilla.org/2016/11/cooperative-scheduling-with-requestidlecallback/).
+
+As of [bug 1353206](https://bugzilla.mozilla.org/show_bug.cgi?id=1353206),
+you can also schedule idle events in non-DOM contexts by using
+**Services.tm.idleDispatchToMainThread**. See the
+**nsIThreadManager.idl** file for more details.
+
+## Hide your panels
+
+If you’re adding a new XUL *\<xul:popup\>* or *\<xul:panel\>* to a
+document, set the **hidden** attribute to **true** by default. By doing
+so, you cause the binding applied on demand rather than at load time,
+which makes initial construction of the XUL document faster.
+
+## Get familiar with the pipeline that gets pixels to the screen
+
+Learn how pixels you draw make their way to the screen. Knowing the path
+they will take through the various layers of the browser engine will
+help you optimize your code to avoid pitfalls.
+
+The rendering process goes through the following steps:
+
+![This is the pipeline that a browser uses to get pixels to the screen](img/rendering.png)
+
+The above image is used under [Creative Commons Attribution 3.0](https://creativecommons.org/licenses/by/3.0/),
+courtesy of [this page](https://developers.google.com/web/fundamentals/performance/rendering/avoid-large-complex-layouts-and-layout-thrashing)
+from our friends at Google, which itself is well worth the read.
+
+For a very down-to-earth explanation of the Style, Layout, Paint and
+Composite steps of the pipeline, [this Hacks blog post](https://hacks.mozilla.org/2017/08/inside-a-super-fast-css-engine-quantum-css-aka-stylo/)
+does a great job of explaining it.
+
+To achieve a 60 FPS frame rate, all of the above has to happen in 16
+milliseconds or less, every frame.
+
+Note that **requestAnimationFrame()** lets you queue up JavaScript to
+**run right before the style flush occurs**. This allows you to put all
+of your DOM writes (most importantly, anything that could change the
+size or position of things in the DOM) just before the style and layout
+steps of the pipeline, combining all the style and layout calculations
+into a single batch so it all happens once, in a single frame tick,
+instead of across multiple frames.
+
+See [Detecting and avoiding synchronous reflow](#detecting-and-avoiding-synchronous-reflow)
+below for more information.
+
+This also means that *requestAnimationFrame()* is **not a good place**
+to put queries for layout or style information.
+
+## Detecting and avoiding synchronous style flushes
+
+### What are style flushes?
+When CSS is applied to a document (HTML or XUL, it doesn’t matter), the
+browser does calculations to figure out which CSS styles will apply to
+each element. This happens the first time the page loads and the CSS is
+initially applied, but can happen again if JavaScript modifies the DOM.
+
+JavaScript code might, for example, change DOM node attributes (either
+directly or by adding or removing classes from elements), and can also
+add, remove, or delete DOM nodes. Because styles are normally scoped to
+the entire document, the cost of doing these style calculations is
+proportional to the number of DOM nodes in the document (and the number
+of styles being applied).
+
+It is expected that over time, script will update the DOM, requiring us
+to recalculate styles. Normally, the changes to the DOM just result in
+the standard style calculation occurring immediately after the
+JavaScript has finished running during the 16ms window, inside the
+"Style" step. That's the ideal scenario.
+
+However, it's possible for script to do things that force multiple style
+calculations (or **style flushes**) to occur synchronously during the
+JavaScript part of the 16 ms window. The more of them there are, the
+more likely they'll exceed the 16ms frame budget. If that happens, some
+of them will be postponed until the next frame (or possibly multiple
+frames, if necessary), this skipping of frames is called **jank**.
+
+Generally speaking, you force a synchronous style flush any time you
+query for style information after the DOM has changed within the same
+frame tick. Depending on whether or not [the style information you’re
+asking for has something to do with size or position](https://gist.github.com/paulirish/5d52fb081b3570c81e3a)
+you may also cause a layout recalculation (also referred to as *layout
+flush* or *reflow*), which is also an expensive step see [Detecting
+and avoiding synchronous reflow](#detecting-and-avoiding-synchronous-reflow) below.
+
+To avoid this: avoid reading style information if you can. If you *must*
+read style information, do so at the very beginning of the frame, before
+any changes have been made to the DOM since the last time a style flush
+occurred.
+
+Historically, there hasn't been an easy way of doing this - however,
+[bug 1434376](https://bugzilla.mozilla.org/show_bug.cgi?id=1434376)
+has landed some ChromeOnly helpers to the window binding to
+make this simpler.
+
+If you want to queue up some JavaScript to run after the next *natural*
+style and layout flush, try:
+
+
+ // Suppose we want to get the computed "display" style of some node without
+ // causing a style flush. We could do it this way:
+ async function nodeIsDisplayNone(node) {
+ let display = await window.promiseDocumentFlushed(() => {
+ // Do _not_ under any circumstances write to the DOM in one of these
+ // callbacks!
+ return window.getComputedStyle(node).display;
+ });
+
+ return display == "none";
+ }
+
+See [Detecting and avoiding synchronous reflow](#detecting-and-avoiding-synchronous-reflow)
+for a more advanced example of getting layout information, and then
+setting it safely, without causing flushes.
+
+bestpractices.html#detecting-and-avoiding-synchronous-reflow
+
+
+*promiseDocumentFlushed* is only available to privileged script, and
+should be called on the inner window of a top-level frame. Calling it on
+the outer window of a subframe is not supported, and calling it from
+within the inner window of a subframe might cause the callback to fire
+even though a style and layout flush will still be required. These
+gotchas should be fixed by
+[bug 1441173](https://bugzilla.mozilla.org/show_bug.cgi?id=1441173).
+
+For now, it is up to you as the consumer of this API to not accidentally
+write to the DOM within the *promiseDocumentFlushed* callback. Doing
+so might cause flushes to occur for other *promiseDocumentFlushed*
+callbacks that are scheduled to fire in the same tick of the refresh
+driver.
+[bug 1441168](https://bugzilla.mozilla.org/show_bug.cgi?id=1441168)
+tracks work to make it impossible to modify the DOM within a
+*promiseDocumentFlushed* callback.
+
+### Writing tests to ensure you don’t add more synchronous style flushes
+
+Unlike reflow, there isn’t a “observer” mechanism for style
+recalculations. However, as of Firefox 49, the
+*nsIDOMWindowUtils.elementsRestyled* attribute records a count of how
+many style calculations have occurred for a particular DOM window.
+
+It should be possible to write a test that gets the
+*nsIDOMWindowUtils* for a browser window, records the number of
+styleFlushes, then **synchronously calls the function** that you want to
+test, and immediately after checks the styleFlushes attribute again. If
+the value went up, your code caused synchronous style flushes to occur.
+
+Note that your test and function *must be called synchronously* in order
+for this test to be accurate. If you ever go back to the event loop (by
+yielding, waiting for an event, etc), style flushes unrelated to your
+code are likely to run, and your test will give you a false positive.
+
+## Detecting and avoiding synchronous reflow
+
+This is also sometimes called “sync layout”, "sync layout flushes" or
+“sync layout calculations”
+
+*Sync reflow* is a term bandied about a lot, and has negative
+connotations. It's not unusual for an engineer to have only the vaguest
+sense of what it is—and to only know to avoid it. This section will
+attempt to demystify things.
+
+The first time a document (XUL or HTML) loads, we parse the markup, and
+then apply styles. Once the styles have been calculated, we then need to
+calculate where things are going to be placed on the page. This layout
+step can be seen in the “16ms” pipeline graphic above, and occurs just
+before we paint things to be composited for the user to see.
+
+It is expected that over time, script will update the DOM, requiring us
+to recalculate styles, and then update layout. Normally, however, the
+changes to the DOM just result in the standard style calculation that
+occurs immediately after the JavaScript has finished running during the
+16ms window.
+
+### Interruptible reflow
+
+Since [the early days](https://bugzilla.mozilla.org/show_bug.cgi?id=67752), Gecko has
+had the notion of interruptible reflow. This is a special type of
+**content-only** reflow that checks at particular points whether or not
+it should be interrupted (usually to respond to user events).
+
+Because **interruptible reflows can only be interrupted when laying out
+content, and not chrome UI**, the rest of this section is offered only
+as context.
+
+When an interruptible reflow is interrupted, what really happens is that
+certain layout operations can be skipped in order to paint and process
+user events sooner.
+
+When an interruptible reflow is interrupted, the best-case scenario is
+that all layout is skipped, and the layout operation ends.
+
+The worst-case scenario is that none of the layout can be skipped
+despite being interrupted, and the entire layout calculation occurs.
+
+Reflows that are triggered "naturally" by the 16ms tick are all
+considered interruptible. Despite not actually being interuptible when
+laying out chrome UI, striving for interruptible layout is always good
+practice because uninterruptible layout has the potential to be much
+worse (see next section).
+
+**To repeat, only interruptible reflows in web content can be
+interrupted.**
+
+### Uninterruptible reflow
+
+Uninterruptible reflow is what we want to **avoid at all costs**.
+Uninterruptible reflow occurs when some DOM node’s styles have changed
+such that the size or position of one or more nodes in the document will
+need to be updated, and then **JavaScript asks for the size or position
+of anything**. Since everything is pending a reflow, the answer isn't
+available, so everything stalls until the reflow is complete and the
+script can be given an answer. Flushing layout also means that styles
+must be flushed to calculate the most up-to-date state of things, so
+it's a double-whammy.
+
+Here’s a simple example, cribbed from [this blog post by Paul
+Rouget](http://paulrouget.com/e/fxoshud):
+
+
+ div1.style.margin = "200px"; // Line 1
+ var height1 = div1.clientHeight; // Line 2
+ div2.classList.add("foobar"); // Line 3
+ var height2 = div2.clientHeight; // Line 4
+ doSomething(height1, height2); // Line 5
+
+At line 1, we’re setting some style information on a DOM node that’s
+going to result in a reflow - but (at just line 1) it’s okay, because
+that reflow will happen after the style calculation.
+
+Note line 2 though - we’re asking for the height of some DOM node. This
+means that Gecko needs to synchronously calculate layout (and styles)
+using an uninterruptible reflow in order to answer the question that
+JavaScript is asking (“What is the *clientHeight* of *div1*?”).
+
+It’s possible for our example to avoid this synchronous, uninterruptible
+reflow by moving lines 2 and 4 above line 1. Assuming there weren’t any
+style changes requiring size or position recalculation above line 1, the
+*clientHeight* information should be cached since the last reflow, and
+will not result in a new layout calculation.
+
+If you can avoid querying for the size or position of things in
+JavaScript, that’s the safest option—especially because it’s always
+possible that something earlier in this tick of JavaScript execution
+caused a style change in the DOM without you knowing it.
+
+Note that given the same changes to the DOM of a chrome UI document, a
+single synchronous uninterruptible reflow is no more computationally
+expensive than an interruptible reflow triggered by the 16ms tick. It
+is, however, advantageous to strive for reflow to only occur in the one
+place (the layout step of the 16ms tick) as opposed to multiple times
+during the 16ms tick (which has a higher probability of running through
+the 16ms budget).
+
+### How do I avoid triggering uninterruptible reflow?
+
+Here's a [list of things that JavaScript can ask for that can cause
+uninterruptible reflow](https://gist.github.com/paulirish/5d52fb081b3570c81e3a), to
+help you think about the problem. Note that some items in the list may
+be browser-specific or subject to change, and that an item not occurring
+explicitly in the list doesn't mean it doesn't cause reflow. For
+instance, at time of writing accessing *event.rangeOffset* [triggers
+reflow](https://searchfox.org/mozilla-central/rev/6bfadf95b4a6aaa8bb3b2a166d6c3545983e179a/dom/events/UIEvent.cpp#215-226)
+in Gecko, and does not occur in the earlier link. If you're unsure
+whether something causes reflow, check!
+
+Note how abundant the properties in that first list are. This means that
+when enumerating properties on DOM objects (e.g. elements/nodes, events,
+windows, etc.) **accessing the value of each enumerated property will
+almost certainly (accidentally) cause uninterruptible reflow**, because
+a lot of DOM objects have one or even several properties that do so.
+
+If you require size or position information, you have a few options.
+
+[bug 1434376](https://bugzilla.mozilla.org/show_bug.cgi?id=1434376)
+has landed a helper in the window binding to make it easier for
+privileged code to queue up JavaScript to run when we know that the DOM
+is not dirty, and size, position, and style information is cheap to
+query for.
+
+Here's an example:
+
+ async function matchWidth(elem, otherElem) {
+ let width = await window.promiseDocumentFlushed(() => {
+ // Do _not_ under any circumstances write to the DOM in one of these
+ // callbacks!
+ return elem.clientWidth;
+ });
+
+ requestAnimationFrame(() => {
+ otherElem.style.width = `${width}px`;
+ });
+ }
+
+Please see the section on *promiseDocumentFlushed* in [Detecting and
+avoiding synchronous style flushes](#detecting-and-avoiding-synchronous-style-flushes)
+for more information on how to use the API.
+
+Note that queries for size and position information are only expensive
+if the DOM has been written to. Otherwise, we're doing a cheap look-up
+of cached information. If we work hard to move all DOM writes into
+*requestAnimationFrame()*, then we can be sure that all size and
+position queries are cheap.
+
+It's also possible (though less infallible than
+*promiseDocumentFlushed*) to queue JavaScript to run very soon after
+the frame has been painted, where the likelihood is highest that the DOM
+has not been written to, and layout and style information queries are
+still cheap. This can be done by using a *setTimeout* or dispatching a
+runnable inside a *requestAnimationFrame* callback, for example:
+
+
+ requestAnimationFrame(() => {
+ setTimeout(() => {
+ // This code will be run ASAP after Style and Layout information have
+ // been calculated and the paint has occurred. Unless something else
+ // has dirtied the DOM very early, querying for style and layout information
+ // here should be cheap.
+ }, 0);
+ });
+
+ // Or, if you are running in privileged JavaScript and want to avoid the timer overhead,
+ // you could also use:
+
+ requestAnimationFrame(() => {
+ Services.tm.dispatchToMainThread(() => {
+ // Same-ish as above.
+ });
+ });
+
+This also implies that *querying for size and position information* in
+*requestAnimationFrame()* has a high probability of causing a
+synchronous reflow.
+
+### Other useful methods
+
+Below you'll find some suggestions for other methods which may come in
+handy when you need to do things without incurring synchronous reflow.
+These methods generally return the most-recently-calculated value for
+the requested value, which means the value may no longer be current, but
+may still be "close enough" for your needs. Unless you need precisely
+accurate information, they can be valuable tools in your performance
+toolbox.
+
+#### nsIDOMWindowUtils.getBoundsWithoutFlushing()
+
+*getBoundsWithoutFlushing()* does exactly what its name suggests: it
+allows you to get the bounds rectangle for a DOM node contained in a
+window without flushing layout. This means that the information you get
+is potentially out-of-date, but allows you to avoid a sync reflow. If
+you can make do with information that may not be quite current, this can
+be helpful.
+
+#### nsIDOMWindowUtils.getRootBounds()
+
+Like *getBoundsWithoutFlushing()*, *getRootBounds()* lets you get
+the dimensions of the window without risking a synchronous reflow.
+
+#### nsIDOMWindowUtils.getScrollXY()
+
+Returns the window's scroll offsets without taking the chance of causing
+a sync reflow.
+
+### Writing tests to ensure you don’t add more unintentional reflow
+
+The interface
+[nsIReflowObserver](https://dxr.mozilla.org/mozilla-central/source/docshell/base/nsIReflowObserver.idl)
+lets us detect both interruptible and uninterruptible reflows. A number
+of tests have been written that exercise various functions of the
+browser [opening tabs](http://searchfox.org/mozilla-central/rev/78cefe75fb43195e7f5aee1d8042b8d8fc79fc70/browser/base/content/test/general/browser_tabopen_reflows.js),
+[opening windows](http://searchfox.org/mozilla-central/source/browser/base/content/test/general/browser_windowopen_reflows.js)
+and ensure that we don’t add new uninterruptible reflows accidentally
+while those actions occur.
+
+You should add tests like this for your feature if you happen to be
+touching the DOM.
+
+## Detecting over-painting
+
+Painting is, in general, cheaper than both style calculation and layout
+calculation; still, the more you can avoid, the better. Generally
+speaking, the larger an area that needs to be repainted, the longer it
+takes. Similarly, the more things that need to be repainted, the longer
+it takes.
+
+If a profile says a lot of time is spent in painting or display-list building,
+and you're unsure why, consider talking to our always helpful graphics team in
+the [gfx room](https://chat.mozilla.org/#/room/%23gfx:mozilla.org) on
+[Matrix](https://wiki.mozilla.org/Matrix), and they can probably advise you.
+
+Note that a significant number of the graphics team members are in the US
+Eastern Time zone (UTC-5 or UTC-4 during Daylight Saving Time), so let that
+information guide your timing when you ask questions in the
+[gfx room](https://chat.mozilla.org/#/room/%23gfx:mozilla.org).
+
+## Adding nodes using DocumentFragments
+
+Sometimes you need to add several DOM nodes as part of an existing DOM
+tree. For example, when using XUL *\<xul:menupopup\>s*, you often have
+script which dynamically inserts *\<xul:menuitem\>s*. Inserting items
+into the DOM has a cost. If you're adding a number of children to a DOM
+node in a loop, it's often more efficient to batch them into a single
+insertion by creating a *DocumentFragment*, adding the new nodes to
+that, then inserting the *DocumentFragment* as a child of the desired
+node.
+
+A *DocumentFragment* is maintained in memory outside the DOM itself,
+so changes don't cause reflow. The API is straightforward:
+
+1. Create the *DocumentFragment* by calling
+ *Document.createDocumentFragment()*.
+
+2. Create each child element (by calling *Document.createElement()*
+ for example), and add each one to the fragment by calling
+ *DocumentFragment.appendChild()*.
+
+3. Once the fragment is populated, append the fragment to the DOM by
+ calling *appendChild()* on the parent element for the new elements.
+
+This example has been cribbed from [davidwalsh’s blog
+post](https://davidwalsh.name/documentfragment):
+
+
+ // Create the fragment
+
+ var frag = document.createDocumentFragment();
+
+ // Create numerous list items, add to fragment
+
+ for(var x = 0; x < 10; x++) {
+ var li = document.createElement("li");
+ li.innerHTML = "List item " + x;
+ frag.appendChild(li);
+ }
+
+ // Mass-add the fragment nodes to the list
+
+ listNode.appendChild(frag);
+
+The above is strictly cheaper than individually adding each node to the
+DOM.
+
+## The Gecko profiler add-on is your friend
+
+The Gecko profiler is your best friend when diagnosing performance
+problems and looking for bottlenecks. There’s plenty of excellent
+documentation on MDN about the Gecko profiler:
+
+- [Basic instructions for gathering and sharing a performance profile](reporting_a_performance_problem.md)
+
+- [Advanced profile analysis](https://developer.mozilla.org/en-US/docs/Mozilla/Performance/Profiling_with_the_Built-in_Profiler)
+
+## Don’t guess—measure.
+
+If you’re working on a performance improvement, this should go without
+saying: ensure that what you care about is actually improving by
+measuring before and after.
+
+Landing a speculative performance enhancement is the same thing as
+landing speculative bug fixes—these things need to be tested. Even if
+that means instrumenting a function with a *Date.now()* recording at
+the entrance, and another *Date.now()* at the exit points in order to
+measure processing time changes.
+
+Prove to yourself that you’ve actually improved something by measuring
+before and after.
+
+### Use the performance API
+
+The [performance
+API](https://developer.mozilla.org/en-US/docs/Web/API/Performance_API)
+is very useful for taking high-resolution measurements. This is usually
+much better than using your own hand-rolled timers to measure how long
+things take. You access the API through *Window.performance*.
+
+Also, the Gecko profiler back-end is in the process of being modified to
+expose things like markers (from *window.performance.mark()*).
+
+### Use the compositor for animations
+
+Performing animations on the main thread should be treated as
+**deprecated**. Avoid doing it. Instead, animate using
+*Element.animate()*. See the article [Animating like you just don't
+care](https://hacks.mozilla.org/2016/08/animating-like-you-just-dont-care-with-element-animate/)
+for more information on how to do this.
+
+### Explicitly define start and end animation values
+
+Some optimizations in the animation code of Gecko are based on an
+expectation that the *from* (0%) and the *to* (100%) values will be
+explicitly defined in the *@keyframes* definition. Even though these
+values may be inferred through the use of initial values or the cascade,
+the offscreen animation optimizations are dependent on the explicit
+definition. See [this comment](https://bugzilla.mozilla.org/show_bug.cgi?id=1419096#c18)
+and a few previous comments on that bug for more information.
+
+## Use IndexedDB for storage
+
+[AppCache](https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/en-US/docs/Web/HTML/Using_the_application_cache)
+and
+[LocalStorage](https://developer.mozilla.org/en-US/docs/Web/API/Storage/LocalStorage)
+are synchronous storage APIs that will block the main thread when you
+use them. Avoid them at all costs!
+
+[IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB)
+is preferable, as the API is asynchronous (all disk operations occur off
+of the main thread), and can be accessed from web workers.
+
+IndexedDB is also arguably better than storing and retrieving JSON from
+a file—particularly if the JSON encoding or decoding is occurring on the
+main thread. IndexedDB will do JavaScript object serialization and
+deserialization for you using the [structured clone
+algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm)
+meaning that you can stash [things like maps, sets, dates, blobs, and
+more](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#Supported_types)
+without having to do conversions for JSON compatibility.
+
+A Promise-based wrapper for IndexedDB,
+[IndexedDB.sys.mjs](http://searchfox.org/mozilla-central/source/toolkit/modules/IndexedDB.sys.mjs)
+is available for chrome code.
+
+## Test on weak hardware
+
+For the folks paid to work on Firefox, we tend to have pretty powerful
+hardware for development. This is great, because it reduces build times,
+and means we can do our work faster.
+
+We should remind ourselves that the majority of our user base is
+unlikely to have similar hardware. Look at the [Firefox Hardware
+Report](https://data.firefox.com/dashboard/hardware) to get
+a sense of what our users are working with. Test on slower machines to
+make it more obvious to yourself if what you’ve written impacts the
+performance of the browser.
+
+## Consider loading scripts with the subscript loader asynchronously
+
+If you've ever used the subscript loader, you might not know that it can
+load scripts asynchronously, and return a Promise once they're loaded.
+For example:
+
+
+ Services.scriptloader.loadSubScriptWithOptions(myScriptURL, { async: true }).then(() => {
+ console.log("Script at " + myScriptURL + " loaded asynchronously!");
+ });
diff --git a/docs/performance/build_metrics/build_metrics.md b/docs/performance/build_metrics/build_metrics.md
new file mode 100644
index 0000000000..cb27a42be7
--- /dev/null
+++ b/docs/performance/build_metrics/build_metrics.md
@@ -0,0 +1,31 @@
+# Build Metrics
+
+**Build Metrics** is a catch-all term for performance measures that are
+generated by the Firefox build system and tracked by Perfherder.
+
+## num_constructors
+
+Number of static constructors found by the compiler in the Firefox C++
+codebase. Lower is better. Static constructors are undesirable because
+their initialization imposes an unavoidable time penalty every time
+Firefox is started.
+
+## installer size
+
+Size in bytes of the Firefox installer. Lower is better here, especially
+on space-restricted platforms like Android.
+
+## build times
+
+Amount of time it takes to build Firefox in automation on a specific
+platform / configuration. Lower is better.
+
+## compiler warnings
+
+Number of compiler warnings detected during a build. Lower is better.
+
+Due to the way the build system works, compiler warnings are not
+consistently detected. So the value may fluctuate from build to build
+even if the number of compiler warnings didn\'t actually change. Since
+Perfherder alerts are calculated based on the mean value of a range, a
+regression may be reported as a fractional value.
diff --git a/docs/performance/dtrace.md b/docs/performance/dtrace.md
new file mode 100644
index 0000000000..68e5114297
--- /dev/null
+++ b/docs/performance/dtrace.md
@@ -0,0 +1,49 @@
+# dtrace
+
+`dtrace` is a powerful Mac OS X kernel instrumentation system that can
+be used to profile wakeups. This article provides a light introduction
+to it.
+
+:::
+**Note**: The [power profiling overview](power_profiling_overview.md) is
+worth reading at this point if you haven't already. It may make parts
+of this document easier to understand.
+:::
+
+## Invocation
+
+`dtrace` must be invoked as the super-user. A good starting command for
+profiling wakeups is the following.
+
+```
+sudo dtrace -n 'mach_kernel::wakeup { @[ustack()] = count(); }' -p $FIREFOX_PID > $OUTPUT_FILE
+```
+
+Let's break that down further.
+
+- The` -n` option combined with the `mach_kernel::wakeup` selects a
+ *probe point*. `mach_kernel` is the *module name* and `wakeup` is
+ the *probe name*. You can see a complete list of probes by running
+ `sudo dtrace -l`.
+- The code between the braces is run when the probe point is hit. The
+ above code counts unique stack traces when wakeups occur; `ustack`
+ is short for \"user stack\", i.e. the stack of the userspace program
+ executing.
+
+Run that command for a few seconds and then hit [Ctrl]{.kbd} + [C]{.kbd}
+to interrupt it. `dtrace` will then print to the output file a number of
+stack traces, along with a wakeup count for each one. The ordering of
+the stack traces can be non-obvious, so look at them carefully.
+
+Sometimes the stack trace has less information than one would like.
+It's unclear how to improve upon this.
+
+## See also
+
+dtrace is *very* powerful, and you can learn more about it by consulting
+the following resources:
+
+- [The DTrace one-liner
+ tutorial](https://wiki.freebsd.org/DTrace/Tutorial) from FreeBSD.
+- [DTrace tools](http://www.brendangregg.com/dtrace.html), by Brendan
+ Gregg.
diff --git a/docs/performance/img/ActMon-Energy.png b/docs/performance/img/ActMon-Energy.png
new file mode 100644
index 0000000000..1133ca314b
--- /dev/null
+++ b/docs/performance/img/ActMon-Energy.png
Binary files differ
diff --git a/docs/performance/img/EJCrt4N.png b/docs/performance/img/EJCrt4N.png
new file mode 100644
index 0000000000..5397386f18
--- /dev/null
+++ b/docs/performance/img/EJCrt4N.png
Binary files differ
diff --git a/docs/performance/img/PerfDotHTMLRedLines.png b/docs/performance/img/PerfDotHTMLRedLines.png
new file mode 100644
index 0000000000..fbedc92b4e
--- /dev/null
+++ b/docs/performance/img/PerfDotHTMLRedLines.png
Binary files differ
diff --git a/docs/performance/img/annotation.png b/docs/performance/img/annotation.png
new file mode 100644
index 0000000000..23655e0594
--- /dev/null
+++ b/docs/performance/img/annotation.png
Binary files differ
diff --git a/docs/performance/img/battery-status-menu.png b/docs/performance/img/battery-status-menu.png
new file mode 100644
index 0000000000..f8468387b7
--- /dev/null
+++ b/docs/performance/img/battery-status-menu.png
Binary files differ
diff --git a/docs/performance/img/dominators-1.png b/docs/performance/img/dominators-1.png
new file mode 100644
index 0000000000..163a80016c
--- /dev/null
+++ b/docs/performance/img/dominators-1.png
Binary files differ
diff --git a/docs/performance/img/dominators-10.png b/docs/performance/img/dominators-10.png
new file mode 100644
index 0000000000..e6688060af
--- /dev/null
+++ b/docs/performance/img/dominators-10.png
Binary files differ
diff --git a/docs/performance/img/dominators-2.png b/docs/performance/img/dominators-2.png
new file mode 100644
index 0000000000..99b7db7b09
--- /dev/null
+++ b/docs/performance/img/dominators-2.png
Binary files differ
diff --git a/docs/performance/img/dominators-3.png b/docs/performance/img/dominators-3.png
new file mode 100644
index 0000000000..2d380f6e21
--- /dev/null
+++ b/docs/performance/img/dominators-3.png
Binary files differ
diff --git a/docs/performance/img/dominators-4.png b/docs/performance/img/dominators-4.png
new file mode 100644
index 0000000000..d3d5eef59c
--- /dev/null
+++ b/docs/performance/img/dominators-4.png
Binary files differ
diff --git a/docs/performance/img/dominators-5.png b/docs/performance/img/dominators-5.png
new file mode 100644
index 0000000000..41a03488e9
--- /dev/null
+++ b/docs/performance/img/dominators-5.png
Binary files differ
diff --git a/docs/performance/img/dominators-6.png b/docs/performance/img/dominators-6.png
new file mode 100644
index 0000000000..a3d3026eb2
--- /dev/null
+++ b/docs/performance/img/dominators-6.png
Binary files differ
diff --git a/docs/performance/img/dominators-7.png b/docs/performance/img/dominators-7.png
new file mode 100644
index 0000000000..160f205391
--- /dev/null
+++ b/docs/performance/img/dominators-7.png
Binary files differ
diff --git a/docs/performance/img/dominators-8.png b/docs/performance/img/dominators-8.png
new file mode 100644
index 0000000000..e9512b9b05
--- /dev/null
+++ b/docs/performance/img/dominators-8.png
Binary files differ
diff --git a/docs/performance/img/dominators-9.png b/docs/performance/img/dominators-9.png
new file mode 100644
index 0000000000..af396abc21
--- /dev/null
+++ b/docs/performance/img/dominators-9.png
Binary files differ
diff --git a/docs/performance/img/memory-1-small.png b/docs/performance/img/memory-1-small.png
new file mode 100644
index 0000000000..a2076330b8
--- /dev/null
+++ b/docs/performance/img/memory-1-small.png
Binary files differ
diff --git a/docs/performance/img/memory-2-small.png b/docs/performance/img/memory-2-small.png
new file mode 100644
index 0000000000..569b0d9d66
--- /dev/null
+++ b/docs/performance/img/memory-2-small.png
Binary files differ
diff --git a/docs/performance/img/memory-3-small.png b/docs/performance/img/memory-3-small.png
new file mode 100644
index 0000000000..5d77bd7f60
--- /dev/null
+++ b/docs/performance/img/memory-3-small.png
Binary files differ
diff --git a/docs/performance/img/memory-4-small.png b/docs/performance/img/memory-4-small.png
new file mode 100644
index 0000000000..9a1e18da6f
--- /dev/null
+++ b/docs/performance/img/memory-4-small.png
Binary files differ
diff --git a/docs/performance/img/memory-5-small.png b/docs/performance/img/memory-5-small.png
new file mode 100644
index 0000000000..e3277186dc
--- /dev/null
+++ b/docs/performance/img/memory-5-small.png
Binary files differ
diff --git a/docs/performance/img/memory-6-small.png b/docs/performance/img/memory-6-small.png
new file mode 100644
index 0000000000..da69b93e51
--- /dev/null
+++ b/docs/performance/img/memory-6-small.png
Binary files differ
diff --git a/docs/performance/img/memory-7-small.png b/docs/performance/img/memory-7-small.png
new file mode 100644
index 0000000000..844565a8b4
--- /dev/null
+++ b/docs/performance/img/memory-7-small.png
Binary files differ
diff --git a/docs/performance/img/memory-graph-dominator-multiple-references.svg b/docs/performance/img/memory-graph-dominator-multiple-references.svg
new file mode 100644
index 0000000000..0aaa0546ef
--- /dev/null
+++ b/docs/performance/img/memory-graph-dominator-multiple-references.svg
@@ -0,0 +1,4 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="114 20 158 212" width="158pt" height="212pt"><defs><linearGradient x1="0" x2="1" id="a" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#e5f2f2"/><stop offset="1" stop-color="#cff2f2"/></linearGradient><linearGradient id="b" href="#a" gradientTransform="matrix(0 38 -38 0 183 90.5)"/><linearGradient id="c" href="#a" gradientTransform="matrix(0 38 -38 0 135 141.5)"/><linearGradient id="d" href="#a" gradientTransform="rotate(90 44.5 186) scale(38)"/><linearGradient id="f" href="#a" gradientTransform="matrix(0 38 -38 0 183 191.5)"/><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="e" viewBox="-1 -4 10 8" markerWidth="10" markerHeight="8" color="#000"><path d="M8 0L0-3v6z" fill="currentColor" stroke="currentColor"/></marker><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="g" viewBox="-1 -4 10 8" markerWidth="10" markerHeight="8" color="#000"><path d="M8 0H0m0-3l8 3-8 3" fill="none" stroke="currentColor"/></marker></defs><g fill="none"><circle cx="183" cy="109.5" r="19" fill="url(#b)"/><circle cx="183" cy="109.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><circle cx="135" cy="160.5" r="19" fill="url(#c)"/><circle cx="135" cy="160.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><circle cx="230.5" cy="160.5" r="19" fill="url(#d)"/><circle cx="230.5" cy="160.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><path marker-end="url(#e)" stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M169.978 123.336l-15.171 16.119m41.143-16.051l14.853 15.948"/><circle cx="183" cy="210.5" r="19" fill="url(#f)"/><circle cx="183" cy="210.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><text transform="translate(172.8 201.5)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="5.016" y="15" textLength="10.368">A</tspan></text><path marker-end="url(#e)" stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M148.158 174.206l14.828 15.446"/><text transform="translate(169 26.5)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x=".096" y="15" textLength="10.368">A</tspan><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="9.28" y="15" textLength="4.448">’</tspan><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="12.544" y="15" textLength="85.36">s dominator</tspan></text><path marker-end="url(#g)" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="4,4" d="M210.905 50.5l-15.546 32.87"/><path marker-end="url(#e)" stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M217.414 174.275l-14.509 15.272"/></g></svg>
diff --git a/docs/performance/img/memory-graph-dominators.svg b/docs/performance/img/memory-graph-dominators.svg
new file mode 100644
index 0000000000..0525d0cb5a
--- /dev/null
+++ b/docs/performance/img/memory-graph-dominators.svg
@@ -0,0 +1,4 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="60 57 353 232" width="353pt" height="232pt"><defs><linearGradient x1="0" x2="1" id="a" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#e5f2f2"/><stop offset="1" stop-color="#cff2f2"/></linearGradient><linearGradient id="b" href="#a" gradientTransform="matrix(0 38 -38 0 183 90.5)"/><linearGradient id="c" href="#a" gradientTransform="matrix(0 38 -38 0 135 141.5)"/><linearGradient id="d" href="#a" gradientTransform="rotate(90 44.5 186) scale(38)"/><linearGradient id="f" href="#a" gradientTransform="matrix(0 38 -38 0 81.5 196.5)"/><linearGradient id="g" href="#a" gradientTransform="rotate(90 41 238.5) scale(38)"/><linearGradient id="h" href="#a" gradientTransform="rotate(90 -8.5 240) scale(38)"/><linearGradient id="i" href="#a" gradientTransform="matrix(0 38 -38 0 327 248.5)"/><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="e" viewBox="-1 -4 10 8" markerWidth="10" markerHeight="8" color="#000"><path d="M8 0L0-3v6z" fill="currentColor" stroke="currentColor"/></marker><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="j" viewBox="-1 -4 10 8" markerWidth="10" markerHeight="8" color="#000"><path d="M8 0H0m0-3l8 3-8 3" fill="none" stroke="currentColor"/></marker></defs><g fill="none"><circle cx="183" cy="109.5" r="19" fill="url(#b)"/><circle cx="183" cy="109.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><text transform="translate(172.8 100.5)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="4.72" y="15" textLength="10.96">R</tspan></text><circle cx="135" cy="160.5" r="19" fill="url(#c)"/><circle cx="135" cy="160.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><circle cx="230.5" cy="160.5" r="19" fill="url(#d)"/><circle cx="230.5" cy="160.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><path marker-end="url(#e)" stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M169.978 123.336l-15.171 16.119m41.143-16.051l14.853 15.948"/><circle cx="81.5" cy="215.5" r="19" fill="url(#f)"/><circle cx="81.5" cy="215.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><path marker-end="url(#e)" stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M121.752 174.119l-20.101 20.665"/><circle cx="279.5" cy="216.5" r="19" fill="url(#g)"/><circle cx="279.5" cy="216.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><circle cx="231.5" cy="267.5" r="19" fill="url(#h)"/><circle cx="231.5" cy="267.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><circle cx="327" cy="267.5" r="19" fill="url(#i)"/><circle cx="327" cy="267.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><text transform="translate(316.8 258.5)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="5.016" y="15" textLength="10.368">A</tspan></text><path marker-end="url(#e)" stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M243.012 174.799l17.457 19.951m31.981 35.654l14.853 15.948m-40.825-16.016l-15.171 16.119"/><text transform="translate(301.5 63)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x=".096" y="15" textLength="10.368">A</tspan><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="9.28" y="15" textLength="4.448">’</tspan><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="12.544" y="15" textLength="93.36">s dominators</tspan></text><path marker-end="url(#j)" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="4,4" d="M296.5 84.682l-85.265 18.644M333.483 87l-79.457 56.709M346.715 87l-53.899 103.845"/></g></svg>
diff --git a/docs/performance/img/memory-graph-immediate-dominator.svg b/docs/performance/img/memory-graph-immediate-dominator.svg
new file mode 100644
index 0000000000..f88b482820
--- /dev/null
+++ b/docs/performance/img/memory-graph-immediate-dominator.svg
@@ -0,0 +1,4 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="60 57 350 232" width="350pt" height="232pt"><defs><linearGradient x1="0" x2="1" id="a" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#e5f2f2"/><stop offset="1" stop-color="#cff2f2"/></linearGradient><linearGradient id="b" href="#a" gradientTransform="matrix(0 38 -38 0 183 90.5)"/><linearGradient id="c" href="#a" gradientTransform="matrix(0 38 -38 0 135 141.5)"/><linearGradient id="d" href="#a" gradientTransform="rotate(90 44.5 186) scale(38)"/><linearGradient id="f" href="#a" gradientTransform="matrix(0 38 -38 0 81.5 196.5)"/><linearGradient id="g" href="#a" gradientTransform="rotate(90 41 238.5) scale(38)"/><linearGradient id="h" href="#a" gradientTransform="rotate(90 -8.5 240) scale(38)"/><linearGradient id="i" href="#a" gradientTransform="matrix(0 38 -38 0 327 248.5)"/><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="e" viewBox="-1 -4 10 8" markerWidth="10" markerHeight="8" color="#000"><path d="M8 0L0-3v6z" fill="currentColor" stroke="currentColor"/></marker><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="j" viewBox="-1 -4 10 8" markerWidth="10" markerHeight="8" color="#000"><path d="M8 0H0m0-3l8 3-8 3" fill="none" stroke="currentColor"/></marker></defs><g fill="none"><circle cx="183" cy="109.5" r="19" fill="url(#b)"/><circle cx="183" cy="109.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><text transform="translate(172.8 100.5)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="4.72" y="15" textLength="10.96">R</tspan></text><circle cx="135" cy="160.5" r="19" fill="url(#c)"/><circle cx="135" cy="160.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><circle cx="230.5" cy="160.5" r="19" fill="url(#d)"/><circle cx="230.5" cy="160.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><path marker-end="url(#e)" stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M169.978 123.336l-15.171 16.119m41.143-16.051l14.853 15.948"/><circle cx="81.5" cy="215.5" r="19" fill="url(#f)"/><circle cx="81.5" cy="215.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><path marker-end="url(#e)" stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M121.752 174.119l-20.101 20.665"/><circle cx="279.5" cy="216.5" r="19" fill="url(#g)"/><circle cx="279.5" cy="216.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><circle cx="231.5" cy="267.5" r="19" fill="url(#h)"/><circle cx="231.5" cy="267.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><circle cx="327" cy="267.5" r="19" fill="url(#i)"/><circle cx="327" cy="267.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><text transform="translate(316.8 258.5)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="5.016" y="15" textLength="10.368">A</tspan></text><path marker-end="url(#e)" stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M243.012 174.799l17.457 19.951m31.981 35.654l14.853 15.948m-40.825-16.016l-15.171 16.119"/><text transform="translate(304.5 62)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x=".2" y="15" textLength="10.368">A</tspan><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="9.384" y="15" textLength="4.448">’</tspan><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="12.648" y="15" textLength="87.152">s immediate</tspan><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="13.544" y="33" textLength="72.912">dominator</tspan></text><path marker-end="url(#j)" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="4,4" d="M341.863 103l-48.444 88.167"/></g></svg>
diff --git a/docs/performance/img/memory-graph-unreachable.svg b/docs/performance/img/memory-graph-unreachable.svg
new file mode 100644
index 0000000000..5bc29d6163
--- /dev/null
+++ b/docs/performance/img/memory-graph-unreachable.svg
@@ -0,0 +1,4 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="60 88 287 200" width="287pt" height="200pt"><defs><linearGradient x1="0" x2="1" id="a" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#e5f2f2"/><stop offset="1" stop-color="#cff2f2"/></linearGradient><linearGradient id="c" href="#a" gradientTransform="matrix(0 38 -38 0 183 90.5)"/><linearGradient id="d" href="#a" gradientTransform="matrix(0 38 -38 0 135 141.5)"/><linearGradient id="e" href="#a" gradientTransform="rotate(90 44.5 186) scale(38)"/><linearGradient x1="0" x2="1" id="b" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f29a8b"/><stop offset="1" stop-color="#f23d1c"/></linearGradient><linearGradient id="f" href="#b" gradientTransform="rotate(90 41 237.5) scale(38)"/><linearGradient id="g" href="#b" gradientTransform="rotate(90 -8.5 239) scale(38)"/><linearGradient id="h" href="#b" gradientTransform="matrix(0 38 -38 0 326 247.5)"/><linearGradient id="j" href="#a" gradientTransform="matrix(0 38 -38 0 81.5 196.5)"/><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="i" viewBox="-1 -4 10 8" markerWidth="10" markerHeight="8" color="#000"><path d="M8 0L0-3v6z" fill="currentColor" stroke="currentColor"/></marker></defs><g fill="none"><circle cx="183" cy="109.5" r="19" fill="url(#c)"/><circle cx="183" cy="109.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><text transform="translate(172.8 100.5)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="4.72" y="15" textLength="10.96">R</tspan></text><circle cx="135" cy="160.5" r="19" fill="url(#d)"/><circle cx="135" cy="160.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><circle cx="230.5" cy="160.5" r="19" fill="url(#e)"/><circle cx="230.5" cy="160.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><circle cx="278.5" cy="215.5" r="19" fill="url(#f)"/><circle cx="278.5" cy="215.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><circle cx="230.5" cy="266.5" r="19" fill="url(#g)"/><circle cx="230.5" cy="266.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><circle cx="326" cy="266.5" r="19" fill="url(#h)"/><circle cx="326" cy="266.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><path marker-end="url(#i)" stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M169.978 123.336l-15.171 16.119m41.143-16.051l14.853 15.948m54.675 89.984l-15.171 16.119m41.143-16.051l14.853 15.948"/><circle cx="81.5" cy="215.5" r="19" fill="url(#j)"/><circle cx="81.5" cy="215.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><path marker-end="url(#i)" stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M121.752 174.119l-20.101 20.665"/></g></svg>
diff --git a/docs/performance/img/memory-graph.svg b/docs/performance/img/memory-graph.svg
new file mode 100644
index 0000000000..e39168c11c
--- /dev/null
+++ b/docs/performance/img/memory-graph.svg
@@ -0,0 +1,4 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="60 88 288 201" width="384" height="268"><defs><linearGradient x1="0" x2="1" id="a" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#e5f2f2"/><stop offset="1" stop-color="#cff2f2"/></linearGradient><linearGradient id="b" href="#a" gradientTransform="matrix(0 38 -38 0 183 90.5)"/><linearGradient id="c" href="#a" gradientTransform="matrix(0 38 -38 0 135 141.5)"/><linearGradient id="d" href="#a" gradientTransform="rotate(90 44.5 186) scale(38)"/><linearGradient id="f" href="#a" gradientTransform="matrix(0 38 -38 0 81.5 196.5)"/><linearGradient id="g" href="#a" gradientTransform="rotate(90 41 238.5) scale(38)"/><linearGradient id="h" href="#a" gradientTransform="rotate(90 -8.5 240) scale(38)"/><linearGradient id="i" href="#a" gradientTransform="matrix(0 38 -38 0 327 248.5)"/><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="e" viewBox="-1 -4 10 8" markerWidth="10" markerHeight="8" color="#000"><path d="M8 0L0-3v6z" fill="currentColor" stroke="currentColor"/></marker></defs><g fill="none"><circle cx="183" cy="109.5" r="19" fill="url(#b)"/><circle cx="183" cy="109.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><text transform="translate(172.8 100.5)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="4.72" y="15" textLength="10.96">R</tspan></text><circle cx="135" cy="160.5" r="19" fill="url(#c)"/><circle cx="135" cy="160.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><circle cx="230.5" cy="160.5" r="19" fill="url(#d)"/><circle cx="230.5" cy="160.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><path marker-end="url(#e)" stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M169.978 123.336l-15.171 16.119m41.143-16.051l14.853 15.948"/><circle cx="81.5" cy="215.5" r="19" fill="url(#f)"/><circle cx="81.5" cy="215.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><path marker-end="url(#e)" stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M121.752 174.119l-20.101 20.665"/><circle cx="279.5" cy="216.5" r="19" fill="url(#g)"/><circle cx="279.5" cy="216.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><circle cx="231.5" cy="267.5" r="19" fill="url(#h)"/><circle cx="231.5" cy="267.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><circle cx="327" cy="267.5" r="19" fill="url(#i)"/><circle cx="327" cy="267.5" r="19" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><path marker-end="url(#e)" stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M243.012 174.799l17.457 19.951m31.981 35.654l14.853 15.948m-40.825-16.016l-15.171 16.119"/></g></svg>
diff --git a/docs/performance/img/memory-tool-aggregate-view.png b/docs/performance/img/memory-tool-aggregate-view.png
new file mode 100644
index 0000000000..653710979f
--- /dev/null
+++ b/docs/performance/img/memory-tool-aggregate-view.png
Binary files differ
diff --git a/docs/performance/img/memory-tool-call-stack-expanded.png b/docs/performance/img/memory-tool-call-stack-expanded.png
new file mode 100644
index 0000000000..fe2364da58
--- /dev/null
+++ b/docs/performance/img/memory-tool-call-stack-expanded.png
Binary files differ
diff --git a/docs/performance/img/memory-tool-call-stack.png b/docs/performance/img/memory-tool-call-stack.png
new file mode 100644
index 0000000000..52a96015da
--- /dev/null
+++ b/docs/performance/img/memory-tool-call-stack.png
Binary files differ
diff --git a/docs/performance/img/memory-tool-in-group-icon.png b/docs/performance/img/memory-tool-in-group-icon.png
new file mode 100644
index 0000000000..6354a3d377
--- /dev/null
+++ b/docs/performance/img/memory-tool-in-group-icon.png
Binary files differ
diff --git a/docs/performance/img/memory-tool-in-group-retaining-paths.png b/docs/performance/img/memory-tool-in-group-retaining-paths.png
new file mode 100644
index 0000000000..191115f041
--- /dev/null
+++ b/docs/performance/img/memory-tool-in-group-retaining-paths.png
Binary files differ
diff --git a/docs/performance/img/memory-tool-in-group.png b/docs/performance/img/memory-tool-in-group.png
new file mode 100644
index 0000000000..88aac55e9e
--- /dev/null
+++ b/docs/performance/img/memory-tool-in-group.png
Binary files differ
diff --git a/docs/performance/img/memory-tool-inverted-call-stack.png b/docs/performance/img/memory-tool-inverted-call-stack.png
new file mode 100644
index 0000000000..5a951c2e8c
--- /dev/null
+++ b/docs/performance/img/memory-tool-inverted-call-stack.png
Binary files differ
diff --git a/docs/performance/img/memory-tool-switch-view.png b/docs/performance/img/memory-tool-switch-view.png
new file mode 100644
index 0000000000..bb3cb0cdb3
--- /dev/null
+++ b/docs/performance/img/memory-tool-switch-view.png
Binary files differ
diff --git a/docs/performance/img/monsters.svg b/docs/performance/img/monsters.svg
new file mode 100644
index 0000000000..2f12ef43e8
--- /dev/null
+++ b/docs/performance/img/monsters.svg
@@ -0,0 +1,4 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="62 78 464 484" width="464pt" height="484pt"><defs><linearGradient x1="0" x2="1" id="a" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#e5f2f2"/><stop offset="1" stop-color="#cff2f2"/></linearGradient><linearGradient id="b" href="#a" gradientTransform="matrix(0 31.5 -31.5 0 103.25 304)"/><linearGradient id="c" href="#a" gradientTransform="matrix(0 31.5 -31.5 0 231.25 136)"/><linearGradient id="d" href="#a" gradientTransform="matrix(0 31.5 -31.5 0 359.25 136)"/><linearGradient id="e" href="#a" gradientTransform="matrix(0 31.5 -31.5 0 359.25 80)"/><linearGradient id="f" href="#a" gradientTransform="matrix(0 31.5 -31.5 0 359.25 192)"/><linearGradient id="g" href="#a" gradientTransform="matrix(0 31.5 -31.5 0 484.75 81)"/><linearGradient id="h" href="#a" gradientTransform="matrix(0 31.5 -31.5 0 484.75 136)"/><linearGradient id="j" href="#a" gradientTransform="matrix(0 31.5 -31.5 0 231.25 304)"/><linearGradient id="k" href="#a" gradientTransform="matrix(0 31.5 -31.5 0 359.25 304)"/><linearGradient id="l" href="#a" gradientTransform="matrix(0 31.5 -31.5 0 359.25 248)"/><linearGradient id="m" href="#a" gradientTransform="matrix(0 31.5 -31.5 0 359.25 360)"/><linearGradient id="n" href="#a" gradientTransform="matrix(0 31.5 -31.5 0 484.75 249)"/><linearGradient id="o" href="#a" gradientTransform="matrix(0 31.5 -31.5 0 484.75 304)"/><linearGradient id="p" href="#a" gradientTransform="matrix(0 31.5 -31.5 0 231.25 472)"/><linearGradient id="q" href="#a" gradientTransform="matrix(0 31.5 -31.5 0 359.25 472)"/><linearGradient id="r" href="#a" gradientTransform="matrix(0 31.5 -31.5 0 359.25 416)"/><linearGradient id="s" href="#a" gradientTransform="matrix(0 31.5 -31.5 0 359.25 528)"/><linearGradient id="t" href="#a" gradientTransform="matrix(0 31.5 -31.5 0 484.75 417)"/><linearGradient id="u" href="#a" gradientTransform="matrix(0 31.5 -31.5 0 484.75 472)"/><marker orient="auto" overflow="visible" markerUnits="strokeWidth" id="i" viewBox="-1 -4 10 8" markerWidth="10" markerHeight="8" color="#000"><path d="M8 0L0-3v6z" fill="currentColor" stroke="currentColor"/></marker></defs><g fill="none"><path fill="url(#b)" d="M64.5 304H142v31.5H64.5z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M64.5 304H142v31.5H64.5z"/><text transform="translate(69.5 310.75)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="10.038" y="15" textLength="47.424">Object</tspan></text><path fill="url(#c)" d="M192.5 136H270v31.5h-77.5z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M192.5 136H270v31.5h-77.5z"/><text transform="translate(197.5 142.75)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="14.942" y="15" textLength="37.616">Array</tspan></text><path fill="url(#d)" d="M320.5 136H398v31.5h-77.5z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M320.5 136H398v31.5h-77.5z"/><text transform="translate(325.5 142.75)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="4.262" y="15" textLength="58.976">Monster</tspan></text><path fill="url(#e)" d="M320.5 80H398v31.5h-77.5z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M320.5 80H398v31.5h-77.5z"/><text transform="translate(325.5 86.75)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="4.262" y="15" textLength="58.976">Monster</tspan></text><path fill="url(#f)" d="M320.5 192H398v31.5h-77.5z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M320.5 192H398v31.5h-77.5z"/><text transform="translate(325.5 198.75)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="25.75" y="15" textLength="16">…</tspan></text><path fill="url(#g)" d="M446 81h77.5v31.5H446z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M446 81h77.5v31.5H446z"/><text transform="translate(451 87.75)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="12.566" y="15" textLength="42.368">String</tspan></text><path fill="url(#h)" d="M446 136h77.5v31.5H446z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M446 136h77.5v31.5H446z"/><text transform="translate(451 142.75)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="12.566" y="15" textLength="42.368">String</tspan></text><path d="M103.25 304V151.75h79.35m112.65 0h15.35m-15.35 0v-56h15.35m-40.6 56h25.25v56h15.35m87.4-112l38.101.439M398 151.75h38.1" marker-end="url(#i)" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><path fill="url(#j)" d="M192.5 304H270v31.5h-77.5z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M192.5 304H270v31.5h-77.5z"/><text transform="translate(197.5 310.75)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="14.942" y="15" textLength="37.616">Array</tspan></text><path fill="url(#k)" d="M320.5 304H398v31.5h-77.5z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M320.5 304H398v31.5h-77.5z"/><text transform="translate(325.5 310.75)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="4.262" y="15" textLength="58.976">Monster</tspan></text><path fill="url(#l)" d="M320.5 248H398v31.5h-77.5z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M320.5 248H398v31.5h-77.5z"/><text transform="translate(325.5 254.75)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="4.262" y="15" textLength="58.976">Monster</tspan></text><path fill="url(#m)" d="M320.5 360H398v31.5h-77.5z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M320.5 360H398v31.5h-77.5z"/><text transform="translate(325.5 366.75)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="25.75" y="15" textLength="16">…</tspan></text><path fill="url(#n)" d="M446 249h77.5v31.5H446z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M446 249h77.5v31.5H446z"/><text transform="translate(451 255.75)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="12.566" y="15" textLength="42.368">String</tspan></text><path fill="url(#o)" d="M446 304h77.5v31.5H446z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M446 304h77.5v31.5H446z"/><text transform="translate(451 310.75)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="12.566" y="15" textLength="42.368">String</tspan></text><path d="M142 319.75h40.6m112.65 0h15.35m-15.35 0v-56h15.35m-40.6 56h25.25v56h15.35m87.4-112l38.101.439M398 319.75h38.1" marker-end="url(#i)" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/><path fill="url(#p)" d="M192.5 472H270v31.5h-77.5z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M192.5 472H270v31.5h-77.5z"/><text transform="translate(197.5 478.75)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="14.942" y="15" textLength="37.616">Array</tspan></text><path fill="url(#q)" d="M320.5 472H398v31.5h-77.5z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M320.5 472H398v31.5h-77.5z"/><text transform="translate(325.5 478.75)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="4.262" y="15" textLength="58.976">Monster</tspan></text><path fill="url(#r)" d="M320.5 416H398v31.5h-77.5z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M320.5 416H398v31.5h-77.5z"/><text transform="translate(325.5 422.75)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="4.262" y="15" textLength="58.976">Monster</tspan></text><path fill="url(#s)" d="M320.5 528H398v31.5h-77.5z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M320.5 528H398v31.5h-77.5z"/><text transform="translate(325.5 534.75)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="25.75" y="15" textLength="16">…</tspan></text><path fill="url(#t)" d="M446 417h77.5v31.5H446z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M446 417h77.5v31.5H446z"/><text transform="translate(451 423.75)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="12.566" y="15" textLength="42.368">String</tspan></text><path fill="url(#u)" d="M446 472h77.5v31.5H446z"/><path stroke="#000" stroke-linecap="round" stroke-linejoin="round" d="M446 472h77.5v31.5H446z"/><text transform="translate(451 478.75)" fill="#000"><tspan font-family="Helvetica Neue" font-size="16" font-weight="500" x="12.566" y="15" textLength="42.368">String</tspan></text><path d="M103.25 335.5v152.25h79.35m112.65 0h15.35m-15.35 0v-56h15.35m-40.6 56h25.25v56h15.35m87.4-112l38.101.439M398 487.75h38.1" marker-end="url(#i)" stroke="#000" stroke-linecap="round" stroke-linejoin="round"/></g></svg>
diff --git a/docs/performance/img/pid.png b/docs/performance/img/pid.png
new file mode 100644
index 0000000000..bdad5d2cb8
--- /dev/null
+++ b/docs/performance/img/pid.png
Binary files differ
diff --git a/docs/performance/img/power-planes.jpg b/docs/performance/img/power-planes.jpg
new file mode 100644
index 0000000000..a564fae248
--- /dev/null
+++ b/docs/performance/img/power-planes.jpg
Binary files differ
diff --git a/docs/performance/img/rendering.png b/docs/performance/img/rendering.png
new file mode 100644
index 0000000000..c4995dbef8
--- /dev/null
+++ b/docs/performance/img/rendering.png
Binary files differ
diff --git a/docs/performance/img/reportingperf1.png b/docs/performance/img/reportingperf1.png
new file mode 100644
index 0000000000..e2285280af
--- /dev/null
+++ b/docs/performance/img/reportingperf1.png
Binary files differ
diff --git a/docs/performance/img/reportingperf2.png b/docs/performance/img/reportingperf2.png
new file mode 100644
index 0000000000..c43eba2342
--- /dev/null
+++ b/docs/performance/img/reportingperf2.png
Binary files differ
diff --git a/docs/performance/img/reportingperf3.png b/docs/performance/img/reportingperf3.png
new file mode 100644
index 0000000000..5eb3b58fb7
--- /dev/null
+++ b/docs/performance/img/reportingperf3.png
Binary files differ
diff --git a/docs/performance/img/treemap-bbc.png b/docs/performance/img/treemap-bbc.png
new file mode 100644
index 0000000000..55552b8382
--- /dev/null
+++ b/docs/performance/img/treemap-bbc.png
Binary files differ
diff --git a/docs/performance/img/treemap-domnodes.png b/docs/performance/img/treemap-domnodes.png
new file mode 100644
index 0000000000..1192e390da
--- /dev/null
+++ b/docs/performance/img/treemap-domnodes.png
Binary files differ
diff --git a/docs/performance/img/treemap-monsters.png b/docs/performance/img/treemap-monsters.png
new file mode 100644
index 0000000000..513adab923
--- /dev/null
+++ b/docs/performance/img/treemap-monsters.png
Binary files differ
diff --git a/docs/performance/index.md b/docs/performance/index.md
new file mode 100644
index 0000000000..70e57c89e9
--- /dev/null
+++ b/docs/performance/index.md
@@ -0,0 +1,53 @@
+# Performance
+
+This page explains how to optimize the performance of the Firefox code base.
+
+The [test documentation](/testing/perfdocs/index.rst)
+explains how to test for performance in Firefox.
+The [profiler documentation](/tools/profiler/index.rst)
+explains how to use the Gecko profiler.
+
+## General Performance references
+* Tips on generating valid performance metrics by [benchmarking](Benchmarking.md)
+* [GPU Performance](GPU_performance.md) Tips for reducing impacts on browser performance in front-end code.
+* [Automated Performance testing and Sheriffing](automated_performance_testing_and_sheriffing.md) Information on automated performance testing and sheriffing at Mozilla.
+* [Performance best practices for Firefox front-end engineers](bestpractices.md) Tips for reducing impacts on browser performance in front-end code.
+* [Reporting a performance problem](reporting_a_performance_problem.md) A user friendly guide to reporting a performance problem. A development environment is not required.
+* [Scroll Linked Effects](scroll-linked_effects.md) Information on scroll-linked effects, their effect on performance, related tools, and possible mitigation techniques.
+
+## Memory profiling and leak detection tools
+* The [Developer Tools Memory panel](memory/memory.md) supports taking heap snapshots, diffing them, computing dominator trees to surface "heavy retainers", and recording allocation stacks.
+* [About:memory](memory/about_colon_memory.md) about:memory is the easiest-to-use tool for measuring memory usage in Mozilla code, and is the best place to start. It also lets you do other memory-related operations like trigger GC and CC, dump GC & CC logs, and dump DMD reports. about:memory is built on top of Firefox's memory reporting infrastructure.
+* [DMD](memory/dmd.md) is a tool that identifies shortcomings in about:memory's measurements, and can also do multiple kinds of general heap profiling.
+* [AWSY](memory/awsy.md) (are we slim yet?) is a memory usage and regression tracker.
+* [Bloatview](memory/bloatview.md) prints per-class statistics on allocations and refcounts, and provides gross numbers on the amount of memory being leaked broken down by class. It is used as part of Mozilla's continuous integration testing.
+* [Refcount Tracing and Balancing](memory/refcount_tracing_and_balancing.md) are ways to track down leaks caused by incorrect uses of reference counting. They are slow and not particular easy to use, and thus most suitable for use by expert developers.
+* [GC and CC Logs](memory/gc_and_cc_logs.md)
+* [Leak Gauge](memory/leak_gauge.md) can be generated and analyzed to in various ways. In particular, they can help you understand why a particular object is being kept alive.
+* [LogAlloc](https://searchfox.org/mozilla-central/source/memory/replace/logalloc/README) is a tool that dumps a log of memory allocations in Gecko. That log can then be replayed against Firefox's default memory allocator independently or through another replace-malloc library, allowing the testing of other allocators under the exact same workload.
+* [See also the documentation on Leak-hunting strategies and tips.](memory/leak_hunting_strategies_and_tips.md)
+
+## Profiling and performance tools
+
+* [JIT Profiling with perf](jit_profiling_with_perf.md) Using perf to collect JIT profiles.
+* [Profiling with Instruments](profiling_with_instruments.md) How to use Apple's Instruments tool to profile Mozilla code.
+* [Profiling with xperf](profiling_with_xperf.md) How to use Microsoft's Xperf tool to profile Mozilla code.
+* [Profiling with Concurrency Visualizer](profiling_with_concurrency_visualizer.md) How to use Visual Studio's Concurrency Visualizer tool to profile Mozilla code.
+* [Profiling with Zoom](profiling_with_zoom.md) Zoom is a profiler for Linux done by the people who made Shark.
+* [Adding a new telemetry probe](https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/start/adding-a-new-probe.html) Information on how to add a new measurement to the Telemetry performance-reporting system
+
+## Power Profiling
+
+* [An overview of power profiling](power_profiling_overview.md). It includes details about hardware, what can be measured, and recommended approaches. It should be the starting point for anybody new to power profiling.
+* **(Mac, Linux)** [tools/power/rapl](tools_power_rapl.md) is a command-line utility in the Mozilla codebase that uses the Intel RAPL interface to gather direct power estimates for the package, cores, GPU and memory.
+* **(Mac-only)** [powermetrics](powermetrics.md) is a command-line utility that gathers and displays a wide range of global and per-process measurements, including CPU usage, GPU usage, and various wakeups frequencies.
+* **(All-platforms)** [TimerFirings](timerfirings_logging.md) logging is a built-in logging mechanism that prints data on every time fired.
+* **(Mac-only)** [Activity Monitor and top](activity_monitor_and_top.md) The battery status menu, Activity Monitor and top are three related Mac tools that have major flaws but often consulted by users, and so are worth understanding.
+* **(Windows, Mac and Linux)** [Intel Power Gadget](intel_power_gadget.md) Intel Power Gadget provides real-time graphs for package and processor RAPL estimates. It also provides an API through which those estimates can be obtained.
+* **(Linux only)** [perf](perf.md) perf is a powerful command-line utility that can measure many different things, including energy estimates and high-context measurements of things such as wakeups.
+* **(Linux-only)** [turbostat](turbostat.md) is a command-line utility that gathers and displays various power-related measurements, with a focus on per-CPU measurements such as frequencies and C-states.
+* **(Linux-only)** [powertop](https://01.org/powertop) is an interactive command-line utility that gathers and displays various power-related measurements.
+
+## Performance Metrics
+
+* [PerfStats](perfstats.md) - A framework for low-overhead collection of internal performance metrics.
diff --git a/docs/performance/intel_power_gadget.md b/docs/performance/intel_power_gadget.md
new file mode 100644
index 0000000000..74f7801cff
--- /dev/null
+++ b/docs/performance/intel_power_gadget.md
@@ -0,0 +1,56 @@
+# Intel Power Gadget
+
+[Intel Power Gadget](https://software.intel.com/en-us/articles/intel-power-gadget/)
+provides real-time graphs of various power-related measures and
+estimates, all taken from the Intel RAPL MSRs. This article provides a
+basic introduction.
+
+**Note**: The [power profiling
+overview](power_profiling_overview.md) is
+worth reading at this point if you haven\'t already. It may make parts
+of this document easier to understand.
+
+The main strengths of this tool are (a) it works on Windows, unlike most
+other power-related tools, and (b) it shows this data in graph form,
+which is occasionally useful. On Mac and Linux, `tools/power/rapl`
+[](tools_power_rapl.md) is probably a better tool
+to use.
+
+## Understanding the Power Gadget output
+
+The following screenshot (from the Mac version) demonstrates the
+available measurements.
+
+![](https://mdn.mozillademos.org/files/11365/Intel-Power-Gadget.png)
+
+The three panes display the following information:
+
+- **Power**: Shows power estimates for the package and the cores
+ (\"IA\"). These are reasonably useful for power profiling purposes,
+ but Mozilla\'s `rapl` utility provides these along with GPU and RAM
+ estimates, and in a command-line format that is often easier to use.
+- **Frequency**: Shows operating frequency measurements for the cores
+ (\"IA\") and the GPU (\"GT\"). These measurements aren\'t
+ particularly useful for power profiling purposes.
+- **Temperature**: Shows the package temperature. This is interesting,
+ but again not useful for power profiling purposes. Specifically,
+ the temperature is a proxy measurement that is *affected by*
+ processor power consumption, rather than one that *affects* it,
+ which makes it even less useful than most proxy measurements.
+
+Intel Power Gadget can also log these results to a file. This feature
+has been used in [energia](https://github.com/mozilla/energia), Roberto
+Vitillo\'s tool for systematically measuring differential power usage
+between different browsers. (An energia dashboard can be seen
+[here](http://people.mozilla.org/~rvitillo/dashboard/); please note that
+the data has not been updated since early 2014.)
+
+Version 3.0 (available on Mac and Windows, but not on Linux) also
+exposes an API from which the same measurements can be extracted
+programmatically. At one point the Gecko Profiler [used this
+API](https://benoitgirard.wordpress.com/2012/06/29/correlating-power-usage-with-performance-data-using-the-gecko-profiler-and-intel-sandy-bridge/)
+on Windows to implement experimental package power estimates.
+Unfortunately, the Gecko profiler takes 1000 samples per second on
+desktop and is CPU intensive and so is likely to skew the RAPL estimates
+significantly, so the API integration was removed. The API is otherwise
+unlikely to be of interest to Mozilla developers.
diff --git a/docs/performance/jit_profiling_with_perf.md b/docs/performance/jit_profiling_with_perf.md
new file mode 100644
index 0000000000..81feac2733
--- /dev/null
+++ b/docs/performance/jit_profiling_with_perf.md
@@ -0,0 +1,119 @@
+# JIT Profiling with perf
+
+perf is a performance profiling tool available on Linux that is capable of measuring performance events such as cycles, instructions executed, cache misses, etc and providing assembly and source code annotation.
+It is possible to collect performance profiles of the SpiderMonkey JIT using perf on Linux and also annotate the generated assembly with the IR opcodes that were used during compilation as shown below.
+
+![](img/annotation.png)
+
+## Build setup
+
+To enable JIT profiling with perf jitdump, you must build Firefox or the JS shell with the following flag:
+
+```
+ac_add_options --enable-perf
+```
+
+## Environment Variables
+
+Environment variables that must be defined for perf JIT profiling:
+
+`PERF_SPEW_DIR`: Location of jitdump output files. Making this directory a tmpfs filesystem could help reduce overhead.\
+`IONPERF`: Valid options include: `func`, `src`, `ir`, `ir-ops`.
+
+`IONPERF=func` will disable all annotation and only function names will be available. It is the fastest option.\
+`IONPERF=ir` will enable IR annotation.\
+`IONPERF=ir-ops` will enable IR annotation with operand support. **Requires --enable-jitspew** and adds additional overhead to "ir".\
+`IONPERF=src` will enable source code annotation **only if** perf can read the source file locally. Only really works well in the JS shell.
+
+## Profiling the JS shell
+
+Profiling the JS shell requires the following commands but is very straight forward.
+
+Begin by removing any pre-existing jitdump files:
+
+`rm -rf output` or `rm -f jitted-*.so jit.data perf.data jit-*.dump jitdump-*.txt`
+
+Next define environment variables:
+```
+export IONPERF=ir
+export PERF_SPEW_DIR=output
+```
+
+Run your test case with perf attached:
+```
+perf record -g -k 1 /home/denis/src/mozilla-central/obj-js/dist/bin/js test.js
+```
+
+Inject the jitdump files into your perf.data file:
+```
+perf inject -j -i perf.data -o jit.data
+```
+
+View the profile:
+```
+perf report --no-children --call-graph=graph,0 -i jit.data
+```
+
+All of the above commands can be put into a single shell script.
+
+## Profiling the Browser
+
+Profiling the browser is less straight forward than the shell, but the only main difference is that perf must attach to the content process while it is running.
+
+Begin by removing any pre-existing jitdump files:
+
+`rm -rf output` or `rm -f jitted-*.so jit.data perf.data jit-*.dump jitdump-*.txt`
+
+Next define environment variables:
+```
+export IONPERF=ir
+export PERF_SPEW_DIR=output
+export MOZ_DISABLE_CONTENT_SANDBOX=1
+```
+
+Run the Firefox browser
+```
+~/mozilla-central/obj-opt64/dist/bin/firefox -no-remote -profile ~/mozilla-central/obj-opt64/tmp/profile-default &
+```
+
+Navigate to the test case, but do not start it yet. Then hover over the tab to get the content process PID.
+
+![](img/pid.png)
+
+Attach perf to begin profiling:
+```
+perf record -g -k 1 -p <pid>
+```
+
+Close the browser when finished benchmarking.
+
+Inject the jitdump files into your perf.data file:
+```
+perf inject -j -i perf.data -o jit.data
+```
+
+View the profile (--call-graph=graph,0 shows all call stacks instead of the default threshold of >= 0.5%):
+```
+perf report --no-children --call-graph=graph,0 -i jit.data
+```
+
+## Additional Information
+
+Some Linux distributions offer a "libc6-prof" package that includes frame pointers. This can help resolve symbols and call stacks that involve libc calls.
+
+On Ubuntu, you can install this with:
+```
+sudo apt-get install libc6-prof
+```
+
+libc6-prof can be used with `LD_LIBRARY_PATH=/lib/libc6-prof/x86_64-linux-gnu`
+
+It may also be useful to have access to kernel addresses during profiling. These can be exposed with:
+```
+sudo sh -c "echo 0 > /proc/sys/kernel/kptr_restrict"
+```
+
+The max stack depth is 127 by default. This is often too few. It can be increased with:
+```
+sudo sh -c "echo 4000 > /proc/sys/kernel/perf_event_max_stack"
+```
diff --git a/docs/performance/memory/DOM_allocation_example.md b/docs/performance/memory/DOM_allocation_example.md
new file mode 100644
index 0000000000..db9a1f2c71
--- /dev/null
+++ b/docs/performance/memory/DOM_allocation_example.md
@@ -0,0 +1,57 @@
+# DOM allocation example
+
+This article describes a very simple web page that we\'ll use to
+illustrate some features of the Memory tool.
+
+You can try out the site at
+<https://mdn.github.io/performance-scenarios/dom-allocs/alloc.html>.
+
+It just contains a script that creates a large number of DOM nodes:
+
+```js
+var toolbarButtonCount = 20;
+var toolbarCount = 200;
+
+function getRandomInt(min, max) {
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+}
+
+function createToolbarButton() {
+ var toolbarButton = document.createElement("span");
+ toolbarButton.classList.add("toolbarbutton");
+ // stop Spidermonkey from sharing instances
+ toolbarButton[getRandomInt(0,5000)] = "foo";
+ return toolbarButton;
+}
+
+function createToolbar() {
+ var toolbar = document.createElement("div");
+ // stop Spidermonkey from sharing instances
+ toolbar[getRandomInt(0,5000)] = "foo";
+ for (var i = 0; i < toolbarButtonCount; i++) {
+ var toolbarButton = createToolbarButton();
+ toolbar.appendChild(toolbarButton);
+ }
+ return toolbar;
+}
+
+function createToolbars() {
+ var container = document.getElementById("container");
+ for (var i = 0; i < toolbarCount; i++) {
+ var toolbar = createToolbar();
+ container.appendChild(toolbar);
+ }
+}
+
+createToolbars();
+```
+
+A simple pseudocode representation of how this code operates looks like
+this:
+
+ createToolbars()
+ -> createToolbar() // called 200 times, creates 1 DIV element each time
+ -> createToolbarButton() // called 20 times per toolbar, creates 1 SPAN element each time
+
+In total, then, it creates 200 `HTMLDivElement` objects, and 4000
+`HTMLSpanElement` objects.
diff --git a/docs/performance/memory/about_colon_memory.md b/docs/performance/memory/about_colon_memory.md
new file mode 100644
index 0000000000..ab9dc81062
--- /dev/null
+++ b/docs/performance/memory/about_colon_memory.md
@@ -0,0 +1,274 @@
+# about:memory
+
+about:memory is a special page within Firefox that lets you view, save,
+load, and diff detailed measurements of Firefox's memory usage. It also
+lets you do other memory-related operations like trigger GC and CC, dump
+GC & CC logs, and dump DMD reports. It is present in all builds and does
+not require any preparation to be used.
+
+## How to generate memory reports
+
+Let's assume that you want to measure Firefox's memory usage. Perhaps
+you want to investigate it yourself, or perhaps someone has asked you to
+use about:memory to generate "memory reports" so they can investigate
+a problem you are having. Follow these steps.
+
+- At the moment of interest (e.g. once Firefox's memory usage has
+ gotten high) open a new tab and type "about:memory" into the
+ address bar and hit "Enter".
+- If you are using a communication channel where files can be sent,
+ such as Bugzilla or email, click on the "Measure and save..."
+ button. This will open a file dialog that lets you save the memory
+ reports to a file of your choosing. (The filename will have a
+ `.json.gz` suffix.) You can then attach or upload the file
+ appropriately. The recipients will be able to view the contents of
+ this file within about:memory in their own Firefox instance.
+- If you are using a communication channel where only text can be
+ sent, such as a comment thread on a website, click on the
+ "Measure..." button. This will cause a tree-like structure to be
+ generated text within about:memory. This structure is just text, so
+ you can copy and paste some or all of this text into any kind of
+ text buffer. (You don't need to take a screenshot.) This text
+ contains fewer measurements than a memory reports file, but is often
+ good enough to diagnose problems. Don't click "Measure..."
+ repeatedly, because that will cause the memory usage of about:memory
+ itself to rise, due to it discarding and regenerating large numbers
+ of DOM nodes.
+
+Note that in both cases the generated data contains privacy-sensitive
+details such as the full list of the web pages you have open in other
+tabs. If you do not wish to share this information, you can select the
+"anonymize" checkbox before clicking on "Measure and save..." or
+"Measure...". This will cause the privacy-sensitive data to be
+stripped out, but it may also make it harder for others to investigate
+the memory usage.
+
+## Loading memory reports from file
+
+The easiest way to load memory reports from file is to use the
+"Load..." button. You can also use the "Load and diff..." button
+to get the difference between two memory report files.
+
+Single memory report files can also be loaded automatically when
+about:memory is loaded by appending a `file` query string, for example:
+
+ about:memory?file=/home/username/reports.json.gz
+
+This is most useful when loading memory reports files obtained from a
+Firefox OS device.
+
+Memory reports are saved to file as gzipped JSON. These files can be
+loaded as is, but they can also be loaded after unzipping.
+
+## Interpreting memory reports
+
+Almost everything you see in about:memory has an explanatory tool-tip.
+Hover over any button to see a description of what it does. Hover over
+any measurement to see a description of what it means.
+
+### [Measurement basics]
+
+Most measurements use bytes as their unit, but some are counts or
+percentages.
+
+Most measurements are presented within trees. For example:
+
+ 585 (100.0%) -- preference-service
+ └──585 (100.0%) -- referent
+ ├──493 (84.27%) ── strong
+ └───92 (15.73%) -- weak
+ ├──92 (15.73%) ── alive
+ └───0 (00.00%) ── dead
+
+Leaf nodes represent actual measurements; the value of each internal
+node is the sum of all its children.
+
+The use of trees allows measurements to be broken down into further
+categories, sub-categories, sub-sub-categories, etc., to arbitrary
+depth, as needed. All the measurements within a single tree are
+non-overlapping.
+
+Tree paths can be written using \'/\' as a separator. For example,
+`preference/referent/weak/dead` represents the path to the final leaf
+node in the example tree above.
+
+Sub-trees can be collapsed or expanded by clicking on them. If you find
+any particular tree overwhelming, it can be helpful to collapse all the
+sub-trees immediately below the root, and then gradually expand the
+sub-trees of interest.
+
+### [Sections]
+
+Memory reports are displayed on a per-process basis, with one process
+per section. Within each process's measurements, there are the
+following subsections.
+
+#### Explicit Allocations
+
+This section contains a single tree, called "explicit", that measures
+all the memory allocated via explicit calls to heap allocation functions
+(such as `malloc` and `new`) and to non-heap allocations functions (such
+as `mmap` and `VirtualAlloc`).
+
+Here is an example for a browser session where tabs were open to
+cnn.com, techcrunch.com, and arstechnica.com. Various sub-trees have
+been expanded and others collapsed for the sake of presentation.
+
+ 191.89 MB (100.0%) -- explicit
+ ├───63.15 MB (32.91%) -- window-objects
+ │ ├──24.57 MB (12.80%) -- top(http://edition.cnn.com/, id=8)
+ │ │ ├──20.18 MB (10.52%) -- active
+ │ │ │ ├──10.57 MB (05.51%) -- window(http://edition.cnn.com/)
+ │ │ │ │ ├───4.55 MB (02.37%) ++ js-compartment(http://edition.cnn.com/)
+ │ │ │ │ ├───2.60 MB (01.36%) ++ layout
+ │ │ │ │ ├───1.94 MB (01.01%) ── style-sheets
+ │ │ │ │ └───1.48 MB (00.77%) -- (2 tiny)
+ │ │ │ │ ├──1.43 MB (00.75%) ++ dom
+ │ │ │ │ └──0.05 MB (00.02%) ── property-tables
+ │ │ │ └───9.61 MB (05.01%) ++ (18 tiny)
+ │ │ └───4.39 MB (02.29%) -- js-zone(0x7f69425b5800)
+ │ ├──15.75 MB (08.21%) ++ top(http://techcrunch.com/, id=20)
+ │ ├──12.85 MB (06.69%) ++ top(http://arstechnica.com/, id=14)
+ │ ├───6.40 MB (03.33%) ++ top(chrome://browser/content/browser.xul, id=3)
+ │ └───3.59 MB (01.87%) ++ (4 tiny)
+ ├───45.74 MB (23.84%) ++ js-non-window
+ ├───33.73 MB (17.58%) ── heap-unclassified
+ ├───22.51 MB (11.73%) ++ heap-overhead
+ ├────6.62 MB (03.45%) ++ images
+ ├────5.82 MB (03.03%) ++ workers/workers(chrome)
+ ├────5.36 MB (02.80%) ++ (16 tiny)
+ ├────4.07 MB (02.12%) ++ storage
+ ├────2.74 MB (01.43%) ++ startup-cache
+ └────2.16 MB (01.12%) ++ xpconnect
+
+Some expertise is required to understand the full details here, but
+there are various things worth pointing out.
+
+- This "explicit" value at the root of the tree represents all the
+ memory allocated via explicit calls to allocation functions.
+- The "window-objects" sub-tree represents all JavaScript `window`
+ objects, which includes the browser tabs and UI windows. For
+ example, the "top(http://edition.cnn.com/, id=8)" sub-tree
+ represents the tab open to cnn.com, and
+ "top(chrome://browser/content/browser.xul, id=3)" represents the
+ main browser UI window.
+- Within each window's measurements are sub-trees for JavaScript
+ ("js-compartment(...)" and "js-zone(...)"), layout,
+ style-sheets, the DOM, and other things.
+- It's clear that the cnn.com tab is using more memory than the
+ techcrunch.com tab, which is using more than the arstechnica.com
+ tab.
+- Sub-trees with names like "(2 tiny)" are artificial nodes inserted
+ to allow insignificant sub-trees to be collapsed by default. If you
+ select the "verbose" checkbox before measuring, all trees will be
+ shown fully expanded and no artificial nodes will be inserted.
+- The "js-non-window" sub-tree represents JavaScript memory usage
+ that doesn't come from windows, but from the browser core.
+- The "heap-unclassified" value represents heap-allocated memory
+ that is not measured by any memory reporter. This is typically
+ 10--20% of "explicit". If it gets higher, it indicates that
+ additional memory reporters should be added.
+ [DMD](./dmd.md)
+ can be used to determine where these memory reporters should be
+ added.
+- There are measurements for other content such as images and workers,
+ and for browser subsystems such as the startup cache and XPConnect.
+
+Some add-on memory usage is identified, as the following example shows.
+
+ ├───40,214,384 B (04.17%) -- add-ons
+ │ ├──21,184,320 B (02.20%) ++ {d10d0bf8-f5b5-c8b4-a8b2-2b9879e08c5d}/js-non-window/zones/zone(0x100496800)/compartment([System Principal], jar:file:///Users/njn/Library/Application%20Support/Firefox/Profiles/puna0zr8.new/extensions/%7Bd10d0bf8-f5b5-c8b4-a8b2-2b9879e08c5d%7D.xpi!/bootstrap.js (from: resource://gre/modules/addons/XPIProvider.jsm:4307))
+ │ ├──11,583,312 B (01.20%) ++ jid1-xUfzOsOFlzSOXg@jetpack/js-non-window/zones/zone(0x100496800)
+ │ ├───5,574,608 B (00.58%) -- {59c81df5-4b7a-477b-912d-4e0fdf64e5f2}
+ │ │ ├──5,529,280 B (00.57%) -- window-objects
+ │ │ │ ├──4,175,584 B (00.43%) ++ top(chrome://chatzilla/content/chatzilla.xul, id=4293)
+ │ │ │ └──1,353,696 B (00.14%) ++ top(chrome://chatzilla/content/output-window.html, id=4298)
+ │ │ └─────45,328 B (00.00%) ++ js-non-window/zones/zone(0x100496800)/compartment([System Principal], file:///Users/njn/Library/Application%20Support/Firefox/Profiles/puna0zr8.new/extensions/%7B59c81df5-4b7a-477b-912d-4e0fdf64e5f2%7D/components/chatzilla-service.js)
+ │ └───1,872,144 B (00.19%) ++ treestyletab@piro.sakura.ne.jp/js-non-window/zones/zone(0x100496800)
+
+More things worth pointing out are as follows.
+
+- Some add-ons are identified by a name, such as Tree Style Tab.
+ Others are identified only by a hexadecimal identifier. You can look
+ in about:support to see which add-on a particular identifier belongs
+ to. For example, `59c81df5-4b7a-477b-912d-4e0fdf64e5f2` is
+ Chatzilla.
+- All JavaScript memory usage for an add-on is measured separately and
+ shown in this sub-tree.
+- For add-ons that use separate windows, such as Chatzilla, the memory
+ usage of those windows will show up in this sub-tree.
+- For add-ons that use XUL overlays, such as AdBlock Plus, the memory
+ usage of those overlays will not show up in this sub-tree; it will
+ instead be in the non-add-on sub-trees and won't be identifiable as
+ being caused by the add-on.
+
+#### Other Measurements
+
+This section contains multiple trees, includes many that cross-cut the
+measurements in the "explicit" tree. For example, in the "explicit"
+tree all DOM and layout measurements are broken down by window by
+window, but in "Other Measurements" those measurements are aggregated
+into totals for the whole browser, as the following example shows.
+
+ 26.77 MB (100.0%) -- window-objects
+ ├──14.59 MB (54.52%) -- layout
+ │ ├───6.22 MB (23.24%) ── style-sets
+ │ ├───4.00 MB (14.95%) ── pres-shell
+ │ ├───1.79 MB (06.68%) ── frames
+ │ ├───0.89 MB (03.33%) ── style-contexts
+ │ ├───0.62 MB (02.33%) ── rule-nodes
+ │ ├───0.56 MB (02.10%) ── pres-contexts
+ │ ├───0.47 MB (01.75%) ── line-boxes
+ │ └───0.04 MB (00.14%) ── text-runs
+ ├───6.53 MB (24.39%) ── style-sheets
+ ├───5.59 MB (20.89%) -- dom
+ │ ├──3.39 MB (12.66%) ── element-nodes
+ │ ├──1.56 MB (05.84%) ── text-nodes
+ │ ├──0.54 MB (02.03%) ── other
+ │ └──0.10 MB (00.36%) ++ (4 tiny)
+ └───0.06 MB (00.21%) ── property-tables
+
+Some of the trees in this section measure things that do not cross-cut
+the measurements in the "explicit" tree, such as those in the
+"preference-service" example above.
+
+Finally, at the end of this section are individual measurements, as the
+following example shows.
+
+ 0.00 MB ── canvas-2d-pixels
+ 5.38 MB ── gfx-surface-xlib
+ 0.00 MB ── gfx-textures
+ 0.00 MB ── gfx-tiles-waste
+ 0 ── ghost-windows
+ 109.22 MB ── heap-allocated
+ 164 ── heap-chunks
+ 1.00 MB ── heap-chunksize
+ 114.51 MB ── heap-committed
+ 164.00 MB ── heap-mapped
+ 4.84% ── heap-overhead-ratio
+ 1 ── host-object-urls
+ 0.00 MB ── imagelib-surface-cache
+ 5.27 MB ── js-main-runtime-temporary-peak
+ 0 ── page-faults-hard
+ 203,349 ── page-faults-soft
+ 274.99 MB ── resident
+ 251.47 MB ── resident-unique
+ 1,103.64 MB ── vsize
+
+Some measurements of note are as follows.
+
+- "resident". Physical memory usage. If you want a single
+ measurement to summarize memory usage, this is probably the best
+ one.
+- "vsize". Virtual memory usage. This is often much higher than any
+ other measurement (particularly on Mac). It only really matters on
+ 32-bit platforms such as Win32. There is also
+ "vsize-max-contiguous" (not measured on all platforms, and not
+ shown in this example), which indicates the largest single chunk of
+ available virtual address space. If this number is low, it's likely
+ that memory allocations will fail due to lack of virtual address
+ space quite soon.
+- Various graphics-related measurements ("gfx-*"). The measurements
+ taken vary between platforms. Graphics is often a source of high
+ memory usage, and so these measurements can be helpful for detecting
+ such cases.
diff --git a/docs/performance/memory/aggregate_view.md b/docs/performance/memory/aggregate_view.md
new file mode 100644
index 0000000000..9a4f01e01e
--- /dev/null
+++ b/docs/performance/memory/aggregate_view.md
@@ -0,0 +1,198 @@
+# Aggregate view
+
+Before Firefox 48, this was the default view of a heap snapshot. After
+Firefox 48, the default view is the [Tree map
+view](tree_map_view.md), and you can switch to the
+Aggregate view using the dropdown labeled \"View:\":
+
+![](../img/memory-tool-switch-view.png)
+
+The Aggregate view looks something like this:
+
+![](../img/memory-tool-aggregate-view.png)
+
+It presents a breakdown of the heap\'s contents, as a table. There are
+three main ways to group the data:
+
+- Type
+- Call Stack
+- Inverted Call Stack
+
+You can switch between them using the dropdown menu labeled \"Group
+by:\" located at the top of the panel:
+
+There\'s also a box labeled \"Filter\" at the top-right of the pane. You
+can use this to filter the contents of the snapshot that are displayed,
+so you can quickly see, for example, how many objects of a specific
+class were allocated.
+
+## Type
+
+This is the default view, which looks something like this:
+
+![](../img/memory-tool-aggregate-view.png)
+
+It groups the things on the heap into types, including:
+
+- **JavaScript objects:** such as `Function` or `Array`
+- **DOM elements:** such as `HTMLSpanElement` or `Window`
+- **Strings:** listed as `"strings"`
+- **JavaScript sources:** listed as \"`JSScript"`
+- **Internal objects:** such as \"`js::Shape`\". These are prefixed
+ with `"js::"`.
+
+Each type gets a row in the table, and rows are ordered by the amount of
+memory occupied by objects of that type. For example, in the screenshot
+above you can see that JavaScript `Object`s account for most memory,
+followed by strings.
+
+- The \"Total Count\" column shows you the number of objects of each
+ category that are currently allocated.
+- The \"Total Bytes\" column shows you the number of bytes occupied by
+ objects in each category, and that number as a percentage of the
+ whole heap size for that tab.
+
+The screenshots in this section are taken from a snapshot of the
+[monster example page](monster_example.md).
+
+For example, in the screenshot above, you can see that:
+
+- there are four `Array` objects
+- that account for 15% of the total heap.
+
+Next to the type\'s name, there\'s an icon that contains three stars
+arranged in a triangle:
+
+![](../img/memory-tool-in-group-icon.png)
+
+Click this to see every instance of that type. For example, the entry
+for `Array` tells us that there are four `Array` objects in the
+snapshot. If we click the star-triangle, we\'ll see all four `Array`
+instances:
+
+![](../img/memory-tool-in-group.png)
+
+For each instance, you can see the [retained size and shallow
+size](dominators.md#shallow_and_retained_size) of
+that instance. In this case, you can see that the first three arrays
+have a fairly large shallow size (5% of the total heap usage) and a much
+larger retained size (26% of the total).
+
+On the right-hand side is a pane that just says \"Select an item to view
+its retaining paths\". If you select an item, you\'ll see the [Retaining
+paths
+panel](dominators_view.md#retaining_paths_panel)
+for that item:
+
+![](../img/memory-tool-in-group-retaining-paths.png)
+
+<iframe width="595" height="325" src="https://www.youtube.com/embed/uLjzrvx_VCg" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"></iframe>
+
+
+## Call Stack
+
+The Call Stack shows you exactly where in your code you are making heap
+allocations.
+
+Because tracing allocations has a runtime cost, it must be explicitly
+enabled by checking \"Record call stacks\" *before* you allocate the
+memory in the snapshot.
+
+You\'ll then see a list of all the functions that allocated objects,
+ordered by the size of the allocations they made:
+
+![](../img/memory-tool-call-stack.png)
+\
+The first entry says that:
+
+- 4,832,592 bytes, comprising 93% of the total heap usage, were
+ allocated in a function at line 35 of \"alloc.js\", **or in
+ functions called by that function**
+
+We can use the disclosure triangle to drill down the call tree, to find
+the exact place your code made those allocations.
+
+It\'s easier to explain this with reference to a simple example. For
+this we\'ll use the [DOM allocation
+example](DOM_allocation_example.md). This page
+runs a script that creates a large number of DOM nodes (200
+`HTMLDivElement` objects and 4000 `HTMLSpanElement` objects).
+
+Let\'s get an allocation trace:
+
+1. open the Memory tool
+2. check \"Record call stacks\"
+3. load
+ <https://mdn.github.io/performance-scenarios/dom-allocs/alloc.html>
+4. take a snapshot
+5. select \"View/Aggregate\"
+6. select \"Group by/Call Stack\"
+
+<iframe width="595" height="325" src="https://www.youtube.com/embed/DyLulu9eoKY" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"></iframe>
+
+You should see something like this:
+
+![](../img/memory-tool-call-stack.png)
+
+This is telling us that 93% of the total heap snapshot was allocated in
+functions called from \"alloc.js\", line 35 (our initial
+`createToolbars()` call).
+
+We can use the disclosure arrow to expand the tree to find out exactly
+where we\'re allocating memory:
+
+![](../img/memory-tool-call-stack-expanded.png)
+
+This is where the \"Bytes\" and \"Count\" columns are useful: they show
+allocation size and number of allocations at that exact point.
+
+So in the example above, we can see that we made 4002 allocations,
+accounting for 89% of the total heap, in `createToolbarButton()`, at
+[alloc.js line 9, position
+23](https://github.com/mdn/performance-scenarios/blob/gh-pages/dom-allocs/scripts/alloc.js#L9):
+that is, the exact point where we create the span
+elements.
+
+The file name and line number is a link: if we click it, we go directly
+to that line in the debugger:
+
+<iframe width="595" height="325" src="https://www.youtube.com/embed/zlnJcr1IFyY" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"></iframe>
+
+
+## Inverted Call Stack
+
+The Call Stack view is top-down: it shows allocations that happen at
+that point **or points deeper in the call tree**. So it\'s good for
+getting an overview of where your program is memory-hungry. However,
+this view means you have to drill a long way down to find the exact
+place where the allocations are happening.
+
+The \"Inverted Call Stack\" view helps with that. It gives you the
+bottom-up view of the program showing the exact places where allocations
+are happening, ranked by the size of allocation at each place. The
+disclosure arrow then walks you back up the call tree towards the top
+level.
+
+Let\'s see what the example looks like when we select \"Inverted Call
+Stack\":
+
+![](../img/memory-tool-inverted-call-stack.png)
+
+Now at the top we can immediately see the `createToolbarButton()` call
+accounting for 89% of the heap usage in our page.
+
+## no stack available
+
+In the example above you\'ll note that 7% of the heap is marked \"(no
+stack available)\". This is because not all heap usage results from your
+JavaScript.
+
+For example:
+
+- any scripts the page loads occupy heap space
+- sometimes an object is allocated when there is no JavaScript on the
+ stack. For example, DOM Event objects are allocated
+ before the JavaScript is run and event handlers are called.
+
+Many real-world pages will have a much higher \"(no stack available)\"
+share than 7%.
diff --git a/docs/performance/memory/awsy.md b/docs/performance/memory/awsy.md
new file mode 100644
index 0000000000..5026f055aa
--- /dev/null
+++ b/docs/performance/memory/awsy.md
@@ -0,0 +1,22 @@
+# Are We Slim Yet (AWSY)
+
+The Are We Slim Yet project (commonly known as AWSY) for several years
+tracked memory usage across builds on the (now defunct) website.
+It used the same
+infrastructure as
+[about:memory](about_colon_memory.md) to measure
+memory usage on a predefined snapshot of Alexa top 100 pages known as
+tp5.
+
+Since Firefox transitioned to using multiple processes by default, we
+[moved AWSY into the
+TaskCluster](https://bugzilla.mozilla.org/show_bug.cgi?id=1272113)
+infrastructure. This allowed us to run measurements on all branches and
+platforms. The results are posted to
+[perfherder](https://treeherder.mozilla.org/perf.html) where we can
+track regressions automatically.
+
+As new processes are added to Firefox we want to make sure their memory
+usage is also tracked by AWSY. To this end we request that memory
+reporting be integrated into any new process before it is enabled on
+Nightly.
diff --git a/docs/performance/memory/basic_operations.md b/docs/performance/memory/basic_operations.md
new file mode 100644
index 0000000000..276c38bc2e
--- /dev/null
+++ b/docs/performance/memory/basic_operations.md
@@ -0,0 +1,82 @@
+# Basic operations
+
+## Opening the Memory tool
+
+Before Firefox 50, the Memory tool is not enabled by default. To enable
+it, open the developer tool settings, and check the "Memory" box under
+"Default Firefox Developer Tools":
+
+<iframe width="595" height="325" src="https://www.youtube.com/embed/qi-0CoCOXw" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"></iframe>
+
+From Firefox 50 onwards, the Memory tool is enabled by default.
+
+## Taking a heap snapshot
+
+To take a snapshot of the heap, click the "Take snapshot" button, or
+the camera icon on the left:
+
+![memoryimage1](../img/memory-1-small.png)
+
+The snapshot will occupy the large pane on the right-hand side. On the
+left, you'll see an entry for the new snapshot, including its
+timestamp, size, and controls to save or clear this snapshot:
+
+![memoryimage2](../img/memory-2-small.png)
+
+## Clearing a snapshot
+
+To remove a snapshot, click the "X" icon:
+
+![memoryimage3](../img/memory-3-small.png)
+
+## Saving and loading snapshots
+
+If you close the Memory tool, all unsaved snapshots will be discarded.
+To save a snapshot click "Save":
+
+![memoryimage4](../img/memory-4-small.png)
+
+You'll be prompted for a name and location, and the file will be saved
+with an `.fxsnapshot` extension.
+
+To load a snapshot from an existing `.fxsnapshot` file, click the import
+button, which looks like a rectangle with an arrow rising from it
+(before Firefox 49, this button was labeled with the text
+"Import\...\"):
+
+![memoryimage5](../img/memory-5-small.png)
+
+You'll be prompted to find a snapshot file on disk.
+
+## Comparing snapshots
+
+Starting in Firefox 45, you can diff two heap snapshots. The diff shows
+you where memory was allocated or freed between the two snapshots.
+
+To create a diff, click the button that looks like a Venn diagram next
+to the camera icon (before Firefox 47, this looked like a \"+/-\" icon):
+
+![memoryimage6](../img/memory-6-small.png)
+
+You'll be prompted to select the snapshot to use as a baseline, then
+the snapshot to compare. The tool then shows you the differences between
+the two snapshots:
+
+<iframe width="595" height="325" src="https://www.youtube.com/embed/3Ow-mdK6b2M" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"></iframe>
+
+
+::: {.note}
+When you're looking at a comparison, you can't use the Dominators view
+or the Tree Map view.
+:::
+
+## Recording call stacks
+
+The Memory tool can tell you exactly where in your code you are
+allocating memory. However, recording this information has a run-time
+cost, so you must ask the tool to record memory calls *before* the
+memory is allocated, if you want to see memory call sites in the
+snapshot. To do this, check "Record call stacks" (before Firefox 49
+this was labeled "Record allocation stacks"):
+
+![memoryimage7](../img/memory-7-small.png)
diff --git a/docs/performance/memory/bloatview.md b/docs/performance/memory/bloatview.md
new file mode 100644
index 0000000000..9e290011b1
--- /dev/null
+++ b/docs/performance/memory/bloatview.md
@@ -0,0 +1,245 @@
+# Bloatview
+
+BloatView is a tool that shows information about cumulative memory usage
+and leaks. If it finds leaks, you can use [refcount tracing and balancing](refcount_tracing_and_balancing.md)
+to discover the root cause.
+
+## How to build with BloatView
+
+Build with `--enable-debug` or `--enable-logrefcnt`.
+
+## How to run with BloatView
+
+The are two environment variables that can be used.
+
+ XPCOM_MEM_BLOAT_LOG
+
+If set, this causes a *bloat log* to be printed on program exit, and
+each time `nsTraceRefcnt::DumpStatistics` is called. This log contains
+data on leaks and bloat (a.k.a. usage).
+
+ XPCOM_MEM_LEAK_LOG
+
+This is similar to `XPCOM_MEM_BLOAT_LOG`, but restricts the log to only
+show data on leaks.
+
+You can set these environment variables to any of the following values.
+
+- **1** - log to stdout.
+- **2** - log to stderr.
+- ***filename*** - write log to a file.
+
+## Reading individual bloat logs
+
+Full BloatView output contains per-class statistics on allocations and
+refcounts, and provides gross numbers on the amount of memory being
+leaked broken down by class. Here's a sample of the BloatView output.
+
+ == BloatView: ALL (cumulative) LEAK AND BLOAT STATISTICS, tab process 1862
+ |<----------------Class--------------->|<-----Bytes------>|<----Objects---->|
+ | | Per-Inst Leaked| Total Rem|
+ 0 |TOTAL | 17 2484|253953338 38|
+ 17 |AsyncTransactionTrackersHolder | 40 40| 10594 1|
+ 78 |CompositorChild | 472 472| 1 1|
+ 79 |CondVar | 24 48| 3086 2|
+ 279 |MessagePump | 8 8| 30 1|
+ 285 |Mutex | 20 60| 89987 3|
+ 302 |PCompositorChild | 412 412| 1 1|
+ 308 |PImageBridgeChild | 416 416| 1 1|
+
+The first line tells you the pid of the leaking process, along with the
+type of process.
+
+Here's how you interpret the columns.
+
+- The first, numerical column [is the index](https://searchfox.org/mozilla-central/source/xpcom/base/nsTraceRefcnt.cpp#365)
+ of the leaking class.
+- **Class** - The name of the class in question (truncated to 20
+ characters).
+- **Bytes Per-Inst** - The number of bytes returned if you were to
+ write `sizeof(Class)`. Note that this number does not reflect any
+ memory held onto by the class, such as internal buffers, etc. (E.g.
+ for `nsString` you'll see the size of the header struct, not the
+ size of the string contents!)
+- **Bytes Leaked** - The number of bytes per instance times the number
+ of objects leaked: (Bytes Per-Inst) x (Objects Rem). Use this number
+ to look for the worst offenders. (Should be zero!)
+- **Objects Total** - The total count of objects allocated of a given
+ class.
+- **Objects Rem** - The number of objects allocated of a given class
+ that weren't deleted. (Should be zero!)
+
+Interesting things to look for:
+
+- **Are your classes in the list?** - Look! If they aren't, then
+ you're not using the `NS_IMPL_ADDREF` and `NS_IMPL_RELEASE` (or
+ `NS_IMPL_ISUPPORTS` which calls them) for xpcom objects, or
+ `MOZ_COUNT_CTOR` and `MOZ_COUNT_DTOR` for non-xpcom objects. Not
+ having your classes in the list is *not* ok. That means no one is
+ looking at them, and we won't be able to tell if someone introduces
+ a leak. (See
+ [below](#how-to-instrument-your-objects-for-bloatview)
+ for how to fix this.)
+- **The Bytes Leaked for your classes should be zero!** - Need I say
+ more? If it isn't, you should use the other tools to fix it.
+- **The number of objects remaining might not be equal to the total
+ number of objects.** This could indicate a hand-written Release
+ method (that doesn't use the `NS_LOG_RELEASE` macro from
+ nsTraceRefcnt.h), or perhaps you're just not freeing any of the
+ instances you've allocated. These sorts of leaks are easy to fix.
+- **The total number of objects might be 1.** This might indicate a
+ global variable or service. Usually this will have a large number of
+ refcounts.
+
+If you find leaks, you can use [refcount tracing and balancing](refcount_tracing_and_balancing.md)
+to discover the root cause.
+
+## Combining and sorting bloat logs
+
+You can view one or more bloat logs in your browser by running the
+following program.
+
+ perl tools/bloatview/bloattable.pl *log1* *log2* \... *logn* >
+ *htmlfile*
+
+This will produce an HTML file that contains a table similar to the
+following (but with added JavaScript so you can sort the data by
+column).
+
+ Byte Bloats
+
+ ---------- ---------------- --------------------------
+ Name File Date
+ blank `blank.txt` Tue Aug 29 14:17:40 2000
+ mozilla `mozilla.txt` Tue Aug 29 14:18:42 2000
+ yahoo `yahoo.txt` Tue Aug 29 14:19:32 2000
+ netscape `netscape.txt` Tue Aug 29 14:20:14 2000
+ ---------- ---------------- --------------------------
+
+The numbers do not include malloc'd data such as string contents.
+
+Click on a column heading to sort by that column. Click on a class name
+to see details for that class.
+
+ -------------------- --------------- ----------------- --------- --------- ---------- ---------- ------------------------------- --------- -------- ---------- ---------
+ Class Name Instance Size Bytes allocated Bytes allocated but not freed
+ blank mozilla yahoo netscape Total blank mozilla yahoo netscape Total
+ TOTAL 1754408 432556 179828 404184 2770976
+ nsStr 20 6261600 3781900 1120920 1791340 12955760 222760 48760 13280 76160 360960
+ nsHashKey 8 610568 1842400 2457872 1134592 6045432 32000 536 568 1216 34320
+ nsTextTransformer 548 8220 469088 1414936 1532756 3425000 0 0 0 0 0
+ nsStyleContextData 736 259808 325312 489440 338560 1413120 141312 220800 -11040 94944 446016
+ nsLineLayout 1100 2200 225500 402600 562100 1192400 0 0 0 0 0
+ nsLocalFile 424 558832 19928 1696 1272 581728 72080 1272 424 -424 73352
+ -------------------- --------------- ----------------- --------- --------- ---------- ---------- ------------------------------- --------- -------- ---------- ---------
+
+The first set of columns, **Bytes allocated**, shows the amount of
+memory allocated for the first log file (`blank.txt`), the difference
+between the first log file and the second (`mozilla.txt`), the
+difference between the second log file and the third (`yahoo.txt`), the
+difference between the third log file and the fourth (`netscape.txt`),
+and the total amount of memory allocated in the fourth log file. These
+columns provide an idea of how hard the memory allocator has to work,
+but they do not indicate the size of the working set.
+
+The second set of columns, **Bytes allocated but not freed**, shows the
+net memory gain or loss by subtracting the amount of memory freed from
+the amount allocated.
+
+The **Show Objects** and **Show References** buttons show the same
+statistics but counting objects or `AddRef`'d references rather than
+bytes.
+
+## Comparing Bloat Logs
+
+You can also compare any two bloat logs (either those produced when the
+program shuts down, or written to the bloatlogs directory) by running
+the following program.
+
+ `perl tools/bloatview/bloatdiff.pl` <previous-log> <current-log>
+
+This will give you output of the form:
+
+ Bloat/Leak Delta Report
+ Current file: dist/win32_D.OBJ/bin/bloatlogs/all-1999-10-22-133450.txt
+ Previous file: dist/win32_D.OBJ/bin/bloatlogs/all-1999-10-16-010302.txt
+ --------------------------------------------------------------------------
+ CLASS LEAKS delta BLOAT delta
+ --------------------------------------------------------------------------
+ TOTAL 6113530 2.79% 67064808 9.18%
+ StyleContextImpl 265440 81.19% 283584 -26.99%
+ CToken 236500 17.32% 306676 20.64%
+ nsStr 217760 14.94% 5817060 7.63%
+ nsXULAttribute 113048 -70.92% 113568 -71.16%
+ LiteralImpl 53280 26.62% 75840 19.40%
+ nsXULElement 51648 0.00% 51648 0.00%
+ nsProfile 51224 0.00% 51224 0.00%
+ nsFrame 47568 -26.15% 48096 -50.49%
+ CSSDeclarationImpl 42984 0.67% 43488 0.67%
+
+This "delta report" shows the leak offenders, sorted from most leaks
+to fewest. The delta numbers show the percentage change between runs for
+the amount of leaks and amount of bloat (negative numbers are better!).
+The bloat number is a metric determined by multiplying the total number
+of objects allocated of a given class by the class size. Note that
+although this isn't necessarily the amount of memory consumed at any
+given time, it does give an indication of how much memory we're
+consuming. The more memory in general, the worse the performance and
+footprint. The percentage 99999.99% will show up indicating an
+"infinite" amount of leakage. This happens when something that didn't
+leak before is now leaking.
+
+## BloatView and continuous integration
+
+BloatView runs on debug builds for many of the test suites Mozilla has
+running under continuous integration. If a new leak occurs, it will
+trigger a test job failure.
+
+BloatView's output file can also show you where the leaked objects are
+allocated. To do so, the `XPCOM_MEM_LOG_CLASSES` environment variable
+should be set to the name of the class from the BloatView table:
+
+ XPCOM_MEM_LOG_CLASSES=MyClass mach mochitest [options]
+
+Multiple class names can be specified by setting `XPCOM_MEM_LOG_CLASSES`
+to a comma-separated list of names:
+
+ XPCOM_MEM_LOG_CLASSES=MyClass,MyOtherClass,DeliberatelyLeakedClass mach mochitest [options]
+
+Test harness scripts typically accept a `--setenv` option for specifying
+environment variables, which may be more convenient in some cases:
+
+ mach mochitest --setenv=XPCOM_MEM_LOG_CLASSES=MyClass [options]
+
+For getting allocation stacks in automation, you can add the appropriate
+`--setenv` options to the test configurations for the platforms you're
+interested in. Those configurations are located in
+`testing/mozharness/configs/`. The most likely configs you'll want to
+modify are listed below:
+
+- Linux: `unittests/linux_unittest.py`
+- Mac: `unittests/mac_unittest.py`
+- Windows: `unittests/win_unittest.py`
+- Android: `android/androidarm.py`
+
+## How to instrument your objects for BloatView
+
+First, if your object is an xpcom object and you use the
+`NS_IMPL_ADDREF` and `NS_IMPL_RELEASE` (or a variation thereof) macro to
+implement your `AddRef` and `Release` methods, then there is nothing you
+need do. By default, those macros support refcnt logging directly.
+
+If your object is not an xpcom object then some manual editing is in
+order. The following sample code shows what must be done:
+
+ MyType::MyType()
+ {
+ MOZ_COUNT_CTOR(MyType);
+ ...
+ }
+
+ MyType::~MyType()
+ {
+ MOZ_COUNT_DTOR(MyType);
+ ...
+ }
diff --git a/docs/performance/memory/dmd.md b/docs/performance/memory/dmd.md
new file mode 100644
index 0000000000..ebd6b5a2f8
--- /dev/null
+++ b/docs/performance/memory/dmd.md
@@ -0,0 +1,489 @@
+# Dark Matter Detector (DMD)
+
+DMD (short for "dark matter detector") is a heap profiler within
+Firefox. It has four modes.
+
+- "Dark Matter" mode. In this mode, DMD tracks the contents of the
+ heap, including which heap blocks have been reported by memory
+ reporters. It helps us reduce the "heap-unclassified" value in
+ Firefox's about:memory page, and also detects if any heap blocks
+ are reported twice. Originally, this was the only mode that DMD had,
+ which explains DMD's name. This is the default mode.
+- "Live" mode. In this mode, DMD tracks the current contents of the
+ heap. You can dump that information to file, giving a profile of the
+ live heap blocks at that point in time. This is good for
+ understanding how memory is used at an interesting point in time,
+ such as peak memory usage.
+- "Cumulative" mode. In this mode, DMD tracks both the past and
+ current contents of the heap. You can dump that information to file,
+ giving a profile of the heap usage for the entire session. This is
+ good for finding parts of the code that cause high heap churn, e.g.
+ by allocating many short-lived allocations.
+- "Heap scanning" mode. This mode is like live mode, but it also
+ records the contents of every live block in the log. This can be
+ used to investigate leaks by figuring out which objects might be
+ holding references to other objects.
+
+## Building and Running
+
+### Nightly Firefox
+
+The easiest way to use DMD is with the normal Nightly Firefox build,
+which has DMD already enabled in the build. To have DMD active while
+running it, you just need to set the environment variable `DMD=1` when
+running. For instance, on OSX, you can run something like:
+
+ DMD=1 /Applications/Firefox\ Nightly.app/Contents/MacOS/firefox
+
+You can tell it is working by going to `about:memory` and looking for
+"Save DMD Output". If DMD has been properly enabled, the "Save"
+button won't be grayed out. Look at the "Trigger" section below to
+see the full list of ways to get a DMD report once you have it
+activated. Note that the stack information you get will likely be less
+detailed, due to being unable to symbolicate. You will be able to get
+function names, but not line numbers.
+
+### Desktop Firefox
+
+#### Build
+
+Build Firefox with this option added to your mozconfig:
+
+ ac_add_options --enable-dmd
+
+If building via try server, modify
+`browser/config/mozconfigs/linux64/common-opt` or a similar file before
+pushing.
+
+#### Launch
+
+Use `mach run --dmd`; use `--mode` to choose the mode.
+
+On a Windows build done by the try server, [these
+instructions](https://bugzilla.mozilla.org/show_bug.cgi?id=936784#c69) from
+2013 may or may not be useful.
+
+#### Trigger
+
+There are a few ways to trigger a DMD snapshot. Most of these will also
+first get a memory report. When DMD is working on writing its output, it
+will print logging like this:
+
+ DMD[5222] opened /tmp/dmd-1414556492-5222.json.gz for writing
+ DMD[5222] Dump 1 {
+ DMD[5222] Constructing the heap block list...
+ DMD[5222] Constructing the stack trace table...
+ DMD[5222] Constructing the stack frame table...
+ DMD[5222] }
+
+You'll see separate output for each process. This step can take 10 or
+more seconds and may make Firefox freeze temporarily.
+
+If you see the "opened" line, it tells you where the file was saved.
+It's always in a temp directory, and the filenames are always of the
+form dmd-<pid>.
+
+The ways to trigger a DMD snapshot are:
+
+1. Visit about:memory and click the "Save" button under "Save DMD output".
+ The button won't be present in non-DMD builds, and will be grayed out
+ in DMD builds if DMD isn't enabled at start-up.
+
+2. If you wish to trigger DMD dumps from within C++ or JavaScript code,
+ you can use `nsIMemoryInfoDumper.dumpMemoryInfoToTempDir`. For example,
+ from JavaScript code you can do the following.
+
+ const Cc = Components.classes;
+ let mydumper = Cc["@mozilla.org/memory-info-dumper;1"]
+ .getService(Ci.nsIMemoryInfoDumper);
+ mydumper.dumpMemoryInfoToTempDir(identifier, anonymize, minimize);
+
+ This will dump memory reports and DMD output to the temporary
+ directory. `identifier` is a string that will be used for part of
+ the filename (or a timestamp will be used if it is an empty string);
+ `anonymize` is a boolean that indicates if the memory reports should
+ be anonymized; and `minimize` is a boolean that indicates if memory
+ usage should be minimized first.
+
+3. On Linux, you can send signal 34 to the firefox process, e.g.
+ with the following command.
+
+ $ killall -34 firefox
+
+4. The `MOZ_DMD_SHUTDOWN_LOG` environment variable, if set, triggers a DMD
+ run at shutdown; its value must be a directory where the logs will be
+ placed. This is mostly useful for debugging leaks. Which processes get
+ logged is controlled by the `MOZ_DMD_LOG_PROCESS` environment variable.
+ If this is not set, it will log all processes. It can be set to any valid
+ value of `XRE_GetProcessTypeString()` and will log only those processes.
+ For instance, if set to `default` it will only log the parent process. If
+ set to `tab`, it will log content processes only.
+
+ For example, if you have
+
+ MOZ_DMD_SHUTDOWN_LOG=~/dmdlogs/ MOZ_DMD_LOG_PROCESS=tab
+
+ then DMD will create logs at shutdown for content processes and save them to
+ `~/dmdlogs/`.
+
+**NOTE:**
+
+- To dump DMD data from content processes, you'll need to disable the
+ sandbox with `MOZ_DISABLE_CONTENT_SANDBOX=1`.
+- MOZ_DMD_SHUTDOWN_LOG must (currently) include the trailing separator
+ (\'\'/\")
+
+
+### Fennec
+
+**NOTE:**
+
+You'll note from the name of this section being "Fennec" that these instructions
+are very old. Hopefully they'll be more useful than not having them.
+
+**NOTE:**
+
+In order to use DMD on Fennec you will need root access on the Android
+device. Instructions on how to root your device is outside the scope of
+this document.
+
+
+#### Build
+
+Build with these options:
+
+ ac_add_options --enable-dmd
+
+#### Prep
+
+In order to prepare your device for running Fennec with DMD enabled, you
+will need to do a few things. First, you will need to push the libdmd.so
+library to the device so that it can by dynamically loaded by Fennec.
+You can do this by running:
+
+ adb push $OBJDIR/dist/bin/libdmd.so /sdcard/
+
+Second, you will need to make an executable wrapper for Fennec which
+sets an environment variable before launching it. (If you are familiar
+with the recommended "--es env0" method for setting environment
+variables when launching Fennec, note that you cannot use this method
+here because those are processed too late in the startup process. If you
+are not familiar with that method, you can ignore this parenthetical
+note.) First make the executable wrapper on your host machine using the
+editor of your choice. Name the file dmd_fennec and enter this as the
+contents:
+
+ #!/system/bin/sh
+ export MOZ_REPLACE_MALLOC_LIB=/sdcard/libdmd.so
+ exec "$@"
+
+If you want to use other DMD options, you can enter additional
+environment variables above. You will need to push this to the device
+and make it executable. Since you cannot mark files in /sdcard/ as
+executable, we will use /data/local/tmp for this purpose:
+
+ adb push dmd_fennec /data/local/tmp
+ adb shell
+ cd /data/local/tmp
+ chmod 755 dmd_fennec
+
+The final step is to make Android use the above wrapper script while
+launching Fennec, so that the environment variable is present when
+Fennec starts up. Assuming you have done a local build, the app
+identifier will be `org.mozilla.fennec_$USERNAME` (`$USERNAME` is your
+username on the host machine) and so we do this as shown below. If you
+are using a DMD-enabled try build, or build from other source, adjust
+the app identifier as necessary.
+
+ adb shell
+ su # You need root access for the setprop command to take effect
+ setprop wrap.org.mozilla.fennec_$USERNAME "/data/local/tmp/dmd_fennec"
+
+Once this is set up, starting the `org.mozilla.fennec_$USERNAME` app
+will use the wrapper script.
+
+#### Launch
+
+Launch Fennec either by tapping on the icon as usual, or from the
+command line (as before, be sure to replace
+`org.mozilla.fennec_$USERNAME` with the app identifier as appropriate).
+
+ adb shell am start -n org.mozilla.fennec_$USERNAME/.App
+
+#### Trigger
+
+Use the existing memory-report dumping hook:
+
+ adb shell am broadcast -a org.mozilla.gecko.MEMORY_DUMP
+
+In logcat, you should see output similar to this:
+
+ I/DMD (20731): opened /storage/emulated/0/Download/memory-reports/dmd-default-20731.json.gz for writing
+ ...
+ I/GeckoConsole(20731): nsIMemoryInfoDumper dumped reports to /storage/emulated/0/Download/memory-reports/unified-memory-report-default-20731.json.gz
+
+The path is where the memory reports and DMD reports get dumped to. You
+can pull them like so:
+
+ adb pull /sdcard/Download/memory-reports/dmd-default-20731.json.gz
+ adb pull /sdcard/Download/memory-reports/unified-memory-report-default-20731.json.gz
+
+## Processing the output
+
+DMD outputs one gzipped JSON file per process that contains a
+description of that process's heap. You can analyze these files (either
+gzipped or not) using `dmd.py`. On Nightly Firefox, `dmd.py` is included
+in the distribution. For instance on OS X, it is located in the
+directory `/Applications/Firefox Nightly.app/Contents/Resources/`. For
+Nightly, symbolication will fail, but you can at least get some
+information. In a local build, `dmd.py` will be located in the directory
+`$OBJDIR/dist/bin/`.
+
+Some platforms (Linux, Mac, Android) require stack fixing, which adds
+missing filenames, function names and line number information. This will
+occur automatically the first time you run `dmd.py` on the output file.
+This can take 10s of seconds or more to complete. (This will fail if
+your build does not contain symbols. However, if you have crash reporter
+symbols for your build -- as tryserver builds do -- you can use [this
+script](https://github.com/mstange/analyze-tryserver-profiles/blob/master/resymbolicate_dmd.py)
+instead: clone the whole repo, edit the paths at the top of
+`resymbolicate_dmd.py` and run it.) The simplest way to do this is to
+just run the `dmd.py` script on your DMD report while your working
+directory is `$OBJDIR/dist/bin`. This will allow the local libraries to
+be found and used.
+
+If you invoke `dmd.py` without arguments you will get output appropriate
+for the mode in which DMD was invoked.
+
+### "Dark matter" mode output
+
+For "dark matter" mode, `dmd.py`'s output describes how the live heap
+blocks are covered by memory reports. This output is broken into
+multiple sections.
+
+1. "Invocation". This tells you how DMD was invoked, i.e. what
+ options were used.
+2. "Twice-reported stack trace records". This tells you which heap
+ blocks were reported twice or more. The presence of any such records
+ indicates bugs in one or more memory reporters.
+3. "Unreported stack trace records". This tells you which heap blocks
+ were not reported, which indicate where additional memory reporters
+ would be most helpful.
+4. "Once-reported stack trace records": like the "Unreported stack
+ trace records" section, but for blocks reported once.
+5. "Summary": gives measurements of the total heap, and the
+ unreported/once-reported/twice-reported portions of it.
+
+The "Twice-reported stack trace records" and "Unreported stack trace
+records" sections are the most important, because they indicate ways in
+which the memory reporters can be improved.
+
+Here's an example stack trace record from the "Unreported stack trace
+records" section.
+
+ Unreported {
+ 150 blocks in heap block record 283 of 5,495
+ 21,600 bytes (20,400 requested / 1,200 slop)
+ Individual block sizes: 144 x 150
+ 0.00% of the heap (16.85% cumulative)
+ 0.02% of unreported (94.68% cumulative)
+ Allocated at {
+ #01: replace_malloc (/home/njn/moz/mi5/go64dmd/memory/replace/dmd/../../../../memory/replace/dmd/DMD.cpp:1286)
+ #02: malloc (/home/njn/moz/mi5/go64dmd/memory/build/../../../memory/build/replace_malloc.c:153)
+ #03: moz_xmalloc (/home/njn/moz/mi5/memory/mozalloc/mozalloc.cpp:84)
+ #04: nsCycleCollectingAutoRefCnt::incr(void*, nsCycleCollectionParticipant*) (/home/njn/moz/mi5/go64dmd/dom/xul/../../dist/include/nsISupportsImpl.h:250)
+ #05: nsXULElement::Create(nsXULPrototypeElement*, nsIDocument*, bool, bool,mozilla::dom::Element**) (/home/njn/moz/mi5/dom/xul/nsXULElement.cpp:287)
+ #06: nsXBLContentSink::CreateElement(char16_t const**, unsigned int, mozilla::dom::NodeInfo*, unsigned int, nsIContent**, bool*, mozilla::dom::FromParser) (/home/njn/moz/mi5/dom/xbl/nsXBLContentSink.cpp:874)
+ #07: nsCOMPtr<nsIContent>::StartAssignment() (/home/njn/moz/mi5/go64dmd/dom/xml/../../dist/include/nsCOMPtr.h:753)
+ #08: nsXMLContentSink::HandleStartElement(char16_t const*, char16_t const**, unsigned int, unsigned int, bool) (/home/njn/moz/mi5/dom/xml/nsXMLContentSink.cpp:1007)
+ }
+ }
+
+It tells you that there were 150 heap blocks that were allocated from
+the program point indicated by the "Allocated at" stack trace, that
+these blocks took up 21,600 bytes, that all 150 blocks had a size of 144
+bytes, and that 1,200 of those bytes were "slop" (wasted space caused
+by the heap allocator rounding up request sizes). It also indicates what
+percentage of the total heap size and the unreported portion of the heap
+these blocks represent.
+
+Within each section, records are listed from largest to smallest.
+
+Once-reported and twice-reported stack trace records also have stack
+traces for the report point(s). For example:
+
+ Reported at {
+ #01: mozilla::dmd::Report(void const*) (/home/njn/moz/mi2/memory/replace/dmd/DMD.cpp:1740) 0x7f68652581ca
+ #02: CycleCollectorMallocSizeOf(void const*) (/home/njn/moz/mi2/xpcom/base/nsCycleCollector.cpp:3008) 0x7f6860fdfe02
+ #03: nsPurpleBuffer::SizeOfExcludingThis(unsigned long (*)(void const*)) const (/home/njn/moz/mi2/xpcom/base/nsCycleCollector.cpp:933) 0x7f6860fdb7af
+ #04: nsCycleCollector::SizeOfIncludingThis(unsigned long (*)(void const*), unsigned long*, unsigned long*, unsigned long*, unsigned long*, unsigned long*) const (/home/njn/moz/mi2/xpcom/base/nsCycleCollector.cpp:3029) 0x7f6860fdb6b1
+ #05: CycleCollectorMultiReporter::CollectReports(nsIMemoryMultiReporterCallback*, nsISupports*) (/home/njn/moz/mi2/xpcom/base/nsCycleCollector.cpp:3075) 0x7f6860fde432
+ #06: nsMemoryInfoDumper::DumpMemoryReportsToFileImpl(nsAString_internal const&) (/home/njn/moz/mi2/xpcom/base/nsMemoryInfoDumper.cpp:626) 0x7f6860fece79
+ #07: nsMemoryInfoDumper::DumpMemoryReportsToFile(nsAString_internal const&, bool, bool) (/home/njn/moz/mi2/xpcom/base/nsMemoryInfoDumper.cpp:344) 0x7f6860febaf9
+ #08: mozilla::(anonymous namespace)::DumpMemoryReportsRunnable::Run() (/home/njn/moz/mi2/xpcom/base/nsMemoryInfoDumper.cpp:58) 0x7f6860fefe03
+ }
+
+You can tell which memory reporter made the report by the name of the
+`MallocSizeOf` function near the top of the stack trace. In this case it
+was the cycle collector's reporter.
+
+By default, DMD does not record an allocation stack trace for most
+blocks, to make it run faster. The decision on whether to record is done
+probabilistically, and larger blocks are more likely to have an
+allocation stack trace recorded. All unreported blocks that lack an
+allocation stack trace will end up in a single record. For example:
+
+ Unreported {
+ 420,010 blocks in heap block record 2 of 5,495
+ 29,203,408 bytes (27,777,288 requested / 1,426,120 slop)
+ Individual block sizes: 2,048 x 3; 1,024 x 103; 512 x 147; 496 x 7; 480 x 31; 464 x 6; 448 x 50; 432 x 41; 416 x 28; 400 x 53; 384 x 43; 368 x 216; 352 x 141; 336 x 58; 320 x 104; 304 x 5,130; 288 x 150; 272 x 591; 256 x 6,017; 240 x 1,372; 224 x 93; 208 x 488; 192 x 1,919; 176 x 18,903; 160 x 1,754; 144 x 5,041; 128 x 36,709; 112 x 5,571; 96 x 6,280; 80 x 40,738; 64 x 37,925; 48 x 78,392; 32 x 136,199; 16 x 31,001; 8 x 4,706
+ 3.78% of the heap (10.24% cumulative)
+ 21.24% of unreported (57.53% cumulative)
+ Allocated at {
+ #01: (no stack trace recorded due to --stacks=partial)
+ }
+ }
+
+In contrast, stack traces are always recorded when a block is reported,
+which means you can end up with records like this where the allocation
+point is unknown but the reporting point *is* known:
+
+ Once-reported {
+ 104,491 blocks in heap block record 13 of 4,689
+ 10,392,000 bytes (10,392,000 requested / 0 slop)
+ Individual block sizes: 512 x 124; 256 x 242; 192 x 813; 128 x 54,664; 64 x 48,648
+ 1.35% of the heap (48.65% cumulative)
+ 1.64% of once-reported (59.18% cumulative)
+ Allocated at {
+ #01: (no stack trace recorded due to --stacks=partial)
+ }
+ Reported at {
+ #01: mozilla::dmd::DMDFuncs::Report(void const*) (/home/njn/moz/mi5/go64dmd/memory/replace/dmd/../../../../memory/replace/dmd/DMD.cpp:1646)
+ #02: WindowsMallocSizeOf(void const*) (/home/njn/moz/mi5/dom/base/nsWindowMemoryReporter.cpp:189)
+ #03: nsAttrAndChildArray::SizeOfExcludingThis(unsigned long (*)(void const*)) const (/home/njn/moz/mi5/dom/base/nsAttrAndChildArray.cpp:880)
+ #04: mozilla::dom::FragmentOrElement::SizeOfExcludingThis(unsigned long (*)(void const*)) const (/home/njn/moz/mi5/dom/base/FragmentOrElement.cpp:2337)
+ #05: nsINode::SizeOfIncludingThis(unsigned long (*)(void const*)) const (/home/njn/moz/mi5/go64dmd/parser/html/../../../dom/base/nsINode.h:307)
+ #06: mozilla::dom::NodeInfo::NodeType() const (/home/njn/moz/mi5/go64dmd/dom/base/../../dist/include/mozilla/dom/NodeInfo.h:127)
+ #07: nsHTMLDocument::DocAddSizeOfExcludingThis(nsWindowSizes*) const (/home/njn/moz/mi5/dom/html/nsHTMLDocument.cpp:3710)
+ #08: nsIDocument::DocAddSizeOfIncludingThis(nsWindowSizes*) const (/home/njn/moz/mi5/dom/base/nsDocument.cpp:12820)
+ }
+ }
+
+The choice of whether to record an allocation stack trace for all blocks
+is controlled by an option (see below).
+
+### "Live" mode output
+
+
+For "live" mode, dmd.py's output describes what live heap blocks are
+present. This output is broken into multiple sections.
+
+1. "Invocation". This tells you how DMD was invoked, i.e. what
+ options were used.
+2. "Live stack trace records". This tells you which heap blocks were
+ present.
+3. "Summary": gives measurements of the total heap.
+
+The individual records are similar to those output in "dark matter"
+mode.
+
+### "Cumulative" mode output
+
+For "cumulative" mode, dmd.py's output describes how the live heap
+blocks are covered by memory reports. This output is broken into
+multiple sections.
+
+1. "Invocation". This tells you how DMD was invoked, i.e. what
+ options were used.
+2. "Cumulative stack trace records". This tells you which heap blocks
+ were allocated during the session.
+3. "Summary": gives measurements of the total (cumulative) heap.
+
+The individual records are similar to those output in "dark matter"
+mode.
+
+### "Scan" mode output
+
+For "scan" mode, the output of `dmd.py` is the same as "live" mode.
+A separate script, `block_analyzer.py`, can be used to find out
+information about which blocks refer to a particular block.
+`dmd.py --clamp-contents` needs to be run on the log first. See [this
+other page](heap_scan_mode.md) for an
+overview of how to use heap scan mode to fix a leak involving refcounted
+objects.
+
+## Options
+
+### Runtime
+
+When you run `mach run --dmd` you can specify additional options to
+control how DMD runs. Run `mach help run` for documentation on these.
+
+The most interesting one is `--mode`. Acceptable values are
+`dark-matter` (the default), `live`, `cumulative`, and `scan`.
+
+Another interesting one is `--stacks`. Acceptable values are `partial`
+(the default) and `full`. In the former case most blocks will not have
+an allocation stack trace recorded. However, because larger blocks are
+more likely to have one recorded, most allocated bytes should have an
+allocation stack trace even though most allocated blocks do not. Use
+`--stacks=full` if you want complete information, but note that DMD will
+run substantially slower in that case.
+
+The options may also be put in the environment variable DMD, or set DMD
+to 1 to enable DMD with default options (dark-matter and partial
+stacks).
+
+### Post-processing
+
+`dmd.py` also takes options that control how it works. Run `dmd.py -h`
+for documentation. The following options are the most interesting ones.
+
+- `-f` / `--max-frames`. By default, records show up to 8 stack
+ frames. You can choose a smaller number, in which case more
+ allocations will be aggregated into each record, but you'll have
+ less context. Or you can choose a larger number, in which cases
+ allocations will be split across more records, but you will have
+ more context. There is no single best value, but values in the range
+ 2..10 are often good. The maximum is 24.
+
+- `-a` / `--ignore-alloc-fns`. Many allocation stack traces start
+ with multiple frames that mention allocation wrapper functions, e.g.
+ `js_calloc()` calls `replace_calloc()`. This option filters these
+ out. It often helps improve the quality of the output when using a
+ small `--max-frames` value.
+
+- `-s` / `--sort-by`. This controls how records are sorted. Acceptable
+ values are `usable` (the default), `req`, `slop` and `num-blocks`.
+
+- `--clamp-contents`. For a heap scan log, this performs a
+ conservative pointer analysis on the contents of each block,
+ changing any value that is a pointer into the middle of a live block
+ into a pointer to the start of that block. All other values are
+ changes to null. In addition, all trailing nulls are removed from
+ the block contents.
+
+As an example that combines multiple options, if you apply the following
+command to a profile obtained in "live" mode:
+
+ dmd.py -r -f 2 -a -s slop
+
+it will give you a good idea of where the major sources of slop are.
+
+`dmd.py` can also compute the difference between two DMD output files,
+so long as those files were produced in the same mode. Simply pass it
+two filenames instead of one to get the difference.
+
+## Which heap blocks are reported?
+
+At this stage you might wonder how DMD knows, in "dark matter" mode,
+which allocations have been reported and which haven't. DMD only knows
+about heap blocks that are measured via a function created with one of
+the following two macros:
+
+ MOZ_DEFINE_MALLOC_SIZE_OF
+ MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC
+
+Fortunately, most of the existing memory reporters do this. See
+[Performance/Memory_Reporting](https://developer.mozilla.org/en-US/docs/Mozilla/Performance/Memory_reporting "Platform/Memory Reporting")
+for more details about how memory reporters are written.
diff --git a/docs/performance/memory/dominators.md b/docs/performance/memory/dominators.md
new file mode 100644
index 0000000000..e64c465e62
--- /dev/null
+++ b/docs/performance/memory/dominators.md
@@ -0,0 +1,90 @@
+# Dominators
+
+This article provides an introduction to the concepts of *Reachability*,
+*Shallow* versus *Retained* size, and *Dominators*, as they apply in
+garbage-collected languages like JavaScript.
+
+These concepts matter in memory analysis, because often an object may
+itself be small, but may hold references to other much larger objects,
+and by doing this will prevent the garbage collector from freeing that
+extra memory.
+
+You can see the dominators in a page using the [Dominators
+view](dominators_view.md) in the Memory tool.
+
+With a garbage-collected language, like JavaScript, the programmer
+doesn\'t generally have to worry about deallocating memory. They can
+just create and use objects, and when the objects are no longer needed,
+the runtime takes care of cleaning up, and frees the memory the objects
+occupied.
+
+## Reachability
+
+In modern JavaScript implementations, the runtime decides whether an
+object is no longer needed based on *reachability*. In this system the
+heap is represented as one or more graphs. Each node in the graph
+represents an object, and each connection between nodes (edge)
+represents a reference from one object to another. The graph starts at a
+root node, indicated in these diagrams with \"R\".
+
+![](../img/memory-graph.svg)
+
+During garbage collection, the runtime traverses the graph, starting at
+the root, and marks every object it finds. Any objects it doesn\'t find
+are unreachable, and can be deallocated.
+
+So when an object becomes unreachable (for example, because it is only
+referenced by a single local variable which goes out of scope) then any
+objects it references also become unreachable, as long as no other
+objects reference them:
+
+![](../img/memory-graph-unreachable.svg)
+
+Conversely, this means that objects are kept alive as long as some other
+reachable object is holding a reference to them.
+
+## Shallow and retained size
+
+This gives rise to a distinction between two ways to look at the size of
+an object:
+
+- *shallow size*: the size of the object itself
+- *retained size*: the size of the object itself, plus the size of
+ other objects that are kept alive by this object
+
+Often, objects will have a small shallow size but a much larger retained
+size, through the references they contain to other objects. Retained
+size is an important concept in analyzing memory usage, because it
+answers the question \"if this object ceases to exist, what\'s the total
+amount of memory freed?\".
+
+## Dominators
+
+A related concept is that of the *dominator*. Node B is said to dominate
+node A if every path from the root to A passes through B:
+
+![](../img/memory-graph-dominators.svg)
+
+If any of node A\'s dominators are freed, then node A itself becomes
+eligible for garbage collection.
+
+[If node B dominates node A, but does not dominate any of A\'s other
+dominators, then B is the *immediate dominator* of
+A:]
+
+![](../img/memory-graph-immediate-dominator.svg)
+
+[One slight subtlety here is that if an object A is referenced by two
+other objects B and C, then neither object is its
+dominator], because you could remove either B or C from
+the graph, and A would still be retained by its other referrer. Instead,
+the immediate dominator of A would be its first common ancestor:\
+![](../img/memory-graph-dominator-multiple-references.svg)
+
+## See also
+
+[Dominators in graph
+theory](https://en.wikipedia.org/wiki/Dominator_%28graph_theory%29).
+
+[Tracing garbage
+collection](https://en.wikipedia.org/wiki/Tracing_garbage_collection).
diff --git a/docs/performance/memory/dominators_view.md b/docs/performance/memory/dominators_view.md
new file mode 100644
index 0000000000..05de01fa4e
--- /dev/null
+++ b/docs/performance/memory/dominators_view.md
@@ -0,0 +1,221 @@
+# Dominators view
+
+The Dominators view is new in Firefox 46.
+
+Starting in Firefox 46, the Memory tool includes a new view called the
+Dominators view. This is useful for understanding the \"retained size\"
+of objects allocated by your site: that is, the size of the objects
+themselves plus the size of the objects that they keep alive through
+references.
+
+If you already know what shallow size, retained size, and dominators
+are, skip to the Dominators UI section. Otherwise, you might want to
+review the article on [Dominators
+concepts](dominators.md).
+
+## Dominators UI
+
+To see the Dominators view for a snapshot, select \"Dominators\" in the
+\"View\" drop-down list. It looks something like this:
+
+![](../img/dominators-1.png)
+
+The Dominators view consists of two panels:
+
+- the [Dominators Tree
+ panel](#dominators-tree-panel)
+ shows you which nodes in the snapshot are retaining the most memory
+- the [Retaining Paths
+ panel](#retaining-paths-panel)
+ (new in Firefox 47) shows the 5 shortest retaining paths for a
+ single node.
+
+![](../img/dominators-2.png)
+
+### Dominators Tree panel
+
+The Dominators Tree tells you which objects in the snapshot are
+retaining the most memory.
+
+In the main part of the UI, the first row is labeled \"GC Roots\".
+Immediately underneath that is an entry for:
+
+- Every GC root node. In Gecko, there is more than one memory graph,
+ and therefore more than one root. There may be many (often
+ temporary) roots. For example: variables allocated on the stack need
+ to be rooted, or internal caches may need to root their elements.
+- Any other node that\'s referenced from two different roots (since in
+ this case, neither root dominates it).
+
+Each entry displays:
+
+- the retained size of the node, as bytes and as a percentage of the
+ total
+- the shallow size of the node, as bytes and as a percentage of the
+ total
+- the nodes\'s name and address in memory.
+
+Entries are ordered by the amount of memory that they retain. For
+example:
+
+![](../img/dominators-3.png)
+
+In this screenshot we can see five entries under \"GC Roots\". The first
+two are Call and Window objects, and retain about 21% and 8% of the
+total size of the memory snapshot, respectively. You can also see that
+these objects have a relatively tiny \"Shallow Size\", so almost all of
+the retained size is in the objects that they dominate.
+
+Immediately under each GC root, you\'ll see all the nodes for which this
+root is the [immediate
+dominator](/dominators.html#immediate_dominator).
+These nodes are also ordered by their retained size.
+
+For example, if we click on the first Window object:
+
+![](../img/dominators-4.png)
+
+We can see that this Window dominates a CSS2Properties object, whose
+retained size is 2% of the total snapshot size. Again the shallow size
+is very small: almost all of its retained size is in the nodes that it
+dominates. By clicking on the disclosure arrow next to the Function, we
+can see those nodes.
+
+In this way you can quickly get a sense of which objects retain the most
+memory in the snapshot.
+
+You can use [Alt]{.kbd} + click to expand the whole graph under a node.
+
+#### Call Stack {#Call_Stack}
+
+In the toolbar at the top of the tool is a dropdown called \"Label by\":
+
+![](../img/dominators-5.png)
+
+By default, this is set to \"Type\". However, you can set it instead to
+\"Call Stack\" to see exactly where in your code the objects are being
+allocated.
+
+::: {.note}
+This option is called \"Allocation Stack\" in Firefox 46.
+:::
+
+To enable this, you must check the box labeled \"Record call stacks\"
+*before* you run the code that allocates the objects. Then take a
+snapshot, then select \"Call Stack\" in the \"Label by\" drop-down.
+
+Now the node\'s name will contain the name of the function that
+allocated it, and the file, line number and character position of the
+exact spot where the function allocated it. Clicking the file name will
+take you to that spot in the Debugger.
+
+<iframe width="595" height="325" src="https://www.youtube.com/embed/qTF5wCSD124" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"></iframe>
+
+:::
+Sometimes you\'ll see \"(no stack available)\" here. In particular,
+allocation stacks are currently only recorded for objects, not for
+arrays, strings, or internal structures.
+:::
+
+### Retaining Paths panel
+
+::: {.geckoVersionNote}
+The Retaining Paths panel is new in Firefox 47.
+:::
+
+The Retaining Paths panel shows you, for a given node, the 5 shortest
+paths back from this node to a GC root. This enables you to see all the
+nodes that are keeping the given node from being garbage-collected. If
+you suspect that an object is being leaked, this will show you exactly
+which objects are holding a reference to it.
+
+To see the retaining paths for a node, you have to select the node in
+the Dominators Tree panel:
+
+![](../img/dominators-6.png)
+
+Here, we\'ve selected an object, and can see a single path back to a GC
+root.
+
+The `Window` GC root holds a reference to an `HTMLDivElement` object,
+and that holds a reference to an `Object`, and so on. If you look in the
+Dominators Tree panel, you can trace the same path there. If either of
+these references were removed, the items below them could be
+garbage-collected.
+
+Each connection in the graph is labeled with the variable name for the
+referenced object.
+
+Sometimes there\'s more than one retaining path back from a node:
+
+![](../img/dominators-7.png)
+
+Here there are three paths back from the `DocumentPrototype` node to a
+GC root. If one were removed, then the `DocumentPrototype` would still
+not be garbage-collected, because it\'s still retained by the other two
+path.
+
+## Example {#Example}
+
+Let\'s see how some simple code is reflected in the Dominators view.
+
+We\'ll use the [monster allocation
+example](monster_example.md), which creates three
+arrays, each containing 5000 monsters, each monster having a
+randomly-generated name.
+
+### Taking a snapshot
+
+To see what it looks like in the Dominators view:
+
+- load the page
+- enable the Memory tool in the
+ [Settings](https://developer.mozilla.org/en-US/docs/Tools/Tools_Toolbox#settings), if you
+ haven\'t already
+- open the Memory tool
+- check \"Record call stacks\"
+- press the button labeled \"Make monsters!\"
+- take a snapshot
+- switch to the \"Dominators\" view
+
+<iframe width="595" height="325" src="https://www.youtube.com/embed/HiWnfMoMs2c" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"></iframe>
+
+### Analyzing the Dominators Tree
+
+You\'ll see the three arrays as the top three GC roots, each retaining
+about 23% of the total memory usage:
+
+![](../img/dominators-8.png)
+
+If you expand an array, you\'ll see the objects (monsters) it contains.
+Each monster has a relatively small shallow size of 160 bytes. This
+includes the integer eye- and tentacle-counts. Each monster has a bigger
+retained size, which is accounted for by the string used for the
+monster\'s name:
+
+![](../img/dominators-9.png)
+
+All this maps closely to the [memory graph we were expecting to
+see](/monster_example.html#allocation-graph). One
+thing you might be wondering, though, is: where\'s the top-level object
+that retains all three arrays? If we look at the Retaining Paths panel
+for one of the arrays, we\'ll see it:
+
+![](../img/dominators-10.png)
+
+Here we can see the retaining object, and even that this particular
+array is the array of `fierce` monsters. But the array is also rooted
+directly, so if the object were to stop referencing the array, it would
+still not be eligible for garbage collection.
+
+This means that the object does not dominate the array, and is therefore
+not shown in the Dominators Tree view. [See the relevant section of the
+Dominators concepts
+article](dominators.html#multiple-paths).
+
+### Using the Call Stack view {#Using_the_Call_Stack_view}
+
+Finally, you can switch to the Call Stack view, see where the objects
+are being allocated, and jump to that point in the Debugger:
+
+<iframe width="595" height="325" src="https://www.youtube.com/embed/qTF5wCSD124" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"></iframe>
diff --git a/docs/performance/memory/gc_and_cc_logs.md b/docs/performance/memory/gc_and_cc_logs.md
new file mode 100644
index 0000000000..62e151dff4
--- /dev/null
+++ b/docs/performance/memory/gc_and_cc_logs.md
@@ -0,0 +1,109 @@
+# GC and CC logs
+
+Garbage collector (GC) and cycle collector (CC) logs give information
+about why various JS and C++ objects are alive in the heap. Garbage
+collector logs and cycle collector logs can be analyzed in various ways.
+In particular, CC logs can be used to understand why the cycle collector
+is keeping an object alive. These logs can either be manually or
+automatically generated, and they can be generated in both debug and
+non-debug builds.
+
+This logs the contents of the Javascript heap to a file named
+`gc-edges-NNNN.log`. It also creates a file named `cc-edges-NNNN.log` to
+which it dumps the parts of the heap visible to the cycle collector,
+which includes native C++ objects that participate in cycle collection,
+as well as JS objects being held alive by those C++ objects.
+
+## Generating logs
+
+### From within Firefox
+
+To manually generate GC and CC logs, navigate to `about:memory` and use
+the buttons under \"Save GC & CC logs.\" \"Save concise\" will generate
+a smaller CC log, \"Save verbose\" will provide a more detailed CC log.
+(The GC log will be the same size in either case.)
+
+With multiprocess Firefox, you can't record logs from the content
+process, due to sandboxing. You'll need to disable sandboxing by
+setting `MOZ_DISABLE_CONTENT_SANDBOX=t` when you run Firefox.
+
+### From the commandline
+
+TLDR: if you just want shutdown GC/CC logs to debug leaks that happen in
+our automated tests, you probably want something along the lines of:
+
+ MOZ_DISABLE_CONTENT_SANDBOX=t MOZ_CC_LOG_DIRECTORY=/full/path/to/log/directory/ MOZ_CC_LOG_SHUTDOWN=1 MOZ_CC_ALL_TRACES=shutdown ./mach ...
+
+As noted in the previous section, with multiprocess Firefox, you can't
+record logs from the content process, due to sandboxing. You'll need to
+disable sandboxing by setting `MOZ_DISABLE_CONTENT_SANDBOX=t` when you
+run Firefox.
+
+On desktop Firefox you can override the default location of the log
+files by setting the `MOZ_CC_LOG_DIRECTORY` environment variable. By
+default, they go to a temporary directory which differs per OS - it's
+`/tmp/` on Linux/BSD, `$LOCALAPPDATA\Temp\` on Windows, and somewhere in
+`/var/folders/` on Mac (whatever the directory service returns for
+`TmpD`/`NS_OS_TEMP_DIR`). Note that just `MOZ_CC_LOG_DIRECTORY=.` won't
+work - you need to specify a full path. On Firefox for Android you can
+use the cc-dump.xpi
+extension to save the files to `/sdcard`. By default, the file is
+created in some temp directory, and the path to the file is printed to
+the Error Console.
+
+To log every cycle collection, set the `MOZ_CC_LOG_ALL` environment
+variable. To log only shutdown collections, set `MOZ_CC_LOG_SHUTDOWN`.
+To make all CCs verbose, set `MOZ_CC_ALL_TRACES to "all`\", or to
+\"`shutdown`\" to make only shutdown CCs verbose.
+
+Live GC logging can be enabled with the pref
+`javascript.options.mem.log`. Output to a file can be controlled with
+the MOZ_GCTIMER environment variable. See the [Statistics
+API](https://developer.mozilla.org/en-US/docs/Tools/Tools_Toolbox#settings/en-US/docs/SpiderMonkey/Internals/GC/Statistics_API) page for
+details on values.
+
+Set the environment variable `MOZ_CC_LOG_THREAD` to `main` to only log
+main thread CCs, or to `worker` to only log worker CCs. The default
+value is `all`, which will log all CCs.
+
+To get cycle collector logs on Try server, set `MOZ_CC_LOG_DIRECTORY` to
+`MOZ_UPLOAD_DIR`, then set the other variables appropriately to generate
+CC logs. The way to set environment variables depends on the test
+harness, or you can modify the code in nsCycleCollector to set that
+directly. To find the CC logs once the try run has finished, click on
+the particular job, then click on \"Job Details\" in the bottom pane in
+TreeHerder, and you should see download links.
+
+To set the environment variable, find the `buildBrowserEnv` method in
+the Python file for the test suite you are interested in, and add
+something like this code to the file:
+
+ browserEnv["MOZ_CC_LOG_DIRECTORY"] = os.environ["MOZ_UPLOAD_DIR"]
+ browserEnv["MOZ_CC_LOG_SHUTDOWN"] = "1"
+
+## Analyzing GC and CC logs
+
+There are numerous scripts that analyze GC and CC logs on
+[GitHub](https://github.com/amccreight/heapgraph/)
+
+
+To find out why an object is being kept alive, you should use `find_roots.py`
+in the root of the github repository. Calling `find_roots.py` on a CC log
+with a specific object or kind of object will produce paths from rooting
+objects to the specified objects. Most big leaks include an `nsGlobalWindow`,
+so that's a good class to try if you don't have any better idea.
+
+To fix a leak, the next step is to figure out why the rooting object is
+alive. For a C++ object, you need to figure out where the missing
+references are from. For a JS object, you need to figure out why the JS
+object is reachable from a JS root. For the latter, you can use the
+corresponding [`find_roots.py` for
+JS](https://github.com/amccreight/heapgraph/tree/master/g)
+on the GC log.
+
+## Alternatives
+
+There are two add-ons that can be used to create and analyze CC graphs.
+
+- [about:cc](https://bugzilla.mozilla.org/show_bug.cgi?id=726346)
+ is simple, ugly, but rather powerful.
diff --git a/docs/performance/memory/heap_scan_mode.md b/docs/performance/memory/heap_scan_mode.md
new file mode 100644
index 0000000000..ea5a45016a
--- /dev/null
+++ b/docs/performance/memory/heap_scan_mode.md
@@ -0,0 +1,313 @@
+# DMD heap scan mode
+
+Firefox's DMD heap scan mode tracks the set of all live blocks of
+malloc-allocated memory and their allocation stacks, and allows you to
+log these blocks, and the values stored in them, to a file. When
+combined with cycle collector logging, this can be used to investigate
+leaks of refcounted cycle collected objects, by figuring out what holds
+a strong reference to a leaked object.
+
+**When should you use this?** DMD heap scan mode is intended to be used
+to investigate leaks of cycle collected (CCed) objects. DMD heap scan
+mode is a "tool of last resort" that should only be used when all
+other avenues have been tried and failed, except possibly [ref count
+logging](refcount_tracing_and_balancing.md).
+It is particularly useful if you have no idea what is causing the leak.
+If you have a patch that introduces a leak, you are probably better off
+auditing all of the strong references that your patch creates before
+trying this.
+
+The particular steps given below are intended for the case where the
+leaked object is alive all the way through shutdown. You could modify
+these steps for leaks that go away in shutdown by collecting a CC and
+DMD log prior to shutdown. However, in that case it may be easier to use
+refcount logging, or rr with a conditional breakpoint set on calls to
+`Release()` for the leaking object, to see what object actually does the
+release that causes the leaked object to go away.
+
+## Prerequisites
+
+- A debug DMD build of Firefox. [This
+ page](dmd.md)
+ describes how to do that. This should probably be an optimized
+ build. Non-optimized DMD builds will generate better stack traces,
+ but they can be so slow as to be useless.
+- The build is going to be very slow, so you may need to disable some
+ shutdown checks. First, in
+ `toolkit/components/terminator/nsTerminator.cpp`, delete everything
+ in `RunWatchDog` but the call to `NS_SetCurrentThreadName`. This
+ will keep the watch dog from killing the browser when shut down
+ takes multiple minutes. Secondly, you may need to comment out the
+ call to `MOZ_CRASH("NSS_Shutdown failed");` in
+ `xpcom/build/XPCOMInit.cpp`, as this also seems to trigger when
+ shutdown is extremely slow.
+- You need the cycle collector analysis script `find_roots.py`, which
+ can be downloaded as part of [this repo on
+ Github](https://github.com/amccreight/heapgraph).
+
+## Generating Logs
+
+The next step is to generate a number of log files. You need to get a
+shutdown CC log and a DMD log, for a single run.
+
+**Definitions** I'll write `$objdir` for the object directory for your
+Firefox DMD build, `$srcdir` for the top level of the Firefox source
+directory, and `$heapgraph` for the location of the heapgraph repo, and
+`$logdir` for the location you want logs to go to. `$logdir` should end
+in a path separator. For instance, `~/logs/leak/`.
+
+The command you need to run Firefox will look something like this:
+
+ XPCOM_MEM_BLOAT_LOG=1 MOZ_CC_LOG_SHUTDOWN=1 MOZ_DISABLE_CONTENT_SANDBOX=t MOZ_CC_LOG_DIRECTORY=$logdir
+ MOZ_CC_LOG_PROCESS=content MOZ_CC_LOG_THREAD=main MOZ_DMD_SHUTDOWN_LOG=$logdir MOZ_DMD_LOG_PROCESS=tab ./mach run --dmd --mode=scan
+
+Breaking this down:
+
+- `XPCOM_MEM_BLOAT_LOG=1`: This reports a list of the counts of every
+ object created and destroyed and tracked by the XPCOM leak tracking
+ system. From this chart, you can see how many objects of a
+ particular type were leaked through shutdown. This can come in handy
+ during the manual analysis phase later, to get evidence to support
+ your hunches. For instance, if you think that an `nsFoo` object
+ might be holding your leaking object alive, you can use this to
+ easily see if we leaked an `nsFoo` object.
+- `MOZ_CC_LOG_SHUTDOWN=1`: This generates a cycle collector log during
+ shutdown. Creating this log during shutdown is nice because there
+ are less things unrelated to the leak in the log, and various cycle
+ collector optimizations are disabled. A garbage collector log will
+ also be created, which you may not need.
+- `MOZ_DISABLE_CONTENT_SANDBOX=t`: This disables the content process
+ sandbox, which is needed because the DMD and CC log files are
+ created directly by the child processes.
+- `MOZ_CC_LOG_DIRECTORY=$logdir`: This selects the location for cycle
+ collector logs to be saved.
+- `MOZ_CC_LOG_PROCESS=content MOZ_CC_LOG_THREAD=main`: These options
+ specify that we only want CC logs for the main thread of content
+ processes, to make shutdown less slow. If your leak is happening in
+ a different process or thread, change the options, which are listed
+ in `xpcom/base/nsCycleCollector.cpp`.
+- `MOZ_DMD_SHUTDOWN_LOG=$logdir`: This option specifies that we want a
+ DMD log to be taken very late in XPCOM shutdown, and the location
+ for that log to be saved. Like with the CC log, we want this log
+ very late to avoid as many non-leaking things as possible.
+- `MOZ_DMD_LOG_PROCESS=tab`: As with the CC, this means that we only
+ want these logs in content processes, in order to make shutdown
+ faster. The allowed values here are the same as those returned by
+ `XRE_GetProcessType()`, so adjust as needed.
+- Finally, the `--dmd` option need to be passed in so that DMD will be
+ run. `--mode=scan` is needed so that when we get a DMD log the
+ entire contents of each block of memory is saved for later analysis.
+
+With that command line in hand, you can start Firefox. Be aware that
+this may take multiple minutes if you have optimization disabled.
+
+Once it has started, go through the steps you need to reproduce your
+leak. If your leak is a ghost window, it can be handy to get an
+`about:memory` report and write down the PID of the leaking process. You
+may want to wait 10 or so seconds after this to make sure as much as
+possible is cleaned up.
+
+Next, exit the browser. This will cause a lot of logs to be written out,
+so it can take a while.
+
+## Analyzing the Logs
+
+### Getting the PID and address of the leaking object
+
+The first step is to figure out the **PID** of the leaking process. The
+second step is to figure out **the address of the leaking object**,
+usually a window. Conveniently, you can usually do both at once using
+the cycle collector log. If you are investigating a leak of
+`www.example.com`, then from `$logdir` you can do
+`"grep nsGlobalWindow cc-edges* | grep example.com"`. This looks through
+all of the windows in all of the CC logs (which may leaked, this late in
+shutdown), and then filters out windows where the URL contains
+`example.com`.
+
+The result of that grep will contain output that looks something like
+this:
+
+ cc-edges.15873.log:0x7f0897082c00 [rc=1285] nsGlobalWindowInner # 2147483662 inner https://www.example.com/
+
+cc-edges.15873.log: The first part is the file name where it was
+found. `15873` is the PID of the process that leaked. You'll want to
+write down the name of the file and the PID. Let's call the file
+`$cclog` and the pid `$pid`.
+
+0x7f0897082c00: This is the address of the leaking window. You'll
+also want to write that down. Let's call this `$winaddr`.
+
+If there are multiple files, you'll end up with one that looks like
+`cc-edges.$pid.log` and one or more that look like
+`cc-edges.$pid-$n.log` for various values of `$n`. You want the one with
+the largest `$n`, as this was recorded the latest, and so it will
+contain the least non-garbage.
+
+### Identifying the root in the cycle collector log
+
+The next step is to figure out why the cycle collector could not collect
+the window, using the `find_roots.py` script from the heapgraph
+repository. The command to invoke this looks like this:
+
+ python $heapgraph/find_roots.py $cclog $winaddr
+
+This may take a few seconds. It will eventually produce some output.
+You'll want to save a copy of this output for later.
+
+The output will look something like this, after a message about loading
+progress:
+
+ 0x7f0882fe3230 [FragmentOrElement (xhtml) script https://www.example.com]
+ --[[via hash] mListenerManager]--> 0x7f0899b4e550 [EventListenerManager]
+ --[mListeners event=onload listenerType=3 [i]]--> 0x7f0882ff8f80 [CallbackObject]
+ --[mIncumbentGlobal]--> 0x7f0897082c00 [nsGlobalWindowInner # 2147483662 inner https://www.example.com]
+
+Root 0x7f0882fe3230 is a ref counted object with 1 unknown edge(s).
+ known edges:
+ 0x7f08975a24c0 [FragmentOrElement (xhtml) head https://www.example.com] --[mAttrsAndChildren[i]]--> 0x7f0882fe3230
+ 0x7f08967e7b20 [JS Object (HTMLScriptElement)] --[UnwrapDOMObject(obj)]--> 0x7f0882fe3230
+
+The first two lines mean that the script element `0x7f0882fe3230`
+contains a strong reference to the EventListenerManager
+`0x7f0899b4e550`. "[via hash] mListenerManager" is a description of
+that strong reference. Together, these lines show a chain of strong
+references from an object the cycle collector thinks needs to be kept
+alive, `0x7f0899b4e550`, to the object` 0x7f0897082c00` that you asked
+about. Most of the time, the actual chain is not important, because the
+cycle collector can only tell us about what went right. Let us call the
+address of the leaking object (`0x7f0882fe3230` in this case)
+`$leakaddr`.
+
+Besides `$leakaddr`, the other interesting part is the chunk at the
+bottom. It tells us that there is 1 unknown edge, and 2 known edges.
+What this means is that the leaking object has a refcount of 3, but the
+cycle collector was only told about these two references. In this case,
+a head element and a JS object (the JS reflector of the script element).
+We need to figure out what the unknown reference is from, as that is
+where our leak really is.
+
+### Figure out what is holding the leaking object alive.
+
+Now we need to use the DMD heap scan logs. These contain the contents of
+every live block of memory.
+
+The first step to using the DMD heap scan logs is to do some
+pre-processing for the DMD log. Stacks need to be symbolicated, and we
+need to clamp the values contained in the heap. Clamping is the same
+kind of analysis that a conservative GC does: if a word-aligned value in
+a heap block points to somewhere within another heap block, replace that
+value with the address of the block.
+
+Both kinds of preprocessing are done by the `dmd.py` script, which can
+be invoked like this:
+
+ $objdir/dist/bin/dmd.py --clamp-contents dmd-$pid.log.gz
+
+This can take a few minutes due to symbolification, but you only need to
+run it once on a log file.
+
+You can also locally symbolicate stacks from DMD logs generated on TreeHerder,
+but it will [take a few extra steps](/contributing/debugging/local_symbols.rst)
+that you need to do before running `dmd.py`.
+
+After that is done, we can finally find out which objects (possibly)
+point to other objects, using the block_analyzer script:
+
+ python $srcdir/memory/replace/dmd/block_analyzer.py dmd-$pid.log.gz $leakaddr
+
+This will look through every block of memory in the log, and give some
+basic information about any block of memory that (possibly) contains a
+pointer to that object. You can pass some additional options to affect
+how the results are displayed. "-sfl 10000 -a" is useful. The -sfl 10000
+tells it to not truncate stack frames, and -a tells it to not display
+generic frames related to the allocator.
+
+Caveat: I think block_analyzer.py does not attempt to clamp the address
+you pass into it, so if it is an offset from the start of the block, it
+won't find it.
+
+ block_analyzer.py` will return a series of entries that look like this
+ with the [...] indicating where I have removed things):
+ 0x7f089306b000 size = 4096 bytes at byte offset 2168
+ nsAttrAndChildArray::GrowBy[...]
+ nsAttrAndChildArray::InsertChildAt[...]
+ [...]
+
+`0x7f089306b000` is the address of the block that contains `$leakaddr`.
+144 bytes is the size of that block. That can be useful for confirming
+your guess about what class the block actually is. The byte offset tells
+you were in the block the pointer is. This is mostly useful for larger
+objects, and you can potentially combine this with debugging information
+to figure out exactly what field this is. The rest of the entry is the
+stack trace for the allocation of the block, which is the most useful
+piece of information.
+
+What you need to do now is to go through every one of these entries and
+place it into three categories: strong reference known to the cycle
+collector, weak reference, or something else! The goal is to eventually
+shrink down the "something else" category until there are only as many
+things in it as there are unknown references to the leaking object, and
+then you have your leaker.
+
+To place an entry into one of the categories, you must look at the code
+locations given in the stack trace, and see if you can tell what the
+object is based on that, then compare that to what `find_roots.py` told
+you.
+
+For instance, one of the strong references in the CC log is from a head
+element to its child via `mAttrsAndChildren`, and that sounds a lot like
+this, so we can mark it as being a strong known reference.
+
+This is an iterative process, where you first go through and mark off
+the things that are easily categorizable, and repeat until you have a
+small list of things to analyze.
+
+### Example analysis of block_analyzer.py results
+
+In one debugging session where I was investigating the leak from bug
+1451985, I eventually reduced the list of entries until this was the
+most suspicious looking entry:
+
+ 0x7f0892f29630 size = 392 bytes at byte offset 56
+ mozilla::dom::ScriptLoader::ProcessExternalScript[...]
+ [...]
+
+I went to that line of `ScriptLoader::ProcessExternalScript()`, and it
+contained a call to ScriptLoader::CreateLoadRequest(). Fortunately, this
+method mostly just contains two calls to `new`, one for
+`ScriptLoadRequest` and one for `ModuleLoadRequest`. (This is where an
+unoptimized build comes in handy, as it would have pointed out the exact
+line. Unfortunately, in this particular case, the unoptimized build was
+so slow I wasn't getting any logs.) I then looked through the list of
+leaked objects generated by `XPCOM_MEM_BLOAT_LOG` and saw that we were
+leaking a `ScriptLoadRequest`, so I went and looked at its class
+definition, where I noticed that `ScriptLoadRequest` had a strong
+reference to an element that it wasn't telling the cycle collector
+about, which seemed suspicious.
+
+The first thing I did to try to confirm that this was the source of the
+leak was pass the address of this object into the cycle collector
+analysis log, `find_roots.py`, that we used at an earlier step. That
+gave a result that contained this:
+
+ 0x7f0882fe3230 [FragmentOrElement (xhtml) script [...]
+ --[mNodeInfo]--> 0x7f0897431f00 [NodeInfo (xhtml) script]
+ [...]
+ --[mLoadingAsyncRequests]--> 0x7f0892f29630 [ScriptLoadRequest]
+
+This confirms that this block is actually a ScriptLoadRequest. Secondly,
+notice that the load request is being held alive by the very same script
+element that is causing the window leak! This strongly suggests that
+there is a cycle of strong references between the script element and the
+load request. I then added the missing field to the traverse and unlink
+methods of ScriptLoadRequest, and confirmed that I couldn't reproduce
+the leak.
+
+Keep in mind that you may need to run `block_analyzer.py` multiple
+times. For instance, if the script element was being held alive by some
+container being held alive by a runnable, we'd first need to figure out
+that the container was holding the element. If it isn't possible to
+figure out what is holding that alive, you'd have to run block_analyzer
+again. This isn't too bad, because unlike ref count logging, we have the
+full state of memory in our existing log, so we don't need to run the
+browser again.
diff --git a/docs/performance/memory/leak_gauge.md b/docs/performance/memory/leak_gauge.md
new file mode 100644
index 0000000000..153303549e
--- /dev/null
+++ b/docs/performance/memory/leak_gauge.md
@@ -0,0 +1,45 @@
+# Leak Gauge
+
+Leak Gauge is a tool that can be used to detect certain kinds of leaks
+in Gecko, including those involving documents, window objects, and
+docshells. It has two parts: instrumentation in Gecko that produces a
+log file, and a script to post-process the log file.
+
+## Getting a log file
+
+To get a log file, run the browser with these settings:
+
+ NSPR_LOG_MODULES=DOMLeak:5,DocumentLeak:5,nsDocShellLeak:5,NodeInfoManagerLeak:5
+ NSPR_LOG_FILE=nspr.log # or any other filename of your choice
+
+This overwrites any existing file named `nspr.log`. The browser runs
+with a negligible slowdown. For reliable results, exit the browser
+before post-processing the log file.
+
+## Post-processing the log file
+
+Post-process the log file with
+[tools/leak-gauge/leak-gauge.pl](https://searchfox.org/mozilla-central/source/tools/leak-gauge/leak-gauge.html)
+
+If there are no leaks, the output looks like this:
+
+ Results of processing log leak.log :
+ Summary:
+ Leaked 0 out of 11 DOM Windows
+ Leaked 0 out of 44 documents
+ Leaked 0 out of 3 docshells
+ Leaked content nodes in 0 out of 0 documents
+
+If there are leaks, the output looks like this:
+
+ Results of processing log leak2.log :
+ Leaked outer window 2c6e410 at address 2c6e410.
+ Leaked outer window 2c6ead0 at address 2c6ead0.
+ Leaked inner window 2c6ec80 (outer 2c6ead0) at address 2c6ec80.
+ Summary:
+ Leaked 13 out of 15 DOM Windows
+ Leaked 35 out of 46 documents
+ Leaked 4 out of 4 docshells
+ Leaked content nodes in 42 out of 53 documents
+
+If you find leaks, please file a bug report.
diff --git a/docs/performance/memory/leak_hunting_strategies_and_tips.md b/docs/performance/memory/leak_hunting_strategies_and_tips.md
new file mode 100644
index 0000000000..a5689223ea
--- /dev/null
+++ b/docs/performance/memory/leak_hunting_strategies_and_tips.md
@@ -0,0 +1,219 @@
+# Leak hunting strategies and tips
+
+This document is old and some of the information is out-of-date. Use
+with caution.
+
+## Strategy for finding leaks
+
+When trying to make a particular testcase not leak, I recommend focusing
+first on the largest object graphs (since these entrain many smaller
+objects), then on smaller reference-counted object graphs, and then on
+any remaining individual objects or small object graphs that don't
+entrain other objects.
+
+Because (1) large graphs of leaked objects tend to include some objects
+pointed to by global variables that confuse GC-based leak detectors,
+which can make leaks look smaller (as in [bug
+99180](https://bugzilla.mozilla.org/show_bug.cgi?id=99180){.external
+.text}) or hide them completely and (2) large graphs of leaked objects
+tend to hide smaller ones, it's much better to go after the large
+graphs of leaks first.
+
+A good general pattern for finding and fixing leaks is to start with a
+task that you want not to leak (for example, reading email). Start
+finding and fixing leaks by running part of the task under nsTraceRefcnt
+logging, gradually building up from as little as possible to the
+complete task, and fixing most of the leaks in the first steps before
+adding additional steps. (By most of the leaks, I mean the leaks of
+large numbers of different types of objects or leaks of objects that are
+known to entrain many non-logged objects such as JS objects. Seeing a
+leaked `GlobalWindowImpl`, `nsXULPDGlobalObject`,
+`nsXBLDocGlobalObject`, or `nsXPCWrappedJS` is a sign that there could
+be significant numbers of JS objects leaked.)
+
+For example, start with bringing up the mail window and closing the
+window without doing anything. Then go on to selecting a folder, then
+selecting a message, and then other activities one does while reading
+mail.
+
+Once you've done this, and it doesn't leak much, then try the action
+under trace-malloc or LSAN or Valgrind to find the leaks of smaller
+graphs of objects. (When I refer to the size of a graph of objects, I'm
+referring to the number of objects, not the size in bytes. Leaking many
+copies of a string could be a very large leak, but the object graphs are
+small and easy to identify using GC-based leak detection.)
+
+## What leak tools do we have?
+
+| Tool | Finds | Platforms | Requires |
+|------------------------------------------|------------------------------------------------------|---------------------|--------------|
+| Leak tools for large object graphs | | | |
+| [Leak Gauge](leak_gauge.md) | Windows, documents, and docshells only | All platforms | Any build |
+| [GC and CC logs](gc_and_cc_logs.md) | JS objects, DOM objects, many other kinds of objects | All platforms | Any build |
+| Leak tools for medium-size object graphs | | | |
+| [BloatView](bloatview.md), [refcount tracing and balancing](refcount_tracing_and_balancing.md) | Objects that implement `nsISupports` or use `MOZ_COUNT_{CTOR,DTOR}` | All tier 1 platforms | Debug build (or build opt with `--enable-logrefcnt`)|
+| Leak tools for debugging memory growth that is cleaned up on shutdown | | |
+
+## Common leak patterns
+
+When trying to find a leak of reference-counted objects, there are a
+number of patterns that could cause the leak:
+
+1. Ownership cycles. The most common source of hard-to-fix leaks is
+ ownership cycles. If you can avoid creating cycles in the first
+ place, please do, since it's often hard to be sure to break the
+ cycle in every last case. Sometimes these cycles extend through JS
+ objects (discussed further below), and since JS is
+ garbage-collected, every pointer acts like an owning pointer and the
+ potential for fan-out is larger. See [bug
+ 106860](https://bugzilla.mozilla.org/show_bug.cgi?id=106860){.external
+ .text} and [bug
+ 84136](https://bugzilla.mozilla.org/show_bug.cgi?id=84136){.external
+ .text} for examples. (Is this advice still accurate now that we have
+ a cycle collector? \--Jesse)
+2. Dropping a reference on the floor by:
+ 1. Forgetting to release (because you weren't using `nsCOMPtr`
+ when you should have been): See [bug
+ 99180](https://bugzilla.mozilla.org/show_bug.cgi?id=99180){.external
+ .text} or [bug
+ 93087](https://bugzilla.mozilla.org/show_bug.cgi?id=93087){.external
+ .text} for an example or [bug
+ 28555](https://bugzilla.mozilla.org/show_bug.cgi?id=28555){.external
+ .text} for a slightly more interesting one. This is also a
+ frequent problem around early returns when not using `nsCOMPtr`.
+ 2. Double-AddRef: This happens most often when assigning the result
+ of a function that returns an AddRefed pointer (bad!) into an
+ `nsCOMPtr` without using `dont_AddRef()`. See [bug
+ 76091](https://bugzilla.mozilla.org/show_bug.cgi?id=76091){.external
+ .text} or [bug
+ 49648](https://bugzilla.mozilla.org/show_bug.cgi?id=49648){.external
+ .text} for an example.
+ 3. \[Obscure\] Double-assignment into the same variable: If you
+ release a member variable and then assign into it by calling
+ another function that does the same thing, you can leak the
+ object assigned into the variable by the inner function. (This
+ can happen equally with or without `nsCOMPtr`.) See [bug
+ 38586](https://bugzilla.mozilla.org/show_bug.cgi?id=38586){.external
+ .text} and [bug
+ 287847](https://bugzilla.mozilla.org/show_bug.cgi?id=287847){.external
+ .text} for examples.
+3. Dropping a non-refcounted object on the floor (especially one that
+ owns references to reference counted objects). See [bug
+ 109671](https://bugzilla.mozilla.org/show_bug.cgi?id=109671){.external
+ .text} for an example.
+4. Destructors that should have been virtual: If you expect to override
+ an object's destructor (which includes giving a derived class of it
+ an `nsCOMPtr` member variable) and delete that object through a
+ pointer to the base class using delete, its destructor better be
+ virtual. (But we have many virtual destructors in the codebase that
+ don't need to be -- don't do that.)
+
+## Debugging leaks that go through XPConnect
+
+Many large object graphs that leak go through
+[XPConnect](http://www.mozilla.org/scriptable/){.external .text}. This
+can mean there will be XPConnect wrapper objects showing up as owning
+the leaked objects, but it doesn't mean it's XPConnect's fault
+(although that [has been known to
+happen](https://bugzilla.mozilla.org/show_bug.cgi?id=76102){.external
+.text}, it's rare). Debugging leaks that go through XPConnect requires
+a basic understanding of what XPConnect does. XPConnect allows an XPCOM
+object to be exposed to JavaScript, and it allows certain JavaScript
+objects to be exposed to C++ code as normal XPCOM objects.
+
+When a C++ object is exposed to JavaScript (the more common of the two),
+an XPCWrappedNative object is created. This wrapper owns a reference to
+the native object until the corresponding JavaScript object is
+garbage-collected. This means that if there are leaked GC roots from
+which the wrapper is reachable, the wrapper will never release its
+reference on the native object. While this can be debugged in detail,
+the quickest way to solve these problems is often to simply debug the
+leaked JS roots. These roots are printed on shutdown in DEBUG builds,
+and the name of the root should give the type of object it is associated
+with.
+
+One of the most common ways one could leak a JS root is by leaking an
+`nsXPCWrappedJS` object. This is the wrapper object in the reverse
+direction \-- when a JS object is used to implement an XPCOM interface
+and be used transparently by native code. The `nsXPCWrappedJS` object
+creates a GC root that exists as long as the wrapper does. The wrapper
+itself is just a normal reference-counted object, so a leaked
+`nsXPCWrappedJS` can be debugged using the normal refcount-balancer
+tools.
+
+If you really need to debug leaks that involve JS objects closely, you
+can get detailed printouts of the paths JS uses to mark objects when it
+is determining the set of live objects by using the functions added in
+[bug
+378261](https://bugzilla.mozilla.org/show_bug.cgi?id=378261){.external
+.text} and [bug
+378255](https://bugzilla.mozilla.org/show_bug.cgi?id=378255){.external
+.text}. (More documentation of this replacement for GC_MARK_DEBUG, the
+old way of doing it, would be useful. It may just involve setting the
+`XPC_SHUTDOWN_HEAP_DUMP` environment variable to a file name, but I
+haven't tested that.)
+
+## Post-processing of stack traces
+
+On Mac and Linux, the stack traces generated by our internal debugging
+tools don't have very good symbol information (since they just show the
+results of `dladdr`). The stacks can be significantly improved (better
+symbols, and file name / line number information) by post-processing.
+Stacks can be piped through the script `tools/rb/fix_stacks.py` to do
+this. These scripts are designed to be run on balance trees in addition
+to raw stacks; since they are rather slow, it is often **much faster**
+to generate balance trees (e.g., using `make-tree.pl` for the refcount
+balancer or `diffbloatdump.pl --use-address` for trace-malloc) and*then*
+run the balance trees (which are much smaller) through the
+post-processing.
+
+## Getting symbol information for system libraries
+
+### Windows
+
+Setting the environment variable `_NT_SYMBOL_PATH` to something like
+`symsrv*symsrv.dll*f:\localsymbols*http://msdl.microsoft.com/download/symbols`
+as described in [Microsoft's
+article](http://support.microsoft.com/kb/311503){.external .text}. This
+needs to be done when running, since we do the address to symbol mapping
+at runtime.
+
+### Linux
+
+Many Linux distros provide packages containing external debugging
+symbols for system libraries. `fix_stacks.py` uses this debugging
+information (although it does not verify that they match the library
+versions on the system).
+
+For example, on Fedora, these are in \*-debuginfo RPMs (which are
+available in yum repositories that are disabled by default, but easily
+enabled by editing the system configuration).
+
+## Tips
+
+### Disabling Arena Allocation
+
+With many lower-level leak tools (particularly trace-malloc based ones,
+like leaksoup) it can be helpful to disable arena allocation of objects
+that you're interested in, when possible, so that each object is
+allocated with a separate call to malloc. Some places you can do this
+are:
+
+layout engine
+: Define `DEBUG_TRACEMALLOC_FRAMEARENA` where it is commented out in
+ `layout/base/nsPresShell.cpp`
+
+glib
+: Set the environment variable `G_SLICE=always-malloc`
+
+## Other References
+
+- [Performance
+ tools](https://wiki.mozilla.org/Performance:Tools "Performance:Tools")
+- [Leak Debugging Screencasts](https://dbaron.org/mozilla/leak-screencasts/){.external
+ .text}
+- [LeakingPages](https://wiki.mozilla.org/LeakingPages "LeakingPages") -
+ a list of pages known to leak
+- [mdc:Performance](https://developer.mozilla.org/en/Performance "mdc:Performance"){.extiw} -
+ contains documentation for all of our memory profiling and leak
+ detection tools
diff --git a/docs/performance/memory/memory.md b/docs/performance/memory/memory.md
new file mode 100644
index 0000000000..d571fb6b9c
--- /dev/null
+++ b/docs/performance/memory/memory.md
@@ -0,0 +1,64 @@
+# Memory Tools
+
+The Memory tool lets you take a snapshot of the current tab's memory
+[heap](https://en.wikipedia.org/wiki/Memory_management#HEAP).
+It then provides a number of views of the heap that can
+show you which objects account for memory usage and exactly where in
+your code you are allocating memory.
+
+<iframe width="595" height="325" src="https://www.youtube.com/embed/DJLoq5E5ww0" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"></iframe>
+
+------------------------------------------------------------------------
+
+## The basics
+- Opening [the memory
+ tool](basic_operations.md#opening-the-memory-tool)
+- [Taking a heap
+ snapshot](basic_operations.md#saving-and-loading-snapshots)
+- [Comparing two
+ snapshots](basic_operations.md#comparing-snapshots)
+- [Deleting
+ snapshots](basic_operations.md#clearing-a-snapshot)
+- [Saving and loading
+ snapshots](basic_operations.md#saving-and-loading-snapshots)
+- [Recording call
+ stacks](basic_operations.md#recording-call-stacks)
+
+------------------------------------------------------------------------
+
+## Analyzing snapshots
+
+The Tree map view is new in Firefox 48, and the Dominators view is new
+in Firefox 46.
+
+Once you've taken a snapshot, there are three main views the Memory
+tool provides:
+
+- [the Tree map view](tree_map_view.md) shows
+ memory usage as a
+ [treemap](https://en.wikipedia.org/wiki/Treemapping).
+- [the Aggregate view](aggregate_view.md) shows
+ memory usage as a table of allocated types.
+- [the Dominators view](dominators_view.md)
+ shows the "retained size" of objects: that is, the size of objects
+ plus the size of other objects that they keep alive through
+ references.
+
+If you've opted to record allocation stacks for the snapshot, the
+Aggregate and Dominators views can show you exactly where in your code
+allocations are happening.
+
+------------------------------------------------------------------------
+
+## Concepts
+
+- What are [Dominators](dominators.md)?
+
+------------------------------------------------------------------------
+
+## Example pages
+
+Examples used in the Memory tool documentation.
+
+- The [Monster example](monster_example.md)
+- The [DOM allocation example](DOM_allocation_example.md)
diff --git a/docs/performance/memory/monster_example.md b/docs/performance/memory/monster_example.md
new file mode 100644
index 0000000000..d351803a8d
--- /dev/null
+++ b/docs/performance/memory/monster_example.md
@@ -0,0 +1,79 @@
+# Monster example slug
+
+This article describes a very simple web page that we'll use to
+illustrate some features of the Memory tool.
+
+You can try the site at
+<https://mdn.github.io/performance-scenarios/js-allocs/alloc.html>.
+Heres the code:
+
+```js
+var MONSTER_COUNT = 5000;
+var MIN_NAME_LENGTH = 2;
+var MAX_NAME_LENGTH = 48;
+
+function Monster() {
+
+ function randomInt(min, max) {
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+ }
+
+ function randomName() {
+ var chars = "abcdefghijklmnopqrstuvwxyz";
+ var nameLength = randomInt(MIN_NAME_LENGTH, MAX_NAME_LENGTH);
+ var name = "";
+ for (var j = 0; j &lt; nameLength; j++) {
+ name += chars[randomInt(0, chars.length-1)];
+ }
+ return name;
+ }
+
+ this.name = randomName();
+ this.eyeCount = randomInt(0, 25);
+ this.tentacleCount = randomInt(0, 250);
+}
+
+function makeMonsters() {
+ var monsters = {
+ "friendly": [],
+ "fierce": [],
+ "undecided": []
+ };
+
+ for (var i = 0; i &lt; MONSTER_COUNT; i++) {
+ monsters.friendly.push(new Monster());
+ }
+
+ for (var i = 0; i &lt; MONSTER_COUNT; i++) {
+ monsters.fierce.push(new Monster());
+ }
+
+ for (var i = 0; i &lt; MONSTER_COUNT; i++) {
+ monsters.undecided.push(new Monster());
+ }
+
+ console.log(monsters);
+}
+
+var makeMonstersButton = document.getElementById("make-monsters");
+makeMonstersButton.addEventListener("click", makeMonsters);
+```
+
+The page contains a button: when you push the button, the code creates
+some monsters. Specifically:
+
+- the code creates an object with three properties, each an array:
+ - one for fierce monsters
+ - one for friendly monsters
+ - one for monsters who haven't decided yet.
+- for each array, the code creates and appends 5000
+ randomly-initialized monsters. Each monster has:
+ - a string, for the monster's name
+ - a number representing the number of eyes it has
+ - a number representing the number of tentacles it has.
+
+So the structure of the memory allocated on the JavaScript heap is an
+object containing three arrays, each containing 5000 objects (monsters),
+each object containing a string and two integers:
+
+[![](../img/monsters.svg)]
diff --git a/docs/performance/memory/refcount_tracing_and_balancing.md b/docs/performance/memory/refcount_tracing_and_balancing.md
new file mode 100644
index 0000000000..fe3e3c7ae4
--- /dev/null
+++ b/docs/performance/memory/refcount_tracing_and_balancing.md
@@ -0,0 +1,235 @@
+# Refcount Tracing and Balancing
+
+Refcount tracing and balancing are advanced techniques for tracking down
+leak of refcounted objects found with
+[BloatView](bloatview.md). The first step
+is to run Firefox with refcount tracing enabled, which produces one or
+more log files. Refcount tracing logs calls to `Addref` and `Release`,
+preferably for a particular set of classes, including call-stacks in
+symbolic form (on platforms that support this). Refcount balancing is a
+follow-up step that analyzes the resulting log to help a developer
+figure out where refcounting went wrong.
+
+## How to build for refcount tracing
+
+Build with `--enable-debug` or `--enable-logrefcnt`.
+
+## How to run with refcount tracing on
+
+There are several environment variables that can be used.
+
+First, you select one of three environment variables to choose what kind
+of logging you want. You almost certainly want `XPCOM_MEM_REFCNT_LOG`.
+
+NOTE: Due to an issue with the sandbox on Windows (bug
+[1345568](https://bugzilla.mozilla.org/show_bug.cgi?id=1345568)
+refcount logging currently requires the MOZ_DISABLE_CONTENT_SANDBOX
+environment variable to be set.
+
+`XPCOM_MEM_REFCNT_LOG`
+
+Setting this environment variable enables refcount tracing. If you set
+this environment variable to the name of a file, the log will be output
+to that file. You can also set it to 1 to log to stdout or 2 to log to
+stderr, but these logs are large and expensive to capture, so you
+probably don't want to do that. **WARNING**: you should never use this
+without `XPCOM_MEM_LOG_CLASSES` and/or `XPCOM_MEM_LOG_OBJECTS`, because
+without some filtering the logging will be completely useless due to how
+slow the browser will run and how large the logs it produces will be.
+
+`XPCOM_MEM_COMPTR_LOG`
+
+This environment variable enables logging of additions and releases of
+objects into `nsCOMPtr`s. This requires C++ dynamic casts, so it is not
+supported on all platforms. However, having an nsCOMPtr log and using it
+in the creation of the balance tree allows AddRef and Release calls that
+we know are matched to be eliminated from the tree, so it makes it much
+easier to debug reference count leaks of objects that have a large
+amount of reference counting traffic.
+
+`XPCOM_MEM_ALLOC_LOG`
+
+For platforms that don't have stack-crawl support, XPCOM supports
+logging at the call site to `AddRef`/`Release` using the usual cpp
+`__FILE__` and __LINE__ number macro expansion hackery. This results
+in slower code, but at least you get some data about where the leaks
+might be occurring from.
+
+You must also set one or two additional environment variables,
+`XPCOM_MEM_LOG_CLASSES` and `XPCOM_MEM_LOG_OBJECTS,` to reduce the set
+of objects being logged, in order to improve performance to something
+vaguely tolerable.
+
+`XPCOM_MEM_LOG_CLASSES`
+
+This variable should contain a comma-separated list of names which will
+be used to compare against the types of the objects being logged. For
+example:
+
+ env XPCOM_MEM_LOG_CLASSES=nsDocShell XPCOM_MEM_REFCNT_LOG=./refcounts.log ./mach run
+
+This will log the `AddRef` and `Release` calls only for instances of
+`nsDocShell` while running the browser using `mach`, to a file
+`refcounts.log`. Note that setting `XPCOM_MEM_LOG_CLASSES` will also
+list the *serial number* of each object that leaked in the "bloat log"
+(that is, the file specified by the `XPCOM_MEM_BLOAT_LOG` variable; see
+[the BloatView documentation](bloatview.md)
+for more details). An object's serial number is simply a unique number,
+starting at one, that is assigned to the object when it is allocated.
+
+You may use an object's serial number with the following variable to
+further restrict the reference count tracing:
+
+ XPCOM_MEM_LOG_OBJECTS
+
+Set this variable to a comma-separated list of object *serial number* or
+ranges of *serial number*, e.g., `1,37-42,73,165` (serial numbers start
+from 1, not 0). When this is set, along with `XPCOM_MEM_LOG_CLASSES` and
+`XPCOM_MEM_REFCNT_LOG`, a stack track will be generated for *only* the
+specific objects that you list. For example,
+
+ env XPCOM_MEM_LOG_CLASSES=nsDocShell XPCOM_MEM_LOG_OBJECTS=2 XPCOM_MEM_REFCNT_LOG=./refcounts.log ./mach run
+
+will log stack traces to `refcounts.log` for the 2nd `nsDocShell` object
+that gets allocated, and nothing else.
+
+## **Post-processing step 1: finding the leakers**
+
+First you have to figure out which objects leaked. The script
+`tools/rb/find_leakers.py` does this. It grovels through the log file,
+and figures out which objects got allocated (it knows because they were
+just allocated because they got `AddRef()`-ed and their refcount became
+1). It adds them to a list. When it finds an object that got freed (it
+knows because its refcount goes to 0), it removes it from the list.
+Anything left over is leaked.
+
+The scripts output looks like the following.
+
+ 0x00253ab0 (1)
+ 0x00253ae0 (2)
+ 0x00253bd0 (4)
+
+The number in parentheses indicates the order in which it was allocated,
+if you care. Pick one of these pointers for use with Step 2.
+
+## Post-processing step 2: filtering the log
+
+Once you've picked an object that leaked, you can use
+`tools/rb/filter-log.pl` to filter the log file to drop the call
+stack for other objects; This process reduces the size of the log file
+and also improves the performance.
+
+ perl -w tools/rb/filter-log.pl --object 0x00253ab0 < ./refcounts.log > my-leak.log
+
+### Symbolicating stacks
+
+The log files often lack function names, file
+names and line numbers. You'll need to run a script to fix the call
+stack.
+
+ python3 tools/rb/fix_stacks.py < ./refcounts.log > fixed_stack.log
+
+Also, it is possible to [locally symbolicate](/contributing/debugging/local_symbols.rst)
+logs generated on TreeHerder.
+
+## **Post-processing step 3: building the balance tree**
+
+Now that you've the log file fully prepared, you can build a *balance
+tree*. This process takes all the stack `AddRef()` and `Release()` stack
+traces and munges them into a call graph. Each node in the graph
+represents a call site. Each call site has a *balance factor*, which is
+positive if more `AddRef()`s than `Release()`es have happened at the
+site, zero if the number of `AddRef()`s and `Release()`es are equal, and
+negative if more `Release()`es than `AddRef()`s have happened at the
+site.
+
+To build the balance tree, run `tools/rb/make-tree.pl`, specifying the
+object of interest. For example:
+
+ perl -w tools/rb/make-tree.pl --object 0x00253ab0 < my-leak.log
+
+This will build an indented tree that looks something like this (except
+probably a lot larger and leafier):
+
+ .root: bal=1
+ main: bal=1
+ DoSomethingWithFooAndReturnItToo: bal=2
+ NS_NewFoo: bal=1
+
+Let's pretend in our toy example that `NS_NewFoo()` is a factory method
+that makes a new foo and returns it.
+`DoSomethingWithFooAndReturnItToo()` is a method that munges the foo
+before returning it to `main()`, the main program.
+
+What this little tree is telling you is that you leak *one refcount*
+overall on object `0x00253ab0`. But, more specifically, it shows you
+that:
+
+- `NS_NewFoo()` "leaks" a refcount. This is probably "okay"
+ because it's a factory method that creates an `AddRef()`-ed object.
+- `DoSomethingWithFooAndReturnItToo()` leaks *two* refcounts.
+ Hmm...this probably isn't okay, especially because...
+- `main()` is back down to leaking *one* refcount.
+
+So from this, we can deduce that `main()` is correctly releasing the
+refcount that it got on the object returned from
+`DoSomethingWithFooAndReturnItToo()`, so the leak *must* be somewhere in
+that function.
+
+So now say we go fix the leak in `DoSomethingWithFooAndReturnItToo()`,
+re-run our trace, grovel through the log by hand to find the object that
+corresponds to `0x00253ab0` in the new run, and run `make-tree.pl`. What
+we'd hope to see is a tree that looks like:
+
+ .root: bal=0
+ main: bal=0
+ DoSomethingWithFooAndReturnItToo: bal=1
+ NS_NewFoo: bal=1
+
+That is, `NS_NewFoo()` "leaks" a single reference count; this leak is
+"inherited" by `DoSomethingWithFooAndReturnItToo()`; but is finally
+balanced by a `Release()` in `main()`.
+
+## Hints
+
+Clearly, this is an iterative and analytical process. Here are some
+tricks that make it easier.
+
+**Check for leaks from smart pointers.** If the leak comes from a smart
+pointer that is logged in the XPCOM_MEM_COMPTR_LOG, then
+find-comptr-leakers.pl will find the exact stack for you, and you don't
+have to look at trees.
+
+**Ignore balanced trees**. The `make-tree.pl` script accepts an option
+`--ignore-balanced`, which tells it *not* to bother printing out the
+children of a node whose balance factor is zero. This can help remove
+some of the clutter from an otherwise noisy tree.
+
+**Ignore matched releases from smart pointers.** If you've checked (see
+above) that the leak wasn't from a smart pointer, you can ignore the
+references that came from smart pointers (where we can use the pointer
+identity of the smart pointer to match the AddRef and the Release). This
+requires using an XPCOM_MEM_REFCNT_LOG and an XPCOM_MEM_COMPTR_LOG that
+were collected at the same time. For more details, see the [old
+documentation](http://www-archive.mozilla.org/performance/leak-tutorial.html)
+(which should probably be incorporated here). This is best used with
+`--ignore-balanced`
+
+**Play Mah Jongg**. An unbalanced tree is not necessarily an evil thing.
+More likely, it indicates that one `AddRef()` is cancelled by another
+`Release()` somewhere else in the code. So the game is to try to match
+them with one another.
+
+**Exclude Functions.** To aid in this process, you can create an
+"excludes file", that lists the name of functions that you want to
+exclude from the tree building process (presumably because you've
+matched them). `make-tree.pl` has an option `--exclude [file]`, where
+`[file]` is a newline-separated list of function names that will be
+*excluded* from consideration while building the tree. Specifically, any
+call stack that contains that call site will not contribute to the
+computation of balance factors in the tree.
+
+## How to instrument your objects for refcount tracing and balancing
+
+The process is the same as instrumenting them for BloatView because BloatView
+and refcount tracing share underlying infrastructure.
diff --git a/docs/performance/memory/tree_map_view.md b/docs/performance/memory/tree_map_view.md
new file mode 100644
index 0000000000..30d9968db6
--- /dev/null
+++ b/docs/performance/memory/tree_map_view.md
@@ -0,0 +1,62 @@
+# Tree map view
+
+The Tree map view is new in Firefox 48.
+
+The Tree map view provides a visual representation of the snapshot, that
+helps you quickly get an idea of which objects are using the most
+memory.
+
+A treemap displays [\"hierarchical (tree-structured) data as a set of
+nested rectangles\"](https://en.wikipedia.org/wiki/Treemapping). The
+size of the rectangles corresponds to some quantitative aspect of the
+data.
+
+For the treemaps shown in the Memory tool, things on the heap are
+divided at the top level into four categories:
+
+- **objects**: JavaScript and DOM objects, such as `Function`,
+ `Object`, or `Array`, and DOM types like `Window` and
+ `HTMLDivElement`.
+- **scripts**: JavaScript sources loaded by the page.
+- **strings**
+- **other**: this includes internal
+ [SpiderMonkey](https://developer.mozilla.org/en-US/docs/Tools/Tools_Toolbox#settings/en-US/docs/Mozilla/Projects/SpiderMonkey) objects.
+
+Each category is represented with a rectangle, and the size of the
+rectangle corresponds to the proportion of the heap occupied by items in
+that category. This means you can quickly get an idea of roughly what
+sorts of things allocated by your site are using the most memory.
+
+Within top-level categories:
+
+- **objects** is further divided by the object's type.
+- **scripts** is further subdivided by the script's origin. It also
+ includes a separate rectangle for code that can't be correlated
+ with a file, such as JIT-optimized code.
+- **other** is further subdivided by the object's type.
+
+Here are some example snapshots, as they appear in the Tree map view:
+
+![](../img/treemap-domnodes.png)
+
+This treemap is from the [DOM allocation
+example](DOM_allocation_example.md), which runs a
+script that creates a large number of DOM nodes (200 `HTMLDivElement`
+objects and 4000 `HTMLSpanElement` objects). You can see how almost all
+the heap usage is from the `HTMLSpanElement` objects that it creates.
+
+![](../img/treemap-monsters.png)
+
+This treemap is from the [monster allocation
+example](monster_example.md), which creates three
+arrays, each containing 5000 monsters, each monster having a
+randomly-generated name. You can see that most of the heap is occupied
+by the strings used for the monsters' names, and the objects used to
+contain the monsters' other attributes.
+
+![](../img/treemap-bbc.png)
+
+This treemap is from <http://www.bbc.com/>, and is probably more
+representative of real life than the examples. You can see the much
+larger proportion of the heap occupied by scripts, that are loaded from
+a large number of origins.
diff --git a/docs/performance/perf.md b/docs/performance/perf.md
new file mode 100644
index 0000000000..47177c3cfd
--- /dev/null
+++ b/docs/performance/perf.md
@@ -0,0 +1,57 @@
+# Perf
+
+`perf` is a powerful system-wide instrumentation service that is part of
+Linux. This article discusses how it can be relevant to power profiling.
+
+**Note**: The [power profiling
+overview](power_profiling_overview.md) is
+worth reading at this point if you haven't already. It may make parts
+of this document easier to understand.
+
+## Energy estimates
+
+`perf` can access the Intel RAPL energy estimates. The following example
+shows how to invoke it for this purpose.
+
+```
+sudo perf stat -a -r 1 \
+ -e "power/energy-pkg/" \
+ -e "power/energy-cores/" \
+ -e "power/energy-gpu/" \
+ -e "power/energy-ram/" \
+ <command>
+```
+
+The `-a` is necessary; it means \"all cores\", and without it all the
+measurements will be zero. The `-r 1` means `<command>` is executed
+once; higher values can be used to get variations.
+
+The output will look like the following.
+
+```
+Performance counter stats for 'system wide':
+
+ 51.58 Joules power/energy-pkg/ [100.00%]
+ 14.80 Joules power/energy-cores/ [100.00%]
+ 9.93 Joules power/energy-gpu/ [100.00%]
+ 27.38 Joules power/energy-ram/ [100.00%]
+
+5.003049064 seconds time elapsed
+```
+
+It's not clear from the output, but the following relationship holds.
+
+```
+energy-pkg >= energy-cores + energy-gpu
+```
+
+The measurement is in Joules, which is usually less useful than Watts.
+
+For these reasons
+[rapl](tools_power_rapl.md) is usually a
+better tool for measuring power consumption on Linux.
+
+## Wakeups {#Wakeups}
+
+`perf` can also be used to do [high-context profiling of
+wakeups](http://robertovitillo.com/2014/02/04/idle-wakeups-are-evil/).
diff --git a/docs/performance/perfstats.md b/docs/performance/perfstats.md
new file mode 100644
index 0000000000..6ffaa55da9
--- /dev/null
+++ b/docs/performance/perfstats.md
@@ -0,0 +1,30 @@
+# PerfStats
+
+PerfStats is a framework for the low-overhead selective collection of internal performance metrics.
+The results are accessible through ChromeUtils, Browsertime output, and in select performance tests.
+
+## Adding a new PerfStat
+Define the new PerfStat by adding it to [this list](https://searchfox.org/mozilla-central/rev/b1e5f2c7c96be36974262551978d54f457db2cae/tools/performance/PerfStats.h#34-53) in [`PerfStats.h`](https://searchfox.org/mozilla-central/rev/52da19becaa3805e7f64088e91e9dade7dec43c8/tools/performance/PerfStats.h).
+Then, in C++ code, wrap execution in an RAII object, e.g.
+```
+PerfStats::AutoMetricRecording<PerfStats::Metric::MyMetric>()
+```
+or call the following function manually:
+```
+PerfStats::RecordMeasurement(PerfStats::Metric::MyMetric, Start, End)
+```
+For incrementing counters, use the following:
+```
+PerfStats::RecordMeasurementCount(PerfStats::Metric::MyMetric, incrementCount)
+```
+
+[Here's an example of a patch where a new PerfStat was added and used.](https://hg.mozilla.org/mozilla-central/rev/3e85a73d1fa5c816fdaead66ecee603b38f9b725)
+
+## Enabling collection
+To enable collection, use `ChromeUtils.SetPerfStatsCollectionMask(MetricMask mask)`, where `mask=0` disables all metrics and `mask=0xFFFFFFFF` enables all of them.
+`MetricMask` is a bitmask based on `Metric`, i.e. `Metric::LayerBuilding (2)` is synonymous to `1 << 2` in `MetricMask`.
+
+## Accessing results
+Results can be accessed with `ChromeUtils.CollectPerfStats()`.
+The Browsertime test framework will sum results across processes and report them in its output.
+The raptor-browsertime Windows essential pageload tests also collect all PerfStats.
diff --git a/docs/performance/platform_microbenchmarks/platform_microbenchmarks.md b/docs/performance/platform_microbenchmarks/platform_microbenchmarks.md
new file mode 100644
index 0000000000..761cd0f30d
--- /dev/null
+++ b/docs/performance/platform_microbenchmarks/platform_microbenchmarks.md
@@ -0,0 +1,21 @@
+# Platform microbenchmarks
+
+Platform microbenchmarks benchmarks specific low-level operations used
+by the gecko platform. If a test regresses, it could result in the
+degradation in the performance of some user-visible feature.
+
+The list of tests and their descriptions is currently incomplete. If
+something is missing, please search for it in the gecko source and
+update this page (or ask the original author to do so, if you're still
+not sure).
+
+## String tests
+
+* PerfStripWhitespace
+* PerfCompressWhitespace
+* PerfStripCharsWhitespace
+* PerfStripCRLF
+* PerfStripCharsCRLF
+
+These tests measure the amount of time it takes to perform a large
+number of operations on low-level strings.
diff --git a/docs/performance/power_profiling_overview.md b/docs/performance/power_profiling_overview.md
new file mode 100644
index 0000000000..bb8f511fe2
--- /dev/null
+++ b/docs/performance/power_profiling_overview.md
@@ -0,0 +1,326 @@
+# Power profiling
+
+This article covers important background information about power
+profiling, with an emphasis on Intel processors used in desktop and
+laptop machines. It serves as a starting point for anybody doing power
+profiling for the first time.
+
+## Basic physics concepts
+
+In physics, *[power](https://en.wikipedia.org/wiki/Power_%28physics%29)*
+is the rate of doing
+*[work](https://en.wikipedia.org/wiki/Work_%28physics%29 "Work (physics)")*.
+It is equivalent to an amount of
+*[energy](https://en.wikipedia.org/wiki/Energy_%28physics%29 "Energy (physics)"){.mw-redirect}*
+consumed per unit time. In SI units, energy is measured in Joules, and
+power is measured in Watts, which is equivalent to Joules per second.
+
+Although power is an instantaneous concept, in practice measurements of
+it are determined in a non-instantaneous fashion, i.e. by dividing an
+energy amount by a non-infinitesimal time period. Strictly speaking,
+such a computation gives the *average power* but this is often referred
+to as just the *power* when context makes it clear.
+
+In the context of computing, a fully-charged mobile device battery (as
+found in a laptop or smartphone) holds a certain amount of energy, and
+the speed at which that stored energy is depleted depends on the power
+consumption of the mobile device. That in turn depends on the software
+running on the device. Web browsers are popular applications and can be
+power-intensive, and therefore can significantly affect battery life. As
+a result, it is worth optimizing (i.e. reducing) the power consumption
+caused by Firefox and Firefox OS.
+
+## Intel processor basics
+
+### Processor layout
+
+The following diagram (from the [Intel Power Governor
+documentation)](https://software.intel.com/en-us/articles/intel-power-governor)
+shows how machines using recent Intel processors are constructed.
+
+![](img/power-planes.jpg)
+
+The important points are as follows.
+
+- The processor has one or more *packages*. These are part of the
+ actual processor that you buy from Intel. Client processors (e.g.
+ Core i3/i5/i7) have one package. Server processors (e.g. Xeon)
+ typically have two or more packages.
+- Each package contains multiple *cores*.
+- Each core typically has
+ [hyper-threading](https://en.wikipedia.org/wiki/Hyper-threading),
+ which means it contains two logical *CPUs*.
+- The part of the package outside the cores is called the [*uncore* or
+ *system agent*](https://en.wikipedia.org/wiki/Uncore)*.* It includes
+ various components including the L3 cache, memory controller, and,
+ for processors that have one, the integrated GPU.
+- RAM is separate from the processor.
+
+### C-states
+
+Intel processors have aggressive power-saving features. The first is the
+ability to switch frequently (thousands of times per second) between
+active and idle states, and there are actually several different kinds
+of idle states. These different states are called
+*[C-states](https://en.wikipedia.org/wiki/Advanced_Configuration_and_Power_Interface#Processor_states).*
+C0 is the active/busy state, where instructions are being executed. The
+other states have higher numbers and reflect increasing deeper idle
+states. The deeper an idle state is, the less power it uses, but the
+longer it takes to wake up from.
+
+Note: the [ACPI
+standard](https://en.wikipedia.org/wiki/Advanced_Configuration_and_Power_Interface)
+specifies four states, C0, C1, C2 and C3. Intel maps these to
+processor-specific states such as C0, C1, C2, C6 and C7. and many tools
+report C-states using the latter names. The exact relationship is
+confusing, and chapter 13 of the [Intel optimization
+manual](http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-optimization-manual.html)
+has more details. The important thing is that C0 is always the active
+state, and for the idle states a higher number always means less power
+consumption.
+
+The other thing to note about C-states is that they apply both to cores
+and the entire package --- i.e. if all cores are idle then the entire
+package can also become idle, which reduces power consumption even
+further.
+
+The fraction of time that a package or core spends in an idle C-state is
+called the *C-state residency*. This is a misleading term --- the active
+state, C0, is also a C-state --- but one that is nonetheless common.
+
+Intel processors have model-specific registers (MSRs) containing
+measurements of how much time is spent in different C-states, and tools
+such as [powermetrics](powermetrics.md)
+(Mac), powertop and
+[turbostat](turbostat.md) (Linux) can
+expose this information.
+
+A *wakeup* occurs when a core or package transitions from an idle state
+to the active state. This happens when the OS schedules a process to run
+due to some kind of event. Common causes of wakeups include scheduled
+timers going off and blocked I/O system calls receiving data.
+Maintaining C-state residency is crucial to keep power consumption low,
+and so reducing wakeup frequency is one of the best ways to reduce power
+consumption.
+
+One consequence of the existence of C-states is that observations made
+during power profiling --- even more than with other kinds of profiling
+--- can disturb what is being observed. For example, the Gecko Profiler
+takes samples at 1000Hz using a timer. Each of these samples can trigger
+a wakeup, which consumes power and obscures Firefox's natural wakeup
+patterns. For this reason, integrating power measurements into the Gecko
+Profiler is unlikely to be useful, and other power profiling tools
+typically use much lower sampling rates (e.g. 1Hz.)
+
+### P-states
+
+Intel processors also support multiple *P-states*. P0 is the state where
+the processor is operating at maximum frequency and voltage, and
+higher-numbered P-states operate at a lower frequency and voltage to
+reduce power consumption. Processors can have dozens of P-states, but
+the transitions are controlled by the hardware and OS and so P-states
+are of less interest to application developers than C-states.
+
+## Power and power-related measurements
+
+There are several kinds of power and power-related measurements. Some
+are global (whole-system) and some are per-process. The following
+sections list them from best to worst.
+
+### Power measurements
+
+The best measurements are measured in joules and/or watts, and are taken
+by measuring the actual hardware in some fashion. These are global
+(whole-system) measurements that are affected by running programs but
+also by other things such as (for laptops) how bright the monitor
+backlight is.
+
+- Devices such as ammeters give the best results, but these can be
+ expensive and difficult to set up.
+- A cruder technique that works with mobile machines and devices is to
+ run a program for a long time and simply time how long it takes for
+ the battery to drain. The long measurement times required are a
+ disadvantage, though.
+
+### Power estimates
+
+The next best measurements come from recent (Sandy Bridge and later)
+Intel processors that implement the *RAPL* (Running Average Power Limit)
+interface that provides MSRs containing energy consumption estimates for
+up to four *power planes* or *domains* of a machine, as seen in the
+diagram above.
+
+- PKG: The entire package.
+ - PP0: The cores.
+ - PP1: An uncore device, usually the GPU (not available on all
+ processor models.)
+- DRAM: main memory (not available on all processor models.)
+
+The following relationship holds: PP0 + PP1 \<= PKG. DRAM is independent
+of the other three domains.
+
+These values are computed using a power model that uses
+processor-internal counts as inputs, and they have been
+[verified](http://www.computer.org/csdl/proceedings/ispass/2013/5776/00/06557170.pdf)
+as being fairly accurate. They are also updated frequently, at
+approximately 1,000 Hz, though the variability in their update latency
+means that they are probably only accurate at lower frequencies, e.g. up
+to 20 Hz or so. See section 14.9 of Volume 3 of the [Intel Software
+Developer's
+Manual](http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html)
+for more details about RAPL.
+
+Tools that can take RAPL readings include the following.
+
+- `tools/power/rapl`: all planes; Linux and Mac.
+- [Intel Power
+ Gadget](intel_power_gadget.md): PKG and
+ PP0 planes; Windows, Mac and Linux.
+- [powermetrics](powermetrics.md): PKG
+ plane; Mac.
+- [perf](perf.md): all planes; Linux.
+- [turbostat](turbostat.md): PKG, PP0 and
+ PP1 planes; Linux.
+
+Of these,
+[tools/power/rapl](tools_power_rapl.md) is
+generally the easiest and best to use because it reads all power planes,
+it's a command line utility, and it doesn't measure anything else.
+
+### Proxy measurements
+
+The next best measurements are proxy measurements, i.e. measurements of
+things that affect power consumption such as CPU activity, GPU activity,
+wakeup frequency, C-state residency, disk activity, and network
+activity. Some of these are measured on a global basis, and some can be
+measured on a per-process basis. Some can also be measured via
+instrumentation within Firefox itself.
+
+The correlation between each proxy measure and power consumption is hard
+to know and can vary greatly. When used carefully, however, they can
+still be useful. This is because they can often be measured in a more
+fine-grained fashion than power measurements and estimates, which is
+vital for gaining insight into how a program can reduce power
+consumption.
+
+Most profiling tools provide at least some proxy measurements.
+
+### Hybrid proxy measurements
+
+These are combinations of proxy measurements. The combinations are
+semi-arbitrary, they amplify the unreliability of proxy measurements,
+and unlike non-hybrid proxy measurements, they don't have a clear
+physical meaning. Avoid them.
+
+The most notable example of a hybrid proxy measurement is the ["Energy
+Impact" used by OS X's Activity
+[Monitor](activity_monitor_and_top.md#What-does-Energy-Impact-measure).
+
+## Ways to user power-related measurements
+
+### Low-context measurements
+
+Most power-related measurements are global or per-process. Such
+low-context measurements are typically good for understand *if* power
+consumption is good or bad, but in the latter case they often don't
+provide much insight into why the problem is occurring, which part of
+the code is at fault, or how it can be fixed. Nonetheless, they can
+still help improve understanding of a problem by using *differential
+profiling*.
+
+- Compare browsers to see if Firefox is doing better or worse than
+ another browser on a particular workload.
+- Compare different versions of Firefox to see if Firefox has improved
+ or worsened over time on a particular workload. This can identify
+ specific changes that caused regressions, for example.
+- Compare different configurations of Firefox to see if a particular
+ feature is affecting things.
+- Compare different workloads. This can be particularly useful if the
+ workloads only vary slightly. For example, it can be useful to
+ gradually remove elements from a web page and see how the
+ power-related measurements change. Even just switching a tab from
+ the foreground to the background can make a difference.
+
+### High-context measurements
+
+A few power-related measurements can be obtained in a high-context
+fashion, e.g. with stack traces that clearly pinpoint specific parts of
+the code as being responsible.
+
+- Standard performance profiling tools that measure CPU usage or
+ proxies of CPU usage (such as instruction counts) typically provide
+ high-context measurements. This is useful because high CPU usage
+ typically causes high power consumption.
+- Some tools can provide high-context wakeup measurements:
+ [dtrace](dtrace.md) (on Mac) and
+ [perf](perf.md) (on Linux.)
+- Source-level instrumentation, such as [TimerFirings
+ logging](timerfirings_logging.md), can
+ identify which timers are firing frequently.
+
+## Power profiling how-to
+
+This section aims to put together all the above information and provide
+a set of strategies for finding, diagnosing and fixing cases of high
+power consumption.
+
+- First of all, all measurements are best done on a quiet machine that
+ is running little other than the program of interest. Global
+ measurements in particular can be completely skewed and unreliable
+ if this is not the case.
+- Find or confirm a test case where Firefox's power consumption is
+ high. "High" can most easily be gauged by comparing against other
+ browsers. Use power measurements or estimates (e.g. via
+ [tools/power/rapl](tools_power_rapl.md),
+ or `mach power` on Mac, or [Intel Power
+ Gadget](intel_power_gadget.md) on
+ Windows) for the comparisons. Avoid lower-quality measurements,
+ especially Activity Monitor's "Energy Impact".
+- Try using differential profiling to narrow down the cause.
+ - Try turning hardware acceleration on or off; e10s on or off;
+ Flash on or off.
+ - Try putting the relevant tab in the foreground vs. in the
+ background.
+ - If the problem manifests on a particular website, try saving a
+ local copy of the site and then manually removing HTML elements
+ to see if a particular page feature is causing the problem
+- Many power problems are caused by either high CPU usage or high
+ wakeup frequency. Use one of the low-context tools to determine if
+ this is the case (e.g. on Mac use
+ [powermetrics](powermetrics.md).) If
+ so, follow that up by using a tool that gives high-context
+ measurements, which hopefully will identify the cause of the
+ problem.
+ - For high CPU usage, many profilers can be used: Firefox's dev
+ tools profiler, the Gecko Profiler, or generic performance
+ profilers.
+ - For high wakeup counts, use
+ [dtrace](dtrace.md) or
+ [perf](perf.md) or [TimerFirings logging](timerfirings_logging.md).
+- On Mac workloads that use graphics, Activity Monitor's "Energy"
+ tab can tell you if the high-performance GPU is being used, which
+ uses more power than the integrated GPU.
+- If neither CPU usage nor wakeup frequency identifies the problem,
+ more ingenuity may be needed. Looking at other measurements (C-state
+ residency, GPU usage, etc.) may be helpful.
+- Animations are sometimes the cause of high power consumption. The
+ [animation
+ inspector](/devtools-user/page_inspector/how_to/work_with_animations/index.rst#animation-inspector)
+ in the Firefox Devtools can identify them. Alternatively, [here is
+ an
+ explanation](https://bugzilla.mozilla.org/show_bug.cgi?id=1190721#c10)
+ of how one developer diagnosed two animation-related problems the
+ hard way (which required genuine platform expertise).
+- The approximate cause of power problems often isn't that hard to
+ find. Fixing them is often the hard part. Good luck.
+- If you do fix a problem by improving a proxy measurement, you should
+ verify that it also improves a power measurement or estimate. That
+ way you know the fix had a genuine effect.
+
+## Further reading
+
+Chapter 13 of the [Intel optimization
+manual](http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-optimization-manual.html)
+has many details about optimizing for power consumption. Section 13.5
+("Tuning Software for Intelligent Power Consumption") in particular is
+worth reading.
diff --git a/docs/performance/powermetrics.md b/docs/performance/powermetrics.md
new file mode 100644
index 0000000000..44df0eda9c
--- /dev/null
+++ b/docs/performance/powermetrics.md
@@ -0,0 +1,167 @@
+# powermetrics
+
+`powermetrics` is a Mac-only command-line utility that provides many
+high-quality power-related measurements. It is most useful for getting
+CPU, GPU and wakeup measurements in a precise and easily scriptable
+fashion (unlike [Activity Monitor and
+top](activity_monitor_and_top.md))
+especially in combination with
+[rapl](tools_power_rapl.md) via the
+`mach power` command. This document describes the version of
+`powermetrics` that comes with Mac OS 10.10. The one that comes with
+10.9 is less powerful.
+
+**Note**: The [power profiling
+overview](power_profiling_overview.md) is
+worth reading at this point if you haven\'t already. It may make parts
+of this document easier to understand.
+
+## Quick start
+
+`powermetrics` provides a vast number of measurements. The following
+command encompasses the most useful ones:
+
+sudo powermetrics --samplers tasks --show-process-coalition --show-process-gpu -n 1 -i 5000
+
+- `--samplers tasks` tells it to just do per-process measurements.
+- `--show-process-coalition`` `tells it to group *coalitions* of
+ related processes, e.g. the Firefox parent process and child
+ processes.
+- `--show-process-gpu` tells it to show per-process GPU measurements.
+- `-n 1` tells it to take one sample and then stop.
+- `-i 5000` tells it to use a sample length of 5 seconds (5000 ms).
+ Change this number to get shorter or longer samples.
+
+The following is example output from such an invocation:
+
+ *** Sampled system activity (Fri Sep 4 17:15:14 2015 +1000) (5009.63ms elapsed) ***
+
+ *** Running tasks ***
+
+ Name ID CPU ms/s User% Deadlines (<2 ms, 2-5 ms) Wakeups (Intr, Pkg idle) GPU ms/s
+ com.apple.Terminal 293 447.66 274.83 120.35 221.74
+ firefox 84627 77.59 55.55 15.37 2.59 91.42 42.12 204.47
+ plugin-container 84628 377.22 37.18 43.91 18.56 178.65 75.85 17.29
+ Terminal 694 9.86 79.94 0.00 0.00 4.39 2.20 0.00
+ powermetrics 84694 1.21 31.53 0.00 0.00 0.20 0.20 0.00
+ com.google.Chrome 489 233.83 48.10 25.95 0.00
+ Google Chrome Helper 84688 181.57 92.81 0.00 0.00 23.95 12.77 0.00
+ Google Chrome 84681 57.26 76.07 4.39 0.00 23.75 12.97 0.00
+ Google Chrome Helper 84685 0.13 48.08 0.00 0.00 0.40 0.20 0.00
+ kernel_coalition 1 128.64 780.19 330.52 0.00
+ kernel_task 0 109.97 0.00 0.20 0.00 779.47 330.35 0.00
+ launchd 1 18.88 2.44 0.00 0.00 0.40 0.20 0.00
+ com.apple.Safari 488 90.60 108.58 56.48 26.65
+ com.apple.WebKit.WebContent 84679 64.21 84.69 0.00 0.00 104.19 54.89 26.66
+ com.apple.WebKit.Networking 84678 26.89 58.89 0.40 0.00 1.60 0.00 0.00
+ Safari 84676 1.56 55.74 0.00 0.00 2.59 1.40 0.00
+ com.apple.Safari.SearchHelper 84690 0.15 49.49 0.00 0.00 0.20 0.20 0.00
+ org.mozilla.firefox 482 76.56 124.34 63.47 0.00
+ firefox 84496 76.70 89.18 10.58 5.59 124.55 63.48 0.00
+
+This sample was taken while the following programs were running:
+
+- Firefox Beta (single process, invoked from the Mac OS dock, shown in
+ the `org.mozilla.firefox` coalition.)
+- Firefox Nightly (multi-process, invoked from the command line, shown
+ in the `com.apple.Terminal` coalition.)
+- Google Chrome.
+- Safari.
+
+The grouping of parent and child processes (in coalitions) is obvious.
+The meaning of the columns is as follows.
+
+- **Name**: Coalition/process name. Process names within coalitions
+ are indented.
+- **ID**: Coalition/process ID number.
+- **CPU ms/s**: CPU time used by the coalition/process, per second,
+ during the sample period. The sum of the process values typically
+ exceeds the coalition value slightly, for unknown reasons.
+- **User%**: Percentage of that CPU time spent in user space (as
+ opposed to kernel mode.)
+- **Deadlines (\<2 ms, 2-5 ms)**: These two columns count how many
+ \"short\" timers woke up threads in the process, per second, during
+ the sample period. High frequency timers, which typically have short
+ time-to-deadlines, can cause high power consumption and should be
+ avoided if possible.
+- **Wakeups (Intr, Pkg idle)**: These two columns count how many
+ wakeups occurred, per second, during the sample period. The first
+ column counts interrupt-level wakeups that resulted in a thread
+ being dispatched in the process. The second column counts \"package
+ idle exit\" wakeups, which wake up the entire package as opposed to
+ just a single core; such wakeups are particularly expensive, and
+ this count is a subset of the first column\'s count.
+- **GPU ms/s**: GPU time used by the coalition/process, per second,
+ during the sample period.
+
+Other things to note.
+
+- Smaller is better --- i.e. results in lower power consumption ---
+ for all of these measurements.
+- There is some overlap between the two \"Deadlines\" columns and the
+ two \"Wakeups\" columns. For example, firing a single sub-2ms
+ deadline can also cause a package idle exit wakeup.
+- Many of these measurements are also obtainable by passing the
+ `TASK_POWER_INFO` flag and a `task_power_info` struct to the
+ `task_info` function.
+- By default, the coalitions/processes are sorted by a composite value
+ computed from several factors, though this can be changed via
+ command-line options.
+
+## Other measurements
+
+`powermetrics` can also report measurements of backlight usage, network
+activity, disk activity, interrupt distribution, device power states,
+C-state residency, P-state residency, quality of service classes, and
+thermal pressure. These are less likely to be useful for profiling
+Firefox, however. Run with the `--show-all` to see all of these at once,
+but note that you\'ll need a very wide window to see all the data.
+
+Also note that `powermetrics -h` is a better guide to the the
+command-line options than `man powermetrics`.
+
+## mach power
+
+You can use the `mach power` command to run `powermetrics` in
+combination with `rapl` in a way that gives the most useful summary
+measurements for each of Firefox, Chrome and Safari. The following is
+sample output.
+
+ total W = _pkg_ (cores + _gpu_ + other) + _ram_ W
+ #01 17.14 W = 14.98 ( 5.50 + 1.19 + 8.29) + 2.16 W
+
+ 1 sample taken over a period of 30.000 seconds
+
+ Name ID CPU ms/s User% Deadlines (<2 ms, 2-5 ms) Wakeups (Intr, Pkg idle) GPU ms/s
+ com.google.Chrome 500 439.64 585.35 218.62 19.17
+ Google Chrome Helper 67319 284.75 83.03 296.67 0.00 454.05 172.74 0.00
+ Google Chrome Helper 67304 55.23 64.83 0.03 0.00 9.43 4.33 19.17
+ Google Chrome 67301 63.77 68.09 29.46 0.13 76.11 22.26 0.00
+ Google Chrome Helper 67320 38.30 66.70 17.83 0.00 45.78 19.29 0.00
+ com.apple.WindowServer 68 102.58 112.36 43.15 80.52
+ WindowServer 141 103.03 58.19 60.48 6.40 112.36 43.15 80.53
+ com.apple.Safari 499 267.19 110.53 46.05 1.69
+ com.apple.WebKit.WebContent 67372 190.15 79.34 2.02 0.14 129.28 53.79 2.33
+ com.apple.WebKit.Networking 67292 65.23 52.74 0.07 0.00 4.33 1.40 0.00
+ Safari 67290 29.09 77.65 0.23 0.00 7.13 3.37 0.00
+ com.apple.Safari.SearchHelper 67371 13.88 91.18 0.00 0.00 0.36 0.05 0.00
+ com.apple.WebKit.WebContent 67297 0.81 56.84 0.10 0.00 2.20 1.30 0.00
+ com.apple.WebKit.WebContent 67293 0.46 76.40 0.03 0.00 0.57 0.20 0.00
+ com.apple.WebKit.WebContent 67295 0.24 67.72 0.00 0.00 0.90 0.37 0.00
+ com.apple.WebKit.WebContent 67298 0.17 59.88 0.00 0.00 0.50 0.13 0.00
+ com.apple.WebKit.WebContent 67296 0.07 43.51 0.00 0.00 0.10 0.03 0.00
+ kernel_coalition 1 111.76 724.80 213.09 0.12
+ kernel_task 0 107.06 0.00 5.86 0.00 724.46 212.99 0.12
+ org.mozilla.firefox 498 92.17 212.69 75.67 1.81
+ firefox 63865 61.00 87.18 1.00 0.87 25.79 9.00 1.81
+ plugin-container 67269 31.49 72.46 1.80 0.00 186.90 66.68 0.00
+ com.apple.WebKit.Plugin.64 67373 55.55 74.38 0.74 0.00 9.51 3.13 0.02
+ com.apple.Terminal 109 6.22 0.40 0.23 0.00
+ Terminal 208 6.25 92.99 0.00 0.00 0.33 0.20 0.00
+
+The `rapl` output is first, then the `powermetrics` output. As well as
+the browser processes, the `WindowServer` and kernel tasks are shown
+because browsers often trigger significant load in them.
+
+The default sample period is 30,000 milliseconds (30 seconds), but that
+can be changed with the `-i` option.
diff --git a/docs/performance/profiling_with_concurrency_visualizer.md b/docs/performance/profiling_with_concurrency_visualizer.md
new file mode 100644
index 0000000000..495fa15538
--- /dev/null
+++ b/docs/performance/profiling_with_concurrency_visualizer.md
@@ -0,0 +1,5 @@
+# Profiling with Concurrency Visualizer
+
+Concurrency Visualizer is an excellent alternative to xperf. In newer versions of Visual Studio, it is an addon that needs to be downloaded.
+
+Here are some scripts that you can be used for manipulating the profiles that have been exported to CSV: [https://github.com/jrmuizel/concurrency-visualizer-scripts](https://github.com/jrmuizel/concurrency-visualizer-scripts)
diff --git a/docs/performance/profiling_with_instruments.md b/docs/performance/profiling_with_instruments.md
new file mode 100644
index 0000000000..ac37bb2660
--- /dev/null
+++ b/docs/performance/profiling_with_instruments.md
@@ -0,0 +1,110 @@
+# Profiling with Instruments
+
+Instruments can be used for memory profiling and for statistical
+profiling.
+
+## Official Apple documentation
+
+- [Instruments User
+ Guide](https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/)
+- [Instruments User
+ Reference](https://developer.apple.com/library/mac/documentation/AnalysisTools/Reference/Instruments_User_Reference/)
+- [Instruments Help
+ Articles](https://developer.apple.com/library/mac/recipes/Instruments_help_articles/)
+- [Instruments
+ Help](https://developer.apple.com/library/mac/recipes/instruments_help-collection/)
+- [Performance
+ Overview](https://developer.apple.com/library/mac/documentation/Performance/Conceptual/PerformanceOverview/)
+
+### Basic Usage
+
+- Select \"Time Profiler\" from the \"Choose a profiling template
+ for:\" dialog.
+- In the top left, next to the record and pause button, there will be
+ a \"\[machine name\] \> All Processes\". Click \"All Processes\" and
+ select \"firefox\" from the \"Running Applications\" section.
+- Click the record button (red circle in top left)
+- Wait for the amount of time that you want to profile
+- Click the stop button
+
+## Command line tools
+
+There is
+[instruments](https://developer.apple.com/library/mac/documentation/Darwin/Reference/Manpages/man1/instruments.1.html)
+and
+[iprofiler](https://developer.apple.com/library/mac/documentation/Darwin/Reference/Manpages/man1/iprofiler.1.html).
+
+How do we monitor performance counters (cache miss etc.)? Instruments
+has a \"Counters\" instrument that can do this.
+
+## Memory profiling
+
+Instruments will record a call stack at each allocation point. The call
+tree view can be quite helpful here. Switch from \"Statistics\". This
+`malloc` profiling is done using the `malloc_logger` infrastructure
+(similar to `MallocStackLogging`). Currently this means you need to
+build with jemalloc disabled (`ac_add_options --disable-jemalloc`). You
+also need the fix to [Bug
+719427](https://bugzilla.mozilla.org/show_bug.cgi?id=719427 "https://bugzilla.mozilla.org/show_bug.cgi?id=719427")
+
+## Kernel stacks
+
+Under "File" -> "Recording Options" you can enable "Record Kernel Callstacks".
+To get full symbols and not just the exported ones, you'll to install the matching
+[Kernel Debug Kit](https://developer.apple.com/download/all/?q=Kernel%20Debug%20Kit).
+Make sure you install the one whose macOS version exactly matches your version,
+including the identifier at the end (e.g. "12.3.1 (21E258)").
+
+### Allow Instruments to find kernel symbols
+
+Installing the KDK is often not enough for Instruments to find the symbols.
+Instruments uses Spotlight to find the dSYMs with the matching UUID, so you
+need to put the dSYM in a place where Spotlight will index it.
+
+First, check the UUID of your macOS installation's kernel. To do so, run the
+following:
+
+```
+% dwarfdump --uuid /System/Library/Kernels/kernel.release.t6000
+UUID: C342869F-FFB9-3CCE-A5A3-EA711C1E87F6 (arm64e) /System/Library/Kernels/kernel.release.t6000
+```
+
+Then, find the corresponding dSYM file in the KDK that you installed, and
+run `mdls` on it. For example:
+
+```
+% mdls /Library/Developer/KDKs/KDK_12.3.1_21E258.kdk/System/Library/Kernels/kernel.release.t6000.dSYM
+```
+
+(Make sure you use the `.release` variant, not the `.development` variant
+or any of the others.)
+
+If the output from `mdls` contains the string `com_apple_xcode_dsym_uuids`
+and the UUID matches, you're done.
+
+Otherwise, try copying the `kernel.release.t6000.dSYM` bundle to your home
+directory, and then run `mdls` on the copied file. For example:
+
+```
+% cp -r /Library/Developer/KDKs/KDK_12.3.1_21E258.kdk/System/Library/Kernels/kernel.release.t6000.dSYM ~/
+% mdls ~/kernel.release.t6000.dSYM
+_kMDItemDisplayNameWithExtensions = "kernel.release.t6000.dSYM"
+com_apple_xcode_dsym_paths = (
+ "Contents/Resources/DWARF/kernel.release.t6000"
+)
+com_apple_xcode_dsym_uuids = (
+ "C342869F-FFB9-3CCE-A5A3-EA711C1E87F6"
+)
+kMDItemContentCreationDate = 2022-03-21 15:25:57 +0000
+[...]
+```
+
+Now Instruments should be able to pick up the kernel symbols.
+
+## Misc
+
+The `DTPerformanceSession` api can be used to control profiling from
+applications like the old CHUD API we use in Shark builds. [Bug
+667036](https://bugzilla.mozilla.org/show_bug.cgi?id=667036 "https://bugzilla.mozilla.org/show_bug.cgi?id=667036")
+
+System Trace might be useful.
diff --git a/docs/performance/profiling_with_xperf.md b/docs/performance/profiling_with_xperf.md
new file mode 100644
index 0000000000..030dae7c68
--- /dev/null
+++ b/docs/performance/profiling_with_xperf.md
@@ -0,0 +1,180 @@
+# Profiling with xperf
+
+Xperf is part of the Microsoft Windows Performance Toolkit, and has
+functionality similar to that of Shark, oprofile, and (for some things)
+dtrace/Instruments. For stack walking, Windows Vista or higher is
+required; I haven't tested it at all on XP.
+
+This page applies to xperf version **4.8.7701 or newer**. To see your
+xperf version, either run '`xperf`' on a command line with no
+arguments, or start '`xperfview`' and look at Help -\> About
+Performance Analyzer. (Note that it's not the first version number in
+the About window; that's the Windows version.)
+
+If you have an older version, you will experience bugs, especially
+around symbol loading for local builds.
+
+## Installation
+
+For all versions, the tools are part of the latest [Windows 7 SDK (SDK
+Version
+7.1)](http://www.microsoft.com/downloads/details.aspx?FamilyID=6b6c21d2-2006-4afa-9702-529fa782d63b&displaylang=en "http://www.microsoft.com/downloads/details.aspx?FamilyID=6b6c21d2-2006-4afa-9702-529fa782d63b&displaylang=en"){.external}.
+Use the web installer to install at least the \"Win32 Development
+Tools\". Once the SDK installs, execute either `wpt_x86.msi` or
+`wpt_x64.msi` in the `Redist/Windows Performance Toolkit `folder of the
+SDK's install location (typically Program Files/Microsoft
+SDKs/Windows/v7.1/Redist/Windows Performance Toolkit) to actually
+install the Windows Performance Toolkit tools.
+
+It might already be installed by the Windows SDK. Check if C:\\Program
+Files\\Microsoft Windows Performance Toolkit already exists.
+
+For 64-bit Windows 7 or Vista, you'll need to do a registry tweak and
+then restart to enable stack walking:\
+\
+`REG ADD "HKLM\System\CurrentControlSet\Control\Session Manager\Memory Management" -v DisablePagingExecutive -d 0x1 -t REG_DWORD -f`
+
+## Symbol Server Setup
+
+With the latest versions of the Windows Performance Toolkit, you can
+modify the symbol path directly from within the program via the Trace
+menu. Just make sure you set the symbol paths before enabling \"Load
+Symbols\" and before opening a summary view. You can also modify the
+`_NT_SYMBOL_PATH` and `_NT_SYMCACHE_PATH` environment variables to make
+these changes permanent.
+
+The standard symbol path that includes both Mozilla's and Microsoft's
+symbol server configuration is as follows:
+
+`_NT_SYMCACHE_PATH: C:\symbols _NT_SYMBOL_PATH: srv*c:\symbols*http://msdl.microsoft.com/download/symbols;srv*c:\symbols*http://symbols.mozilla.org/firefox/`
+
+To add symbols **from your own builds**, add
+`C:\path\to\objdir\dist\bin` to `_NT_SYMBOL_PATH`. As with all Windows
+paths, the symbol path uses semicolons (`;`) as separators.
+
+Make sure you select the Trace -\> Load Symbols menu option in the
+Windows Performance Analyzer (xperfview).
+
+There seems to be a bug in xperf and symbols; it is very sensitive to
+when the symbol path is edited. If you change it within the program,
+you'll have to close all summary tables and reopen them for it to pick
+up the new symbol path data.
+
+You'll have to agree to a EULA for the Microsoft symbols \-- if you're
+not prompted for this, then something isn't configured right in your
+symbol path. (Again, make sure that the directories exist; if they
+don't, it's a silent error.)
+
+## Quick Start
+
+All these tools will live, by default, in C:\\Program Files\\Microsoft
+Windows Performance Toolkit. Either run these commands from there, or
+add the directory to your path. You will need to use an elevated command
+prompt to start or stop profiling.
+
+Start recording data:
+
+`xperf -on latency -stackwalk profile`
+
+\"Latency\" is a special provider name that turns on a few predefined
+kernel providers; run \"xperf -providers k\" to view a full list of
+providers and groups. You can combine providers, e.g., \"xperf -on
+DiagEasy+FILE_IO\". \"-stackwalk profile\" tells xperf to capture a
+stack for each PROFILE event; you could also do \"-stackwalk
+profile+file_io\" to capture a stack on each cpu profile tick and each
+file io completion event.
+
+Stop:
+
+`xperf -d out.etl`
+
+View:
+
+`xperfview out.etl`
+
+The MSDN
+\"[Quickstart](http://msdn.microsoft.com/en-us/library/ff190971%28v=VS.85%29.aspx){.external}\"
+page goes over this in more detail, and also has good explanations of
+how to use xperfview. I'm not going to repeat it here, because I'd be
+using essentially the same screenshots, so go look there.
+
+The 'stack' view will give results similar to shark.
+
+## Heap Profiling
+
+xperf has good tools for heap allocation profiling, but they have one
+major limitation: you can't build with jemalloc and get heap events
+generated. The stock windows CRT allocator is horrible about
+fragmentation, and causes memory usage to rise drastically even if only
+a small fraction of that memory is in use. However, even despite this,
+it's a useful way to track allocations/deallocations.
+
+### Capturing Heap Data
+
+The \"-heap\" option is used to set up heap tracing. Firefox generates
+lots of events, so you may want to play with the
+BufferSize/MinBuffers/MaxBuffers options as well to ensure that you
+don't get dropped events. Also, when recording the stack, I've found
+that a heap trace is often missing module information (I believe this is
+a bug in xperf). It's possible to get around that by doing a
+simultaneous capture of non-heap data.
+
+To start a trace session, launching a new Firefox instance:
+
+`xperf -on base xperf -start heapsession -heap -PidNewProcess "./firefox.exe -P test -no-remote" -stackwalk HeapAlloc+HeapRealloc -BufferSize 512 -MinBuffers 128 -MaxBuffers 512`
+
+To stop a session and merge the resulting files:
+
+`xperf -stop heapsession -d heap.etl xperf -d main.etl xperf -merge main.etl heap.etl result.etl`
+
+\"result.etl\" will contain your merged data; you can delete main.etl
+and heap.etl. Note that it's possible to capture even more data for the
+non-heap profile; for example, you might want to be able to correlate
+heap events with performance data, so you can do
+\"`xperf -on base -stackwalk profile`\".
+
+In the viewer, when summary data is viewed for heap events (Heap
+Allocations Outstanding, etc. all lead to the same summary graphs), 3
+types of allocations are listed \-- AIFI, AIFO, AOFI. This is shorthand
+for \"Allocated Inside, Freed Inside\", \"Allocated Inside, Freed
+Outside\", \"Allocated Outside, Freed Inside\". These refer to the time
+range that was selected for the summary graph; for example, something
+that's in the AOFI category was allocated before the start of the
+selected time range, but the free event happened inside.
+
+## Tips
+
+- In the summary views, the yellow bar can be dragged left and right
+ to change the grouping \-- for example, drag it to the left of the
+ Module column to have grouping happen only by process (stuff that's
+ to the left), so that you get symbols in order of weight, regardless
+ of what module they're in.
+- Dragging the columns around will change grouping in various ways;
+ experiment to get the data that you're looking for. Also experiment
+ with turning columns on and off; removing a column will allow data
+ to be aggregated without considering that column's contributions.
+- Disabling all but one core will make the numbers add up to 100%.
+ This can be done by running 'msconfig' and going to Advance
+ Options from the \"Boot\" tab.
+
+## Building Firefox
+
+To get good data from a Firefox build, it is important to build with the
+following options in your mozconfig:
+
+`export CFLAGS="-Oy-" export CXXFLAGS="-Oy-"`
+
+This disables frame-pointer optimization which lets xperf do a much
+better job unwinding the stack. Traces can be captured fine without this
+option (for example, from nightlies), but the stack information will not
+be useful.
+
+`ac_add_options --enable-debug-symbols`
+
+This gives us symbols.
+
+## For More Information
+
+Microsoft's [documentation for xperf](http://msdn.microsoft.com/en-us/library/ff191077.aspx "http://msdn.microsoft.com/en-us/library/ff191077.aspx")
+is pretty good; there is a lot of depth to this tool, and you should
+look there for more details.
diff --git a/docs/performance/profiling_with_zoom.md b/docs/performance/profiling_with_zoom.md
new file mode 100644
index 0000000000..053fa0cbce
--- /dev/null
+++ b/docs/performance/profiling_with_zoom.md
@@ -0,0 +1,5 @@
+# Profiling with Zoom
+
+Zoom is a profiler very similar to Shark for Linux.
+
+You can get the profiler from here: <http://www.rotateright.com/>
diff --git a/docs/performance/reporting_a_performance_problem.md b/docs/performance/reporting_a_performance_problem.md
new file mode 100644
index 0000000000..efe4f09f9c
--- /dev/null
+++ b/docs/performance/reporting_a_performance_problem.md
@@ -0,0 +1,94 @@
+# Reporting a Performance Problem
+
+This article will guide you in reporting a performance problem using the
+built-in Gecko Profiler tool.
+
+## Enabling the Profiler toolbar button
+
+These steps only work in Firefox 75+.
+
+1. Visit [https://profiler.firefox.com/](https://profiler.firefox.com/)
+2. Click on *Enable Profiler Menu Button*
+3. The profiler toolbar button will show up in the top right of the URL
+ bar as a small stopwatch icon.
+
+![image1](img/reportingperf1.png)
+
+4. You can right-click on the button and remove it from the toolbar
+ when you're done with it.
+
+## Using the Profiler
+
+When enabled, the profiler toolbar button is not recording by default.
+Recording can be done by clicking on the toolbar icon to open its panel.
+Make sure to choose an appropriate setting for the recording (if you're
+not sure, choose Firefox Platform), and then choosing **Start
+Recording**. The toolbar icon turns blue when it is recording.
+
+The profiler uses a fixed size buffer to store sample data. When it runs
+out of space in its buffer, it discards old entries so you may want to
+increase the buffer size if you find you are unable to capture the
+profile quickly enough after you notice a performance problem. If you
+choose Custom Settings (and then clicking Edit Settings) for the
+profiler, you can adjust the size of the buffer (presently defaults to
+90 MB) and the time interval between data collection (presently defaults
+to 1 ms). Note that increasing the buffer size uses more memory and can
+make capturing a profile take longer.
+
+![image2](img/reportingperf2.png)
+
+Using the keyboard shortcuts is often more convenient than using the
+mouse to interact with the UI:
+
+* Ctrl+Shift+1 - Start/Stop the profiler
+* Ctrl+Shift+2 - Take a profile and launch the viewer to view it
+
+## Capturing and sharing a profile
+
+1. While the profiler is recording, reproduce the performance problem.
+ If possible let the problem manifest itself for 5-10 seconds.
+2. Press **Ctrl+Shift+2** or click on the profiler toolbar icon in the
+ top right and select **Capture**. Try to do this within a few
+ seconds from reproducing the performance problem as only the last
+ few seconds are recorded. If the timeline has a large red block
+ it's a good sign. ![Jank markers appearing in the Perf.html profile analysis tool.](img/PerfDotHTMLRedLines.png)
+3. The data will open in a new tab. Wait until the \"Symbolicating call
+ stacks\" notification disappears before sharing the profile.
+4. There will be a button in the top right labeled **Upload Local Profile** which
+ will allow you to upload this profile and once completed will write
+ out a link. Before uploading, the Upload button asks you what data
+ you'd like to publish to our servers.
+5. Note that while it's possible to strip profiles of potentially
+ privacy sensitive information, the less information a profile
+ contains, *the harder it is to analyze and turn into actionable
+ data.*
+6. Once uploaded, copy permalink URL to your clipboard by right
+ clicking and [add the profile URL to a bug](https://bugzilla.mozilla.org/enter_bug.cgi?product=Core&component=Performance)
+ for your performance problem and/or send it to the appropriate
+ person. Try to give some context about what you were doing when the
+ performance problem arose such as the URL you were viewing and what
+ actions were you doing (ex. scrolling on gmail.com).
+
+![image3](img/reportingperf3.png)
+
+## Viewing addon performance in GeckoView
+
+Sometimes an addon or more are slowing down Firefox. These addons might
+be using the extension API in ways that were not meant to. You can see
+which of these addons are causing problems by adding the
+**moz-extension** filter.
+
+![moz-extension filter print screen](img/EJCrt4N.png)
+
+Make sure you are selecting the process that is using up the CPU since
+all of the processes are shown. You might have a content process using
+up the CPU and not the main one.
+
+Make sure you are doing whatever it is that slows down Firefox while
+recording the profile. For example you might have one addon that slows down page load
+and another one that slows down tab switch.
+
+Your first reflex once you find what addon is slowing down the profile
+might be to disable it and search for alternatives. Before you do this,
+please share the performance profile with the addon authors through a
+bug report. Gecko profiler allows you to share a link with the profile.
diff --git a/docs/performance/scroll-linked_effects.md b/docs/performance/scroll-linked_effects.md
new file mode 100644
index 0000000000..90d3c33ed1
--- /dev/null
+++ b/docs/performance/scroll-linked_effects.md
@@ -0,0 +1,177 @@
+# Scroll-linked effects
+
+The definition of a scroll-linked effect is an effect implemented on a
+webpage where something changes based on the scroll position, for
+example updating a positioning property with the aim of producing a
+parallax scrolling effect. This article discusses scroll-linked effects,
+their effect on performance, related tools, and possible mitigation
+techniques.
+
+## Scrolling effects explained
+
+Often scrolling effects are implemented by listening for the `scroll`
+event and then updating elements on the page in some way (usually the
+CSS
+[`position`]((https://developer.mozilla.org/en-US/docs/Web/CSS/position "The position CSS property sets how an element is positioned in a document. The top, right, bottom, and left properties determine the final location of positioned elements.")
+or
+[`transform`](https://developer.mozilla.org/en-US/docs/Web/CSS/transform "The transform CSS property lets you rotate, scale, skew, or translate an element. It modifies the coordinate space of the CSS visual formatting model.")
+property.) You can find a sampling of such effects at [CSS Scroll API:
+Use
+Cases](https://github.com/RByers/css-houdini-drafts/blob/master/css-scroll-api/UseCases.md).
+
+These effects work well in browsers where the scrolling is done
+synchronously on the browser's main thread. However, most browsers now
+support some sort of asynchronous scrolling in order to provide a
+consistent 60 frames per second experience to the user. In the
+asynchronous scrolling model, the visual scroll position is updated in
+the compositor thread and is visible to the user before the `scroll`
+event is updated in the DOM and fired on the main thread. This means
+that the effects implemented will lag a little bit behind what the user
+sees the scroll position to be. This can cause the effect to be laggy,
+janky, or jittery --- in short, something we want to avoid.
+
+Below are a couple of examples of effects that would not work well with
+asynchronous scrolling, along with equivalent versions that would work
+well:
+
+### Example 1: Sticky positioning
+
+Here is an implementation of a sticky-positioning effect, where the
+\"toolbar\" div will stick to the top of the screen as you scroll down.
+
+```html
+<body style="height: 5000px" onscroll="document.getElementById('toolbar').style.top = Math.max(100, window.scrollY) + 'px'">
+ <div id="toolbar" style="position: absolute; top: 100px; width: 100px; height: 20px; background-color: green"></div>
+</body>
+```
+
+This implementation of sticky positioning relies on the scroll event
+listener to reposition the "toolbar" div. As the scroll event listener
+runs in the JavaScript on the browser's main thread, it will be
+asynchronous relative to the user-visible scrolling. Therefore, with
+asynchronous scrolling, the event handler will be delayed relative to
+the user-visible scroll, and so the div will not stay visually fixed as
+intended. Instead, it will move with the user's scrolling, and then
+\"snap\" back into position when the scroll event handler runs. This
+constant moving and snapping will result in a jittery visual effect. One
+way to implement this without the scroll event listener is to use the
+CSS property designed for this purpose:
+
+```html
+<body style="height: 5000px">
+ <div id="toolbar" style="position: sticky; top: 0px; margin-top: 100px; width: 100px; height: 20px; background-color: green"></div>
+</body>
+```
+
+This version works well with asynchronous scrolling because position of
+the \"toolbar\" div is updated by the browser as the user scrolls.
+
+### Example 2: Scroll snapping
+
+Below is an implementation of scroll snapping, where the scroll position
+snaps to a particular destination when the user's scrolling stops near
+that destination.
+
+```html
+<body style="height: 5000px">
+ <script>
+ function snap(destination) {
+ if (Math.abs(destination - window.scrollY) < 3) {
+ scrollTo(window.scrollX, destination);
+ } else if (Math.abs(destination - window.scrollY) < 200) {
+ scrollTo(window.scrollX, window.scrollY + ((destination - window.scrollY) / 2));
+ setTimeout(snap, 20, destination);
+ }
+ }
+ var timeoutId = null;
+ addEventListener("scroll", function() {
+ if (timeoutId) clearTimeout(timeoutId);
+ timeoutId = setTimeout(snap, 200, parseInt(document.getElementById('snaptarget').style.top));
+ }, true);
+ </script>
+ <div id="snaptarget" class="snaptarget" style="position: relative; top: 200px; width: 100%; height: 200px; background-color: green"></div>
+</body>
+```
+
+In this example, there is a scroll event listener which detects if the
+scroll position is within 200 pixels of the top of the \"snaptarget\"
+div. If it is, then it triggers an animation to \"snap\" the scroll
+position to the top of the div. As this animation is driven by
+JavaScript on the browser's main thread, it can be interrupted by other
+JavaScript running in other tabs or other windows. Therefore, the
+animation can end up looking janky and not as smooth as intended.
+Instead, using the CSS snap-points property will allow the browser to
+run the animation asynchronously, providing a smooth visual effect to
+the user.
+
+```html
+<body style="height: 5000px">
+ <style>
+ body, /* blink currently has bug that requires declaration on `body` */
+ html {
+ scroll-snap-type: y proximity;
+ }
+ .snaptarget {
+ scroll-snap-align: start;
+ position: relative;
+ top: 200px;
+ height: 200px;
+ background-color: green;
+ }
+ </style>
+ <div class="snaptarget"></div>
+</body>
+```
+
+This version can work smoothly in the browser even if there is
+slow-running Javascript on the browser's main thread.
+
+### Other effects
+
+In many cases, scroll-linked effects can be reimplemented using CSS and
+made to run on the compositor thread. However, in some cases the current
+APIs offered by the browser do not allow this. In all cases, however,
+Firefox will display a warning to the developer console (starting in
+version 46) if it detects the presence of a scroll-linked effect on a
+page. Pages that use scrolling effects without listening for scroll
+events in JavaScript will not get this warning. See the [Asynchronous
+scrolling in Firefox](https://staktrace.com/spout/entry.php?id=834) blog
+post for some more examples of effects that can be implemented using CSS
+to avoid jank.
+
+## Future improvements
+
+Going forward, we would like to support more effects in the compositor.
+In order to do so, we need you (yes, you!) to tell us more about the
+kinds of scroll-linked effects you are trying to implement, so that we
+can find good ways to support them in the compositor. Currently there
+are a few proposals for APIs that would allow such effects, and they all
+have their advantages and disadvantages. The proposals currently under
+consideration are:
+
+- [Web Animations](https://w3c.github.io/web-animations/): A new API
+ for precisely controlling web animations in JavaScript, with an
+ [additional
+ proposal](https://wiki.mozilla.org/Platform/Layout/Extended_Timelines)
+ to map scroll position to time and use that as a timeline for the
+ animation.
+- [CompositorWorker](https://docs.google.com/document/d/18GGuTRGnafai17PDWjCHHAvFRsCfYUDYsi720sVPkws/edit?pli=1#heading=h.iy9r1phg1ux4):
+ Allows JavaScript to be run on the compositor thread in small
+ chunks, provided it doesn't cause the framerate to drop.
+- [Scroll
+ Customization](https://docs.google.com/document/d/1VnvAqeWFG9JFZfgG5evBqrLGDZYRE5w6G5jEDORekPY/edit?pli=1):
+ Introduces a new API for content to dictate how a scroll delta is
+ applied and consumed. As of this writing, Mozilla does not plan to
+ support this proposal, but it is included for completeness.
+
+### Call to action
+
+If you have thoughts or opinions on:
+
+- Any of the above proposals in the context of scroll-linked effects.
+- Scroll-linked effects you are trying to implement.
+- Any other related issues or ideas.
+
+Please get in touch with us! You can join the discussion on the
+[public-houdini](https://lists.w3.org/Archives/Public/public-houdini/)
+mailing list.
diff --git a/docs/performance/sorting_algorithms_comparison.md b/docs/performance/sorting_algorithms_comparison.md
new file mode 100644
index 0000000000..8450d116e0
--- /dev/null
+++ b/docs/performance/sorting_algorithms_comparison.md
@@ -0,0 +1,52 @@
+# Sorting algorithms comparison
+
+This program compares the performance of three different sorting
+algorithms:
+
+- bubble sort
+- selection sort
+- quicksort
+
+It consists of the following functions:
+
+ ----------------------- ---------------------------------------------------------------------------------------------------
+ **`sortAll()`** Top-level function. Iteratively (200 iterations) generates a randomized array and calls `sort()`.
+ **`sort()`** Calls each of `bubbleSort()`, `selectionSort()`, `quickSort()` in turn and logs the result.
+ **`bubbleSort()`** Implements a bubble sort, returning the sorted array.
+ **`selectionSort()`** Implements a selection sort, returning the sorted array.
+ **`quickSort()`** Implements quicksort, returning the sorted array.
+ `swap()` Helper function for `bubbleSort()` and `selectionSort()`.
+ `partition()` Helper function for `quickSort()`.
+ ----------------------- ---------------------------------------------------------------------------------------------------
+
+Its call graph looks like this:
+
+ sortAll() // (generate random array, then call sort) x 200
+
+ -> sort() // sort with each algorithm, log the result
+
+ -> bubbleSort()
+
+ -> swap()
+
+ -> selectionSort()
+
+ -> swap()
+
+ -> quickSort()
+
+ -> partition()
+
+The implementations of the sorting algorithms in the program are taken
+from <https://github.com/nzakas/computer-science-in-javascript/> and are
+used under the MIT license.
+
+You can try out the example program
+[here](https://mdn.github.io/performance-scenarios/js-call-tree-1/index.html)
+and clone the code [here](https://github.com/mdn/performance-scenarios)
+(be sure to check out the gh-pages branch). You can also [download the
+specific profile we
+discuss](https://github.com/mdn/performance-scenarios/tree/gh-pages/js-call-tree-1/profile)
+- just import it to the Performance tool if you want to follow along. Of
+course, you can generate your own profile, too, but the numbers will be
+a little different.
diff --git a/docs/performance/timerfirings_logging.md b/docs/performance/timerfirings_logging.md
new file mode 100644
index 0000000000..dfbe8dca93
--- /dev/null
+++ b/docs/performance/timerfirings_logging.md
@@ -0,0 +1,136 @@
+# TimerFirings Loggings
+
+TimerFirings logging is a feature built into Gecko that prints a line of
+data for every timer fired. This is useful because timer firings are a
+major cause of wakeups, which can cause high power consumption.
+
+**Note**: The [power profiling
+overview](power_profiling_overview.md)
+is worth reading at this point if you haven\'t already. It may make
+parts of this document easier to understand.
+
+## Invocation
+
+TimerFirings logging uses Gecko\'s own logging mechanism, and so is able
+to be used in any build. Set the following environment variable to
+enable it.
+
+ NSPR_LOG_MODULES=TimerFirings:4
+
+## Output
+
+Once enabled, TimerFirings will print one line of logging output per
+timer fired. It\'s best to redirect this output to a file.
+
+The following sample shows the basics of this output.
+
+ -991946880[7f46c365ba00]: [6775] fn timer (SLACK 100 ms): LayerActivityTracker
+ -991946880[7f46c365ba00]: [6775] fn timer (ONE_SHOT 250 ms): PresShell::sPaintSuppressionCallback
+ -991946880[7f46c365ba00]: [6775] fn timer (ONE_SHOT 160 ms): nsBrowserStatusFilter::TimeoutHandler
+ -991946880[7f46c365ba00]: [6775] iface timer (ONE_SHOT 200 ms): 7f46964d7f80
+ -1340643584[7f46c365ec00]: [6775] obs timer (SLACK 1000 ms): 7f46a95a0200
+
+Each line has the following information.
+
+- The first two values identify the thread. This is not especially
+ useful.
+- The next value is the process ID (pid). This is useful in a
+ multi-process scenario.
+- Next is the timer kind, one of `fn` (function), `iface` (interface)
+ or `obs` (observer), which are the three kinds of timers that Gecko
+ supports.
+- Then comes the function kind, one of `ONE_SHOT` (a single-use
+ timer), `SLACK` or `PRECISE` (repeating timers of differing
+ precision).
+- Then comes the timer period, measured in milliseconds.
+- Finally there is the identifying information for the timer. Function
+ timers have an informative label. Interface and observer timers only
+ have an address, which is less useful, but they are uncommon enough
+ that this usually doesn\'t matter much.
+
+The above example shows only timers from C++ within Gecko. There are
+also timers for `setTimer` or `setInterval` calls in JavaScript code, as
+the following sample shows.
+
+ -991946880[7f46c365ba00]: [6775] fn timer (ONE_SHOT 0 ms): [content] chrome://browser/content/tabbrowser.xml:1816:0
+ 711637568[7f3219c48000]: [6835] fn timer (ONE_SHOT 100 ms): [content] http://edition.cnn.com/:5:7231
+ 711637568[7f3219c48000]: [6835] fn timer (ONE_SHOT 100 ms): [content] http://a.visualrevenue.com/vrs.js:6:9423
+
+These JS timers are annotated with `[content]` and show the JavaScript
+source location where they were created. They can come from chrome code
+within Firefox, or from web content.
+
+The informative labels are only present on function timers that have had
+their creation site annotated. For unannotated function timers, there
+are three possible behaviours.
+
+First, on Mac the code uses `dladdr` to get the name immediately, and
+the output will look like the following.
+
+ 2082435840[100445640]: [81190] fn timer (ONE_SHOT 8000 ms): [from dladdr] gfxFontInfoLoader::DelayedStartCallback(nsITimer*, void*)
+
+Second, on Linux the code uses `dladdr` to get the symbol library and
+address, which can be post-processed by `tools/rb/fix_stacks.py`. The
+following two lines show the output before and after being
+post-processed by that script.
+
+ 2088737280[7f606bf68140]: [30710] fn timer (ONE_SHOT 16 ms): [from dladdr] #0: ???[/home/njn/moz/mi1/o64/dist/bin/libxul.so +0x2144f94]
+ 2088737280[7f606bf68140]: [30710] fn timer (ONE_SHOT 16 ms): [from dladdr] #0: mozilla::RefreshDriverTimer::TimerTick(nsITimer*, void*) (/home/njn/moz/mi1/o64/layout/b
+
+Third, on other platforms `dladdr` is not implemented or doesn\'t work
+well, and the output will look like the following.
+
+ 711637568[7f3219c48000]: [6835] fn timer (ONE_SHOT 16 ms): ???[dladdr is unimplemented or doesn't work well on this OS]
+
+The `???` indicates that the function timer lacks an explicit name, and
+the comment within the square brackets explains why the fallback
+mechanism wasn\'t used`.`
+
+If an unannotated timer function appears frequently it is worth
+explicitly annotating it so that it will be usefully identified on other
+platforms. (Running on Mac or Linux is obviously necessary to learn the
+timer function\'s name.) This is done by initializing it with
+`initWithNamedFuncCallback` or `initWithNameableFuncCallback` instead of
+`initWithNameCallback`.
+
+## Post-processing
+
+TimerFirings logging quickly produces thousands of lines of output. This
+output needs post-processing for it to be useful. If the output is
+redirected to a file called *`out`*, then the following command will
+pull out the timer-related lines, count how many times each unique line
+appears, and then print them with the most common ones first.
+
+ cat out | grep timer | sort | uniq -c | sort -r -n
+
+The following is sample output from this command.
+
+ 204 801266240[7f7c1f248000]: [7163] fn timer (ONE_SHOT 50 ms): [content] http://widgets.outbrain.com/outbrain.js:20:330
+ 135 -495057024[7f74e105ba00]: [7108] fn timer (ONE_SHOT 4 ms): [content] https://self-repair.mozilla.org/en-US/repair/:7:13669
+ 118 801266240[7f7c1f248000]: [7163] fn timer (ONE_SHOT 100 ms): [content] http://a.visualrevenue.com/vrs.js:6:9423
+ 103 801266240[7f7c1f248000]: [7163] fn timer (ONE_SHOT 50 ms): [content] http://static.dynamicyield.com/scripts/12086/dy-min.js?v=12086:3:3389
+ 94 801266240[7f7c1f248000]: [7163] fn timer (ONE_SHOT 50 ms): [content] https://ad.double-click.net/ddm/adi/N7921.1283839CADREON.COM.AU/B9038144.122190976;sz=300x600;click=http://pixel.mathtag.com/click/img?mt_aid=2744535504761193354&mt_id=1895890&mt_adid=148611&mt_sid=973379&mt_exid=9&mt_inapp=0&mt_uuid=353d5460-19f6-4400-9bbd-d0fcc3bcf595&mt_3pck=http%3A//beacon-apac-hkg1.rubiconproject.com/beacon/t/d1f9921d-4e47-448f-b6ba-36cae1c31b65/&redirect=;ord=2744535504761193354?:83:0
+ 94 801266240[7f7c1f248000]: [7163] fn timer (ONE_SHOT 160 ms): nsBrowserStatusFilter::TimeoutHandler
+ 92 -495057024[7f74e105ba00]: [7108] fn timer (ONE_SHOT 160 ms): nsBrowserStatusFilter::TimeoutHandler
+
+The first column shows how many times the particular line appeared.
+
+It is sometimes useful to pre-process the output by stripping out
+certain parts of each line before doing this aggregation step, for
+example, by inserting one or more of the following commands into the
+command pipeline.
+
+ sed 's/^[^:]\+: //' # strip thread IDs
+ sed 's/\[[0-9]\+\] //' # strip process IDs
+ sed 's/ \+[0-9]\+ ms//' # strip timer periods
+
+The following is the previous sample output with all three of these
+commands added into the pipeline.
+
+ 204 fn timer (ONE_SHOT): [content] http://widgets.outbrain.com/outbrain.js:20:330
+ 186 fn timer (ONE_SHOT): nsBrowserStatusFilter::TimeoutHandler
+ 138 fn timer (ONE_SHOT): [content] https://self-repair.mozilla.org/en-US/repair/:7:13669
+ 118 fn timer (ONE_SHOT): [content] http://a.visualrevenue.com/vrs.js:6:9423
+ 108 fn timer (SLACK): LayerActivityTracker
+ 104 fn timer (SLACK): nsIDocument::SelectorCache
+ 104 fn timer (SLACK): CCTimerFired
diff --git a/docs/performance/tools_power_rapl.md b/docs/performance/tools_power_rapl.md
new file mode 100644
index 0000000000..3bf2555bd6
--- /dev/null
+++ b/docs/performance/tools_power_rapl.md
@@ -0,0 +1,113 @@
+# tools/power/rapl
+
+`tools/power/rapl` (or `rapl` for short) is a command-line utility in
+the Mozilla tree that periodically reads and prints all available Intel
+RAPL power estimates. These are machine-wide estimates, so if you want
+to estimate the power consumption of a single program you should
+minimize other activity on the machine while measuring.
+
+**Note**: The [power profiling overview](power_profiling_overview.md) is
+worth reading at this point if you haven't already. It may make parts
+of this document easier to understand.
+
+## Invocation
+
+First, do a [standard build of Firefox](/setup/index.rst).
+
+### Mac
+
+On Mac, `rapl` can be run as follows.
+
+```bash
+$OBJDIR/dist/bin/rapl
+```
+
+### Linux
+
+On Linux, `rapl` can be run as root, as follows.
+
+ sudo $OBJDIR/dist/bin/rapl
+
+Alternatively, it can be run without root privileges by setting the
+contents of
+[/proc/sys/kernel/perf_event_paranoid](http://unix.stackexchange.com/questions/14227/do-i-need-root-admin-permissions-to-run-userspace-perf-tool-perf-events-ar)
+to 0. Note that if you do change this file, its contents may reset when
+the machine is next rebooted.
+
+You must be running Linux kernel version 3.14 or later for `rapl` to
+work. Otherwise, it will fail with an error message explaining this
+requirement.
+
+### Windows
+
+Unfortunately, `rapl` does not work on Windows, and porting it would be
+difficult because Windows does not have APIs that allow easy access to
+the relevant model-specific registers.
+
+## Output
+
+The following is 10 seconds of output from a default invocation of
+`rapl`.
+
+```bash
+ total W = _pkg_ (cores + _gpu_ + other) + _ram_ W
+#01 5.17 W = 1.78 ( 0.12 + 0.10 + 1.56) + 3.39 W
+#02 9.43 W = 5.44 ( 1.44 + 1.20 + 2.80) + 3.98 W
+#03 14.26 W = 10.21 ( 5.47 + 0.19 + 4.55) + 4.04 W
+#04 10.02 W = 6.15 ( 2.62 + 0.43 + 3.10) + 3.86 W
+#05 14.63 W = 10.43 ( 4.41 + 0.81 + 5.22) + 4.19 W
+#06 11.16 W = 6.90 ( 1.91 + 1.68 + 3.31) + 4.26 W
+#07 5.40 W = 1.97 ( 0.20 + 0.10 + 1.67) + 3.44 W
+#08 5.17 W = 1.76 ( 0.07 + 0.08 + 1.60) + 3.41 W
+#09 5.17 W = 1.76 ( 0.09 + 0.08 + 1.58) + 3.42 W
+#10 8.13 W = 4.40 ( 1.55 + 0.11 + 2.74) + 3.73 W
+```
+
+Things to note include the following.
+
+- All measurements are in Watts.
+- The first line indicates the meaning of each column.
+- The underscores in `_pkg_`, `_gpu_` and `_ram_` are present so that
+ each column's name has five characters.
+- The total power is the sum of the package power and the RAM power.
+- The package estimate is divided into three parts: cores, GPU, and
+ \"other\". \"Other\" is computed as the package power minus the
+ cores power and GPU power.
+- If the processor does not support GPU or RAM estimates then
+ \"` n/a `\" will be printed in the relevant column instead of a
+ number, and it will contribute zero to the total.
+
+Once sampling is finished --- either because the user interrupted it, or
+because the requested number of samples has been taken --- the following
+summary data is shown:
+
+```bash
+10 samples taken over a period of 10.000 seconds
+
+Distribution of 'total' values:
+ mean = 8.85 W
+ std dev = 3.50 W
+ 0th percentile = 5.17 W (min)
+ 5th percentile = 5.17 W
+ 25th percentile = 5.17 W
+ 50th percentile = 8.13 W
+ 75th percentile = 11.16 W
+ 95th percentile = 14.63 W
+100th percentile = 14.63 W (max)
+```
+
+The distribution data is omitted if there was zero or one samples taken.
+
+## Options
+
+- `-i --sample-interval`. The length of each sample in milliseconds.
+ Defaults to 1000. A warning is given if you set it below 50 because
+ that is likely to lead to inaccurate estimates.
+- `-n --sample-count`. The number of samples to take. The default is
+ 0, which is interpreted as \"unlimited\".
+
+## Combining with `powermetrics`
+
+On Mac, you can use the [mach power](powermetrics.md#mach-power) command
+to run `rapl` in combination with `powermetrics` in a way that gives the
+most useful summary measurements for each of Firefox, Chrome and Safari.
diff --git a/docs/performance/turbostat.md b/docs/performance/turbostat.md
new file mode 100644
index 0000000000..3eac89c086
--- /dev/null
+++ b/docs/performance/turbostat.md
@@ -0,0 +1,50 @@
+# Turbostat
+
+`turbostat` is a Linux command-line utility that prints various
+measurements, including numerous per-CPU measurements. This article
+provides an introduction to using it.
+
+**Note**: The [power profiling overview](power_profiling_overview.md) is
+worth reading at this point if you haven't already. It may make parts
+of this document easier to understand.
+
+## Invocation
+
+`turbostat` must be invoked as the super-user:
+
+```bash
+sudo turbostat
+```
+
+If you get an error saying `"turbostat: no /dev/cpu/0/msr"`, you need to
+run the following command:
+
+```bash
+sudo modprobe msr
+```
+
+The output is as follows:
+
+```
+ Core CPU Avg_MHz %Busy Bzy_MHz TSC_MHz SMI CPU%c1 CPU%c3 CPU%c6 CPU%c7 CoreTmp PkgTmp Pkg%pc2 Pkg%pc3 Pkg%pc6 Pkg%pc7 PkgWatt CorWatt GFXWatt
+ - - 799 21.63 3694 3398 0 12.02 3.16 1.71 61.48 49 49 0.00 0.00 0.00 0.00 22.68 15.13 1.13
+ 0 0 821 22.44 3657 3398 0 9.92 2.43 2.25 62.96 39 49 0.00 0.00 0.00 0.00 22.68 15.13 1.13
+ 0 4 708 19.14 3698 3398 0 13.22
+ 1 1 743 20.26 3666 3398 0 21.40 4.01 1.42 52.90 49
+ 1 5 1206 31.98 3770 3398 0 9.69
+ 2 2 784 21.29 3681 3398 0 11.78 3.10 1.13 62.70 40
+ 2 6 782 21.15 3698 3398 0 11.92
+ 3 3 702 19.14 3670 3398 0 8.39 3.09 2.03 67.36 39
+ 3 7 648 17.67 3667 3398 0 9.85
+```
+
+The man page has good explanations of what each column measures. The
+various "Watt" measurements come from the Intel RAPL MSRs.
+
+If you run with the `-S` option you get a smaller range of measurements
+that fit on a single line, like the following:
+
+```
+ Avg_MHz %Busy Bzy_MHz TSC_MHz SMI CPU%c1 CPU%c3 CPU%c6 CPU%c7 CoreTmp PkgTmp Pkg%pc2 Pkg%pc3 Pkg%pc6 Pkg%pc7 PkgWatt CorWatt GFXWatt
+ 3614 97.83 3694 3399 0 2.17 0.00 0.00 0.00 77 77 0.00 0.00 0.00 0.00 67.50 57.77 0.46
+```
diff --git a/docs/setup/building_with_debug_symbols.rst b/docs/setup/building_with_debug_symbols.rst
new file mode 100644
index 0000000000..316e04af31
--- /dev/null
+++ b/docs/setup/building_with_debug_symbols.rst
@@ -0,0 +1,61 @@
+Building with Debug Symbols
+===========================
+
++--------------------------------------------------------------------+
+| This page is an import from MDN and the contents might be outdated |
++--------------------------------------------------------------------+
+
+By default, a release build of Firefox will not generate debug symbols
+suitable for debugging or post-processing into the
+:ref:`breakpad <Crash reporting>` symbol format. Use the
+following :ref:`mozconfig <Configuring Build Options>` settings
+to do a build with symbols:
+
+
+
+Building Firefox with symbols
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+There is a single configure option to enable building with symbols on
+all platforms. This is enabled by default so unless you have explcitly
+disabled it your build you should include symbols.
+
+::
+
+ ac_add_options --enable-debug-symbols
+
+This can optionally take an argument for the type of symbols that need
+to be produced (like "-g3"). By default it uses "-g" on Linux and MacOS.
+This value takes precedence over the flags set in ``MOZ_DEBUG_FLAGS``
+
+Note that this will override the values provided for ``CFLAGS`` and
+``CXXFLAGS``.
+
+
+Breakpad symbol files
+~~~~~~~~~~~~~~~~~~~~~
+
+After the build is complete, run the following command to generate an
+archive of :ref:`Breakpad <Crash reporting>` symbol files:
+
+.. code:: bash
+
+ mach buildsymbols
+
+Treeherder uses an additional ``uploadsymbols`` target to upload
+symbols to a socorro server. See
+https://searchfox.org/mozilla-central/source/toolkit/crashreporter/tools/upload_symbols.py
+for more information about the environment variables used by this
+target.
+
+
+``make package``
+~~~~~~~~~~~~~~~~
+
+If you use ``make package`` to package your build, symbols will be
+stripped. If you want to keep the symbols in the patches, you need to
+add this to your mozconfig:
+
+.. code::
+
+ ac_add_options --disable-install-strip
diff --git a/docs/setup/configuring_build_options.rst b/docs/setup/configuring_build_options.rst
new file mode 100644
index 0000000000..fd57930022
--- /dev/null
+++ b/docs/setup/configuring_build_options.rst
@@ -0,0 +1,404 @@
+Configuring Build Options
+=========================
+
++--------------------------------------------------------------------+
+| This page is an import from MDN and the contents might be outdated |
++--------------------------------------------------------------------+
+
+This document details how to configure Firefox builds.
+Most of the time a ``mozconfig`` file is not required. The default
+options are the most well-supported, so it is preferable to add as few
+options as possible. Please read the following directions carefully
+before building, and follow them in order. Skipping any step may cause
+the build to fail, or the built software to be unusable. Build options,
+including options not usable from the command-line, may appear in
+"``confvars.sh``" files in the source tree.
+
+
+Using a ``mozconfig`` configuration file
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The choice of which Mozilla project to build and other configuration
+options can be configured in a ``mozconfig`` file. (It is possible to
+manually call ``configure`` with command-line options, but this is not
+recommended). The ``mozconfig`` file should be in your source directory
+(that is, ``/mozilla-central/mozconfig``).
+
+Create a blank ``mozconfig`` file:
+
+.. code:: bash
+
+ echo "# My first mozilla config" > mozconfig
+
+If your mozconfig isn't in your source directory, you can also use the
+``MOZCONFIG`` environment variable to specify the path to your
+``mozconfig``. The path you specify **must** be an **absolute** path or
+else ``client.mk`` will not find it. This is useful if you choose to
+have multiple ``mozconfig`` files for different projects or
+configurations (see below for a full example). Note that in the
+``export`` example below the filename was not ``mozconfig``. Regardless
+of the name of the actual file you use, we refer to this file as the
+``mozconfig`` file in the examples below.
+
+Setting the ``mozconfig`` path:
+
+.. code:: bash
+
+ export MOZCONFIG=$HOME/mozilla/mozconfig-firefox
+
+.. note::
+
+ Calling the file ``.mozconfig`` (with a leading dot) is also
+ supported, but this is not recommended because it may make the file
+ harder to find. This will also help when troubleshooting because
+ people will want to know which build options you have selected and
+ will assume that you have put them in your ``mozconfig`` file.
+
+
+``mozconfig`` contains two types of options:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- Options prefixed with ``mk_add_options`` are passed to
+ ``client.mk``. The most important of these is ``MOZ_OBJDIR``, which
+ controls where your project gets built (also known as the object
+ directory).
+- Options prefixed with ``ac_add_options`` are passed to ``configure``,
+ and affect the build process.
+
+
+Building with an objdir
+~~~~~~~~~~~~~~~~~~~~~~~
+
+This means that the source code and object files are not intermingled in
+your directory system and you can build multiple projects (e.g.,
+Firefox and Thunderbird) from the same source tree. If you do not
+specify a ``MOZ_OBJDIR``, it will be automatically set to
+``@TOPSRCDIR@/obj-@CONFIG_GUESS@``.
+
+If you need to re-run ``configure``, the easiest way to do it is using
+``./mach configure``; running ``configure`` manually is strongly
+discouraged.
+
+Adding the following line to your ``mozconfig`` allows you to change the
+objdir:
+
+.. code:: bash
+
+ mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-@CONFIG_GUESS@
+
+It is a good idea to have your objdir name start with ``obj`` so that
+Mercurial ignores it.
+
+Sometimes it can be useful to build multiple versions of the source
+(such as with and without diagnostic asserts). To avoid the time it
+takes to do a full rebuild, you can create multiple ``mozconfig`` files
+which specify different objdirs. For example, a ``mozconfig-dbg``:
+
+.. code:: bash
+
+ mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-ff-dbg
+ ac_add_options --enable-debug
+
+and a ``mozconfig-rel-opt``:
+
+.. code:: bash
+
+ mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-ff-rel-opt
+ ac_add_options --disable-debug
+ ac_add_options --enable-optimize
+
+allow for building both versions by specifying the configuration via
+the ``MOZCONFIG`` environment variable:
+
+.. code:: bash
+
+ $ env MOZCONFIG=/path/to/mozconfig-dbg ./mach build
+ $ env MOZCONFIG=/path/to/mozconfig-rel-opt ./mach build
+
+Don't forget to set the ``MOZCONFIG`` environment variable for the
+``mach run`` command as well.
+
+Be aware that changing your ``mozconfig`` will require the configure
+process to be rerun and therefore the build will take considerably
+longer, so if you find yourself changing the same options regularly, it
+may be worth having a separate ``mozconfig`` for each. The main downside
+of this is that each objdir will take up a significant amount of space
+on disk.
+
+
+Parallel compilation
+~~~~~~~~~~~~~~~~~~~~
+
+.. note::
+
+ **Note**: The build system automatically makes an intelligent guess
+ for how many CPU cores to use when building. The option below is
+ typically not needed.
+
+Most modern systems have multiple cores or CPUs, and they can be
+optionally used concurrently to make the build faster. The ``-j`` flag
+controls how many parallel builds will run concurrently. You will see
+(diminishing) returns up to a value approximately 1.5× to 2.0× the
+number of cores on your system.
+
+.. code:: bash
+
+ mk_add_options MOZ_PARALLEL_BUILD=4
+
+If your machine is overheating, you might want to try a lower value.
+
+
+Choose a project
+~~~~~~~~~~~~~~~~
+
+The ``--enable-project=project`` flag is used to select a project to
+build. Firefox is the default.
+
+Choose one of the following options to add to your ``mozconfig`` file:
+
+Browser (Firefox)
+ .. code::
+
+ ac_add_options --enable-project=browser
+
+ .. note::
+
+ **Note**: This is the default
+
+Mail (Thunderbird)
+ .. code::
+
+ ac_add_options --enable-project=comm/mail
+
+Mozilla Suite (SeaMonkey)
+ .. code::
+
+ ac_add_options --enable-project=suite
+
+Calendar (Lightning Extension, uses Thunderbird)
+ .. code::
+
+ ac_add_options --enable-project=comm/mail
+ ac_add_options --enable-calendar
+
+
+Selecting build options
+~~~~~~~~~~~~~~~~~~~~~~~
+
+The build options you choose depends on what project you are
+building and what you will be using the build for. If you want to use
+the build regularly, you will want a release build without extra
+debugging information; if you are a developer who wants to hack the
+source code, you probably want a non-optimized build with extra
+debugging macros.
+
+There are many options recognized by the configure script which are
+special-purpose options intended for embedders or other special
+situations, and should not be used to build the full suite/XUL
+projects. The full list of options can be obtained by running
+``./mach configure -- --help``.
+
+.. warning::
+
+ Do not use a configure option unless you know what it does.
+ The default values are usually the right ones. Each additional option
+ you add to your ``mozconfig`` file reduces the chance that your build
+ will compile and run correctly.
+
+The following build options are very common:
+
+sccache
+^^^^^^^
+
+`SCCache <https://github.com/mozilla/sccache>`__ allows speeding up subsequent
+C / C++ builds by caching compilation results. Unlike
+`ccache <https://ccache.dev>`__, it also allows caching Rust artifacts, and
+supports `distributed compilation
+<https://github.com/mozilla/sccache/blob/master/docs/DistributedQuickstart.md>`__.
+
+In order to enable ``sccache`` for Firefox builds, you can use
+``ac_add_options --with-ccache=sccache``.
+
+Optimization
+^^^^^^^^^^^^
+
+``ac_add_options --enable-optimize``
+ Enables the default compiler optimization options
+
+ .. note::
+
+ **Note**: This is enabled by default
+
+``ac_add_options --enable-optimize=-O2``
+ Chooses particular compiler optimization options. In most cases, this
+ will not give the desired results, unless you know the Mozilla
+ codebase very well; note, however, that if you are building with the
+ Microsoft compilers, you probably **do** want this as ``-O1`` will
+ optimize for size, unlike GCC.
+``ac_add_options --enable-debug``
+ Enables assertions in C++ and JavaScript, plus other debug-only code.
+ This can significantly slow a build, but it is invaluable when
+ writing patches. **People developing patches (especially in C++)
+ should generally use this option.**
+``ac_add_options --disable-optimize``
+ Disables compiler optimization. This makes it much easier to step
+ through code in a debugger.
+``ac_add_options --enable-release``
+ Enables more conservative, release engineering-oriented options. This may
+ slow down builds. This also turns on full optimizations for Rust. Note this
+ is the default when building release/beta/esr.
+``ac_add_options --enable-debug-js-modules``
+ Enable only JavaScript assertions. This is useful when working
+ locally on JavaScript-powered components like the DevTools. This will
+ help catch any errors introduced into the JS code, with less of a
+ performance impact compared to the ``--enable-debug`` option.
+``export RUSTC_OPT_LEVEL=2``
+ Enable full optimizations for Rust code.
+
+You can make an optimized build with debugging symbols. See :ref:`Building
+with Debug Symbols <Building with Debug Symbols>`.
+
+Extensions
+^^^^^^^^^^
+
+``ac_add_options --enable-extensions=default|all|ext1,ext2,-skipext3``
+ There are many optional pieces of code that live in {{
+ Source("extensions/") }}. Many of these extensions are now considered
+ an integral part of the browsing experience. There is a default list
+ of extensions for the suite, and each app-specific ``mozconfig``
+ specifies a different default set. Some extensions are not compatible
+ with all apps, for example:
+
+ - ``cookie`` is not compatible with thunderbird
+ - ``typeaheadfind`` is not compatible with any toolkit app (Firefox,
+ Thunderbird)
+
+ Unless you know which extensions are compatible with which apps, do
+ not use the ``--enable-extensions`` option; the build system will
+ automatically select the proper default set of extensions.
+
+Tests
+^^^^^
+
+``ac_add_options --disable-tests``
+ By default, many auxiliary test programs are built, which can
+ help debug and patch the mozilla source. Disabling these tests can
+ speed build time and reduce disk space considerably. Developers
+ should generally not use this option.
+
+Localization
+^^^^^^^^^^^^
+
+``mk_add_options MOZ_CO_LOCALES=ISOcode``
+ TBD.
+``ac_add_options --enable-ui-locale=ISOcode``
+ TBD.
+``ac_add_options --with-l10n-base=/path/to/base/dir``
+ TBD.
+
+Other Options
+^^^^^^^^^^^^^
+
+``mk_add_options AUTOCLOBBER=1``
+ If a clobber would be required before a build, this will cause mach
+ to clobber and continue with the build instead of asking the user to
+ manually clobber and exiting.
+
+``ac_add_options --enable-crashreporter``
+ This enables the machinery that allows Firefox to write out a
+ `minidump <https://docs.microsoft.com/en-us/windows/desktop/Debug/minidump-files>`__
+ files when crashing as well as the tools to process and submit crash
+ reports to Mozilla. After enabling the crash reporter in your local
+ build, you will need to run mach with the --enable-crash-reporter
+ (note the extra dash) to enable it at runtime, like so:
+ ``./mach run --enable-crash-reporter``
+``ac_add_options --enable-warnings-as-errors``
+ This makes compiler warnings into errors which fail the build. This
+ can be useful since certain warnings coincide with reviewbot lints
+ which must be fixed before merging.
+
+.. _Example_.mozconfig_Files:
+
+Example ``mozconfig`` Files
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Mozilla's official builds use mozconfig files from the appropriate
+directory within each repository.
+
+.. warning::
+
+ These ``mozconfig`` files are taken from production builds
+ and are provided as examples only. It is recommended to use the default
+ build options, and only change the properties from the list above as
+ needed. The production builds aren't really appropriate for local
+ builds."
+
+- .. rubric:: Firefox, `Debugging Build (macOS
+ 64bits) <http://hg.mozilla.org/mozilla-central/file/tip/browser/config/mozconfigs/macosx64/debug>`__
+ :name: Firefox.2C_Default_Release_Configuration
+
+Building multiple projects from the same source tree
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+It is possible to build multiple projects from the same source tree,
+as long as you `use a different objdir <#Building_with_an_Objdir>`__ for
+each project.
+
+You need to create multiple ``mozconfig`` files.
+
+As an example, the following steps can be used to build Firefox and
+Thunderbird. You should first create three ``mozconfig`` files.
+
+``mozconfig-common``:
+
+.. code::
+
+ # add common options here, such as making an optimized release build
+ mk_add_options MOZ_PARALLEL_BUILD=4
+ ac_add_options --enable-optimize --disable-debug
+
+``mozconfig-firefox``:
+
+.. code::
+
+ # include the common mozconfig
+ . ./mozconfig-common
+
+ # Build Firefox
+ mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-firefox
+ ac_add_options --enable-project=browser
+
+``mozconfig-thunderbird``:
+
+.. code::
+
+ # include the common mozconfig
+ . ./mozconfig-common
+
+ # Build Thunderbird
+ mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-thunderbird
+ ac_add_options --enable-project=comm/mail
+
+To build Firefox, run the following commands:
+
+.. code::
+
+ export MOZCONFIG=/path/to/mozilla/mozconfig-firefox
+ ./mach build
+
+To build Thunderbird, run the following commands:
+
+.. code::
+
+ export MOZCONFIG=/path/to/mozilla/mozconfig-thunderbird
+ ./mach build
+
+Using mozconfigwrapper
+^^^^^^^^^^^^^^^^^^^^^^
+
+Mozconfigwrapper is similar to using multiple mozconfig files except
+that it abstracts and hides them so you don't have to worry about where
+they live or which ones you've created. It also saves you from having to
+export the MOZCONFIG variable each time. For information on installing
+and configuring mozconfigwrapper, see
+https://github.com/ahal/mozconfigwrapper.
diff --git a/docs/setup/contributing_code.rst b/docs/setup/contributing_code.rst
new file mode 100644
index 0000000000..8b24dac87d
--- /dev/null
+++ b/docs/setup/contributing_code.rst
@@ -0,0 +1,181 @@
+How To Contribute Code To Firefox
+=================================
+
+The whole process can be a bit long, and it might take time to get things right.
+If at any point you are stuck, please don't hesitate to ask at `https://chat.mozilla.org <https://chat.mozilla.org>`_
+in the `#introduction <https://chat.mozilla.org/#/room/#introduction:mozilla.org>`_ channel.
+
+We make changes to Firefox by writing patches, testing them and pushing them into "the tree", the
+term we use for all the code in Mozilla-Central. Let's get started.
+
+Please see the :ref:`Firefox Contributors Quick Reference <Firefox Contributors' Quick Reference>` for simple check list.
+
+Finding something to work on
+----------------------------
+
+| Bugs listed as 'Assigned' are not usually a good place to start,
+ unless you're sure you have something worthy to contribute. Someone
+ else is already working on it!
+| Even with no assignee, it is polite to check if someone has recently
+ commented that they're looking at fixing the issue.
+| Once you have found something to work on, go ahead and comment! Let
+ the bug submitter, reviewer, and component owner know that you'd like
+ to work on the bug. You might receive some extra information, perhaps
+ also made the assignee.
+
+Find a bug we've identified as a good fit for new contributors.
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+With more than a million bugs filed in Bugzilla, it can be hard to know
+where to start, so we've created these bug categories to make getting
+involved a little easier:
+
+- `Codetribute <https://codetribute.mozilla.org/>`_ - our site for
+ finding bugs that are mentored, some are good first bugs, some are
+ slightly harder. Your mentor will help guide you with the bug fix and
+ through the submission and landing process.
+- `Good First Bugs <https://mzl.la/2yBg3zB>`_
+ - are the best way to take your first steps into the Mozilla
+ ecosystem. They're all about small changes, sometimes as little as a
+ few lines, but they're a great way to learn about setting up your
+ development environment, navigating Bugzilla, and making
+ contributions to the Mozilla codebase.
+- `Student Projects <https://bugzil.la/kw:student-project>`_ - are
+ larger projects, such as might be suitable for a university student
+ for credit. Of course, if you are not a student, feel free to fix one
+ of these bugs. We maintain two lists: one for projects `based on the
+ existing codebase <https://bugzil.la/kw:student-project>`_.
+
+Fix that one bug
+~~~~~~~~~~~~~~~~
+
+If there's one particular bug you'd like to fix about Firefox, Thunderbird, or
+your other favorite Mozilla application, this can be a great place to
+start. There are a number of ways to do this:
+
+- `Search bugzilla <https://bugzilla.mozilla.org/query.cgi>`_ for
+ relevant keywords. See pages on
+ `Bugzilla and Searching Bugzilla <https://bmo.readthedocs.io/en/latest/using/finding.html>`_ for further
+ help
+- Learn the `bugzilla
+ component <https://bugzilla.mozilla.org/describecomponents.cgi>`_,
+ with which your pet bug is implemented, using the components list.
+ Browse this component on bugzilla for related bugs
+
+Fixing your bug
+---------------
+
+We leave this in your hands. Here are some further resources to help:
+
+- Check out
+ :ref:`Our Developer Guide and its parent document <Working on Firefox>`
+- Our :ref:`reviewer checklist <Reviewer Checklist>` is very
+ useful, if you have a patch near completion, and seek a favorable
+ review
+- Utilize our build tool :ref:`mach`, its linting,
+ static analysis, and other code checking features
+
+Getting your code reviewed
+--------------------------
+
+Once you fix the bug, you can advance to having your code reviewed.
+
+Mozilla uses
+`Phabricator <https://moz-conduit.readthedocs.io/en/latest/phabricator-user.html>`_
+for code review.
+
+Who is the right person to ask for a review?
+
+- If you have a mentored bug: ask your mentor. They will help, or can
+ easily find out. It might be them!
+- Run ``{hg, git} blame`` on the file and look for the people who have touched
+ the functions you're working on. They too are good candidates.
+ Running ``{hg, git} log`` and looking for regular reviewers might be a
+ solution too.
+- The bug itself may contain a clear indication of the best person to
+ ask for a review
+- Are there related bugs on similar topics? The reviewer in those bugs
+ might be another good choice
+- We have a :ref:`list of modules <Governance>`, which lists peers and
+ owners for the module. Some of these will be good reviewers. In a
+ worst case scenario, set the module owner as the reviewer, asking
+ them in the comments to pick someone more suitable
+
+Please select only one reviewer.
+
+Following up and responding
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Once you've asked for a review, a reviewer will often respond within a
+day or two, reviewing the patch, or saying when they will be able to
+review it, perhaps due to a backlog. If you don't hear back within this
+time, naturally reach out to them: add a comment to the bug saying
+'review ping?', check the "Need more information from" box, and add the
+reviewer's name. If they don't respond within a day or two, you can ask
+for help on Matrix in the
+`#introduction:mozilla.org <https://riot.im/app/#/room/#introduction:mozilla.org>`_
+or
+`#developers:mozilla.org <https://chat.mozilla.org/#/room/#developers:mozilla.org>`_
+channels.
+
+Don't hesitate to contact your mentor as well if this isn't moving.
+
+For most new contributors, and even for long-time Mozillians, the first
+review of your patch will be "Requested Changes" (or an "r-" in
+Bugzilla). This does not mean you've done bad work. There is more work
+to do before the code can be merged into the tree. Your patch may need
+some changes - perhaps minor, perhaps major - and your reviewer will
+give you some guidance on what needs to be done next.
+
+This is an important process, so don't be discouraged! With our
+long-lived codebase, and hundreds of millions of users, the care and
+attention helping contributors bring good patches is the cornerstone of
+the Mozilla project. Make any changes your reviewer seeks; if you're
+unsure how, be sure to ask! Push your new patch up to Phabricator again and
+ask for a further review from the same reviewer. If they accept your
+changes, this means your patch can be landed into the tree!
+
+Getting code into Firefox
+-------------------------
+
+Once your patch has been accepted, it is ready to go. Before it can be
+merged into the tree, your patch will need to complete a successful run
+through our :ref:`try server <Pushing to Try>`,
+making sure there are no unexpected regressions. If you don't have try
+server access already, your mentor, or the person who reviewed your
+patch, will be able to help.
+
+Ask the reviewer to land the patch for you.
+For more details, see :ref:`push_a_change`
+
+
+Do it all again!
+----------------
+
+Thank you. You've fixed your very first bug, and the Open Web is
+stronger for it. But don't stop now.
+
+Go back to step 3, as there is plenty more to do. Your mentor might
+suggest a new bug for you to work on, or `find one that interests
+you <http://www.whatcanidoformozilla.org/>`_. Now that you've got your
+first bug fixed you should request level 1 access to the repository to
+push to the try server and get automated feedback about your changes on
+multiple platforms. After fixing a nontrivial number of bugs you should
+request level 3 access so you can land your own code after it has been
+reviewed.
+
+More information
+----------------
+
+We're in the process of improving information on this page for newcomers
+to the project. We'll be integrating some information from these pages
+soon, but until then you may find them interesting in their current
+form:
+
+- `A guide to learning the Firefox
+ codebase <http://www.joshmatthews.net/blog/2010/03/getting-involve-with-mozilla/>`_
+- `A beginner's guide to SpiderMonkey, Mozilla's Javascript
+ engine <https://wiki.mozilla.org/JavaScript:New_to_SpiderMonkey>`_
+- `Mozilla platform development
+ cheatsheet <https://web.archive.org/web/20160813112326/http://www.codefirefox.com:80/cheatsheet>`_
+ (archive.org)
diff --git a/docs/setup/index.rst b/docs/setup/index.rst
new file mode 100644
index 0000000000..eb0c68aa4e
--- /dev/null
+++ b/docs/setup/index.rst
@@ -0,0 +1,25 @@
+Getting Set Up To Work On The Firefox Codebase
+==============================================
+
+This page will help you get set up to build Firefox on your own machine.
+
+Don't hesitate to look at the :ref:`Firefox Contributors Quick Reference <Firefox Contributors' Quick Reference>` to read a quick tutorial.
+
+.. toctree::
+ :caption: Thank you for contributing to Firefox
+
+ /contributing/contributing_to_mozilla
+
+.. toctree::
+ :caption: Setting Up Your Machine
+ :maxdepth: 1
+
+ windows_build
+ macos_build
+ linux_build
+ linux_32bit_build_on_64bit_OS
+
+.. toctree::
+ :caption: Getting Ready To Contribute
+
+ contributing_code
diff --git a/docs/setup/linux_32bit_build_on_64bit_OS.rst b/docs/setup/linux_32bit_build_on_64bit_OS.rst
new file mode 100644
index 0000000000..6e8656fc4a
--- /dev/null
+++ b/docs/setup/linux_32bit_build_on_64bit_OS.rst
@@ -0,0 +1,37 @@
+Building Firefox 32-bit On Linux 64-bit
+=======================================
+
+.. note::
+
+ Unless you really want to target older hardware, you probably
+ want to :ref:`Build Firefox 64-bit <Building Firefox On Linux>`
+ since it is better-supported.
+
+Before following these 32-bit-Firefox-specific instructions, follow
+the :ref:`Building Firefox On Linux` instructions to ensure that
+your machine can do a normal build.
+
+Instructions for Ubuntu 19.10
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+These steps were verified to work as of June 2020.
+
+#. Run ``rustup target install i686-unknown-linux-gnu`` to install the
+ 32-bit Rust target.
+#. Install 32-bit dependencies with the following command (this shouldn't try to
+ remove packages. If this is the case, those instructions won't work as-is):
+
+ .. code::
+
+ sudo apt install gcc-multilib g++-multilib libdbus-glib-1-dev:i386 \
+ libgtk2.0-dev:i386 libgtk-3-dev:i386 libpango1.0-dev:i386 libxt-dev:i386 \
+ libx11-xcb-dev:i386 libpulse-dev:i386 libdrm-dev:i386
+
+#. Then, create a ``mozconfig`` file in your Firefox code directory
+ (probably ``mozilla-unified``) that looks like the following example:
+
+ .. code::
+
+ ac_add_options --target=i686
+
+#. Run ``./mach build``.
diff --git a/docs/setup/linux_build.rst b/docs/setup/linux_build.rst
new file mode 100644
index 0000000000..8582a52be6
--- /dev/null
+++ b/docs/setup/linux_build.rst
@@ -0,0 +1,151 @@
+Building Firefox On Linux
+=========================
+
+This document will help you get set up to build Firefox on your own
+computer. Getting set up can take a while - we need to download a
+lot of bytes! Even on a fast connection, this can take ten to fifteen
+minutes of work, spread out over an hour or two.
+
+Requirements
+------------
+
+- **Memory:** 4GB RAM minimum, 8GB+ recommended.
+- **Disk Space:** At least 30GB of free disk space.
+- **Operating System:** A 64-bit installation of Linux. It is strongly advised
+ that you use a supported distribution; see :ref:`build_hosts`. We also
+ recommend that your system is fully up-to-date.
+
+.. note::
+
+ Some Linux distros are better-supported than others. Mozilla maintains
+ bootstrapping code for Ubuntu, but others are managed by the
+ community (thanks!). The more esoteric the distro you're using,
+ the more likely that you'll need to solve unexpected problems.
+
+
+1. System preparation
+---------------------
+
+1.1 Install Python
+~~~~~~~~~~~~~~~~~~
+
+To build Firefox, it's necessary to have a Python of version 3.6 or later
+installed. Python 2 is no longer required to build Firefox, although it is still
+required for running some kinds of tests. Additionally, you will probably need
+Python development files as well to install some pip packages.
+
+You should be able to install Python using your system package manager:
+
+- For Debian-based Linux (such as Ubuntu): ``sudo apt-get install curl python3 python3-pip``
+- For Fedora Linux: ``sudo dnf install python3 python3-pip``
+
+If you need a version of Python that your package manager doesn't have (e.g.:
+the provided Python 3 is too old, or you want Python 2 but it's not available),
+then you can use `pyenv <https://github.com/pyenv/pyenv>`_, assuming that your
+system is supported.
+
+1.2 Install Mercurial
+~~~~~~~~~~~~~~~~~~~~~
+
+Mozilla's source code is hosted in Mercurial repositories. You will
+need Mercurial to download and update the code.
+
+Note that if you'd prefer to use the version of Mercurial that is
+packaged by your distro, you can skip this section. However, keep in
+mind that distro-packaged Mercurial may be outdated, and therefore
+slower and less supported.
+
+.. code-block:: shell
+
+ python3 -m pip install --user mercurial
+
+You can test that Mercurial is installed by running:
+
+.. code-block:: shell
+
+ hg version
+
+.. note::
+
+ If your shell is showing ``command not found: hg``, then Python's packages aren't
+ being found in the ``$PATH``. You can resolve this by doing the following and
+ restarting your shell:
+
+ .. code-block:: shell
+
+ # If you're using zsh
+ echo 'export PATH="'"$(python3 -m site --user-base)"'/bin:$PATH"' >> ~/.zshenv
+
+ # If you're using bash
+ echo 'export PATH="'"$(python3 -m site --user-base)"'/bin:$PATH"' >> ~/.bashrc
+
+ # If you're using a different shell, follow its documentation to see
+ # how to configure your PATH. Ensure that `$(python3 -m site --user-base)/bin`
+ # is prepended.
+
+2. Bootstrap a copy of the Firefox source code
+----------------------------------------------
+
+Now that your system is ready, we can download the source code and have Firefox
+automatically download the other dependencies it needs. The below command
+will download a lot of data (years of Firefox history!) then guide you through
+the interactive setup process.
+
+.. code-block:: shell
+
+ curl https://hg.mozilla.org/mozilla-central/raw-file/default/python/mozboot/bin/bootstrap.py -O
+ python3 bootstrap.py
+
+.. note::
+
+ In general, the Firefox workflow works best with Mercurial. However,
+ if you'd prefer to use ``git``, you can grab the source code in
+ "git" form by running the bootstrap script with the ``vcs`` parameter:
+
+ .. code-block:: shell
+
+ python3 bootstrap.py --vcs=git
+
+ This uses `Git Cinnabar <https://github.com/glandium/git-cinnabar/>`_ under the hood.
+
+Choosing a build type
+~~~~~~~~~~~~~~~~~~~~~
+
+If you aren't modifying the Firefox backend, then select one of the
+:ref:`Artifact Mode <Understanding Artifact Builds>` options. If you are
+building Firefox for Android, you should also see the :ref:`GeckoView Contributor Guide`.
+
+3. Build
+--------
+
+Now that your system is bootstrapped, you should be able to build!
+
+.. code-block:: shell
+
+ cd mozilla-unified
+ hg up -C central
+ ./mach build
+ ./mach run
+
+🎉 Congratulations! You've built your own home-grown Firefox!
+
+Now the fun starts
+------------------
+
+Time to start hacking! You should join us on `Matrix <https://chat.mozilla.org/>`_,
+say hello in the `Introduction channel
+<https://chat.mozilla.org/#/room/#introduction:mozilla.org>`_, and `find a bug to
+start working on <https://codetribute.mozilla.org/>`_.
+See the :ref:`Firefox Contributors' Quick Reference` to learn how to test your changes,
+send patches to Mozilla, update your source code locally, and more.
+
+Troubleshooting
+---------------
+
+Using a non-native file system (NTFS, network drive, etc)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In our experience building Firefox in these hybrid or otherwise complex environments
+always ends in unexpected, often silent and always hard-to-diagnose failure.
+Building Firefox in that environment is far more likely to reveal the flaws and
+shortcomings of those systems than it is to produce a running web browser.
diff --git a/docs/setup/macos_build.rst b/docs/setup/macos_build.rst
new file mode 100644
index 0000000000..3d32b8aed0
--- /dev/null
+++ b/docs/setup/macos_build.rst
@@ -0,0 +1,122 @@
+Building Firefox On macOS
+=========================
+
+This document will help you get set up to build Firefox on your own
+computer. Getting set up can take a while - we need to download a
+lot of bytes! Even on a fast connection, this can take ten to fifteen
+minutes of work, spread out over an hour or two.
+
+Requirements
+------------
+
+- **Memory:** 4GB RAM minimum, 8GB+ recommended.
+- **Disk Space:** At least 30GB of free disk space.
+- **Operating System:** macOS - most recent or prior release. It is advisable
+ to upgrade to the latest “point” release. See :ref:`build_hosts` for more
+ information.
+
+
+1. System preparation
+---------------------
+
+1.1. Install Brew
+~~~~~~~~~~~~~~~~~
+
+Mozilla's source tree requires a number of third-party tools.
+You will need to install `Homebrew <https://brew.sh/>`__ so that we
+can automatically fetch the tools we need.
+
+1.2. Install Xcode
+~~~~~~~~~~~~~~~~~~
+
+Install Xcode from the App Store.
+Once done, finalize the installation in your terminal:
+
+.. code-block:: shell
+
+ sudo xcode-select --switch /Applications/Xcode.app
+ sudo xcodebuild -license
+
+1.3 Install Mercurial
+~~~~~~~~~~~~~~~~~~~~~
+
+Mozilla's source code is hosted in Mercurial repositories. You will
+need Mercurial to download and update the code. Additionally, we'll
+put user-wide python package installations on the ``$PATH``, so that
+both ``hg`` and ``moz-phab`` will be easily accessible:
+
+**NOTE** Pay special attention to the `==6.1.4`, as Mercurial >=6.2 is incompatible with several plugins
+
+.. code-block:: shell
+
+ echo 'export PATH="'"$(python3 -m site --user-base)"'/bin:$PATH"' >> ~/.zshenv
+ python3 -m pip install --user mercurial==6.1.4
+
+Now, restart your shell so that the ``PATH`` change took effect.
+You can test that Mercurial is installed by running:
+
+.. code-block:: shell
+
+ hg version
+
+.. note::
+
+ If you're using a shell other than ``zsh``, you'll need to manually add Python's
+ ``bin`` directory to your ``PATH``, as your shell probably won't pick up our
+ changes in ``~/.zshenv``.
+
+2. Bootstrap a copy of the Firefox source code
+----------------------------------------------
+
+Now that your system is ready, we can download the source code and have Firefox
+automatically download the other dependencies it needs. The below command
+will download a lot of data (years of Firefox history!) then guide you through
+the interactive setup process.
+
+.. code-block:: shell
+
+ curl https://hg.mozilla.org/mozilla-central/raw-file/default/python/mozboot/bin/bootstrap.py -O
+ python3 bootstrap.py
+
+.. note::
+
+ In general, the Firefox workflow works best with Mercurial. However,
+ if you'd prefer to use ``git``, you can grab the source code in
+ "git" form by running the bootstrap script with the ``vcs`` parameter:
+
+ .. code-block:: shell
+
+ python3 bootstrap.py --vcs=git
+
+ This uses `Git Cinnabar <https://github.com/glandium/git-cinnabar/>`_ under the hood.
+
+Choosing a build type
+~~~~~~~~~~~~~~~~~~~~~
+
+If you aren't modifying the Firefox backend, then select one of the
+:ref:`Artifact Mode <Understanding Artifact Builds>` options. If you are
+building Firefox for Android, you should also see the :ref:`GeckoView Contributor Guide`.
+
+3. Build
+--------
+
+Now that your system is bootstrapped, you should be able to build!
+
+.. code-block:: shell
+
+ cd mozilla-unified
+ hg up -C central
+ ./mach build
+ ./mach run
+
+🎉 Congratulations! You've built your own home-grown Firefox!
+
+Now the fun starts
+------------------
+
+Time to start hacking! You should join us on `Matrix <https://chat.mozilla.org/>`_,
+say hello in the `Introduction channel
+<https://chat.mozilla.org/#/room/#introduction:mozilla.org>`_, and `find a bug to
+start working on <https://codetribute.mozilla.org/>`_.
+See the :ref:`Firefox Contributors' Quick Reference` to learn how to test your changes,
+send patches to Mozilla, update your source code locally, and more.
diff --git a/docs/setup/windows_build.rst b/docs/setup/windows_build.rst
new file mode 100644
index 0000000000..2e8da661e9
--- /dev/null
+++ b/docs/setup/windows_build.rst
@@ -0,0 +1,181 @@
+Building Firefox On Windows
+===========================
+
+This document will help you get set up to build Firefox on your own
+computer. Getting set up can take a while - we need to download a
+lot of bytes! Even on a fast connection, this can take ten to fifteen
+minutes of work, spread out over an hour or two.
+
+If you'd prefer to build Firefox for Windows in a virtual machine,
+you may be interested in the `Windows images provided by Microsoft
+<https://developer.microsoft.com/en-us/windows/downloads/virtual-machines/>`_.
+
+Requirements
+------------
+
+- **Memory:** 4GB RAM minimum, 8GB+ recommended.
+- **Disk Space:** At least 40GB of free disk space.
+- **Operating System:** Windows 10. It is advisable to have Windows Update be fully
+ up-to-date. See :ref:`build_hosts` for more information.
+
+1. Install MozillaBuild
+-----------------------
+
+Install `MozillaBuild
+<https://ftp.mozilla.org/pub/mozilla/libraries/win32/MozillaBuildSetup-Latest.exe>`_.
+
+Accept the default installation directory.
+Windows may prompt you to "reinstall with the correct settings", which you
+should click to accept.
+
+When working with Firefox tooling, you'll need to do so from within the MozillaBuild
+shell. You can start it by running ``C:\mozilla-build\start-shell.bat`` (you may want
+to make a shortcut to this file so it's easier to start).
+
+.. note::
+
+ The MozillaBuild shell is a lot more like a Linux shell than the Windows ``cmd``. You can
+ learn more about it `here <https://wiki.mozilla.org/MozillaBuild>`_.
+
+2. Bootstrap a copy of the Firefox source code
+----------------------------------------------
+
+Now that your system is ready, we can download the source code and have Firefox
+automatically download the other dependencies it needs. The below command
+will download a lot of data (years of Firefox history!) then guide you through
+the interactive setup process.
+
+.. code-block:: shell
+
+ # Using the C:\mozilla-build\start-shell.bat shell from step 1:
+ cd c:/
+ mkdir mozilla-source
+ cd mozilla-source
+ wget https://hg.mozilla.org/mozilla-central/raw-file/default/python/mozboot/bin/bootstrap.py
+ python3 bootstrap.py
+.. note::
+
+ When running ``bootstrap.py`` there will be a `UAC (User Account Control) prompt <https://docs.microsoft.com/en-us/windows/security/identity-protection/user-account-control/how-user-account-control-works>`_ for PowerShell after
+ selecting the destination directory for the source code clone. This is
+ necessary to add the Microsoft Defender Antivirus exclusions automatically. You
+ should select ``Yes`` on the UAC prompt, otherwise you will need
+ to :ref:`follow some manual steps below <Ensure antivirus exclusions>`.
+
+.. note::
+
+ In general, the Firefox workflow works best with Mercurial. However,
+ if you'd prefer to use ``git``, you can grab the source code in
+ "git" form by running the bootstrap script with the ``vcs`` parameter:
+
+ .. code-block:: shell
+
+ python3 bootstrap.py --vcs=git
+
+ This uses `Git Cinnabar <https://github.com/glandium/git-cinnabar/>`_ under the hood.
+
+Choosing a build type
+~~~~~~~~~~~~~~~~~~~~~
+
+If you aren't modifying the Firefox backend, then select one of the
+:ref:`Artifact Mode <Understanding Artifact Builds>` options. If you are
+building Firefox for Android, you should also see the :ref:`GeckoView Contributor Guide`.
+
+.. _Ensure antivirus exclusions:
+
+Ensure antivirus exclusions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Microsoft Defender Antivirus and some third-party antivirus products
+are known to significantly degrade build times and sometimes even cause failed
+builds (due to a "missing file"). This is usually because we have tests for
+well-known security bugs that have code samples that antivirus software identifies
+as a threat, automatically quarantining/corrupting the files.
+
+To avoid this, add the following folders to your third-party antivirus exclusion list:
+
+- The ``C:\mozilla-build`` folder.
+- The directory where the Firefox code is (probably ``C:\mozilla-source``).
+- The ``%USERPROFILE%/.mozbuild`` directory (probably ``C:\Users\<user>\.mozbuild``).
+
+The ``bootstrap.py`` script attempts to add the above folders to the Microsoft
+Defender Antivirus exclusion list automatically. You should check that they were
+successfully added, but if they're missing you will need to `add the exclusions to
+Microsoft Defender Antivirus manually
+<https://support.microsoft.com/en-ca/help/4028485/windows-10-add-an-exclusion-to-windows-security>`_.
+
+.. note::
+
+ If you're already missing files (you'll see them listed in ``hg status``, you can have them
+ brought back by reverting your source tree: ``hg update -C``).
+
+3. Build
+--------
+
+Now that your system is bootstrapped, you should be able to build!
+
+.. code-block:: shell
+
+ cd c:/mozilla-source/mozilla-unified
+ hg up -C central
+ ./mach build
+ ./mach run
+
+🎉 Congratulations! You've built your own home-grown Firefox!
+
+Now the fun starts
+------------------
+
+Time to start hacking! You should join us on `Matrix <https://chat.mozilla.org/>`_,
+say hello in the `Introduction channel
+<https://chat.mozilla.org/#/room/#introduction:mozilla.org>`_, and `find a bug to
+start working on <https://codetribute.mozilla.org/>`_.
+See the :ref:`Firefox Contributors' Quick Reference` to learn how to test your changes,
+send patches to Mozilla, update your source code locally, and more.
+
+.. note::
+
+ If you'd like to interact with Mach from a different command line environment
+ than MozillaBuild, there's experimental support for it described
+ :ref:`over here <Using Mach on Windows Outside MozillaBuild>`.
+
+Troubleshooting
+---------------
+
+MozillaBuild out-of-date
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+The build system expects that you're using the most-recent MozillaBuild release.
+However, MozillaBuild doesn't auto-update. If you're running into local issues,
+they may be resolved by `upgrading your MozillaBuild <https://wiki.mozilla.org/MozillaBuild>`_.
+
+Spaces in folder names
+~~~~~~~~~~~~~~~~~~~~~~
+
+**Firefox will not build** if the path to the installation
+tool folders contains **spaces** or other breaking characters such as
+pluses, quotation marks, or metacharacters. The Visual Studio tools and
+SDKs are an exception - they may be installed in a directory which
+contains spaces. It is strongly recommended that you accept the default
+settings for all installation locations.
+
+Quotation marks in ``PATH``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Quotation marks (") aren't translated properly when passed to MozillaBuild
+sub-shells. Since they're not usually necessary, you should ensure they're
+not in your ``PATH`` environment variable.
+
+``PYTHON`` environment variable
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If ``PYTHON`` is set, the build may fail with the error: "``The system
+cannot find the file specified``." Ensure that you aren't having
+a ``PYTHON`` environment variable set.
+
+Cygwin interference
+~~~~~~~~~~~~~~~~~~~
+
+If you happen to have Cygwin installed, its tools may erroneously be
+used when building Firefox. Ensure that MozillaBuild directories (in
+``C:\mozilla-build\``) are before Cygwin directories in the ``PATH``
+environment variable.
diff --git a/docs/testing-rust-code/index.md b/docs/testing-rust-code/index.md
new file mode 100644
index 0000000000..fef3ce9c80
--- /dev/null
+++ b/docs/testing-rust-code/index.md
@@ -0,0 +1,123 @@
+# Testing & Debugging Rust Code
+
+This page explains how to test and debug Rust code in Firefox.
+
+The [build documentation](/build/buildsystem/rust.rst) explains how to add
+new Rust code to Firefox. The [code documentation](/writing-rust-code/index.md)
+explains how to write and work with Rust code in Firefox.
+
+## Testing Mozilla crates
+
+Rust code will naturally be tested as part of system tests such as Mochitests.
+This section describes the two methods for unit testing of individual Rust
+crates. Which method should be used depends on the circumstances.
+
+### Rust tests
+
+If a Mozilla crate has "normal" Rust tests (i.e. `#[test]` functions that run
+with `cargo test`), you can add the crate's name to `RUST_TESTS` in
+[toolkit/library/rust/moz.build](https://searchfox.org/mozilla-central/source/toolkit/library/rust/moz.build).
+(Cargo features can be activated for Rust tests by adding them to
+`RUST_TEST_FEATURES` in the same file.)
+
+Rust tests are run with `./mach rusttests`. They run on automation in a couple
+of `rusttests` jobs, but not on all platforms.
+
+Rust tests have one major restriction: they cannot link against Gecko symbols.
+Therefore, Rust tests cannot be used for crates that use Gecko crates like
+`nsstring` and `xpcom`.
+
+It's also possible to use `RUST_TESTS` in a different `moz.build` file. See
+`testing/geckodriver/moz.build` and the [geckodriver testing docs] for an
+example.
+
+[geckodriver testing docs]: /testing/geckodriver/Testing.md
+
+### GTests
+
+Another way to unit test a Mozilla crate is by writing a GTest that uses FFI to
+call into Rust code. This requires the following steps.
+- Create a new test crate whose name is the same as the name of crate being
+ tested, with a `-gtest` suffix.
+- Add to the test crate a Rust file, a C++ file containing GTest `TEST()`
+ functions that use FFI to call into the Rust file, a `Cargo.toml` file that
+ references the Rust file, and a `moz.build` file that references the C++
+ file.
+- Add an entry to the `[dependencies]` section in
+ [toolkit/library/gtest/rust/Cargo.toml](https://searchfox.org/mozilla-central/source/toolkit/library/gtest/rust/Cargo.toml).
+- Add an `extern crate` entry to
+ [toolkit/library/gtest/rust/lib.rs](https://searchfox.org/mozilla-central/source/toolkit/library/gtest/rust/lib.rs).
+
+See
+[xpcom/rust/gtest/nsstring/](https://searchfox.org/mozilla-central/source/xpcom/rust/gtest/nsstring)
+for a simple example. (Note that the `moz.build` file is in the parent
+directory for that crate.)
+
+A Rust GTest can be run like any other GTest via `./mach gtest`, using the C++
+`TEST()` functions as the starting point.
+
+Unlike Rust tests, GTests can be used when linking against Gecko symbols is required.
+
+## Testing third-party crates
+
+In general we don't run tests for third-party crates. The assumption is that
+these crates are sufficiently well-tested elsewhere.
+
+## Debugging Rust code
+
+In theory, Rust code is debuggable much like C++ code, using standard tools
+like `gdb`, `rr`, and the Microsoft Visual Studio Debugger. In practice, the
+experience can be worse, because shortcomings such as the following can occur.
+- Inability to print local variables, even in non-optimized builds.
+- Inability to call generic functions.
+- Missing line numbers and stack frames.
+- Printing of basic types such as `Option` and `Vec` is sometimes sub-optimal.
+ If you see a warning "Missing auto-load script at offset 0 in section
+ `.debug_gdb_scripts`" when starting `gdb`, the `rust-gdb` wrapper may give
+ better results.
+
+## Logging from Rust code
+
+### Rust logging
+
+The `RUST_LOG` environment variable (from the `env_logger` crate) can be used
+to enable logging to stderr from Rust code in Firefox. The logging macros from
+the `log` crate can be used. In order of importance, they are: `error!`,
+`warn!`, `info!`, `debug!`, `trace!`.
+
+For example, to show all log messages of `info` level or higher, run:
+```
+RUST_LOG=info firefox
+```
+Module-level logging can also be specified, see the [documentation] for the
+`env_logger` crate for details.
+
+To restrict logging to child processes, use `RUST_LOG_CHILD` instead of
+`RUST_LOG`.
+
+[documentation]: https://docs.rs/env_logger/
+
+### Gecko logging
+
+Rust logging can also be forwarded to the [Gecko logger] for capture via
+`MOZ_LOG` and `MOZ_LOG_FILE`.
+
+[Gecko logger]: /xpcom/logging.rst
+
+- When parsing modules from `MOZ_LOG`, modules containing `::` are considered
+ to be Rust modules. To log everything in a top-level module like
+ `neqo_transport`, specify it as `neqo_transport::*`. For example:
+```
+MOZ_LOG=timestamp,sync,nsHostResolver:5,neqo_transport::*:5,proxy:5 firefox
+```
+- When logging from a submodule the `::*` is allowed but isn't necessary.
+ So these two lines are equivalent:
+```
+MOZ_LOG=timestamp,sync,neqo_transport::recovery:5 firefox
+MOZ_LOG=timestamp,sync,neqo_transport::recovery::*:5 firefox
+```
+- `debug!` and `trace!` logs will not appear in non-debug builds. This is due
+ to our use of the `release_max_level_info` feature in the `log` crate.
+
+- When using both `MOZ_LOG` and `RUST_LOG`, modules that are specified in
+ `MOZ_LOG` will not appear in `RUST_LOG`.
diff --git a/docs/update-infrastructure/index.md b/docs/update-infrastructure/index.md
new file mode 100644
index 0000000000..450c9f52aa
--- /dev/null
+++ b/docs/update-infrastructure/index.md
@@ -0,0 +1,36 @@
+# Mozilla Update Infrastructure
+
+Firefox, Thunderbird, and Mozilla VPN updates rely on [backend service known as Balrog](https://github.com/mozilla-releng/balrog). Periodically, these applications check for updates by pinging an URL[^1] which contains information about both the application running, and details of the system it is running on. If Balrog finds an update based on the provided information, it returns a simple XML response with a link to the update file, and some metadata about it.
+
+# Watershed Updates
+
+Most of the time, an update will take an application directly from whatever version it is currently running to the latest version of that application. This is greatly preferred, both to save time, bandwidth, reduce restarts, and increase security by ensuring we get to the latest version of something as quickly as possible. Unfortunately, sometimes we must route through an intermediate version first, typically the version that is currently running does not provide Balrog with enough information to accurately serve a correct update to the latest version. These are colloquially known as "watershed" updates. (This name comes from the fact that these updates change something about the update process itself, making them a kind of "watershed moment" for updates.)
+
+At the time of writing (May, 2023), we currently have the following watershed updates in place for Firefox's release channel:
+* 12.0, all platforms. In this release [we added support for distinguishing the running and supported architectures in macOS builds in the update URL](https://bugzilla.mozilla.org/show_bug.cgi?id=583671).
+* 43.0.1, Windows only. This release was [preparation for desupporting SHA-1 hashes in our Windows builds](https://bugzilla.mozilla.org/show_bug.cgi?id=1234277).
+* 45.0.2, Linux only. We added support for [sending the GTK 3 version to the update URL](https://bugzilla.mozilla.org/show_bug.cgi?id=1227023).
+* 47.0.2, Windows only. We [added CPU instruction set information to the update URL](https://bugzilla.mozilla.org/show_bug.cgi?id=1271761) to later allow SSE1 to be deprecated.
+* 56.0, Windows only. We added support for [sending information about the JAWS accessibility application to the update URL](https://bugzilla.mozilla.org/show_bug.cgi?id=1402376) to avoid updating people on crashy versions of it. Additionally, we [added support for LZMA compression to the updater](https://bugzilla.mozilla.org/show_bug.cgi?id=641212) and [swapped to new MAR signing keys](https://bugzilla.mozilla.org/show_bug.cgi?id=1324498).
+* 57.0.4[^2], Linux & Mac only. We [added support for LZMA compression to the updater](https://bugzilla.mozilla.org/show_bug.cgi?id=641212) and [swapped to new MAR signing keys](https://bugzilla.mozilla.org/show_bug.cgi?id=1324498). (Note: Windows was not required here because they picked up this change in the above 56.0 watershed.)
+* 72.0.2, all platforms. This is a required version due to a [necessary two-step password database migration](https://bugzilla.mozilla.org/show_bug.cgi?id=1615382).
+* 109.0.1, macOS only. This updates the channel-prefs.js file (normally excluded from updates) due to [signature issues](https://bugzilla.mozilla.org/show_bug.cgi?id=1804303).
+
+
+[^1]: Here is a sample URL: https://aus5.mozilla.org/update/3/Firefox/85.0/20200518093924/WINNT_x86_64-msvc-x64/en-US/release/Windows_NT%2010.0.0.0.18363.1016%20(x64)/default/default/update.xml
+[^2]: While not directly related to the watershed, it is notable that due to LZMA MAR support shipping in the previous version (56.0), which was _not_ a watershed for Linux and Mac, we shipped two different sets of MARs for 57.0, 57.0.1, 57.0.2, 57.0.3, and 57.0.4: we had bz2 MARs, which builds older than 56.0 were served, and a separate set of LZMA MARs that were shipped to 56.0 and higher. More details on this can be found on [this awfully complicated spreadsheet that was produced around that time](https://docs.google.com/spreadsheets/d/1xcs9iTShJI8WBvxrz547Zr5JClqdRUYEZRMbQqdQ58I/edit#gid=0).
+
+
+# Desupport Updates
+
+From time to time we stop supporting a platform, hardware or library that we depend on. When we do this, we must ensure we don't update affected users to a version that they can no longer run. This can take two forms:
+* In the distant past, we would simply stop serving updates altogether, leaving users on their current version.
+* More recently, we will first move users to our `esr` channel - generally giving them another year of support before ending updates altogether.
+
+Below is a list of desupports and moves to `esr` that we've done on the Firefox release channel:
+* (Ancient, desupported at various old versions): Darwin 6, Darwin 7, Darwin 8, Darwin 9, Windows_95, Windows_98, Windows_NT 4, Windows_NT 5.0, Windows_NT 5.1.0, Windows_NT 5.1.1, GTK 2.0., GTK 2.1., GTK 2.2., GTK 2.3., GTK 2.4., GTK 2.5., GTK 2.6., GTK 2.7., GTK 2.8., GTK 2.9., GTK 2.10., GTK 2.11., GTK 2.12., GTK 2.13., GTK 2.14., GTK 2.15., GTK 2.16., GTK 2.17., PPC Architecture
+* <46.0: [Desupported GTK <3.4](https://bugzilla.mozilla.org/show_bug.cgi?id=1270358)
+* <49.0: Desupported [Darwin 10, 11, and 12](https://bugzilla.mozilla.org/show_bug.cgi?id=1275607) (macOS 10.6, 10.7, 10.8), [SSE and MMX instruction sets](https://bugzilla.mozilla.org/show_bug.cgi?id=1284901)
+* <52.0: [Moved Windows XP, Server 2003, and Vista users to esr52](https://bugzilla.mozilla.org/show_bug.cgi?id=1318922)
+* <53.0: [Desupported 32-bit macOS hardware](https://bugzilla.mozilla.org/show_bug.cgi?id=1325584)
+* <77.0: [Moved Darwin 13, 14, and 15](https://bugzilla.mozilla.org/show_bug.cgi?id=1639199) (macOS 10.9, 10.10, and 10.11) to esr78
diff --git a/docs/writing-rust-code/basics.md b/docs/writing-rust-code/basics.md
new file mode 100644
index 0000000000..d91288133b
--- /dev/null
+++ b/docs/writing-rust-code/basics.md
@@ -0,0 +1,84 @@
+# Basics
+
+## Formatting Rust code
+
+To format all the Rust code within a directory `$DIR`, run:
+```
+./mach lint -l rustfmt --fix $DIR
+```
+
+## Using Cargo
+
+Many Cargo commands can be run on individual crates. Change into the directory
+containing the crate's `Cargo.toml` file, and then run the command with
+`MOZ_TOPOBJDIR` set appropriately. For example, to generate and view rustdocs
+for the `xpcom` crate, run these commands:
+
+```
+cd xpcom/rust/xpcom
+MOZ_TOPOBJDIR=$OBJDIR cargo doc
+cd -
+firefox target/doc/xpcom/index.html
+```
+where `$OBJDIR` is the path to the object directory.
+
+## Using static prefs
+
+Static boolean/integer prefs can be easily accessed from Rust code. Add a
+`rust: true` field to the pref definition in
+[modules/libpref/init/StaticPrefList.yaml](https://searchfox.org/mozilla-central/source/modules/libpref/init/StaticPrefList.yaml),
+like this:
+```yaml
+- name: my.lucky.pref
+ type: RelaxedAtomicBool
+ value: true
+ mirror: always
+ rust: true
+```
+The pref can then be accessed via the `pref!` macro, like this:
+```
+let my_lucky_pref = static_prefs::pref!("my.lucky.pref");
+```
+
+## Helper crates
+
+The following in-tree helper crates provide idiomatic support for some common patterns.
+- [nserror](https://searchfox.org/mozilla-central/source/xpcom/rust/nserror/src/lib.rs)
+reflects `nsresult` codes into Rust.
+- [nsstring](https://searchfox.org/mozilla-central/source/xpcom/rust/nsstring/src/lib.rs)
+ exposes bindings for XPCOM string types. You can use the same `ns{A,C}String`
+ types as C++ for owned strings and pass them back and forth over the
+ boundary. There is also `ns{A,C}Str` for dependent or borrowed strings.
+- [xpcom](https://searchfox.org/mozilla-central/source/xpcom/rust/xpcom/src)
+ provides multiple building blocks for a component's implementation.
+ - The `RefPtr` type is for managing reference-counted pointers.
+ - XPCOM component getters are generated by
+ [xpcom/components/gen_static_components.py](https://searchfox.org/mozilla-central/source/xpcom/components/gen_static_components.py),
+ and can be called like this:
+ ```
+ use xpcom::{interfaces::nsIPrefService, RefPtr};
+ let pref_service: RefPtr<nsIPrefService> = xpcom::components::Preferences::service()?;
+ ```
+ - There is also a `get_service` function that works like `do_GetService` in
+ C++, as an alternative.
+ - A set of `derive` macros help with declaring interface implementations. The
+ [docs](https://searchfox.org/mozilla-central/source/xpcom/rust/xpcom/xpcom_macros/src/lib.rs)
+ have details and examples.
+- [moz_task](https://searchfox.org/mozilla-central/source/xpcom/rust/moz_task/src/lib.rs)
+ wraps XPCOM's threading functions in order to make it easy and safe to write
+ threaded code. It has helpers for getting and creating threads, dispatching
+ async runnables, and thread-safe handles.
+- [storage](https://searchfox.org/mozilla-central/source/storage/rust/src/lib.rs)
+ is an interface to mozStorage, our wrapper for SQLite. It can wrap an
+ existing storage connection, and prepare and execute statements. This crate
+ wraps the synchronous connection API, and lets you execute statements
+ asynchronously via `moz_task`.
+- [storage_variant](https://searchfox.org/mozilla-central/source/storage/variant/src/lib.rs)
+ is for working with variants. It also provides a `HashPropertyBag` type
+ that's useful for passing hash maps over XPCOM to JS.
+
+Unfortunately, rustdocs are [not yet generated and
+hosted](https://bugzilla.mozilla.org/show_bug.cgi?id=1428139) for crates within
+mozilla-central. Therefore, the crate links shown above link to files
+containing the relevant rustdocs source where possible. However, you can
+generate docs locally using the `cargo doc` command described above.
diff --git a/docs/writing-rust-code/cpp-interop.md b/docs/writing-rust-code/cpp-interop.md
new file mode 100644
index 0000000000..f10fad4221
--- /dev/null
+++ b/docs/writing-rust-code/cpp-interop.md
@@ -0,0 +1,240 @@
+# Rust/C++ interop
+
+This document describes how to use FFI in Firefox to get Rust code and C++ code to interoperate.
+
+## Transferable types
+
+Generally speaking, the more complicated is the data you want to transfer, the
+harder it'll be to transfer across the FFI boundary.
+
+Booleans, integers, and pointers cause little trouble.
+- C++ `bool` matches Rust `bool`
+- C++ `uint8_t` matches Rust `u8`, `int32_t` matches Rust `i32`, etc.
+- C++ `const T*` matches Rust `*const T`, `T*` matches Rust `*mut T`.
+
+Lists are handled by C++ `nsTArray` and Rust `ThinVec`.
+
+For strings, it is best to use the `nsstring` helper crate. Using a raw pointer
+plus length is also possible for strings, but more error-prone.
+
+If you need a hashmap, you'll likely want to decompose it into two lists (keys
+and values) and transfer them separately.
+
+Other types can be handled with tools that generate bindings, as the following
+sections describe.
+
+## Accessing C++ code and data from Rust
+
+To call a C++ function from Rust requires adding a function declaration to Rust.
+For example, for this C++ function:
+
+```
+extern "C" {
+bool UniquelyNamedFunction(const nsCString* aInput, nsCString* aRetVal) {
+ return true;
+}
+}
+```
+add this declaration to the Rust code:
+```rust
+extern "C" {
+ pub fn UniquelyNamedFunction(input: &nsCString, ret_val: &mut nsCString) -> bool;
+}
+```
+
+Rust code can now call `UniquelyNamedFunction()` within an `unsafe` block. Note
+that if the declarations do not match (e.g. because the C++ function signature
+changes without the Rust declaration being updated) crashes are likely. (Hence
+the `unsafe` block.)
+
+Because of this unsafety, for non-trivial interfaces (in particular when C++
+structs and classes must be accessed from Rust code) it's common to use
+[rust-bindgen](https://github.com/rust-lang/rust-bindgen), which generates Rust
+bindings. The documentation is
+[here](https://rust-lang.github.io/rust-bindgen/).
+
+## Accessing Rust code and data from C++
+
+A common option for accessing Rust code and data from C++ is to use
+[cbindgen](https://github.com/eqrion/cbindgen), which generates C++ header
+files. for Rust crates that expose a public C API. cbindgen is a very powerful
+tool, and this section only covers some basic uses of it.
+
+### Basics
+
+First, add suitable definitions to your Rust. `#[no_mangle]` and `extern "C"`
+are required.
+
+```rust
+#[no_mangle]
+pub unsafe extern "C" fn unic_langid_canonicalize(
+ langid: &nsCString,
+ ret_val: &mut nsCString
+) -> bool {
+ ret_val.assign("new value");
+ true
+}
+```
+
+Then, add a `cbindgen.toml` file in the root of your crate. It may look like this:
+
+```toml
+header = """/* 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/. */"""
+autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. See RunCbindgen.py */
+#ifndef mozilla_intl_locale_MozLocaleBindings_h
+#error "Don't include this file directly, instead include MozLocaleBindings.h"
+#endif
+"""
+include_version = true
+braces = "SameLine"
+line_length = 100
+tab_width = 2
+language = "C++"
+# Put FFI calls in the `mozilla::intl::ffi` namespace.
+namespaces = ["mozilla", "intl", "ffi"]
+
+# Export `ThinVec` references as `nsTArray`.
+[export.rename]
+"ThinVec" = "nsTArray"
+```
+
+Next, extend the relevant `moz.build` file to invoke cbindgen.
+
+```python
+if CONFIG['COMPILE_ENVIRONMENT']:
+ CbindgenHeader('unic_langid_ffi_generated.h',
+ inputs=['/intl/locale/rust/unic-langid-ffi'])
+
+ EXPORTS.mozilla.intl += [
+ '!unic_langid_ffi_generated.h',
+ ]
+```
+
+This tells the build system to run cbindgen on
+`intl/locale/rust/unic-langid-ffi` to generate `unic_langid_ffi_generated.h`,
+which will be placed in `$OBJDIR/dist/include/mozilla/intl/`.
+
+Finally, include the generated header into a C++ file and call the function.
+
+```c++
+#include "mozilla/intl/unic_langid_ffi_generated.h"
+
+using namespace mozilla::intl::ffi;
+
+void Locale::MyFunction(nsCString& aInput) const {
+ nsCString result;
+ unic_langid_canonicalize(aInput, &result);
+}
+```
+
+### Complex types
+
+Many complex Rust types can be exposed to C++, and cbindgen will generate
+appropriate bindings for all `pub` types. For example:
+
+```rust
+#[repr(C)]
+pub enum FluentPlatform {
+ Linux,
+ Windows,
+ Macos,
+ Android,
+ Other,
+}
+
+extern "C" {
+ pub fn FluentBuiltInGetPlatform() -> FluentPlatform;
+}
+```
+
+```c++
+ffi::FluentPlatform FluentBuiltInGetPlatform() {
+ return ffi::FluentPlatform::Linux;
+}
+```
+
+For an example using cbindgen to expose much more complex Rust types to C++,
+see [this blog post].
+
+[this blog post]: https://crisal.io/words/2020/02/28/C++-rust-ffi-patterns-1-complex-data-structures.html
+
+### Instances
+
+If you need to create and destroy a Rust struct from C++ code, the following
+example may be helpful.
+
+First, define constructor, destructor and getter functions in Rust. (C++
+declarations for these will be generated by cbindgen.)
+
+```rust
+#[no_mangle]
+pub unsafe extern "C" fn unic_langid_new() -> *mut LanguageIdentifier {
+ let langid = LanguageIdentifier::default();
+ Box::into_raw(Box::new(langid))
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn unic_langid_destroy(langid: *mut LanguageIdentifier) {
+ drop(Box::from_raw(langid));
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn unic_langid_as_string(
+ langid: &mut LanguageIdentifier,
+ ret_val: &mut nsACString,
+) {
+ ret_val.assign(&langid.to_string());
+}
+```
+
+Next, in a C++ header define a destructor via `DefaultDelete`.
+
+```c++
+#include "mozilla/intl/unic_langid_ffi_generated.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+
+template <>
+class DefaultDelete<intl::ffi::LanguageIdentifier> {
+ public:
+ void operator()(intl::ffi::LanguageIdentifier* aPtr) const {
+ unic_langid_destroy(aPtr);
+ }
+};
+
+} // namespace mozilla
+```
+
+(This definition must be visible any place where
+`UniquePtr<intl::ffi::LanguageIdentifier>` is used, otherwise C++ will try to
+free the code, which might lead to strange behaviour!)
+
+Finally, implement the class.
+
+```c++
+class Locale {
+public:
+ explicit Locale(const nsACString& aLocale)
+ : mRaw(unic_langid_new()) {}
+
+ const nsCString Locale::AsString() const {
+ nsCString tag;
+ unic_langid_as_string(mRaw.get(), &tag);
+ return tag;
+ }
+
+private:
+ UniquePtr<ffi::LanguageIdentifier> mRaw;
+}
+```
+
+This makes it possible to instantiate a `Locale` object and call `AsString()`,
+all from C++ code.
+
+## Other examples
+
+For a detailed explanation of an interface in Firefox that doesn't use cbindgen
+or rust-bindgen, see [this blog post](https://hsivonen.fi/modern-cpp-in-rust/).
diff --git a/docs/writing-rust-code/index.md b/docs/writing-rust-code/index.md
new file mode 100644
index 0000000000..721a9be811
--- /dev/null
+++ b/docs/writing-rust-code/index.md
@@ -0,0 +1,16 @@
+# Writing Rust Code
+
+This page explains how to write and work with Rust code in Firefox, with an
+emphasis on interoperation with C++ code.
+
+The [build documentation](/build/buildsystem/rust.rst) explains how to add
+new Rust code to Firefox. The [test documentation](/testing-rust-code/index.md)
+explains how to test and debug Rust code in Firefox.
+
+```{toctree}
+:titlesonly:
+:maxdepth: 1
+:glob:
+
+*
+```
diff --git a/docs/writing-rust-code/uniffi.md b/docs/writing-rust-code/uniffi.md
new file mode 100644
index 0000000000..56ffc0935a
--- /dev/null
+++ b/docs/writing-rust-code/uniffi.md
@@ -0,0 +1,70 @@
+# Generating Javascript bindings with UniFFI
+
+Firefox supports auto-generating JS bindings for Rust components using [UniFFI](https://mozilla.github.io/uniffi-rs/).
+
+## How it works
+
+The Rust crate contains a
+[UniFFI Definition Language (UDL) file](https://mozilla.github.io/uniffi-rs/udl_file_spec.html), which describes the
+interface to generate bindings for.
+
+The UniFFI core generates the scaffolding: Rust code which acts as the FFI layer from the UDL file. The functions of
+this layer all use the C calling convention and all structs use a C layout, this is the de facto standard for FFI
+interoperability.
+
+The [`uniffi-bindgen-gecko-js`](https://searchfox.org/mozilla-central/source/toolkit/components/uniffi-bindgen-gecko-js)
+tool, which lives in the Firefox source tree, generates 2 things:
+ - A JS interface for the scaffolding code, which uses [WebIDL](/dom/bindings/webidl/index.rst)
+ - A module that uses the scaffolding to provide the bindings API.
+
+Currently, this generated code gets checked in to source control. We are working on a system to avoid this and
+auto-generate it at build time instead (see [bugzilla 1756214](https://bugzilla.mozilla.org/show_bug.cgi?id=1756214)).
+
+## Before creating new bindings with UniFFI
+
+Keep a few things in mind before you create a new set of bindings:
+
+ - **UniFFI was not written to maximize performance.** It's code is efficient enough to handle many use cases, but at this
+ point should probably be avoided for performance critical components.
+ - **uniffi-bindgen-gecko-js bindings run with chrome privileges.** Make sure this is acceptable for your project
+ - **Only a subset of Rust types can be exposed via the FFI.** Check the [UniFFI Book](https://mozilla.github.io/uniffi-rs/) to see what
+ types are compatible with UniFFI.
+
+If any of these are blockers for your work, consider discussing it further with the UniFFI devs to see if we can support
+your project:
+
+ - Chat with us on `#uniffi` on Matrix/Element
+ - File an issue on [mozilla/uniffi](https://github.com/mozilla/uniffi-rs/)
+
+## Creating new bindings with UniFFI
+
+You can see an example of this feature in use: [when application-services swapped the tabs js sync engine with rust](https://bugzilla.mozilla.org/show_bug.cgi?id=1791851)
+
+Here's how you can create a new set of bindings using UniFFI:
+
+ 1. UniFFI your crate (if it isn't already):
+ - [Create a UDL file to describe your interface](https://mozilla.github.io/uniffi-rs/udl_file_spec.html)
+ - [Set up your Rust crate to generate scaffolding](https://mozilla.github.io/uniffi-rs/tutorial/Rust_scaffolding.html)
+ 2. Add your crate as a Firefox dependency (if it isn't already)
+ - **If the code will exist in the mozilla-central repo:**
+ - Create a new directory for the Rust crate
+ - Edit `toolkit/library/rust/shared/Cargo.toml` and add a dependency to your library path
+ - **If the code exists in an external repo:**
+ - Edit `toolkit/library/rust/shared/Cargo.toml` and add a dependency to your library URL
+ - Run `mach vendor rust` to vendor in your Rust code
+ 3. Generate bindings code for your crate
+ - Add the path of your UDL (that you made in step 1) in `toolkit/components/uniffi-bindgen-gecko-js/mach_commands.py`
+ - Run `./mach uniffi generate`
+ - add your newly generated `Rust{udl-name}.sys.mjs` file to `toolkit/components/uniffi-bindgen-gecko-js/components/moz.build`
+ - Then simply import your module to the file you want to use it in and start using your APIs!
+
+ Example from tabs module:
+
+ ``` js
+ ChromeUtils.defineESModuleGetters(lazy, {
+ ...
+ TabsStore: "resource://gre/modules/RustTabs.sys.mjs",
+ });
+ ...
+ this._rustStore = await lazy.TabsStore.init(path);
+ ```
diff --git a/docs/writing-rust-code/update-policy.md b/docs/writing-rust-code/update-policy.md
new file mode 100644
index 0000000000..27d575a36e
--- /dev/null
+++ b/docs/writing-rust-code/update-policy.md
@@ -0,0 +1,150 @@
+# Rust Update Policy
+
+We document here the decision making and planning around when we update the
+Rust toolchains used and required to build Firefox.
+
+This allows contributors to know when new features will be usable, and
+downstream packagers to know what toolchain will be required for each Firefox
+release. Both benefit from the predictability of a schedule.
+
+## Policy
+
+### Official builds
+
+_We ship official stable Firefox with a stable Rust._
+
+As a general rule, we update the Rust version used to build Firefox Nightly
+soon after its release, unless it's less than 7 days away from a soft-freeze,
+in which case we wait for the next Nightly train.
+
+We don't upgrade the Rust version in the beta or release branches of Firefox.
+
+The following exceptions apply:
+
+- We may use a Rust version from the Rust beta or nightly channels for new
+ platforms (e.g. we did so for Android, arm64 Windows and arm64 macOS), and
+ later upgrade when that Rust version becomes stable (we may even do so on the
+ Firefox beta branch).
+
+- We may skip the update (or backout the update) if major problems are
+ encountered (typically, we've had to do so because of build problems, crash
+ reporting bustage, or performance issues).
+
+### Developer builds
+
+_Local developer builds use whatever Rust toolchain is available on the
+system._
+
+Someone building Firefox can maintain the latest stable Rust with the `rustup`
+or `mach bootstrap` tools, or try other variations.
+
+### Minimum Supported Rust Version
+
+_We will update the Minimum Supported Rust Version (MSRV) when required._
+
+The MSRV will generally remain unchanged, until a newer version is required
+by some code.
+
+When that happens, we'll prefer to update the MSRV to the strict minimum
+required at that moment (e.g. if we require version 1.47.0, the currently used
+Rust version is 1.51.0, and a crate needs 1.50.0, we'll prefer to update the
+MSRV to 1.50.0 rather than 1.51.0).
+
+The MSRV won't be updated to a version of Rust that hasn't been used for
+Firefox Nightly for at least 14 days.
+
+We expect ESR releases will keep their MSRV, so backporting security fixes may
+require Rust compatibility work.
+
+### Rationale
+
+Historically, the Rust ecosystem quickly required new features provided by new
+Rust compilers, which made it necessary to update the minimum supported version
+quite often, and as such, a scheduled update was deemed a better trade-off.
+
+Fast-forward several years, and new Rust compiler releases more rarely sport
+ground-breaking new features, which has reduced the necessity to update quite
+significantly.
+
+On the flip side, in some instances, we have had to stick to specific versions
+of the Rust compiler for extended periods of time because of e.g. regressions,
+going against the schedule.
+
+## Schedule
+
+Here are the Rust versions for each Firefox version.
+
+- The "Uses" column indicates the version of Rust used to build
+ releases shipped to users.
+
+- The "MSRV" column indicates the minimum supported Rust version to build
+ the sources.
+
+| Firefox Version | Uses | MSRV | Rust "Uses" release date | Nightly Soft Freeze | Firefox release date |
+|-----------------|------|----------|--------------------------|---------------------|----------------------|
+| Firefox 56 | Rust 1.19.0 | 1.17.0 | 2017 April 27 | | 2017 September 26
+| Firefox 57 | Rust 1.19.0 | 1.19.0 | 2017 July 20 | | 2017 November 14
+| Firefox 58 | Rust 1.21.0 | 1.21.0 | 2017 October 12 | | 2018 January 16
+| Firefox 59 | Rust 1.22.1 | 1.22.1 | 2017 November 23 | | 2018 March 13
+| Firefox 60 | Rust 1.24.0 | 1.24.0 | 2018 February 15 | | 2018 May 9
+| Firefox 61 | Rust 1.24.0 | 1.24.0 | 2018 February 15 | | 2018 June 26
+| Firefox 62 | Rust 1.24.0 | 1.24.0 | 2018 February 15 | | 2018 September 5
+| Firefox 63 | Rust 1.28.0 | 1.28.0 | 2018 August 2 | | 2018 October 23
+| Firefox 64 | Rust 1.29.2 | 1.29.0 | 2018 September 13 | 2018 October 15 | 2018 December 11
+| Firefox 65 | Rust 1.30.0 | 1.30.0 | 2018 October 25 | 2018 December 3 | 2019 January 29
+| Firefox 66 | Rust 1.31.0 | 1.31.0 | 2018 December 6 | 2019 January 21 | 2019 March 19
+| Firefox 67 | Rust 1.32.0 | 1.32.0 | 2019 January 17 | 2019 March 11 | 2019 May 21
+| Firefox 68 | Rust 1.34.0 | 1.34.0 | 2019 April 11 | 2019 May 13 | 2019 July 9
+| Firefox 69 | Rust 1.35.0 | 1.35.0 | 2019 May 23 | 2019 July 1 | 2019 September 3
+| Firefox 70 | Rust 1.37.0 | 1.36.0 | 2019 July 4 | 2019 August 26 | 2019 October 22
+| Firefox 71 | Rust 1.37.0 | 1.37.0 | 2019 August 15 | 2019 October 14 | 2019 December 3
+| Firefox 72 | Rust 1.38.0 | 1.37.0 | 2019 August 15 | 2019 November 25 | 2020 January 7
+| Firefox 73 | Rust 1.39.0 | 1.39.0 | 2019 November 7 | 2020 January 1 | 2020 February 11
+| Firefox 74 | Rust 1.39.0 | 1.39.0 | 2019 November 7 | 2020 February 6 | 2020 March 10
+| Firefox 75 | Rust 1.41.0 | 1.41.0 | 2020 January 30 | 2020 March 5 | 2020 April 7
+| Firefox 76 | Rust 1.41.0 | 1.41.0 | 2020 January 30 | 2020 April 2 | 2020 May 5
+| Firefox 77 | Rust 1.41.1 | 1.41.0 | 2020 January 30 | 2020 April 30 | 2020 June 2
+| Firefox 78 | Rust 1.43.0 | 1.41.0 | 2020 April 23 | 2020 May 28 | 2020 June 30
+| Firefox 79 | Rust 1.43.0 | 1.43.0 | 2020 April 23 | 2020 June 26 | 2020 July 28
+| Firefox 80 | Rust 1.43.0 | 1.43.0 | 2020 April 23 | 2020 July 23 | 2020 August 25
+| Firefox 81 | Rust 1.43.0 | 1.43.0 | 2020 April 23 | 2020 August 20 | 2020 September 22
+| Firefox 82 | Rust 1.43.0 | 1.43.0 | 2020 April 23 | 2020 September 17 | 2020 October 20
+| Firefox 83 | Rust 1.43.0 | 1.43.0 | 2020 April 23 | 2020 October 15 | 2020 November 17
+| Firefox 84 | Rust 1.47.0 | 1.43.0 | 2020 October 8 | 2020 November 12 | 2020 December 15
+| Firefox 85 | Rust 1.48.0 | 1.47.0 | 2020 November 19 | 2020 December 10 | 2021 January 26
+| Firefox 86 | Rust 1.49.0 | 1.47.0 | 2020 December 31 | 2021 January 21 | 2021 February 23
+| Firefox 87 | Rust 1.50.0 | 1.47.0 | 2021 February 11 | 2021 February 18 | 2021 March 23
+| Firefox 88 | Rust 1.50.0 | 1.47.0 | 2021 February 11 | 2021 March 18 | 2021 April 19
+| Firefox 89 | Rust 1.51.0 | 1.47.0 | 2021 March 25 | 2021 April 15 | 2021 June 1
+| Firefox 90 | Rust 1.52.0 | 1.47.0 | 2021 May 6 | 2021 May 27 | 2021 June 29
+| Firefox 91 | Rust 1.53.0 | 1.51.0 | 2021 June 17 | 2021 July 8 | 2021 August 10
+| Firefox 92 | Rust 1.54.0 | 1.51.0 | 2021 July 29 | 2021 August 5 | 2021 September 7
+| Firefox 93 | Rust 1.54.0 | 1.51.0 | 2021 July 29 | 2021 September 2 | 2021 October 5
+| Firefox 94 | Rust 1.55.0 | 1.53.0 | 2021 September 9 | 2021 September 30 | 2021 November 2
+| Firefox 95 | Rust 1.56.0 | 1.53.0 | 2021 October 21 | 2021 October 28 | 2021 December 7
+| Firefox 96 | Rust 1.57.0 | 1.53.0 | 2021 December 2 | 2021 December 2 | 2022 January 11
+| Firefox 97 | Rust 1.57.0 | 1.57.0 | 2021 December 2 | 2022 January 6 | 2022 February 8
+| Firefox 98 | Rust 1.58.0 | 1.57.0 | 2022 January 13 | 2022 February 2 | 2022 March 8
+| Firefox 99 | Rust 1.59.0 | 1.57.0 | 2022 February 24 | 2022 March 3 | 2022 April 5
+| Firefox 100 | Rust 1.59.0 | 1.57.0 | 2022 February 24 | 2022 March 31 | 2022 May 3
+| Firefox 101 | Rust 1.60.0 | 1.59.0 | 2022 April 7 | 2022 April 28 | 2022 May 31
+| Firefox 102 | Rust 1.60.0 | 1.59.0 | 2022 April 7 | 2022 May 26 | 2022 June 28
+| Firefox 103 | Rust 1.61.0 | 1.59.0 | 2022 May 19 | 2022 June 23 | 2022 July 27
+| Firefox 104 | Rust 1.62.0 | 1.59.0 | 2022 June 30 | 2022 July 21 | 2022 August 23
+| Firefox 105 | Rust 1.63.0 | 1.61.0 | 2022 August 11 | 2022 August 18 | 2022 September 20
+| Firefox 106 | Rust 1.63.0 | 1.61.0 | 2022 August 11 | 2022 September 15 | 2022 October 18
+| Firefox 107 | Rust 1.64.0 | 1.61.0 | 2022 September 22 | 2022 October 13 | 2022 November 15
+| Firefox 108 | Rust 1.65.0 | 1.63.0 | 2022 November 3 | 2022 November 10 | 2022 December 13
+| Firefox 109 | Rust 1.65.0 | 1.63.0 | 2022 November 3 | 2022 December 8 | 2023 January 17
+| Firefox 110 | Rust 1.66.0 | 1.65.0 | 2022 December 15 | 2023 January 12 | 2023 February 14
+| Firefox 111 | Rust 1.67.0 | 1.65.0 | 2023 January 26 | 2023 February 9 | 2023 March 14
+| Firefox 112 | Rust 1.67.0 | 1.65.0 | 2023 January 26 | 2023 March 9 | 2023 April 11
+| Firefox 113 | Rust 1.68.0 | 1.65.0 | 2023 March 9 | 2023 April 6 | 2023 May 9
+| Firefox 114 | Rust 1.69.0 | 1.65.0 | 2023 April 20 | 2023 May 4 | 2023 June 6
+| **Estimated** |
+| Firefox 115 | Rust 1.69.0 | 1.66.0 | 2023 April 20 | 2023 June 1 | 2023 July 4
+| Firefox 116 | Rust 1.70.0 | ? | 2023 June 1 | 2023 June 29 | 2023 August 1
+| Firefox 117 | Rust 1.71.0 | ? | 2023 July 13 | 2023 July 27 | 2023 August 29
+| Firefox 118 | Rust 1.71.0 | ? | 2023 July 13 | 2023 August 24 | 2023 September 26
+| Firefox 119 | Rust 1.72.0 | ? | 2023 August 24 | 2023 September 21 | 2023 October 24
+| Firefox 120 | Rust 1.73.0 | ? | 2023 October 4 | 2023 October 19 | 2023 November 21
diff --git a/docs/writing-rust-code/xpcom.md b/docs/writing-rust-code/xpcom.md
new file mode 100644
index 0000000000..dbe297e368
--- /dev/null
+++ b/docs/writing-rust-code/xpcom.md
@@ -0,0 +1,120 @@
+# XPCOM components in Rust
+
+XPCOM components can be written in Rust.
+
+## A tiny example
+
+The following example shows a new type that implements `nsIObserver`.
+
+First, create a new empty crate (e.g. with `cargo init --lib`), and add the
+following dependencies in its `Cargo.toml` file.
+
+```toml
+[dependencies]
+libc = "0.2"
+nserror = { path = "../../../xpcom/rust/nserror" }
+nsstring = { path = "../../../xpcom/rust/nsstring" }
+xpcom = { path = "../../../xpcom/rust/xpcom" }
+```
+
+(The number of `../` occurrences will depend on the depth of the crate in the
+file hierarchy.)
+
+Next hook it into the build system according to the [build
+documentation](/build/buildsystem/rust.rst).
+
+The Rust code will need to import some basic types. `xpcom::interfaces`
+contains all the usual `nsI` interfaces.
+
+```rust
+use libc::c_char;
+use nserror::nsresult;
+use std::sync::atomic::{AtomicBool, Ordering};
+use xpcom::{interfaces::nsISupports, RefPtr};
+```
+
+The next part declares the implementation.
+
+```rust
+#[xpcom(implement(nsIObserver), atomic)]
+struct MyObserver {
+ ran: AtomicBool,
+}
+```
+
+This defines the implementation type, which will be refcounted in the specified
+way and implement the listed xpidl interfaces. It will also declare a second
+initializer struct `InitMyObserver` which can be used to allocate a new
+`MyObserver` using the `MyObserver::allocate` method.
+
+Next, all interface methods are declared in the `impl` block as `unsafe` methods.
+
+```rust
+impl MyObserver {
+ #[allow(non_snake_case)]
+ unsafe fn Observe(
+ &self,
+ _subject: *const nsISupports,
+ _topic: *const c_char,
+ _data: *const u16,
+ ) -> nsresult {
+ self.ran.store(true, Ordering::SeqCst);
+ nserror::NS_OK
+ }
+}
+```
+
+These methods always take `&self`, not `&mut self`, so we need to use interior
+mutability: `AtomicBool`, `RefCell`, `Cell`, etc. This is because all XPCOM
+objects are reference counted (like `Arc<T>`), so cannot provide exclusive access.
+
+XPCOM methods are unsafe by default, but the
+[xpcom_method!](https://searchfox.org/mozilla-central/source/xpcom/rust/xpcom/src/method.rs)
+macro can be used to clean this up. It also takes care of null-checking and
+hiding pointers behind references, lets you return a `Result` instead of an
+`nsresult,` and so on.
+
+To use this type within Rust code, do something like the following.
+
+```rust
+let observer = MyObserver::allocate(InitMyObserver {
+ ran: AtomicBool::new(false),
+});
+let rv = unsafe {
+ observer.Observe(x.coerce(),
+ cstr!("some-topic").as_ptr(),
+ ptr::null())
+};
+assert!(rv.succeeded());
+```
+
+The implementation has an (auto-generated) `allocate` method that takes in an
+initialization struct, and returns a `RefPtr` to the instance.
+
+`coerce` casts any XPCOM object to one of its base interfaces; in this case,
+the base interface is `nsISupports`. In C++, this would be handled
+automatically through inheritance, but Rust doesn’t have inheritance, so the
+conversion must be explicit.
+
+## Bigger examples
+
+The following XPCOM components are written in Rust.
+
+- [kvstore](https://searchfox.org/mozilla-central/source/toolkit/components/kvstore),
+ which exposes the LMDB key-value store (via the [Rkv
+ library](https://docs.rs/rkv)) The API is asynchronous, using `moz_task` to
+ schedule all I/O on a background thread, and supports getting, setting, and
+ iterating over keys.
+- [cert_storage](https://searchfox.org/mozilla-central/source/security/manager/ssl/cert_storage),
+ which stores lists of [revoked intermediate certificates](https://blog.mozilla.org/security/2015/03/03/revoking-intermediate-certificates-introducing-onecrl/).
+- [bookmark_sync](https://searchfox.org/mozilla-central/source/toolkit/components/places/bookmark_sync),
+ which [merges](https://mozilla.github.io/dogear) bookmarks from Firefox Sync
+ with bookmarks in the Places database.
+ [There's also some docs on how Rust interacts with Sync](/services/sync/rust-engines.rst)
+- [webext_storage_bridge](https://searchfox.org/mozilla-central/source/toolkit/components/extensions/storage/webext_storage_bridge),
+ which powers the WebExtension storage.sync API. It's a self-contained example
+ that pulls in a crate from application-services for the heavy lifting, wraps
+ that up in a Rust XPCOM component, and then wraps the component in a JS
+ interface. There's also some boilerplate there around adding a
+ `components.conf` file, and a dummy C++ header that declares the component
+ constructor. [It has some in-depth documentation on how it hangs together](../toolkit/components/extensions/webextensions/webext-storage.rst).
diff --git a/docshell/base/BaseHistory.cpp b/docshell/base/BaseHistory.cpp
new file mode 100644
index 0000000000..a38fb71657
--- /dev/null
+++ b/docshell/base/BaseHistory.cpp
@@ -0,0 +1,246 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "BaseHistory.h"
+#include "nsThreadUtils.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Link.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_layout.h"
+
+namespace mozilla {
+
+using mozilla::dom::ContentParent;
+using mozilla::dom::Link;
+
+BaseHistory::BaseHistory() : mTrackedURIs(kTrackedUrisInitialSize) {}
+
+BaseHistory::~BaseHistory() = default;
+
+static constexpr nsLiteralCString kDisallowedSchemes[] = {
+ "about"_ns, "blob"_ns, "data"_ns, "chrome"_ns,
+ "imap"_ns, "javascript"_ns, "mailbox"_ns, "moz-anno"_ns,
+ "news"_ns, "page-icon"_ns, "resource"_ns, "view-source"_ns,
+ "moz-extension"_ns,
+};
+
+bool BaseHistory::CanStore(nsIURI* aURI) {
+ nsAutoCString scheme;
+ if (NS_WARN_IF(NS_FAILED(aURI->GetScheme(scheme)))) {
+ return false;
+ }
+
+ if (!scheme.EqualsLiteral("http") && !scheme.EqualsLiteral("https")) {
+ for (const nsLiteralCString& disallowed : kDisallowedSchemes) {
+ if (scheme.Equals(disallowed)) {
+ return false;
+ }
+ }
+ }
+
+ nsAutoCString spec;
+ aURI->GetSpec(spec);
+ return spec.Length() <= StaticPrefs::browser_history_maxUrlLength();
+}
+
+void BaseHistory::ScheduleVisitedQuery(nsIURI* aURI,
+ dom::ContentParent* aForProcess) {
+ mPendingQueries.WithEntryHandle(aURI, [&](auto&& entry) {
+ auto& set = entry.OrInsertWith([] { return ContentParentSet(); });
+ if (aForProcess) {
+ set.Insert(aForProcess);
+ }
+ });
+ if (mStartPendingVisitedQueriesScheduled) {
+ return;
+ }
+ mStartPendingVisitedQueriesScheduled =
+ NS_SUCCEEDED(NS_DispatchToMainThreadQueue(
+ NS_NewRunnableFunction(
+ "BaseHistory::StartPendingVisitedQueries",
+ [self = RefPtr<BaseHistory>(this)] {
+ self->mStartPendingVisitedQueriesScheduled = false;
+ auto queries = std::move(self->mPendingQueries);
+ self->StartPendingVisitedQueries(std::move(queries));
+ MOZ_DIAGNOSTIC_ASSERT(self->mPendingQueries.IsEmpty());
+ }),
+ EventQueuePriority::Idle));
+}
+
+void BaseHistory::CancelVisitedQueryIfPossible(nsIURI* aURI) {
+ mPendingQueries.Remove(aURI);
+ // TODO(bug 1591393): It could be worth to make this virtual and allow places
+ // to stop the existing database query? Needs some measurement.
+}
+
+void BaseHistory::RegisterVisitedCallback(nsIURI* aURI, Link* aLink) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aURI, "Must pass a non-null URI!");
+ MOZ_ASSERT(aLink, "Must pass a non-null Link!");
+
+ if (!CanStore(aURI)) {
+ aLink->VisitedQueryFinished(/* visited = */ false);
+ return;
+ }
+
+ // Obtain our array of observers for this URI.
+ auto* const links =
+ mTrackedURIs.WithEntryHandle(aURI, [&](auto&& entry) -> ObservingLinks* {
+ MOZ_DIAGNOSTIC_ASSERT(!entry || !entry->mLinks.IsEmpty(),
+ "An empty key was kept around in our hashtable!");
+ if (!entry) {
+ ScheduleVisitedQuery(aURI, nullptr);
+ }
+
+ return &entry.OrInsertWith([] { return ObservingLinks{}; });
+ });
+
+ if (!links) {
+ return;
+ }
+
+ // Sanity check that Links are not registered more than once for a given URI.
+ // This will not catch a case where it is registered for two different URIs.
+ MOZ_DIAGNOSTIC_ASSERT(!links->mLinks.Contains(aLink),
+ "Already tracking this Link object!");
+
+ links->mLinks.AppendElement(aLink);
+
+ // If this link has already been queried and we should notify, do so now.
+ switch (links->mStatus) {
+ case VisitedStatus::Unknown:
+ break;
+ case VisitedStatus::Unvisited:
+ [[fallthrough]];
+ case VisitedStatus::Visited:
+ aLink->VisitedQueryFinished(links->mStatus == VisitedStatus::Visited);
+ break;
+ }
+}
+
+void BaseHistory::UnregisterVisitedCallback(nsIURI* aURI, Link* aLink) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aURI, "Must pass a non-null URI!");
+ MOZ_ASSERT(aLink, "Must pass a non-null Link object!");
+
+ // Get the array, and remove the item from it.
+ auto entry = mTrackedURIs.Lookup(aURI);
+ if (!entry) {
+ MOZ_ASSERT(!CanStore(aURI),
+ "Trying to unregister URI that wasn't registered, "
+ "and that could be visited!");
+ return;
+ }
+
+ ObserverArray& observers = entry->mLinks;
+ if (!observers.RemoveElement(aLink)) {
+ MOZ_ASSERT_UNREACHABLE("Trying to unregister node that wasn't registered!");
+ return;
+ }
+
+ // If the array is now empty, we should remove it from the hashtable.
+ if (observers.IsEmpty()) {
+ entry.Remove();
+ CancelVisitedQueryIfPossible(aURI);
+ }
+}
+
+void BaseHistory::NotifyVisited(
+ nsIURI* aURI, VisitedStatus aStatus,
+ const ContentParentSet* aListOfProcessesToNotify) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aStatus != VisitedStatus::Unknown);
+
+ NotifyVisitedInThisProcess(aURI, aStatus);
+ if (XRE_IsParentProcess()) {
+ NotifyVisitedFromParent(aURI, aStatus, aListOfProcessesToNotify);
+ }
+}
+
+void BaseHistory::NotifyVisitedInThisProcess(nsIURI* aURI,
+ VisitedStatus aStatus) {
+ if (NS_WARN_IF(!aURI)) {
+ return;
+ }
+
+ auto entry = mTrackedURIs.Lookup(aURI);
+ if (!entry) {
+ // If we have no observers for this URI, we have nothing to notify about.
+ return;
+ }
+
+ ObservingLinks& links = entry.Data();
+ links.mStatus = aStatus;
+
+ // If we have a key, it should have at least one observer.
+ MOZ_ASSERT(!links.mLinks.IsEmpty());
+
+ // Dispatch an event to each document which has a Link observing this URL.
+ // These will fire asynchronously in the correct DocGroup.
+
+ const bool visited = aStatus == VisitedStatus::Visited;
+ for (Link* link : links.mLinks.BackwardRange()) {
+ link->VisitedQueryFinished(visited);
+ }
+}
+
+void BaseHistory::SendPendingVisitedResultsToChildProcesses() {
+ MOZ_ASSERT(!mPendingResults.IsEmpty());
+
+ mStartPendingResultsScheduled = false;
+
+ auto results = std::move(mPendingResults);
+ MOZ_ASSERT(mPendingResults.IsEmpty());
+
+ nsTArray<ContentParent*> cplist;
+ nsTArray<dom::VisitedQueryResult> resultsForProcess;
+ ContentParent::GetAll(cplist);
+ for (ContentParent* cp : cplist) {
+ resultsForProcess.ClearAndRetainStorage();
+ for (auto& result : results) {
+ if (result.mProcessesToNotify.IsEmpty() ||
+ result.mProcessesToNotify.Contains(cp)) {
+ resultsForProcess.AppendElement(result.mResult);
+ }
+ }
+ if (!resultsForProcess.IsEmpty()) {
+ Unused << NS_WARN_IF(!cp->SendNotifyVisited(resultsForProcess));
+ }
+ }
+}
+
+void BaseHistory::NotifyVisitedFromParent(
+ nsIURI* aURI, VisitedStatus aStatus,
+ const ContentParentSet* aListOfProcessesToNotify) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (aListOfProcessesToNotify && aListOfProcessesToNotify->IsEmpty()) {
+ return;
+ }
+
+ auto& result = *mPendingResults.AppendElement();
+ result.mResult.visited() = aStatus == VisitedStatus::Visited;
+ result.mResult.uri() = aURI;
+ if (aListOfProcessesToNotify) {
+ for (auto* entry : *aListOfProcessesToNotify) {
+ result.mProcessesToNotify.Insert(entry);
+ }
+ }
+
+ if (mStartPendingResultsScheduled) {
+ return;
+ }
+
+ mStartPendingResultsScheduled = NS_SUCCEEDED(NS_DispatchToMainThreadQueue(
+ NewRunnableMethod(
+ "BaseHistory::SendPendingVisitedResultsToChildProcesses", this,
+ &BaseHistory::SendPendingVisitedResultsToChildProcesses),
+ EventQueuePriority::Idle));
+}
+
+} // namespace mozilla
diff --git a/docshell/base/BaseHistory.h b/docshell/base/BaseHistory.h
new file mode 100644
index 0000000000..f0fa36db99
--- /dev/null
+++ b/docshell/base/BaseHistory.h
@@ -0,0 +1,85 @@
+/* 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_BaseHistory_h
+#define mozilla_BaseHistory_h
+
+#include "IHistory.h"
+#include "mozilla/dom/ContentParent.h"
+#include "nsTHashSet.h"
+
+/* A base class for history implementations that implement link coloring. */
+
+namespace mozilla {
+
+class BaseHistory : public IHistory {
+ public:
+ void RegisterVisitedCallback(nsIURI*, dom::Link*) final;
+ void ScheduleVisitedQuery(nsIURI*, dom::ContentParent*) final;
+ void UnregisterVisitedCallback(nsIURI*, dom::Link*) final;
+ void NotifyVisited(nsIURI*, VisitedStatus,
+ const ContentParentSet* = nullptr) final;
+
+ // Some URIs like data-uris are never going to be stored in history, so we can
+ // avoid doing IPC roundtrips for them or what not.
+ static bool CanStore(nsIURI*);
+
+ protected:
+ void NotifyVisitedInThisProcess(nsIURI*, VisitedStatus);
+ void NotifyVisitedFromParent(nsIURI*, VisitedStatus, const ContentParentSet*);
+ static constexpr const size_t kTrackedUrisInitialSize = 64;
+
+ BaseHistory();
+ ~BaseHistory();
+
+ using ObserverArray = nsTObserverArray<dom::Link*>;
+ struct ObservingLinks {
+ ObserverArray mLinks;
+ VisitedStatus mStatus = VisitedStatus::Unknown;
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ return mLinks.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+ };
+
+ using PendingVisitedQueries = nsTHashMap<nsURIHashKey, ContentParentSet>;
+ struct PendingVisitedResult {
+ dom::VisitedQueryResult mResult;
+ ContentParentSet mProcessesToNotify;
+ };
+ using PendingVisitedResults = nsTArray<PendingVisitedResult>;
+
+ // Starts all the queries in the pending queries list, potentially at the same
+ // time.
+ virtual void StartPendingVisitedQueries(PendingVisitedQueries&&) = 0;
+
+ private:
+ // Cancels a visited query, if it is at all possible, because we know we won't
+ // use the results anymore.
+ void CancelVisitedQueryIfPossible(nsIURI*);
+
+ void SendPendingVisitedResultsToChildProcesses();
+
+ protected:
+ // A map from URI to links that depend on that URI, and whether that URI is
+ // known-to-be-visited-or-unvisited already.
+ nsTHashMap<nsURIHashKey, ObservingLinks> mTrackedURIs;
+
+ private:
+ // The set of pending URIs that we haven't queried yet but need to.
+ PendingVisitedQueries mPendingQueries;
+ // The set of pending query results that we still haven't dispatched to child
+ // processes.
+ PendingVisitedResults mPendingResults;
+ // Whether we've successfully scheduled a runnable to call
+ // StartPendingVisitedQueries already.
+ bool mStartPendingVisitedQueriesScheduled = false;
+ // Whether we've successfully scheduled a runnable to call
+ // SendPendingVisitedResultsToChildProcesses already.
+ bool mStartPendingResultsScheduled = false;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/docshell/base/BrowsingContext.cpp b/docshell/base/BrowsingContext.cpp
new file mode 100644
index 0000000000..e515c4e9aa
--- /dev/null
+++ b/docshell/base/BrowsingContext.cpp
@@ -0,0 +1,3832 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/BrowsingContext.h"
+
+#include "ipc/IPCMessageUtils.h"
+
+#ifdef ACCESSIBILITY
+# include "mozilla/a11y/DocAccessibleParent.h"
+# include "mozilla/a11y/Platform.h"
+# include "mozilla/a11y/RemoteAccessibleBase.h"
+# include "nsAccessibilityService.h"
+# if defined(XP_WIN)
+# include "mozilla/a11y/AccessibleWrap.h"
+# include "mozilla/a11y/Compatibility.h"
+# include "mozilla/a11y/nsWinUtils.h"
+# endif
+#endif
+#include "mozilla/AppShutdown.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/BrowserHost.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/BrowsingContextGroup.h"
+#include "mozilla/dom/BrowsingContextBinding.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLEmbedElement.h"
+#include "mozilla/dom/HTMLIFrameElement.h"
+#include "mozilla/dom/Location.h"
+#include "mozilla/dom/LocationBinding.h"
+#include "mozilla/dom/MediaDevices.h"
+#include "mozilla/dom/PopupBlocker.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/SessionStoreChild.h"
+#include "mozilla/dom/SessionStorageManager.h"
+#include "mozilla/dom/StructuredCloneTags.h"
+#include "mozilla/dom/UserActivationIPCUtils.h"
+#include "mozilla/dom/WindowBinding.h"
+#include "mozilla/dom/WindowContext.h"
+#include "mozilla/dom/WindowGlobalChild.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/dom/WindowProxyHolder.h"
+#include "mozilla/dom/SyncedContextInlines.h"
+#include "mozilla/dom/XULFrameElement.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/net/DocumentLoadListener.h"
+#include "mozilla/net/RequestContextService.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Components.h"
+#include "mozilla/HashTable.h"
+#include "mozilla/Logging.h"
+#include "mozilla/MediaFeatureChange.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPrefs_fission.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/StaticPrefs_page_load.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/URLQueryStringStripper.h"
+#include "mozilla/EventStateManager.h"
+#include "nsIURIFixup.h"
+#include "nsIXULRuntime.h"
+
+#include "nsDocShell.h"
+#include "nsDocShellLoadState.h"
+#include "nsFocusManager.h"
+#include "nsGlobalWindowOuter.h"
+#include "PresShell.h"
+#include "nsIObserverService.h"
+#include "nsISHistory.h"
+#include "nsContentUtils.h"
+#include "nsQueryObject.h"
+#include "nsSandboxFlags.h"
+#include "nsScriptError.h"
+#include "nsThreadUtils.h"
+#include "xpcprivate.h"
+
+#include "AutoplayPolicy.h"
+#include "GVAutoplayRequestStatusIPC.h"
+
+extern mozilla::LazyLogModule gAutoplayPermissionLog;
+extern mozilla::LazyLogModule gTimeoutDeferralLog;
+
+#define AUTOPLAY_LOG(msg, ...) \
+ MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
+
+namespace IPC {
+// Allow serialization and deserialization of OrientationType over IPC
+template <>
+struct ParamTraits<mozilla::dom::OrientationType>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::OrientationType,
+ mozilla::dom::OrientationType::Portrait_primary,
+ mozilla::dom::OrientationType::EndGuard_> {};
+
+template <>
+struct ParamTraits<mozilla::dom::DisplayMode>
+ : public ContiguousEnumSerializer<mozilla::dom::DisplayMode,
+ mozilla::dom::DisplayMode::Browser,
+ mozilla::dom::DisplayMode::EndGuard_> {};
+
+template <>
+struct ParamTraits<mozilla::dom::PrefersColorSchemeOverride>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::PrefersColorSchemeOverride,
+ mozilla::dom::PrefersColorSchemeOverride::None,
+ mozilla::dom::PrefersColorSchemeOverride::EndGuard_> {};
+
+template <>
+struct ParamTraits<mozilla::dom::ExplicitActiveStatus>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::ExplicitActiveStatus,
+ mozilla::dom::ExplicitActiveStatus::None,
+ mozilla::dom::ExplicitActiveStatus::EndGuard_> {};
+
+// Allow serialization and deserialization of TouchEventsOverride over IPC
+template <>
+struct ParamTraits<mozilla::dom::TouchEventsOverride>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::TouchEventsOverride,
+ mozilla::dom::TouchEventsOverride::Disabled,
+ mozilla::dom::TouchEventsOverride::EndGuard_> {};
+
+template <>
+struct ParamTraits<mozilla::dom::EmbedderColorSchemes> {
+ using paramType = mozilla::dom::EmbedderColorSchemes;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mUsed);
+ WriteParam(aWriter, aParam.mPreferred);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &aResult->mUsed) &&
+ ReadParam(aReader, &aResult->mPreferred);
+ }
+};
+
+} // namespace IPC
+
+namespace mozilla {
+namespace dom {
+
+// Explicit specialization of the `Transaction` type. Required by the `extern
+// template class` declaration in the header.
+template class syncedcontext::Transaction<BrowsingContext>;
+
+extern mozilla::LazyLogModule gUserInteractionPRLog;
+
+#define USER_ACTIVATION_LOG(msg, ...) \
+ MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
+
+static LazyLogModule gBrowsingContextLog("BrowsingContext");
+static LazyLogModule gBrowsingContextSyncLog("BrowsingContextSync");
+
+typedef nsTHashMap<nsUint64HashKey, BrowsingContext*> BrowsingContextMap;
+
+// All BrowsingContexts indexed by Id
+static StaticAutoPtr<BrowsingContextMap> sBrowsingContexts;
+// Top-level Content BrowsingContexts only, indexed by BrowserId instead of Id
+static StaticAutoPtr<BrowsingContextMap> sCurrentTopByBrowserId;
+
+static void UnregisterBrowserId(BrowsingContext* aBrowsingContext) {
+ if (!aBrowsingContext->IsTopContent() || !sCurrentTopByBrowserId) {
+ return;
+ }
+
+ // Avoids an extra lookup
+ auto browserIdEntry =
+ sCurrentTopByBrowserId->Lookup(aBrowsingContext->BrowserId());
+ if (browserIdEntry && browserIdEntry.Data() == aBrowsingContext) {
+ browserIdEntry.Remove();
+ }
+}
+
+static void Register(BrowsingContext* aBrowsingContext) {
+ sBrowsingContexts->InsertOrUpdate(aBrowsingContext->Id(), aBrowsingContext);
+ if (aBrowsingContext->IsTopContent()) {
+ sCurrentTopByBrowserId->InsertOrUpdate(aBrowsingContext->BrowserId(),
+ aBrowsingContext);
+ }
+
+ aBrowsingContext->Group()->Register(aBrowsingContext);
+}
+
+// static
+void BrowsingContext::UpdateCurrentTopByBrowserId(
+ BrowsingContext* aNewBrowsingContext) {
+ if (aNewBrowsingContext->IsTopContent()) {
+ sCurrentTopByBrowserId->InsertOrUpdate(aNewBrowsingContext->BrowserId(),
+ aNewBrowsingContext);
+ }
+}
+
+BrowsingContext* BrowsingContext::GetParent() const {
+ return mParentWindow ? mParentWindow->GetBrowsingContext() : nullptr;
+}
+
+bool BrowsingContext::IsInSubtreeOf(BrowsingContext* aContext) {
+ BrowsingContext* bc = this;
+ do {
+ if (bc == aContext) {
+ return true;
+ }
+ } while ((bc = bc->GetParent()));
+ return false;
+}
+
+BrowsingContext* BrowsingContext::Top() {
+ BrowsingContext* bc = this;
+ while (bc->mParentWindow) {
+ bc = bc->GetParent();
+ }
+ return bc;
+}
+
+const BrowsingContext* BrowsingContext::Top() const {
+ const BrowsingContext* bc = this;
+ while (bc->mParentWindow) {
+ bc = bc->GetParent();
+ }
+ return bc;
+}
+
+int32_t BrowsingContext::IndexOf(BrowsingContext* aChild) {
+ int32_t index = -1;
+ for (BrowsingContext* child : Children()) {
+ ++index;
+ if (child == aChild) {
+ break;
+ }
+ }
+ return index;
+}
+
+WindowContext* BrowsingContext::GetTopWindowContext() const {
+ if (mParentWindow) {
+ return mParentWindow->TopWindowContext();
+ }
+ return mCurrentWindowContext;
+}
+
+/* static */
+void BrowsingContext::Init() {
+ if (!sBrowsingContexts) {
+ sBrowsingContexts = new BrowsingContextMap();
+ sCurrentTopByBrowserId = new BrowsingContextMap();
+ ClearOnShutdown(&sBrowsingContexts);
+ ClearOnShutdown(&sCurrentTopByBrowserId);
+ }
+}
+
+/* static */
+LogModule* BrowsingContext::GetLog() { return gBrowsingContextLog; }
+
+/* static */
+LogModule* BrowsingContext::GetSyncLog() { return gBrowsingContextSyncLog; }
+
+/* static */
+already_AddRefed<BrowsingContext> BrowsingContext::Get(uint64_t aId) {
+ return do_AddRef(sBrowsingContexts->Get(aId));
+}
+
+/* static */
+already_AddRefed<BrowsingContext> BrowsingContext::GetCurrentTopByBrowserId(
+ uint64_t aBrowserId) {
+ return do_AddRef(sCurrentTopByBrowserId->Get(aBrowserId));
+}
+
+/* static */
+already_AddRefed<BrowsingContext> BrowsingContext::GetFromWindow(
+ WindowProxyHolder& aProxy) {
+ return do_AddRef(aProxy.get());
+}
+
+CanonicalBrowsingContext* BrowsingContext::Canonical() {
+ return CanonicalBrowsingContext::Cast(this);
+}
+
+bool BrowsingContext::IsOwnedByProcess() const {
+ return mIsInProcess && mDocShell &&
+ !nsDocShell::Cast(mDocShell)->WillChangeProcess();
+}
+
+bool BrowsingContext::SameOriginWithTop() {
+ MOZ_ASSERT(IsInProcess());
+ // If the top BrowsingContext is not same-process to us, it is cross-origin
+ if (!Top()->IsInProcess()) {
+ return false;
+ }
+
+ nsIDocShell* docShell = GetDocShell();
+ if (!docShell) {
+ return false;
+ }
+ Document* doc = docShell->GetDocument();
+ if (!doc) {
+ return false;
+ }
+ nsIPrincipal* principal = doc->NodePrincipal();
+
+ nsIDocShell* topDocShell = Top()->GetDocShell();
+ if (!topDocShell) {
+ return false;
+ }
+ Document* topDoc = topDocShell->GetDocument();
+ if (!topDoc) {
+ return false;
+ }
+ nsIPrincipal* topPrincipal = topDoc->NodePrincipal();
+
+ return principal->Equals(topPrincipal);
+}
+
+/* static */
+already_AddRefed<BrowsingContext> BrowsingContext::CreateDetached(
+ nsGlobalWindowInner* aParent, BrowsingContext* aOpener,
+ BrowsingContextGroup* aSpecificGroup, const nsAString& aName, Type aType,
+ bool aIsPopupRequested, bool aCreatedDynamically) {
+ if (aParent) {
+ MOZ_DIAGNOSTIC_ASSERT(aParent->GetWindowContext());
+ MOZ_DIAGNOSTIC_ASSERT(aParent->GetBrowsingContext()->mType == aType);
+ MOZ_DIAGNOSTIC_ASSERT(aParent->GetBrowsingContext()->GetBrowserId() != 0);
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(aType != Type::Chrome || XRE_IsParentProcess());
+
+ uint64_t id = nsContentUtils::GenerateBrowsingContextId();
+
+ MOZ_LOG(GetLog(), LogLevel::Debug,
+ ("Creating 0x%08" PRIx64 " in %s", id,
+ XRE_IsParentProcess() ? "Parent" : "Child"));
+
+ RefPtr<BrowsingContext> parentBC =
+ aParent ? aParent->GetBrowsingContext() : nullptr;
+ RefPtr<WindowContext> parentWC =
+ aParent ? aParent->GetWindowContext() : nullptr;
+ BrowsingContext* inherit = parentBC ? parentBC.get() : aOpener;
+
+ // Determine which BrowsingContextGroup this context should be created in.
+ RefPtr<BrowsingContextGroup> group = aSpecificGroup;
+ if (aType == Type::Chrome) {
+ MOZ_DIAGNOSTIC_ASSERT(!group);
+ group = BrowsingContextGroup::GetChromeGroup();
+ } else if (!group) {
+ group = BrowsingContextGroup::Select(parentWC, aOpener);
+ }
+
+ // Configure initial values for synced fields.
+ FieldValues fields;
+ fields.Get<IDX_Name>() = aName;
+
+ if (aOpener) {
+ MOZ_DIAGNOSTIC_ASSERT(!aParent,
+ "new BC with both initial opener and parent");
+ MOZ_DIAGNOSTIC_ASSERT(aOpener->Group() == group);
+ MOZ_DIAGNOSTIC_ASSERT(aOpener->mType == aType);
+ fields.Get<IDX_OpenerId>() = aOpener->Id();
+ fields.Get<IDX_HadOriginalOpener>() = true;
+ }
+
+ if (aParent) {
+ MOZ_DIAGNOSTIC_ASSERT(parentBC->Group() == group);
+ MOZ_DIAGNOSTIC_ASSERT(parentBC->mType == aType);
+ fields.Get<IDX_EmbedderInnerWindowId>() = aParent->WindowID();
+ // Non-toplevel content documents are always embededed within content.
+ fields.Get<IDX_EmbeddedInContentDocument>() =
+ parentBC->mType == Type::Content;
+
+ // XXX(farre): Can/Should we check aParent->IsLoading() here? (Bug
+ // 1608448) Check if the parent was itself loading already
+ auto readystate = aParent->GetDocument()->GetReadyStateEnum();
+ fields.Get<IDX_AncestorLoading>() =
+ parentBC->GetAncestorLoading() ||
+ readystate == Document::ReadyState::READYSTATE_LOADING ||
+ readystate == Document::ReadyState::READYSTATE_INTERACTIVE;
+ }
+
+ fields.Get<IDX_BrowserId>() =
+ parentBC ? parentBC->GetBrowserId() : nsContentUtils::GenerateBrowserId();
+
+ fields.Get<IDX_OpenerPolicy>() = nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
+ if (aOpener && aOpener->SameOriginWithTop()) {
+ // We inherit the opener policy if there is a creator and if the creator's
+ // origin is same origin with the creator's top-level origin.
+ // If it is cross origin we should not inherit the CrossOriginOpenerPolicy
+ fields.Get<IDX_OpenerPolicy>() = aOpener->Top()->GetOpenerPolicy();
+
+ // If we inherit a policy which is potentially cross-origin isolated, we
+ // must be in a potentially cross-origin isolated BCG.
+ bool isPotentiallyCrossOriginIsolated =
+ fields.Get<IDX_OpenerPolicy>() ==
+ nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP;
+ MOZ_RELEASE_ASSERT(isPotentiallyCrossOriginIsolated ==
+ group->IsPotentiallyCrossOriginIsolated());
+ } else if (aOpener) {
+ // They are not same origin
+ auto topPolicy = aOpener->Top()->GetOpenerPolicy();
+ MOZ_RELEASE_ASSERT(topPolicy == nsILoadInfo::OPENER_POLICY_UNSAFE_NONE ||
+ topPolicy ==
+ nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_ALLOW_POPUPS);
+ } else if (!aParent && group->IsPotentiallyCrossOriginIsolated()) {
+ // If we're creating a brand-new toplevel BC in a potentially cross-origin
+ // isolated group, it should start out with a strict opener policy.
+ fields.Get<IDX_OpenerPolicy>() =
+ nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP;
+ }
+
+ fields.Get<IDX_HistoryID>() = nsID::GenerateUUID();
+ fields.Get<IDX_ExplicitActive>() = [&] {
+ if (parentBC) {
+ // Non-root browsing-contexts inherit their status from its parent.
+ return ExplicitActiveStatus::None;
+ }
+ if (aType == Type::Content) {
+ // Content gets managed by the chrome front-end / embedder element and
+ // starts as inactive.
+ return ExplicitActiveStatus::Inactive;
+ }
+ // Chrome starts as active.
+ return ExplicitActiveStatus::Active;
+ }();
+
+ fields.Get<IDX_FullZoom>() = parentBC ? parentBC->FullZoom() : 1.0f;
+ fields.Get<IDX_TextZoom>() = parentBC ? parentBC->TextZoom() : 1.0f;
+
+ bool allowContentRetargeting =
+ inherit ? inherit->GetAllowContentRetargetingOnChildren() : true;
+ fields.Get<IDX_AllowContentRetargeting>() = allowContentRetargeting;
+ fields.Get<IDX_AllowContentRetargetingOnChildren>() = allowContentRetargeting;
+
+ // Assume top allows fullscreen for its children unless otherwise stated.
+ // Subframes start with it false unless otherwise noted in SetEmbedderElement.
+ fields.Get<IDX_FullscreenAllowedByOwner>() = !aParent;
+
+ fields.Get<IDX_AllowPlugins>() = inherit ? inherit->GetAllowPlugins() : true;
+
+ fields.Get<IDX_DefaultLoadFlags>() =
+ inherit ? inherit->GetDefaultLoadFlags() : nsIRequest::LOAD_NORMAL;
+
+ fields.Get<IDX_OrientationLock>() = mozilla::hal::ScreenOrientation::None;
+
+ fields.Get<IDX_UseGlobalHistory>() =
+ inherit ? inherit->GetUseGlobalHistory() : false;
+
+ fields.Get<IDX_UseErrorPages>() = true;
+
+ fields.Get<IDX_TouchEventsOverrideInternal>() = TouchEventsOverride::None;
+
+ fields.Get<IDX_AllowJavascript>() =
+ inherit ? inherit->GetAllowJavascript() : true;
+
+ fields.Get<IDX_IsPopupRequested>() = aIsPopupRequested;
+
+ if (!parentBC) {
+ fields.Get<IDX_ShouldDelayMediaFromStart>() =
+ StaticPrefs::media_block_autoplay_until_in_foreground();
+ }
+
+ RefPtr<BrowsingContext> context;
+ if (XRE_IsParentProcess()) {
+ context = new CanonicalBrowsingContext(parentWC, group, id,
+ /* aOwnerProcessId */ 0,
+ /* aEmbedderProcessId */ 0, aType,
+ std::move(fields));
+ } else {
+ context =
+ new BrowsingContext(parentWC, group, id, aType, std::move(fields));
+ }
+
+ context->mEmbeddedByThisProcess = XRE_IsParentProcess() || aParent;
+ context->mCreatedDynamically = aCreatedDynamically;
+ if (inherit) {
+ context->mPrivateBrowsingId = inherit->mPrivateBrowsingId;
+ context->mUseRemoteTabs = inherit->mUseRemoteTabs;
+ context->mUseRemoteSubframes = inherit->mUseRemoteSubframes;
+ context->mOriginAttributes = inherit->mOriginAttributes;
+ }
+
+ nsCOMPtr<nsIRequestContextService> rcsvc =
+ net::RequestContextService::GetOrCreate();
+ if (rcsvc) {
+ nsCOMPtr<nsIRequestContext> requestContext;
+ nsresult rv = rcsvc->NewRequestContext(getter_AddRefs(requestContext));
+ if (NS_SUCCEEDED(rv) && requestContext) {
+ context->mRequestContextId = requestContext->GetID();
+ }
+ }
+
+ return context.forget();
+}
+
+already_AddRefed<BrowsingContext> BrowsingContext::CreateIndependent(
+ Type aType) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess(),
+ "BCs created in the content process must be related to "
+ "some BrowserChild");
+ RefPtr<BrowsingContext> bc(
+ CreateDetached(nullptr, nullptr, nullptr, u""_ns, aType, false));
+ bc->mWindowless = bc->IsContent();
+ bc->mEmbeddedByThisProcess = true;
+ bc->EnsureAttached();
+ return bc.forget();
+}
+
+void BrowsingContext::EnsureAttached() {
+ if (!mEverAttached) {
+ Register(this);
+
+ // Attach the browsing context to the tree.
+ Attach(/* aFromIPC */ false, /* aOriginProcess */ nullptr);
+ }
+}
+
+/* static */
+mozilla::ipc::IPCResult BrowsingContext::CreateFromIPC(
+ BrowsingContext::IPCInitializer&& aInit, BrowsingContextGroup* aGroup,
+ ContentParent* aOriginProcess) {
+ MOZ_DIAGNOSTIC_ASSERT(aOriginProcess || XRE_IsContentProcess());
+ MOZ_DIAGNOSTIC_ASSERT(aGroup);
+
+ uint64_t originId = 0;
+ if (aOriginProcess) {
+ originId = aOriginProcess->ChildID();
+ aGroup->EnsureHostProcess(aOriginProcess);
+ }
+
+ MOZ_LOG(GetLog(), LogLevel::Debug,
+ ("Creating 0x%08" PRIx64 " from IPC (origin=0x%08" PRIx64 ")",
+ aInit.mId, originId));
+
+ RefPtr<WindowContext> parent = aInit.GetParent();
+
+ RefPtr<BrowsingContext> context;
+ if (XRE_IsParentProcess()) {
+ // If the new BrowsingContext has a parent, it is a sub-frame embedded in
+ // whatever process sent the message. If it doesn't, and is not windowless,
+ // it is a new window or tab, and will be embedded in the parent process.
+ uint64_t embedderProcessId = (aInit.mWindowless || parent) ? originId : 0;
+ context = new CanonicalBrowsingContext(parent, aGroup, aInit.mId, originId,
+ embedderProcessId, Type::Content,
+ std::move(aInit.mFields));
+ } else {
+ context = new BrowsingContext(parent, aGroup, aInit.mId, Type::Content,
+ std::move(aInit.mFields));
+ }
+
+ context->mWindowless = aInit.mWindowless;
+ context->mCreatedDynamically = aInit.mCreatedDynamically;
+ context->mChildOffset = aInit.mChildOffset;
+ if (context->GetHasSessionHistory()) {
+ context->CreateChildSHistory();
+ if (mozilla::SessionHistoryInParent()) {
+ context->GetChildSessionHistory()->SetIndexAndLength(
+ aInit.mSessionHistoryIndex, aInit.mSessionHistoryCount, nsID());
+ }
+ }
+
+ // NOTE: Call through the `Set` methods for these values to ensure that any
+ // relevant process-local state is also updated.
+ context->SetOriginAttributes(aInit.mOriginAttributes);
+ context->SetRemoteTabs(aInit.mUseRemoteTabs);
+ context->SetRemoteSubframes(aInit.mUseRemoteSubframes);
+ context->mRequestContextId = aInit.mRequestContextId;
+ // NOTE: Private browsing ID is set by `SetOriginAttributes`.
+
+ Register(context);
+
+ return context->Attach(/* aFromIPC */ true, aOriginProcess);
+}
+
+BrowsingContext::BrowsingContext(WindowContext* aParentWindow,
+ BrowsingContextGroup* aGroup,
+ uint64_t aBrowsingContextId, Type aType,
+ FieldValues&& aInit)
+ : mFields(std::move(aInit)),
+ mType(aType),
+ mBrowsingContextId(aBrowsingContextId),
+ mGroup(aGroup),
+ mParentWindow(aParentWindow),
+ mPrivateBrowsingId(0),
+ mEverAttached(false),
+ mIsInProcess(false),
+ mIsDiscarded(false),
+ mWindowless(false),
+ mDanglingRemoteOuterProxies(false),
+ mEmbeddedByThisProcess(false),
+ mUseRemoteTabs(false),
+ mUseRemoteSubframes(false),
+ mCreatedDynamically(false),
+ mIsInBFCache(false),
+ mCanExecuteScripts(true),
+ mChildOffset(0) {
+ MOZ_RELEASE_ASSERT(!mParentWindow || mParentWindow->Group() == mGroup);
+ MOZ_RELEASE_ASSERT(mBrowsingContextId != 0);
+ MOZ_RELEASE_ASSERT(mGroup);
+}
+
+void BrowsingContext::SetDocShell(nsIDocShell* aDocShell) {
+ // XXX(nika): We should communicate that we are now an active BrowsingContext
+ // process to the parent & do other validation here.
+ MOZ_DIAGNOSTIC_ASSERT(mEverAttached);
+ MOZ_RELEASE_ASSERT(aDocShell->GetBrowsingContext() == this);
+ mDocShell = aDocShell;
+ mDanglingRemoteOuterProxies = !mIsInProcess;
+ mIsInProcess = true;
+ if (mChildSessionHistory) {
+ mChildSessionHistory->SetIsInProcess(true);
+ }
+
+ RecomputeCanExecuteScripts();
+}
+
+// This class implements a callback that will return the remote window proxy for
+// mBrowsingContext in that compartment, if it has one. It also removes the
+// proxy from the map, because the object will be transplanted into another kind
+// of object.
+class MOZ_STACK_CLASS CompartmentRemoteProxyTransplantCallback
+ : public js::CompartmentTransplantCallback {
+ public:
+ explicit CompartmentRemoteProxyTransplantCallback(
+ BrowsingContext* aBrowsingContext)
+ : mBrowsingContext(aBrowsingContext) {}
+
+ virtual JSObject* getObjectToTransplant(
+ JS::Compartment* compartment) override {
+ auto* priv = xpc::CompartmentPrivate::Get(compartment);
+ if (!priv) {
+ return nullptr;
+ }
+
+ auto& map = priv->GetRemoteProxyMap();
+ auto result = map.lookup(mBrowsingContext);
+ if (!result) {
+ return nullptr;
+ }
+ JSObject* resultObject = result->value();
+ map.remove(result);
+
+ return resultObject;
+ }
+
+ private:
+ BrowsingContext* mBrowsingContext;
+};
+
+void BrowsingContext::CleanUpDanglingRemoteOuterWindowProxies(
+ JSContext* aCx, JS::MutableHandle<JSObject*> aOuter) {
+ if (!mDanglingRemoteOuterProxies) {
+ return;
+ }
+ mDanglingRemoteOuterProxies = false;
+
+ CompartmentRemoteProxyTransplantCallback cb(this);
+ js::RemapRemoteWindowProxies(aCx, &cb, aOuter);
+}
+
+bool BrowsingContext::IsActive() const {
+ const BrowsingContext* current = this;
+ do {
+ auto explicit_ = current->GetExplicitActive();
+ if (explicit_ != ExplicitActiveStatus::None) {
+ return explicit_ == ExplicitActiveStatus::Active;
+ }
+ if (mParentWindow && !mParentWindow->IsCurrent()) {
+ return false;
+ }
+ } while ((current = current->GetParent()));
+
+ return false;
+}
+
+bool BrowsingContext::GetIsActiveBrowserWindow() {
+ if (!XRE_IsParentProcess()) {
+ return Top()->GetIsActiveBrowserWindowInternal();
+ }
+
+ // chrome:// urls loaded in the parent won't receive
+ // their own activation so we defer to the top chrome
+ // Browsing Context when in the parent process.
+ RefPtr<CanonicalBrowsingContext> chromeTop =
+ Canonical()->TopCrossChromeBoundary();
+ return chromeTop->GetIsActiveBrowserWindowInternal();
+}
+
+void BrowsingContext::SetIsActiveBrowserWindow(bool aActive) {
+ Unused << SetIsActiveBrowserWindowInternal(aActive);
+}
+
+bool BrowsingContext::FullscreenAllowed() const {
+ for (auto* current = this; current; current = current->GetParent()) {
+ if (!current->GetFullscreenAllowedByOwner()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool OwnerAllowsFullscreen(const Element& aEmbedder) {
+ if (aEmbedder.IsXULElement()) {
+ return !aEmbedder.HasAttr(nsGkAtoms::disablefullscreen);
+ }
+ if (aEmbedder.IsHTMLElement(nsGkAtoms::iframe)) {
+ // This is controlled by feature policy.
+ return true;
+ }
+ if (const auto* embed = HTMLEmbedElement::FromNode(aEmbedder)) {
+ return embed->AllowFullscreen();
+ }
+ return false;
+}
+
+void BrowsingContext::SetEmbedderElement(Element* aEmbedder) {
+ mEmbeddedByThisProcess = true;
+
+ // Update embedder-element-specific fields in a shared transaction.
+ // Don't do this when clearing our embedder, as we're being destroyed either
+ // way.
+ if (aEmbedder) {
+ Transaction txn;
+ txn.SetEmbedderElementType(Some(aEmbedder->LocalName()));
+ txn.SetEmbeddedInContentDocument(
+ aEmbedder->OwnerDoc()->IsContentDocument());
+ if (nsCOMPtr<nsPIDOMWindowInner> inner =
+ do_QueryInterface(aEmbedder->GetOwnerGlobal())) {
+ txn.SetEmbedderInnerWindowId(inner->WindowID());
+ }
+ txn.SetFullscreenAllowedByOwner(OwnerAllowsFullscreen(*aEmbedder));
+ if (XRE_IsParentProcess() && IsTopContent()) {
+ nsAutoString messageManagerGroup;
+ if (aEmbedder->IsXULElement()) {
+ aEmbedder->GetAttr(nsGkAtoms::messagemanagergroup, messageManagerGroup);
+ if (!aEmbedder->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::initiallyactive,
+ nsGkAtoms::_false, eIgnoreCase)) {
+ txn.SetExplicitActive(ExplicitActiveStatus::Active);
+ }
+ }
+ txn.SetMessageManagerGroup(messageManagerGroup);
+
+ bool useGlobalHistory =
+ !aEmbedder->HasAttr(nsGkAtoms::disableglobalhistory);
+ txn.SetUseGlobalHistory(useGlobalHistory);
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(txn.Commit(this));
+ }
+
+ if (XRE_IsParentProcess() && IsTopContent()) {
+ Canonical()->MaybeSetPermanentKey(aEmbedder);
+ }
+
+ mEmbedderElement = aEmbedder;
+
+ if (mEmbedderElement) {
+ if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
+ obs->NotifyWhenScriptSafe(ToSupports(this),
+ "browsing-context-did-set-embedder", nullptr);
+ }
+
+ if (nsContentUtils::ShouldHideObjectOrEmbedImageDocument() &&
+ IsEmbedderTypeObjectOrEmbed()) {
+ Unused << SetSyntheticDocumentContainer(true);
+ }
+ }
+}
+
+bool BrowsingContext::IsEmbedderTypeObjectOrEmbed() {
+ if (const Maybe<nsString>& type = GetEmbedderElementType()) {
+ return nsGkAtoms::object->Equals(*type) || nsGkAtoms::embed->Equals(*type);
+ }
+ return false;
+}
+
+void BrowsingContext::Embed() {
+ if (auto* frame = HTMLIFrameElement::FromNode(mEmbedderElement)) {
+ frame->BindToBrowsingContext(this);
+ }
+}
+
+mozilla::ipc::IPCResult BrowsingContext::Attach(bool aFromIPC,
+ ContentParent* aOriginProcess) {
+ MOZ_DIAGNOSTIC_ASSERT(!mEverAttached);
+ MOZ_DIAGNOSTIC_ASSERT_IF(aFromIPC, aOriginProcess || XRE_IsContentProcess());
+ mEverAttached = true;
+
+ if (MOZ_LOG_TEST(GetLog(), LogLevel::Debug)) {
+ nsAutoCString suffix;
+ mOriginAttributes.CreateSuffix(suffix);
+ MOZ_LOG(GetLog(), LogLevel::Debug,
+ ("%s: Connecting 0x%08" PRIx64 " to 0x%08" PRIx64
+ " (private=%d, remote=%d, fission=%d, oa=%s)",
+ XRE_IsParentProcess() ? "Parent" : "Child", Id(),
+ GetParent() ? GetParent()->Id() : 0, (int)mPrivateBrowsingId,
+ (int)mUseRemoteTabs, (int)mUseRemoteSubframes, suffix.get()));
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(mGroup);
+ MOZ_DIAGNOSTIC_ASSERT(!mIsDiscarded);
+
+ if (mGroup->IsPotentiallyCrossOriginIsolated() !=
+ (Top()->GetOpenerPolicy() ==
+ nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP)) {
+ MOZ_DIAGNOSTIC_ASSERT(aFromIPC);
+ if (aFromIPC) {
+ auto* actor = aOriginProcess
+ ? static_cast<mozilla::ipc::IProtocol*>(aOriginProcess)
+ : static_cast<mozilla::ipc::IProtocol*>(
+ ContentChild::GetSingleton());
+ return IPC_FAIL(
+ actor,
+ "Invalid CrossOriginIsolated state in BrowsingContext::Attach call");
+ } else {
+ MOZ_CRASH(
+ "Invalid CrossOriginIsolated state in BrowsingContext::Attach call");
+ }
+ }
+
+ AssertCoherentLoadContext();
+
+ // Add ourselves either to our parent or BrowsingContextGroup's child list.
+ // Important: We shouldn't return IPC_FAIL after this point, since the
+ // BrowsingContext will have already been added to the tree.
+ if (mParentWindow) {
+ if (!aFromIPC) {
+ MOZ_DIAGNOSTIC_ASSERT(!mParentWindow->IsDiscarded(),
+ "local attach in discarded window");
+ MOZ_DIAGNOSTIC_ASSERT(!GetParent()->IsDiscarded(),
+ "local attach call in discarded bc");
+ MOZ_DIAGNOSTIC_ASSERT(mParentWindow->GetWindowGlobalChild(),
+ "local attach call with oop parent window");
+ MOZ_DIAGNOSTIC_ASSERT(mParentWindow->GetWindowGlobalChild()->CanSend(),
+ "local attach call with dead parent window");
+ }
+ mChildOffset =
+ mCreatedDynamically ? -1 : mParentWindow->Children().Length();
+ mParentWindow->AppendChildBrowsingContext(this);
+ RecomputeCanExecuteScripts();
+ } else {
+ mGroup->Toplevels().AppendElement(this);
+ }
+
+ if (GetIsPopupSpam()) {
+ PopupBlocker::RegisterOpenPopupSpam();
+ }
+
+ if (IsTop() && GetHasSessionHistory() && !mChildSessionHistory) {
+ CreateChildSHistory();
+ }
+
+ // Why the context is being attached. This will always be "attach" in the
+ // content process, but may be "replace" if it's known the context being
+ // replaced in the parent process.
+ const char16_t* why = u"attach";
+
+ if (XRE_IsContentProcess() && !aFromIPC) {
+ // Send attach to our parent if we need to.
+ ContentChild::GetSingleton()->SendCreateBrowsingContext(
+ mGroup->Id(), GetIPCInitializer());
+ } else if (XRE_IsParentProcess()) {
+ // If this window was created as a subframe by a content process, it must be
+ // being hosted within the same BrowserParent as its mParentWindow.
+ // Toplevel BrowsingContexts created by content have their BrowserParent
+ // configured during `RecvConstructPopupBrowser`.
+ if (mParentWindow && aOriginProcess) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ mParentWindow->Canonical()->GetContentParent() == aOriginProcess,
+ "Creator process isn't the same as our embedder?");
+ Canonical()->SetCurrentBrowserParent(
+ mParentWindow->Canonical()->GetBrowserParent());
+ }
+
+ mGroup->EachOtherParent(aOriginProcess, [&](ContentParent* aParent) {
+ MOZ_DIAGNOSTIC_ASSERT(IsContent(),
+ "chrome BCG cannot be synced to content process");
+ if (!Canonical()->IsEmbeddedInProcess(aParent->ChildID())) {
+ Unused << aParent->SendCreateBrowsingContext(mGroup->Id(),
+ GetIPCInitializer());
+ }
+ });
+
+ if (IsTop() && IsContent() && Canonical()->GetWebProgress()) {
+ why = u"replace";
+ }
+
+ // We want to create a BrowsingContextWebProgress for all content
+ // BrowsingContexts.
+ if (IsContent() && !Canonical()->mWebProgress) {
+ Canonical()->mWebProgress = new BrowsingContextWebProgress(Canonical());
+ }
+ }
+
+ if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
+ obs->NotifyWhenScriptSafe(ToSupports(this), "browsing-context-attached",
+ why);
+ }
+
+ if (XRE_IsParentProcess()) {
+ Canonical()->CanonicalAttach();
+ }
+ return IPC_OK();
+}
+
+void BrowsingContext::Detach(bool aFromIPC) {
+ MOZ_LOG(GetLog(), LogLevel::Debug,
+ ("%s: Detaching 0x%08" PRIx64 " from 0x%08" PRIx64,
+ XRE_IsParentProcess() ? "Parent" : "Child", Id(),
+ GetParent() ? GetParent()->Id() : 0));
+
+ MOZ_DIAGNOSTIC_ASSERT(mEverAttached);
+ MOZ_DIAGNOSTIC_ASSERT(!mIsDiscarded);
+
+ if (XRE_IsParentProcess()) {
+ Canonical()->AddPendingDiscard();
+ }
+ auto callListeners =
+ MakeScopeExit([&, listeners = std::move(mDiscardListeners), id = Id()] {
+ for (const auto& listener : listeners) {
+ listener(id);
+ }
+ if (XRE_IsParentProcess()) {
+ Canonical()->RemovePendingDiscard();
+ }
+ });
+
+ nsCOMPtr<nsIRequestContextService> rcsvc =
+ net::RequestContextService::GetOrCreate();
+ if (rcsvc) {
+ rcsvc->RemoveRequestContext(GetRequestContextId());
+ }
+
+ // This will only ever be null if the cycle-collector has unlinked us. Don't
+ // try to detach ourselves in that case.
+ if (NS_WARN_IF(!mGroup)) {
+ MOZ_ASSERT_UNREACHABLE();
+ return;
+ }
+
+ if (mParentWindow) {
+ mParentWindow->RemoveChildBrowsingContext(this);
+ } else {
+ mGroup->Toplevels().RemoveElement(this);
+ }
+
+ if (XRE_IsParentProcess()) {
+ RefPtr<CanonicalBrowsingContext> self{Canonical()};
+ Group()->EachParent([&](ContentParent* aParent) {
+ // Only the embedder process is allowed to initiate a BrowsingContext
+ // detach, so if we've gotten here, the host process already knows we've
+ // been detached, and there's no need to tell it again.
+ //
+ // If the owner process is not the same as the embedder process, its
+ // BrowsingContext will be detached when its nsWebBrowser instance is
+ // destroyed.
+ bool doDiscard = !Canonical()->IsEmbeddedInProcess(aParent->ChildID()) &&
+ !Canonical()->IsOwnedByProcess(aParent->ChildID());
+
+ // Hold a strong reference to ourself, and keep our BrowsingContextGroup
+ // alive, until the responses comes back to ensure we don't die while
+ // messages relating to this context are in-flight.
+ //
+ // When the callback is called, the keepalive on our group will be
+ // destroyed, and the reference to the BrowsingContext will be dropped,
+ // which may cause it to be fully destroyed.
+ mGroup->AddKeepAlive();
+ self->AddPendingDiscard();
+ auto callback = [self](auto) {
+ self->mGroup->RemoveKeepAlive();
+ self->RemovePendingDiscard();
+ };
+
+ aParent->SendDiscardBrowsingContext(this, doDiscard, callback, callback);
+ });
+ } else {
+ // Hold a strong reference to ourself until the responses come back to
+ // ensure the BrowsingContext isn't cleaned up before the parent process
+ // acknowledges the discard request.
+ auto callback = [self = RefPtr{this}](auto) {};
+ ContentChild::GetSingleton()->SendDiscardBrowsingContext(
+ this, !aFromIPC, callback, callback);
+ }
+
+ mGroup->Unregister(this);
+ UnregisterBrowserId(this);
+ mIsDiscarded = true;
+
+ if (XRE_IsParentProcess()) {
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ fm->BrowsingContextDetached(this);
+ }
+ }
+
+ if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
+ // Why the context is being discarded. This will always be "discard" in the
+ // content process, but may be "replace" if it's known the context being
+ // replaced in the parent process.
+ const char16_t* why = u"discard";
+ if (XRE_IsParentProcess() && IsTop() && !Canonical()->GetWebProgress()) {
+ why = u"replace";
+ }
+ obs->NotifyObservers(ToSupports(this), "browsing-context-discarded", why);
+ }
+
+ // NOTE: Doesn't use SetClosed, as it will be set in all processes
+ // automatically by calls to Detach()
+ mFields.SetWithoutSyncing<IDX_Closed>(true);
+
+ if (GetIsPopupSpam()) {
+ PopupBlocker::UnregisterOpenPopupSpam();
+ // NOTE: Doesn't use SetIsPopupSpam, as it will be set all processes
+ // automatically.
+ mFields.SetWithoutSyncing<IDX_IsPopupSpam>(false);
+ }
+
+ AssertOriginAttributesMatchPrivateBrowsing();
+
+ if (XRE_IsParentProcess()) {
+ Canonical()->CanonicalDiscard();
+ }
+}
+
+void BrowsingContext::AddDiscardListener(
+ std::function<void(uint64_t)>&& aListener) {
+ if (mIsDiscarded) {
+ aListener(Id());
+ return;
+ }
+ mDiscardListeners.AppendElement(std::move(aListener));
+}
+
+void BrowsingContext::PrepareForProcessChange() {
+ MOZ_LOG(GetLog(), LogLevel::Debug,
+ ("%s: Preparing 0x%08" PRIx64 " for a process change",
+ XRE_IsParentProcess() ? "Parent" : "Child", Id()));
+
+ MOZ_ASSERT(mIsInProcess, "Must currently be an in-process frame");
+ MOZ_ASSERT(!mIsDiscarded, "We're already closed?");
+
+ mIsInProcess = false;
+ mUserGestureStart = TimeStamp();
+
+ // NOTE: For now, clear our nsDocShell reference, as we're primarily in a
+ // different process now. This may need to change in the future with
+ // Cross-Process BFCache.
+ mDocShell = nullptr;
+ if (mChildSessionHistory) {
+ // This can be removed once session history is stored exclusively in the
+ // parent process.
+ mChildSessionHistory->SetIsInProcess(false);
+ }
+
+ if (!mWindowProxy) {
+ return;
+ }
+
+ // We have to go through mWindowProxy rather than calling GetDOMWindow() on
+ // mDocShell because the mDocshell reference gets cleared immediately after
+ // the window is closed.
+ nsGlobalWindowOuter::PrepareForProcessChange(mWindowProxy);
+ MOZ_ASSERT(!mWindowProxy);
+}
+
+bool BrowsingContext::IsTargetable() const {
+ return !GetClosed() && AncestorsAreCurrent();
+}
+
+bool BrowsingContext::HasOpener() const {
+ return sBrowsingContexts->Contains(GetOpenerId());
+}
+
+bool BrowsingContext::AncestorsAreCurrent() const {
+ const BrowsingContext* bc = this;
+ while (true) {
+ if (bc->IsDiscarded()) {
+ return false;
+ }
+
+ if (WindowContext* wc = bc->GetParentWindowContext()) {
+ if (!wc->IsCurrent() || wc->IsDiscarded()) {
+ return false;
+ }
+
+ bc = wc->GetBrowsingContext();
+ } else {
+ return true;
+ }
+ }
+}
+
+bool BrowsingContext::IsInBFCache() const {
+ if (mozilla::SessionHistoryInParent()) {
+ return mIsInBFCache;
+ }
+ return mParentWindow &&
+ mParentWindow->TopWindowContext()->GetWindowStateSaved();
+}
+
+Span<RefPtr<BrowsingContext>> BrowsingContext::Children() const {
+ if (WindowContext* current = mCurrentWindowContext) {
+ return current->Children();
+ }
+ return Span<RefPtr<BrowsingContext>>();
+}
+
+void BrowsingContext::GetChildren(
+ nsTArray<RefPtr<BrowsingContext>>& aChildren) {
+ aChildren.AppendElements(Children());
+}
+
+Span<RefPtr<BrowsingContext>> BrowsingContext::NonSyntheticChildren() const {
+ if (WindowContext* current = mCurrentWindowContext) {
+ return current->NonSyntheticChildren();
+ }
+ return Span<RefPtr<BrowsingContext>>();
+}
+
+void BrowsingContext::GetWindowContexts(
+ nsTArray<RefPtr<WindowContext>>& aWindows) {
+ aWindows.AppendElements(mWindowContexts);
+}
+
+void BrowsingContext::RegisterWindowContext(WindowContext* aWindow) {
+ MOZ_ASSERT(!mWindowContexts.Contains(aWindow),
+ "WindowContext already registered!");
+ MOZ_ASSERT(aWindow->GetBrowsingContext() == this);
+
+ mWindowContexts.AppendElement(aWindow);
+
+ // If the newly registered WindowContext is for our current inner window ID,
+ // re-run the `DidSet` handler to re-establish the relationship.
+ if (aWindow->InnerWindowId() == GetCurrentInnerWindowId()) {
+ DidSet(FieldIndex<IDX_CurrentInnerWindowId>());
+ MOZ_DIAGNOSTIC_ASSERT(mCurrentWindowContext == aWindow);
+ }
+}
+
+void BrowsingContext::UnregisterWindowContext(WindowContext* aWindow) {
+ MOZ_ASSERT(mWindowContexts.Contains(aWindow),
+ "WindowContext not registered!");
+ mWindowContexts.RemoveElement(aWindow);
+
+ // If our currently active window was unregistered, clear our reference to it.
+ if (aWindow == mCurrentWindowContext) {
+ // Re-read our `CurrentInnerWindowId` value and use it to set
+ // `mCurrentWindowContext`. As `aWindow` is now unregistered and discarded,
+ // we won't find it, and the value will be cleared back to `nullptr`.
+ DidSet(FieldIndex<IDX_CurrentInnerWindowId>());
+ MOZ_DIAGNOSTIC_ASSERT(mCurrentWindowContext == nullptr);
+ }
+}
+
+void BrowsingContext::PreOrderWalkVoid(
+ const std::function<void(BrowsingContext*)>& aCallback) {
+ aCallback(this);
+
+ AutoTArray<RefPtr<BrowsingContext>, 8> children;
+ children.AppendElements(Children());
+
+ for (auto& child : children) {
+ child->PreOrderWalkVoid(aCallback);
+ }
+}
+
+BrowsingContext::WalkFlag BrowsingContext::PreOrderWalkFlag(
+ const std::function<WalkFlag(BrowsingContext*)>& aCallback) {
+ switch (aCallback(this)) {
+ case WalkFlag::Skip:
+ return WalkFlag::Next;
+ case WalkFlag::Stop:
+ return WalkFlag::Stop;
+ case WalkFlag::Next:
+ default:
+ break;
+ }
+
+ AutoTArray<RefPtr<BrowsingContext>, 8> children;
+ children.AppendElements(Children());
+
+ for (auto& child : children) {
+ switch (child->PreOrderWalkFlag(aCallback)) {
+ case WalkFlag::Stop:
+ return WalkFlag::Stop;
+ default:
+ break;
+ }
+ }
+
+ return WalkFlag::Next;
+}
+
+void BrowsingContext::PostOrderWalk(
+ const std::function<void(BrowsingContext*)>& aCallback) {
+ AutoTArray<RefPtr<BrowsingContext>, 8> children;
+ children.AppendElements(Children());
+
+ for (auto& child : children) {
+ child->PostOrderWalk(aCallback);
+ }
+
+ aCallback(this);
+}
+
+void BrowsingContext::GetAllBrowsingContextsInSubtree(
+ nsTArray<RefPtr<BrowsingContext>>& aBrowsingContexts) {
+ PreOrderWalk([&](BrowsingContext* aContext) {
+ aBrowsingContexts.AppendElement(aContext);
+ });
+}
+
+BrowsingContext* BrowsingContext::FindChildWithName(
+ const nsAString& aName, WindowGlobalChild& aRequestingWindow) {
+ if (aName.IsEmpty()) {
+ // You can't find a browsing context with the empty name.
+ return nullptr;
+ }
+
+ for (BrowsingContext* child : NonSyntheticChildren()) {
+ if (child->NameEquals(aName) && aRequestingWindow.CanNavigate(child) &&
+ child->IsTargetable()) {
+ return child;
+ }
+ }
+
+ return nullptr;
+}
+
+BrowsingContext* BrowsingContext::FindWithSpecialName(
+ const nsAString& aName, WindowGlobalChild& aRequestingWindow) {
+ // TODO(farre): Neither BrowsingContext nor nsDocShell checks if the
+ // browsing context pointed to by a special name is active. Should
+ // it be? See Bug 1527913.
+ if (aName.LowerCaseEqualsLiteral("_self")) {
+ return this;
+ }
+
+ if (aName.LowerCaseEqualsLiteral("_parent")) {
+ if (BrowsingContext* parent = GetParent()) {
+ return aRequestingWindow.CanNavigate(parent) ? parent : nullptr;
+ }
+ return this;
+ }
+
+ if (aName.LowerCaseEqualsLiteral("_top")) {
+ BrowsingContext* top = Top();
+
+ return aRequestingWindow.CanNavigate(top) ? top : nullptr;
+ }
+
+ return nullptr;
+}
+
+BrowsingContext* BrowsingContext::FindWithNameInSubtree(
+ const nsAString& aName, WindowGlobalChild* aRequestingWindow) {
+ MOZ_DIAGNOSTIC_ASSERT(!aName.IsEmpty());
+
+ if (NameEquals(aName) &&
+ (!aRequestingWindow || aRequestingWindow->CanNavigate(this)) &&
+ IsTargetable()) {
+ return this;
+ }
+
+ for (BrowsingContext* child : NonSyntheticChildren()) {
+ if (BrowsingContext* found =
+ child->FindWithNameInSubtree(aName, aRequestingWindow)) {
+ return found;
+ }
+ }
+
+ return nullptr;
+}
+
+bool BrowsingContext::IsSandboxedFrom(BrowsingContext* aTarget) {
+ // If no target then not sandboxed.
+ if (!aTarget) {
+ return false;
+ }
+
+ // We cannot be sandboxed from ourselves.
+ if (aTarget == this) {
+ return false;
+ }
+
+ // Default the sandbox flags to our flags, so that if we can't retrieve the
+ // active document, we will still enforce our own.
+ uint32_t sandboxFlags = GetSandboxFlags();
+ if (mDocShell) {
+ if (RefPtr<Document> doc = mDocShell->GetExtantDocument()) {
+ sandboxFlags = doc->GetSandboxFlags();
+ }
+ }
+
+ // If no flags, we are not sandboxed at all.
+ if (!sandboxFlags) {
+ return false;
+ }
+
+ // If aTarget has an ancestor, it is not top level.
+ if (RefPtr<BrowsingContext> ancestorOfTarget = aTarget->GetParent()) {
+ do {
+ // We are not sandboxed if we are an ancestor of target.
+ if (ancestorOfTarget == this) {
+ return false;
+ }
+ ancestorOfTarget = ancestorOfTarget->GetParent();
+ } while (ancestorOfTarget);
+
+ // Otherwise, we are sandboxed from aTarget.
+ return true;
+ }
+
+ // aTarget is top level, are we the "one permitted sandboxed
+ // navigator", i.e. did we open aTarget?
+ if (aTarget->GetOnePermittedSandboxedNavigatorId() == Id()) {
+ return false;
+ }
+
+ // If SANDBOXED_TOPLEVEL_NAVIGATION flag is not on, we are not sandboxed
+ // from our top.
+ if (!(sandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION) && aTarget == Top()) {
+ return false;
+ }
+
+ // If SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION flag is not on, we are not
+ // sandboxed from our top if we have user interaction. We assume there is a
+ // valid transient user gesture interaction if this check happens in the
+ // target process given that we have checked in the triggering process
+ // already.
+ if (!(sandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION) &&
+ mCurrentWindowContext &&
+ (!mCurrentWindowContext->IsInProcess() ||
+ mCurrentWindowContext->HasValidTransientUserGestureActivation()) &&
+ aTarget == Top()) {
+ return false;
+ }
+
+ // Otherwise, we are sandboxed from aTarget.
+ return true;
+}
+
+RefPtr<SessionStorageManager> BrowsingContext::GetSessionStorageManager() {
+ RefPtr<SessionStorageManager>& manager = Top()->mSessionStorageManager;
+ if (!manager) {
+ manager = new SessionStorageManager(this);
+ }
+ return manager;
+}
+
+bool BrowsingContext::CrossOriginIsolated() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return StaticPrefs::
+ dom_postMessage_sharedArrayBuffer_withCOOP_COEP_AtStartup() &&
+ Top()->GetOpenerPolicy() ==
+ nsILoadInfo::
+ OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP &&
+ XRE_IsContentProcess() &&
+ StringBeginsWith(ContentChild::GetSingleton()->GetRemoteType(),
+ WITH_COOP_COEP_REMOTE_TYPE_PREFIX);
+}
+
+void BrowsingContext::SetTriggeringAndInheritPrincipals(
+ nsIPrincipal* aTriggeringPrincipal, nsIPrincipal* aPrincipalToInherit,
+ uint64_t aLoadIdentifier) {
+ mTriggeringPrincipal = Some(
+ PrincipalWithLoadIdentifierTuple(aTriggeringPrincipal, aLoadIdentifier));
+ if (aPrincipalToInherit) {
+ mPrincipalToInherit = Some(
+ PrincipalWithLoadIdentifierTuple(aPrincipalToInherit, aLoadIdentifier));
+ }
+}
+
+std::tuple<nsCOMPtr<nsIPrincipal>, nsCOMPtr<nsIPrincipal>>
+BrowsingContext::GetTriggeringAndInheritPrincipalsForCurrentLoad() {
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal =
+ GetSavedPrincipal(mTriggeringPrincipal);
+ nsCOMPtr<nsIPrincipal> principalToInherit =
+ GetSavedPrincipal(mPrincipalToInherit);
+ return std::make_tuple(triggeringPrincipal, principalToInherit);
+}
+
+nsIPrincipal* BrowsingContext::GetSavedPrincipal(
+ Maybe<PrincipalWithLoadIdentifierTuple> aPrincipalTuple) {
+ if (aPrincipalTuple) {
+ nsCOMPtr<nsIPrincipal> principal;
+ uint64_t loadIdentifier;
+ std::tie(principal, loadIdentifier) = *aPrincipalTuple;
+ // We want to return a principal only if the load identifier for it
+ // matches the current one for this BC.
+ if (auto current = GetCurrentLoadIdentifier();
+ current && *current == loadIdentifier) {
+ return principal;
+ }
+ }
+ return nullptr;
+}
+
+BrowsingContext::~BrowsingContext() {
+ MOZ_DIAGNOSTIC_ASSERT(!mParentWindow ||
+ !mParentWindow->mChildren.Contains(this));
+ MOZ_DIAGNOSTIC_ASSERT(!mGroup || !mGroup->Toplevels().Contains(this));
+
+ mDeprioritizedLoadRunner.clear();
+
+ if (sBrowsingContexts) {
+ sBrowsingContexts->Remove(Id());
+ }
+ UnregisterBrowserId(this);
+}
+
+/* static */
+void BrowsingContext::DiscardFromContentParent(ContentParent* aCP) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (sBrowsingContexts) {
+ AutoTArray<RefPtr<BrowsingContext>, 8> toDiscard;
+ for (const auto& data : sBrowsingContexts->Values()) {
+ auto* bc = data->Canonical();
+ if (!bc->IsDiscarded() && bc->IsEmbeddedInProcess(aCP->ChildID())) {
+ toDiscard.AppendElement(bc);
+ }
+ }
+
+ for (BrowsingContext* bc : toDiscard) {
+ bc->Detach(/* aFromIPC */ true);
+ }
+ }
+}
+
+nsISupports* BrowsingContext::GetParentObject() const {
+ return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
+}
+
+JSObject* BrowsingContext::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return BrowsingContext_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+bool BrowsingContext::WriteStructuredClone(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter,
+ StructuredCloneHolder* aHolder) {
+ MOZ_DIAGNOSTIC_ASSERT(mEverAttached);
+ return (JS_WriteUint32Pair(aWriter, SCTAG_DOM_BROWSING_CONTEXT, 0) &&
+ JS_WriteUint32Pair(aWriter, uint32_t(Id()), uint32_t(Id() >> 32)));
+}
+
+/* static */
+JSObject* BrowsingContext::ReadStructuredClone(JSContext* aCx,
+ JSStructuredCloneReader* aReader,
+ StructuredCloneHolder* aHolder) {
+ uint32_t idLow = 0;
+ uint32_t idHigh = 0;
+ if (!JS_ReadUint32Pair(aReader, &idLow, &idHigh)) {
+ return nullptr;
+ }
+ uint64_t id = uint64_t(idHigh) << 32 | idLow;
+
+ // Note: Do this check after reading our ID data. Returning null will abort
+ // the decode operation anyway, but we should at least be as safe as possible.
+ if (NS_WARN_IF(!NS_IsMainThread())) {
+ MOZ_DIAGNOSTIC_ASSERT(false,
+ "We shouldn't be trying to decode a BrowsingContext "
+ "on a background thread.");
+ return nullptr;
+ }
+
+ JS::Rooted<JS::Value> val(aCx, JS::NullValue());
+ // We'll get rooting hazard errors from the RefPtr destructor if it isn't
+ // destroyed before we try to return a raw JSObject*, so create it in its own
+ // scope.
+ if (RefPtr<BrowsingContext> context = Get(id)) {
+ if (!GetOrCreateDOMReflector(aCx, context, &val) || !val.isObject()) {
+ return nullptr;
+ }
+ }
+ return val.toObjectOrNull();
+}
+
+bool BrowsingContext::CanSetOriginAttributes() {
+ // A discarded BrowsingContext has already been destroyed, and cannot modify
+ // its OriginAttributes.
+ if (NS_WARN_IF(IsDiscarded())) {
+ return false;
+ }
+
+ // Before attaching is the safest time to set OriginAttributes, and the only
+ // allowed time for content BrowsingContexts.
+ if (!EverAttached()) {
+ return true;
+ }
+
+ // Attached content BrowsingContexts may have been synced to other processes.
+ if (NS_WARN_IF(IsContent())) {
+ MOZ_CRASH();
+ return false;
+ }
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+
+ // Cannot set OriginAttributes after we've created our child BrowsingContext.
+ if (NS_WARN_IF(!Children().IsEmpty())) {
+ return false;
+ }
+
+ // Only allow setting OriginAttributes if we have no associated document, or
+ // the document is still `about:blank`.
+ // TODO: Bug 1273058 - should have no document when setting origin attributes.
+ if (WindowGlobalParent* window = Canonical()->GetCurrentWindowGlobal()) {
+ if (nsIURI* uri = window->GetDocumentURI()) {
+ MOZ_ASSERT(NS_IsAboutBlank(uri));
+ return NS_IsAboutBlank(uri);
+ }
+ }
+ return true;
+}
+
+Nullable<WindowProxyHolder> BrowsingContext::GetAssociatedWindow() {
+ // nsILoadContext usually only returns same-process windows,
+ // so we intentionally return nullptr if this BC is out of
+ // process.
+ if (IsInProcess()) {
+ return WindowProxyHolder(this);
+ }
+ return nullptr;
+}
+
+Nullable<WindowProxyHolder> BrowsingContext::GetTopWindow() {
+ return Top()->GetAssociatedWindow();
+}
+
+Element* BrowsingContext::GetTopFrameElement() {
+ return Top()->GetEmbedderElement();
+}
+
+void BrowsingContext::SetUsePrivateBrowsing(bool aUsePrivateBrowsing,
+ ErrorResult& aError) {
+ nsresult rv = SetUsePrivateBrowsing(aUsePrivateBrowsing);
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ }
+}
+
+void BrowsingContext::SetUseTrackingProtectionWebIDL(
+ bool aUseTrackingProtection, ErrorResult& aRv) {
+ SetForceEnableTrackingProtection(aUseTrackingProtection, aRv);
+}
+
+void BrowsingContext::GetOriginAttributes(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aVal,
+ ErrorResult& aError) {
+ AssertOriginAttributesMatchPrivateBrowsing();
+
+ if (!ToJSValue(aCx, mOriginAttributes, aVal)) {
+ aError.NoteJSContextException(aCx);
+ }
+}
+
+NS_IMETHODIMP BrowsingContext::GetAssociatedWindow(
+ mozIDOMWindowProxy** aAssociatedWindow) {
+ nsCOMPtr<mozIDOMWindowProxy> win = GetDOMWindow();
+ win.forget(aAssociatedWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP BrowsingContext::GetTopWindow(mozIDOMWindowProxy** aTopWindow) {
+ return Top()->GetAssociatedWindow(aTopWindow);
+}
+
+NS_IMETHODIMP BrowsingContext::GetTopFrameElement(Element** aTopFrameElement) {
+ RefPtr<Element> topFrameElement = GetTopFrameElement();
+ topFrameElement.forget(aTopFrameElement);
+ return NS_OK;
+}
+
+NS_IMETHODIMP BrowsingContext::GetIsContent(bool* aIsContent) {
+ *aIsContent = IsContent();
+ return NS_OK;
+}
+
+NS_IMETHODIMP BrowsingContext::GetUsePrivateBrowsing(
+ bool* aUsePrivateBrowsing) {
+ *aUsePrivateBrowsing = mPrivateBrowsingId > 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP BrowsingContext::SetUsePrivateBrowsing(bool aUsePrivateBrowsing) {
+ if (!CanSetOriginAttributes()) {
+ bool changed = aUsePrivateBrowsing != (mPrivateBrowsingId > 0);
+ if (changed) {
+ NS_WARNING("SetUsePrivateBrowsing when !CanSetOriginAttributes()");
+ }
+ return changed ? NS_ERROR_FAILURE : NS_OK;
+ }
+
+ return SetPrivateBrowsing(aUsePrivateBrowsing);
+}
+
+NS_IMETHODIMP BrowsingContext::SetPrivateBrowsing(bool aPrivateBrowsing) {
+ if (!CanSetOriginAttributes()) {
+ NS_WARNING("Attempt to set PrivateBrowsing when !CanSetOriginAttributes");
+ return NS_ERROR_FAILURE;
+ }
+
+ bool changed = aPrivateBrowsing != (mPrivateBrowsingId > 0);
+ if (changed) {
+ mPrivateBrowsingId = aPrivateBrowsing ? 1 : 0;
+ if (IsContent()) {
+ mOriginAttributes.SyncAttributesWithPrivateBrowsing(aPrivateBrowsing);
+ }
+
+ if (XRE_IsParentProcess()) {
+ Canonical()->AdjustPrivateBrowsingCount(aPrivateBrowsing);
+ }
+ }
+ AssertOriginAttributesMatchPrivateBrowsing();
+
+ if (changed && mDocShell) {
+ nsDocShell::Cast(mDocShell)->NotifyPrivateBrowsingChanged();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP BrowsingContext::GetUseRemoteTabs(bool* aUseRemoteTabs) {
+ *aUseRemoteTabs = mUseRemoteTabs;
+ return NS_OK;
+}
+
+NS_IMETHODIMP BrowsingContext::SetRemoteTabs(bool aUseRemoteTabs) {
+ if (!CanSetOriginAttributes()) {
+ NS_WARNING("Attempt to set RemoteTabs when !CanSetOriginAttributes");
+ return NS_ERROR_FAILURE;
+ }
+
+ static bool annotated = false;
+ if (aUseRemoteTabs && !annotated) {
+ annotated = true;
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::DOMIPCEnabled,
+ true);
+ }
+
+ // Don't allow non-remote tabs with remote subframes.
+ if (NS_WARN_IF(!aUseRemoteTabs && mUseRemoteSubframes)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mUseRemoteTabs = aUseRemoteTabs;
+ return NS_OK;
+}
+
+NS_IMETHODIMP BrowsingContext::GetUseRemoteSubframes(
+ bool* aUseRemoteSubframes) {
+ *aUseRemoteSubframes = mUseRemoteSubframes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP BrowsingContext::SetRemoteSubframes(bool aUseRemoteSubframes) {
+ if (!CanSetOriginAttributes()) {
+ NS_WARNING("Attempt to set RemoteSubframes when !CanSetOriginAttributes");
+ return NS_ERROR_FAILURE;
+ }
+
+ static bool annotated = false;
+ if (aUseRemoteSubframes && !annotated) {
+ annotated = true;
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::DOMFissionEnabled, true);
+ }
+
+ // Don't allow non-remote tabs with remote subframes.
+ if (NS_WARN_IF(aUseRemoteSubframes && !mUseRemoteTabs)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mUseRemoteSubframes = aUseRemoteSubframes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP BrowsingContext::GetUseTrackingProtection(
+ bool* aUseTrackingProtection) {
+ *aUseTrackingProtection = false;
+
+ if (GetForceEnableTrackingProtection() ||
+ StaticPrefs::privacy_trackingprotection_enabled() ||
+ (UsePrivateBrowsing() &&
+ StaticPrefs::privacy_trackingprotection_pbmode_enabled())) {
+ *aUseTrackingProtection = true;
+ return NS_OK;
+ }
+
+ if (GetParent()) {
+ return GetParent()->GetUseTrackingProtection(aUseTrackingProtection);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP BrowsingContext::SetUseTrackingProtection(
+ bool aUseTrackingProtection) {
+ return SetForceEnableTrackingProtection(aUseTrackingProtection);
+}
+
+NS_IMETHODIMP BrowsingContext::GetScriptableOriginAttributes(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aVal) {
+ AssertOriginAttributesMatchPrivateBrowsing();
+
+ bool ok = ToJSValue(aCx, mOriginAttributes, aVal);
+ NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+BrowsingContext::GetOriginAttributes(OriginAttributes& aAttrs) {
+ aAttrs = mOriginAttributes;
+ AssertOriginAttributesMatchPrivateBrowsing();
+}
+
+nsresult BrowsingContext::SetOriginAttributes(const OriginAttributes& aAttrs) {
+ if (!CanSetOriginAttributes()) {
+ NS_WARNING("Attempt to set OriginAttributes when !CanSetOriginAttributes");
+ return NS_ERROR_FAILURE;
+ }
+
+ AssertOriginAttributesMatchPrivateBrowsing();
+ mOriginAttributes = aAttrs;
+
+ bool isPrivate = mOriginAttributes.mPrivateBrowsingId !=
+ nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID;
+ // Chrome Browsing Context can not contain OriginAttributes.mPrivateBrowsingId
+ if (IsChrome() && isPrivate) {
+ mOriginAttributes.mPrivateBrowsingId =
+ nsIScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID;
+ }
+ SetPrivateBrowsing(isPrivate);
+ AssertOriginAttributesMatchPrivateBrowsing();
+
+ return NS_OK;
+}
+
+void BrowsingContext::AssertCoherentLoadContext() {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ // LoadContext should generally match our opener or parent.
+ if (IsContent()) {
+ if (RefPtr<BrowsingContext> opener = GetOpener()) {
+ MOZ_DIAGNOSTIC_ASSERT(opener->mType == mType);
+ MOZ_DIAGNOSTIC_ASSERT(opener->mGroup == mGroup);
+ MOZ_DIAGNOSTIC_ASSERT(opener->mUseRemoteTabs == mUseRemoteTabs);
+ MOZ_DIAGNOSTIC_ASSERT(opener->mUseRemoteSubframes == mUseRemoteSubframes);
+ MOZ_DIAGNOSTIC_ASSERT(opener->mPrivateBrowsingId == mPrivateBrowsingId);
+ MOZ_DIAGNOSTIC_ASSERT(
+ opener->mOriginAttributes.EqualsIgnoringFPD(mOriginAttributes));
+ }
+ }
+ if (RefPtr<BrowsingContext> parent = GetParent()) {
+ MOZ_DIAGNOSTIC_ASSERT(parent->mType == mType);
+ MOZ_DIAGNOSTIC_ASSERT(parent->mGroup == mGroup);
+ MOZ_DIAGNOSTIC_ASSERT(parent->mUseRemoteTabs == mUseRemoteTabs);
+ MOZ_DIAGNOSTIC_ASSERT(parent->mUseRemoteSubframes == mUseRemoteSubframes);
+ MOZ_DIAGNOSTIC_ASSERT(parent->mPrivateBrowsingId == mPrivateBrowsingId);
+ MOZ_DIAGNOSTIC_ASSERT(
+ parent->mOriginAttributes.EqualsIgnoringFPD(mOriginAttributes));
+ }
+
+ // UseRemoteSubframes and UseRemoteTabs must match.
+ MOZ_DIAGNOSTIC_ASSERT(
+ !mUseRemoteSubframes || mUseRemoteTabs,
+ "Cannot set useRemoteSubframes without also setting useRemoteTabs");
+
+ // Double-check OriginAttributes/Private Browsing
+ AssertOriginAttributesMatchPrivateBrowsing();
+#endif
+}
+
+void BrowsingContext::AssertOriginAttributesMatchPrivateBrowsing() {
+ // Chrome browsing contexts must not have a private browsing OriginAttribute
+ // Content browsing contexts must maintain the equality:
+ // mOriginAttributes.mPrivateBrowsingId == mPrivateBrowsingId
+ if (IsChrome()) {
+ MOZ_DIAGNOSTIC_ASSERT(mOriginAttributes.mPrivateBrowsingId == 0);
+ } else {
+ MOZ_DIAGNOSTIC_ASSERT(mOriginAttributes.mPrivateBrowsingId ==
+ mPrivateBrowsingId);
+ }
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrowsingContext)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsILoadContext)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(BrowsingContext)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(BrowsingContext)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(BrowsingContext)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(BrowsingContext)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(BrowsingContext)
+ if (sBrowsingContexts) {
+ sBrowsingContexts->Remove(tmp->Id());
+ }
+ UnregisterBrowserId(tmp);
+
+ if (tmp->GetIsPopupSpam()) {
+ PopupBlocker::UnregisterOpenPopupSpam();
+ // NOTE: Doesn't use SetIsPopupSpam, as it will be set all processes
+ // automatically.
+ tmp->mFields.SetWithoutSyncing<IDX_IsPopupSpam>(false);
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(
+ mDocShell, mParentWindow, mGroup, mEmbedderElement, mWindowContexts,
+ mCurrentWindowContext, mSessionStorageManager, mChildSessionHistory)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(BrowsingContext)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
+ mDocShell, mParentWindow, mGroup, mEmbedderElement, mWindowContexts,
+ mCurrentWindowContext, mSessionStorageManager, mChildSessionHistory)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+static bool IsCertainlyAliveForCC(BrowsingContext* aContext) {
+ return aContext->HasKnownLiveWrapper() ||
+ (AppShutdown::GetCurrentShutdownPhase() ==
+ ShutdownPhase::NotInShutdown &&
+ aContext->EverAttached() && !aContext->IsDiscarded());
+}
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(BrowsingContext)
+ if (IsCertainlyAliveForCC(tmp)) {
+ if (tmp->PreservingWrapper()) {
+ tmp->MarkWrapperLive();
+ }
+ return true;
+ }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(BrowsingContext)
+ return IsCertainlyAliveForCC(tmp) && tmp->HasNothingToTrace(tmp);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(BrowsingContext)
+ return IsCertainlyAliveForCC(tmp);
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
+class RemoteLocationProxy
+ : public RemoteObjectProxy<BrowsingContext::LocationProxy,
+ Location_Binding::sCrossOriginProperties> {
+ public:
+ typedef RemoteObjectProxy Base;
+
+ constexpr RemoteLocationProxy()
+ : RemoteObjectProxy(prototypes::id::Location) {}
+
+ void NoteChildren(JSObject* aProxy,
+ nsCycleCollectionTraversalCallback& aCb) const override {
+ auto location =
+ static_cast<BrowsingContext::LocationProxy*>(GetNative(aProxy));
+ CycleCollectionNoteChild(aCb, location->GetBrowsingContext(),
+ "JS::GetPrivate(obj)->GetBrowsingContext()");
+ }
+};
+
+static const RemoteLocationProxy sSingleton;
+
+// Give RemoteLocationProxy 2 reserved slots, like the other wrappers,
+// so JSObject::swap can swap it with CrossCompartmentWrappers without requiring
+// malloc.
+template <>
+const JSClass RemoteLocationProxy::Base::sClass =
+ PROXY_CLASS_DEF("Proxy", JSCLASS_HAS_RESERVED_SLOTS(2));
+
+void BrowsingContext::Location(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aLocation,
+ ErrorResult& aError) {
+ aError.MightThrowJSException();
+ sSingleton.GetProxyObject(aCx, &mLocation, /* aTransplantTo = */ nullptr,
+ aLocation);
+ if (!aLocation) {
+ aError.StealExceptionFromJSContext(aCx);
+ }
+}
+
+bool BrowsingContext::RemoveRootFromBFCacheSync() {
+ if (WindowContext* wc = GetParentWindowContext()) {
+ if (RefPtr<Document> doc = wc->TopWindowContext()->GetDocument()) {
+ return doc->RemoveFromBFCacheSync();
+ }
+ }
+ return false;
+}
+
+nsresult BrowsingContext::CheckSandboxFlags(nsDocShellLoadState* aLoadState) {
+ const auto& sourceBC = aLoadState->SourceBrowsingContext();
+ if (sourceBC.IsNull()) {
+ return NS_OK;
+ }
+
+ // We might be called after the source BC has been discarded, but before we've
+ // destroyed our in-process instance of the BrowsingContext object in some
+ // situations (e.g. after creating a new pop-up with window.open while the
+ // window is being closed). In these situations we want to still perform the
+ // sandboxing check against our in-process copy. If we've forgotten about the
+ // context already, assume it is sanboxed. (bug 1643450)
+ BrowsingContext* bc = sourceBC.GetMaybeDiscarded();
+ if (!bc || bc->IsSandboxedFrom(this)) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ return NS_OK;
+}
+
+nsresult BrowsingContext::LoadURI(nsDocShellLoadState* aLoadState,
+ bool aSetNavigating) {
+ // Per spec, most load attempts are silently ignored when a BrowsingContext is
+ // null (which in our code corresponds to discarded), so we simply fail
+ // silently in those cases. Regardless, we cannot trigger loads in/from
+ // discarded BrowsingContexts via IPC, so we need to abort in any case.
+ if (IsDiscarded()) {
+ return NS_OK;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(aLoadState->TargetBrowsingContext().IsNull(),
+ "Targeting occurs in InternalLoad");
+
+ if (mDocShell) {
+ nsCOMPtr<nsIDocShell> docShell = mDocShell;
+ return docShell->LoadURI(aLoadState, aSetNavigating);
+ }
+
+ // Note: We do this check both here and in `nsDocShell::InternalLoad`, since
+ // document-specific sandbox flags are only available in the process
+ // triggering the load, and we don't want the target process to have to trust
+ // the triggering process to do the appropriate checks for the
+ // BrowsingContext's sandbox flags.
+ MOZ_TRY(CheckSandboxFlags(aLoadState));
+ SetTriggeringAndInheritPrincipals(aLoadState->TriggeringPrincipal(),
+ aLoadState->PrincipalToInherit(),
+ aLoadState->GetLoadIdentifier());
+
+ const auto& sourceBC = aLoadState->SourceBrowsingContext();
+
+ if (net::SchemeIsJavascript(aLoadState->URI())) {
+ if (!XRE_IsParentProcess()) {
+ // Web content should only be able to load javascript: URIs into documents
+ // whose principals the caller principal subsumes, which by definition
+ // excludes any document in a cross-process BrowsingContext.
+ return NS_ERROR_DOM_BAD_CROSS_ORIGIN_URI;
+ }
+ MOZ_DIAGNOSTIC_ASSERT(!sourceBC,
+ "Should never see a cross-process javascript: load "
+ "triggered from content");
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(!sourceBC || sourceBC->Group() == Group());
+ if (sourceBC && sourceBC->IsInProcess()) {
+ nsCOMPtr<nsPIDOMWindowOuter> win(sourceBC->GetDOMWindow());
+ if (WindowGlobalChild* wgc =
+ win->GetCurrentInnerWindow()->GetWindowGlobalChild()) {
+ if (!wgc->CanNavigate(this)) {
+ return NS_ERROR_DOM_PROP_ACCESS_DENIED;
+ }
+ wgc->SendLoadURI(this, mozilla::WrapNotNull(aLoadState), aSetNavigating);
+ }
+ } else if (XRE_IsParentProcess()) {
+ if (Canonical()->LoadInParent(aLoadState, aSetNavigating)) {
+ return NS_OK;
+ }
+
+ if (ContentParent* cp = Canonical()->GetContentParent()) {
+ // Attempt to initiate this load immediately in the parent, if it succeeds
+ // it'll return a unique identifier so that we can find it later.
+ uint64_t loadIdentifier = 0;
+ if (Canonical()->AttemptSpeculativeLoadInParent(aLoadState)) {
+ MOZ_DIAGNOSTIC_ASSERT(GetCurrentLoadIdentifier().isSome());
+ loadIdentifier = GetCurrentLoadIdentifier().value();
+ aLoadState->SetChannelInitialized(true);
+ }
+
+ cp->TransmitBlobDataIfBlobURL(aLoadState->URI());
+
+ // Setup a confirmation callback once the content process receives this
+ // load. Normally we'd expect a PDocumentChannel actor to have been
+ // created to claim the load identifier by that time. If not, then it
+ // won't be coming, so make sure we clean up and deregister.
+ cp->SendLoadURI(this, mozilla::WrapNotNull(aLoadState), aSetNavigating)
+ ->Then(GetMainThreadSerialEventTarget(), __func__,
+ [loadIdentifier](
+ const PContentParent::LoadURIPromise::ResolveOrRejectValue&
+ aValue) {
+ if (loadIdentifier) {
+ net::DocumentLoadListener::CleanupParentLoadAttempt(
+ loadIdentifier);
+ }
+ });
+ }
+ } else {
+ MOZ_DIAGNOSTIC_ASSERT(sourceBC);
+ if (!sourceBC) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ // If we're in a content process and the source BC is no longer in-process,
+ // just fail silently.
+ }
+ return NS_OK;
+}
+
+nsresult BrowsingContext::InternalLoad(nsDocShellLoadState* aLoadState) {
+ if (IsDiscarded()) {
+ return NS_OK;
+ }
+ SetTriggeringAndInheritPrincipals(aLoadState->TriggeringPrincipal(),
+ aLoadState->PrincipalToInherit(),
+ aLoadState->GetLoadIdentifier());
+
+ MOZ_DIAGNOSTIC_ASSERT(aLoadState->Target().IsEmpty(),
+ "should already have retargeted");
+ MOZ_DIAGNOSTIC_ASSERT(!aLoadState->TargetBrowsingContext().IsNull(),
+ "should have target bc set");
+ MOZ_DIAGNOSTIC_ASSERT(aLoadState->TargetBrowsingContext() == this,
+ "must be targeting this BrowsingContext");
+
+ if (mDocShell) {
+ RefPtr<nsDocShell> docShell = nsDocShell::Cast(mDocShell);
+ return docShell->InternalLoad(aLoadState);
+ }
+
+ // Note: We do this check both here and in `nsDocShell::InternalLoad`, since
+ // document-specific sandbox flags are only available in the process
+ // triggering the load, and we don't want the target process to have to trust
+ // the triggering process to do the appropriate checks for the
+ // BrowsingContext's sandbox flags.
+ MOZ_TRY(CheckSandboxFlags(aLoadState));
+
+ const auto& sourceBC = aLoadState->SourceBrowsingContext();
+
+ if (net::SchemeIsJavascript(aLoadState->URI())) {
+ if (!XRE_IsParentProcess()) {
+ // Web content should only be able to load javascript: URIs into documents
+ // whose principals the caller principal subsumes, which by definition
+ // excludes any document in a cross-process BrowsingContext.
+ return NS_ERROR_DOM_BAD_CROSS_ORIGIN_URI;
+ }
+ MOZ_DIAGNOSTIC_ASSERT(!sourceBC,
+ "Should never see a cross-process javascript: load "
+ "triggered from content");
+ }
+
+ if (XRE_IsParentProcess()) {
+ ContentParent* cp = Canonical()->GetContentParent();
+ if (!cp || !cp->CanSend()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(
+ SetCurrentLoadIdentifier(Some(aLoadState->GetLoadIdentifier())));
+ Unused << cp->SendInternalLoad(mozilla::WrapNotNull(aLoadState));
+ } else {
+ MOZ_DIAGNOSTIC_ASSERT(sourceBC);
+ MOZ_DIAGNOSTIC_ASSERT(sourceBC->Group() == Group());
+
+ nsCOMPtr<nsPIDOMWindowOuter> win(sourceBC->GetDOMWindow());
+ WindowGlobalChild* wgc =
+ win->GetCurrentInnerWindow()->GetWindowGlobalChild();
+ if (!wgc || !wgc->CanSend()) {
+ return NS_ERROR_FAILURE;
+ }
+ if (!wgc->CanNavigate(this)) {
+ return NS_ERROR_DOM_PROP_ACCESS_DENIED;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(
+ SetCurrentLoadIdentifier(Some(aLoadState->GetLoadIdentifier())));
+ wgc->SendInternalLoad(mozilla::WrapNotNull(aLoadState));
+ }
+
+ return NS_OK;
+}
+
+void BrowsingContext::DisplayLoadError(const nsAString& aURI) {
+ MOZ_LOG(GetLog(), LogLevel::Debug, ("DisplayLoadError"));
+ MOZ_DIAGNOSTIC_ASSERT(!IsDiscarded());
+ MOZ_DIAGNOSTIC_ASSERT(mDocShell || XRE_IsParentProcess());
+
+ if (mDocShell) {
+ bool didDisplayLoadError = false;
+ nsCOMPtr<nsIDocShell> docShell = mDocShell;
+ docShell->DisplayLoadError(NS_ERROR_MALFORMED_URI, nullptr,
+ PromiseFlatString(aURI).get(), nullptr,
+ &didDisplayLoadError);
+ } else {
+ if (ContentParent* cp = Canonical()->GetContentParent()) {
+ Unused << cp->SendDisplayLoadError(this, PromiseFlatString(aURI));
+ }
+ }
+}
+
+WindowProxyHolder BrowsingContext::Window() {
+ return WindowProxyHolder(Self());
+}
+
+WindowProxyHolder BrowsingContext::GetFrames(ErrorResult& aError) {
+ return Window();
+}
+
+void BrowsingContext::Close(CallerType aCallerType, ErrorResult& aError) {
+ if (mIsDiscarded) {
+ return;
+ }
+
+ if (IsSubframe()) {
+ // .close() on frames is a no-op.
+ return;
+ }
+
+ if (GetDOMWindow()) {
+ nsGlobalWindowOuter::Cast(GetDOMWindow())
+ ->CloseOuter(aCallerType == CallerType::System);
+ return;
+ }
+
+ // This is a bit of a hack for webcompat. Content needs to see an updated
+ // |window.closed| value as early as possible, so we set this before we
+ // actually send the DOMWindowClose event, which happens in the process where
+ // the document for this browsing context is loaded.
+ MOZ_ALWAYS_SUCCEEDS(SetClosed(true));
+
+ if (ContentChild* cc = ContentChild::GetSingleton()) {
+ cc->SendWindowClose(this, aCallerType == CallerType::System);
+ } else if (ContentParent* cp = Canonical()->GetContentParent()) {
+ Unused << cp->SendWindowClose(this, aCallerType == CallerType::System);
+ }
+}
+
+/*
+ * Examine the current document state to see if we're in a way that is
+ * typically abused by web designers. The window.open code uses this
+ * routine to determine whether to allow the new window.
+ * Returns a value from the PopupControlState enum.
+ */
+PopupBlocker::PopupControlState BrowsingContext::RevisePopupAbuseLevel(
+ PopupBlocker::PopupControlState aControl) {
+ if (!IsContent()) {
+ return PopupBlocker::openAllowed;
+ }
+
+ RefPtr<Document> doc = GetExtantDocument();
+ PopupBlocker::PopupControlState abuse = aControl;
+ switch (abuse) {
+ case PopupBlocker::openControlled:
+ case PopupBlocker::openBlocked:
+ case PopupBlocker::openOverridden:
+ if (IsPopupAllowed()) {
+ abuse = PopupBlocker::PopupControlState(abuse - 1);
+ }
+ break;
+ case PopupBlocker::openAbused:
+ if (IsPopupAllowed() ||
+ (doc && doc->HasValidTransientUserGestureActivation())) {
+ // Skip PopupBlocker::openBlocked
+ abuse = PopupBlocker::openControlled;
+ }
+ break;
+ case PopupBlocker::openAllowed:
+ break;
+ default:
+ NS_WARNING("Strange PopupControlState!");
+ }
+
+ // limit the number of simultaneously open popups
+ if (abuse == PopupBlocker::openAbused || abuse == PopupBlocker::openBlocked ||
+ abuse == PopupBlocker::openControlled) {
+ int32_t popupMax = StaticPrefs::dom_popup_maximum();
+ if (popupMax >= 0 &&
+ PopupBlocker::GetOpenPopupSpamCount() >= (uint32_t)popupMax) {
+ abuse = PopupBlocker::openOverridden;
+ }
+ }
+
+ // If we're currently in-process, attempt to consume transient user gesture
+ // activations.
+ if (doc) {
+ // HACK: Some pages using bogus library + UA sniffing call window.open()
+ // from a blank iframe, only on Firefox, see bug 1685056.
+ //
+ // This is a hack-around to preserve behavior in that particular and
+ // specific case, by consuming activation on the parent document, so we
+ // don't care about the InProcessParent bits not being fission-safe or what
+ // not.
+ auto ConsumeTransientUserActivationForMultiplePopupBlocking =
+ [&]() -> bool {
+ if (doc->ConsumeTransientUserGestureActivation()) {
+ return true;
+ }
+ if (!doc->IsInitialDocument()) {
+ return false;
+ }
+ Document* parentDoc = doc->GetInProcessParentDocument();
+ if (!parentDoc ||
+ !parentDoc->NodePrincipal()->Equals(doc->NodePrincipal())) {
+ return false;
+ }
+ return parentDoc->ConsumeTransientUserGestureActivation();
+ };
+
+ // If this popup is allowed, let's block any other for this event, forcing
+ // PopupBlocker::openBlocked state.
+ if ((abuse == PopupBlocker::openAllowed ||
+ abuse == PopupBlocker::openControlled) &&
+ StaticPrefs::dom_block_multiple_popups() && !IsPopupAllowed() &&
+ !ConsumeTransientUserActivationForMultiplePopupBlocking()) {
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
+ doc, nsContentUtils::eDOM_PROPERTIES,
+ "MultiplePopupsBlockedNoUserActivation");
+ abuse = PopupBlocker::openBlocked;
+ }
+ }
+
+ return abuse;
+}
+
+void BrowsingContext::IncrementHistoryEntryCountForBrowsingContext() {
+ Unused << SetHistoryEntryCount(GetHistoryEntryCount() + 1);
+}
+
+std::tuple<bool, bool> BrowsingContext::CanFocusCheck(CallerType aCallerType) {
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (!fm) {
+ return {false, false};
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> caller = do_QueryInterface(GetEntryGlobal());
+ BrowsingContext* callerBC = caller ? caller->GetBrowsingContext() : nullptr;
+ RefPtr<BrowsingContext> openerBC = GetOpener();
+ MOZ_DIAGNOSTIC_ASSERT(!openerBC || openerBC->Group() == Group());
+
+ // Enforce dom.disable_window_flip (for non-chrome), but still allow the
+ // window which opened us to raise us at times when popups are allowed
+ // (bugs 355482 and 369306).
+ bool canFocus = aCallerType == CallerType::System ||
+ !Preferences::GetBool("dom.disable_window_flip", true);
+ if (!canFocus && openerBC == callerBC) {
+ canFocus =
+ (callerBC ? callerBC : this)
+ ->RevisePopupAbuseLevel(PopupBlocker::GetPopupControlState()) <
+ PopupBlocker::openBlocked;
+ }
+
+ bool isActive = false;
+ if (XRE_IsParentProcess()) {
+ RefPtr<CanonicalBrowsingContext> chromeTop =
+ Canonical()->TopCrossChromeBoundary();
+ nsCOMPtr<nsPIDOMWindowOuter> activeWindow = fm->GetActiveWindow();
+ isActive = (activeWindow == chromeTop->GetDOMWindow());
+ } else {
+ isActive = (fm->GetActiveBrowsingContext() == Top());
+ }
+
+ return {canFocus, isActive};
+}
+
+void BrowsingContext::Focus(CallerType aCallerType, ErrorResult& aError) {
+ // These checks need to happen before the RequestFrameFocus call, which
+ // is why they are done in an untrusted process. If we wanted to enforce
+ // these in the parent, we'd need to do the checks there _also_.
+ // These should be kept in sync with nsGlobalWindowOuter::FocusOuter.
+
+ auto [canFocus, isActive] = CanFocusCheck(aCallerType);
+
+ if (!(canFocus || isActive)) {
+ return;
+ }
+
+ // Permission check passed
+
+ if (mEmbedderElement) {
+ // Make the activeElement in this process update synchronously.
+ nsContentUtils::RequestFrameFocus(*mEmbedderElement, true, aCallerType);
+ }
+ uint64_t actionId = nsFocusManager::GenerateFocusActionId();
+ if (ContentChild* cc = ContentChild::GetSingleton()) {
+ cc->SendWindowFocus(this, aCallerType, actionId);
+ } else if (ContentParent* cp = Canonical()->GetContentParent()) {
+ Unused << cp->SendWindowFocus(this, aCallerType, actionId);
+ }
+}
+
+bool BrowsingContext::CanBlurCheck(CallerType aCallerType) {
+ // If dom.disable_window_flip == true, then content should not be allowed
+ // to do blur (this would allow popunders, bug 369306)
+ return aCallerType == CallerType::System ||
+ !Preferences::GetBool("dom.disable_window_flip", true);
+}
+
+void BrowsingContext::Blur(CallerType aCallerType, ErrorResult& aError) {
+ if (!CanBlurCheck(aCallerType)) {
+ return;
+ }
+
+ if (ContentChild* cc = ContentChild::GetSingleton()) {
+ cc->SendWindowBlur(this, aCallerType);
+ } else if (ContentParent* cp = Canonical()->GetContentParent()) {
+ Unused << cp->SendWindowBlur(this, aCallerType);
+ }
+}
+
+Nullable<WindowProxyHolder> BrowsingContext::GetWindow() {
+ if (XRE_IsParentProcess() && !IsInProcess()) {
+ return nullptr;
+ }
+ return WindowProxyHolder(this);
+}
+
+Nullable<WindowProxyHolder> BrowsingContext::GetTop(ErrorResult& aError) {
+ if (mIsDiscarded) {
+ return nullptr;
+ }
+
+ // We never return null or throw an error, but the implementation in
+ // nsGlobalWindow does and we need to use the same signature.
+ return WindowProxyHolder(Top());
+}
+
+void BrowsingContext::GetOpener(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aOpener,
+ ErrorResult& aError) const {
+ RefPtr<BrowsingContext> opener = GetOpener();
+ if (!opener) {
+ aOpener.setNull();
+ return;
+ }
+
+ if (!ToJSValue(aCx, WindowProxyHolder(opener), aOpener)) {
+ aError.NoteJSContextException(aCx);
+ }
+}
+
+// We never throw an error, but the implementation in nsGlobalWindow does and
+// we need to use the same signature.
+Nullable<WindowProxyHolder> BrowsingContext::GetParent(ErrorResult& aError) {
+ if (mIsDiscarded) {
+ return nullptr;
+ }
+
+ if (GetParent()) {
+ return WindowProxyHolder(GetParent());
+ }
+ return WindowProxyHolder(this);
+}
+
+void BrowsingContext::PostMessageMoz(JSContext* aCx,
+ JS::Handle<JS::Value> aMessage,
+ const nsAString& aTargetOrigin,
+ const Sequence<JSObject*>& aTransfer,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError) {
+ if (mIsDiscarded) {
+ return;
+ }
+
+ RefPtr<BrowsingContext> sourceBc;
+ PostMessageData data;
+ data.targetOrigin() = aTargetOrigin;
+ data.subjectPrincipal() = &aSubjectPrincipal;
+ RefPtr<nsGlobalWindowInner> callerInnerWindow;
+ nsAutoCString scriptLocation;
+ // We don't need to get the caller's agentClusterId since that is used for
+ // checking whether it's okay to sharing memory (and it's not allowed to share
+ // memory cross processes)
+ if (!nsGlobalWindowOuter::GatherPostMessageData(
+ aCx, aTargetOrigin, getter_AddRefs(sourceBc), data.origin(),
+ getter_AddRefs(data.targetOriginURI()),
+ getter_AddRefs(data.callerPrincipal()),
+ getter_AddRefs(callerInnerWindow), getter_AddRefs(data.callerURI()),
+ /* aCallerAgentClusterId */ nullptr, &scriptLocation, aError)) {
+ return;
+ }
+ if (sourceBc && sourceBc->IsDiscarded()) {
+ return;
+ }
+ data.source() = sourceBc;
+ data.isFromPrivateWindow() =
+ callerInnerWindow &&
+ nsScriptErrorBase::ComputeIsFromPrivateWindow(callerInnerWindow);
+ data.innerWindowId() = callerInnerWindow ? callerInnerWindow->WindowID() : 0;
+ data.scriptLocation() = scriptLocation;
+ JS::Rooted<JS::Value> transferArray(aCx);
+ aError = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransfer,
+ &transferArray);
+ if (NS_WARN_IF(aError.Failed())) {
+ return;
+ }
+
+ JS::CloneDataPolicy clonePolicy;
+ if (callerInnerWindow && callerInnerWindow->IsSharedMemoryAllowed()) {
+ clonePolicy.allowSharedMemoryObjects();
+ }
+
+ // We will see if the message is required to be in the same process or it can
+ // be in the different process after Write().
+ ipc::StructuredCloneData message = ipc::StructuredCloneData(
+ StructuredCloneHolder::StructuredCloneScope::UnknownDestination,
+ StructuredCloneHolder::TransferringSupported);
+ message.Write(aCx, aMessage, transferArray, clonePolicy, aError);
+ if (NS_WARN_IF(aError.Failed())) {
+ return;
+ }
+
+ ClonedOrErrorMessageData messageData;
+ if (ContentChild* cc = ContentChild::GetSingleton()) {
+ // The clone scope gets set when we write the message data based on the
+ // requirements of that data that we're writing.
+ // If the message data contains a shared memory object, then CloneScope
+ // would return SameProcess. Otherwise, it returns DifferentProcess.
+ if (message.CloneScope() ==
+ StructuredCloneHolder::StructuredCloneScope::DifferentProcess) {
+ ClonedMessageData clonedMessageData;
+ if (!message.BuildClonedMessageData(clonedMessageData)) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ messageData = std::move(clonedMessageData);
+ } else {
+ MOZ_ASSERT(message.CloneScope() ==
+ StructuredCloneHolder::StructuredCloneScope::SameProcess);
+
+ messageData = ErrorMessageData();
+
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::warningFlag, "DOM Window"_ns,
+ callerInnerWindow ? callerInnerWindow->GetDocument() : nullptr,
+ nsContentUtils::eDOM_PROPERTIES,
+ "PostMessageSharedMemoryObjectToCrossOriginWarning");
+ }
+
+ cc->SendWindowPostMessage(this, messageData, data);
+ } else if (ContentParent* cp = Canonical()->GetContentParent()) {
+ if (message.CloneScope() ==
+ StructuredCloneHolder::StructuredCloneScope::DifferentProcess) {
+ ClonedMessageData clonedMessageData;
+ if (!message.BuildClonedMessageData(clonedMessageData)) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ messageData = std::move(clonedMessageData);
+ } else {
+ MOZ_ASSERT(message.CloneScope() ==
+ StructuredCloneHolder::StructuredCloneScope::SameProcess);
+
+ messageData = ErrorMessageData();
+
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::warningFlag, "DOM Window"_ns,
+ callerInnerWindow ? callerInnerWindow->GetDocument() : nullptr,
+ nsContentUtils::eDOM_PROPERTIES,
+ "PostMessageSharedMemoryObjectToCrossOriginWarning");
+ }
+
+ Unused << cp->SendWindowPostMessage(this, messageData, data);
+ }
+}
+
+void BrowsingContext::PostMessageMoz(JSContext* aCx,
+ JS::Handle<JS::Value> aMessage,
+ const WindowPostMessageOptions& aOptions,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError) {
+ PostMessageMoz(aCx, aMessage, aOptions.mTargetOrigin, aOptions.mTransfer,
+ aSubjectPrincipal, aError);
+}
+
+void BrowsingContext::SendCommitTransaction(ContentParent* aParent,
+ const BaseTransaction& aTxn,
+ uint64_t aEpoch) {
+ Unused << aParent->SendCommitBrowsingContextTransaction(this, aTxn, aEpoch);
+}
+
+void BrowsingContext::SendCommitTransaction(ContentChild* aChild,
+ const BaseTransaction& aTxn,
+ uint64_t aEpoch) {
+ aChild->SendCommitBrowsingContextTransaction(this, aTxn, aEpoch);
+}
+
+BrowsingContext::IPCInitializer BrowsingContext::GetIPCInitializer() {
+ MOZ_DIAGNOSTIC_ASSERT(mEverAttached);
+ MOZ_DIAGNOSTIC_ASSERT(mType == Type::Content);
+
+ IPCInitializer init;
+ init.mId = Id();
+ init.mParentId = mParentWindow ? mParentWindow->Id() : 0;
+ init.mWindowless = mWindowless;
+ init.mUseRemoteTabs = mUseRemoteTabs;
+ init.mUseRemoteSubframes = mUseRemoteSubframes;
+ init.mCreatedDynamically = mCreatedDynamically;
+ init.mChildOffset = mChildOffset;
+ init.mOriginAttributes = mOriginAttributes;
+ if (mChildSessionHistory && mozilla::SessionHistoryInParent()) {
+ init.mSessionHistoryIndex = mChildSessionHistory->Index();
+ init.mSessionHistoryCount = mChildSessionHistory->Count();
+ }
+ init.mRequestContextId = mRequestContextId;
+ init.mFields = mFields.RawValues();
+ return init;
+}
+
+already_AddRefed<WindowContext> BrowsingContext::IPCInitializer::GetParent() {
+ RefPtr<WindowContext> parent;
+ if (mParentId != 0) {
+ parent = WindowContext::GetById(mParentId);
+ MOZ_RELEASE_ASSERT(parent);
+ }
+ return parent.forget();
+}
+
+already_AddRefed<BrowsingContext> BrowsingContext::IPCInitializer::GetOpener() {
+ RefPtr<BrowsingContext> opener;
+ if (GetOpenerId() != 0) {
+ opener = BrowsingContext::Get(GetOpenerId());
+ MOZ_RELEASE_ASSERT(opener);
+ }
+ return opener.forget();
+}
+
+void BrowsingContext::StartDelayedAutoplayMediaComponents() {
+ if (!mDocShell) {
+ return;
+ }
+ AUTOPLAY_LOG("%s : StartDelayedAutoplayMediaComponents for bc 0x%08" PRIx64,
+ XRE_IsParentProcess() ? "Parent" : "Child", Id());
+ mDocShell->StartDelayedAutoplayMediaComponents();
+}
+
+nsresult BrowsingContext::ResetGVAutoplayRequestStatus() {
+ MOZ_ASSERT(IsTop(),
+ "Should only set GVAudibleAutoplayRequestStatus in the top-level "
+ "browsing context");
+
+ Transaction txn;
+ txn.SetGVAudibleAutoplayRequestStatus(GVAutoplayRequestStatus::eUNKNOWN);
+ txn.SetGVInaudibleAutoplayRequestStatus(GVAutoplayRequestStatus::eUNKNOWN);
+ return txn.Commit(this);
+}
+
+template <typename Callback>
+void BrowsingContext::WalkPresContexts(Callback&& aCallback) {
+ PreOrderWalk([&](BrowsingContext* aContext) {
+ if (nsIDocShell* shell = aContext->GetDocShell()) {
+ if (RefPtr pc = shell->GetPresContext()) {
+ aCallback(pc.get());
+ }
+ }
+ });
+}
+
+void BrowsingContext::PresContextAffectingFieldChanged() {
+ WalkPresContexts([&](nsPresContext* aPc) {
+ aPc->RecomputeBrowsingContextDependentData();
+ });
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_SessionStoreEpoch>,
+ uint32_t aOldValue) {
+ if (!mCurrentWindowContext) {
+ return;
+ }
+ SessionStoreChild* sessionStoreChild =
+ SessionStoreChild::From(mCurrentWindowContext->GetWindowGlobalChild());
+ if (!sessionStoreChild) {
+ return;
+ }
+
+ sessionStoreChild->SetEpoch(GetSessionStoreEpoch());
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_GVAudibleAutoplayRequestStatus>) {
+ MOZ_ASSERT(IsTop(),
+ "Should only set GVAudibleAutoplayRequestStatus in the top-level "
+ "browsing context");
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_GVInaudibleAutoplayRequestStatus>) {
+ MOZ_ASSERT(IsTop(),
+ "Should only set GVAudibleAutoplayRequestStatus in the top-level "
+ "browsing context");
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_ExplicitActive>,
+ ExplicitActiveStatus aOldValue) {
+ const bool isActive = IsActive();
+ const bool wasActive = [&] {
+ if (aOldValue != ExplicitActiveStatus::None) {
+ return aOldValue == ExplicitActiveStatus::Active;
+ }
+ return GetParent() && GetParent()->IsActive();
+ }();
+
+ if (isActive == wasActive) {
+ return;
+ }
+
+ if (IsTop()) {
+ Group()->UpdateToplevelsSuspendedIfNeeded();
+
+ if (XRE_IsParentProcess()) {
+ auto* bc = Canonical();
+ if (BrowserParent* bp = bc->GetBrowserParent()) {
+ bp->RecomputeProcessPriority();
+#if defined(XP_WIN) && defined(ACCESSIBILITY)
+ if (a11y::Compatibility::IsDolphin()) {
+ // update active accessible documents on windows
+ if (a11y::DocAccessibleParent* tabDoc =
+ bp->GetTopLevelDocAccessible()) {
+ HWND window = tabDoc->GetEmulatedWindowHandle();
+ MOZ_ASSERT(window);
+ if (window) {
+ if (isActive) {
+ a11y::nsWinUtils::ShowNativeWindow(window);
+ } else {
+ a11y::nsWinUtils::HideNativeWindow(window);
+ }
+ }
+ }
+ }
+#endif
+ }
+ }
+ }
+
+ PreOrderWalk([&](BrowsingContext* aContext) {
+ if (nsCOMPtr<nsIDocShell> ds = aContext->GetDocShell()) {
+ nsDocShell::Cast(ds)->ActivenessMaybeChanged();
+ }
+ });
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_InRDMPane>, bool aOldValue) {
+ MOZ_ASSERT(IsTop(),
+ "Should only set InRDMPane in the top-level browsing context");
+ if (GetInRDMPane() == aOldValue) {
+ return;
+ }
+ PresContextAffectingFieldChanged();
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_PageAwakeRequestCount>,
+ uint32_t aNewValue, ContentParent* aSource) {
+ return IsTop() && XRE_IsParentProcess() && !aSource;
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_PageAwakeRequestCount>,
+ uint32_t aOldValue) {
+ if (!IsTop() || aOldValue == GetPageAwakeRequestCount()) {
+ return;
+ }
+ Group()->UpdateToplevelsSuspendedIfNeeded();
+}
+
+auto BrowsingContext::CanSet(FieldIndex<IDX_AllowJavascript>, bool aValue,
+ ContentParent* aSource) -> CanSetResult {
+ if (mozilla::SessionHistoryInParent()) {
+ return XRE_IsParentProcess() && !aSource ? CanSetResult::Allow
+ : CanSetResult::Deny;
+ }
+
+ // Without Session History in Parent, session restore code still needs to set
+ // this from content processes.
+ return LegacyRevertIfNotOwningOrParentProcess(aSource);
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_AllowJavascript>, bool aOldValue) {
+ RecomputeCanExecuteScripts();
+}
+
+void BrowsingContext::RecomputeCanExecuteScripts() {
+ const bool old = mCanExecuteScripts;
+ if (!AllowJavascript()) {
+ // Scripting has been explicitly disabled on our BrowsingContext.
+ mCanExecuteScripts = false;
+ } else if (GetParentWindowContext()) {
+ // Otherwise, inherit parent.
+ mCanExecuteScripts = GetParentWindowContext()->CanExecuteScripts();
+ } else {
+ // Otherwise, we're the root of the tree, and we haven't explicitly disabled
+ // script. Allow.
+ mCanExecuteScripts = true;
+ }
+
+ if (old != mCanExecuteScripts) {
+ for (WindowContext* wc : GetWindowContexts()) {
+ wc->RecomputeCanExecuteScripts();
+ }
+ }
+}
+
+bool BrowsingContext::InactiveForSuspend() const {
+ if (!StaticPrefs::dom_suspend_inactive_enabled()) {
+ return false;
+ }
+ // We should suspend a page only when it's inactive and doesn't have any awake
+ // request that is used to prevent page from being suspended because web page
+ // might still need to run their script. Eg. waiting for media keys to resume
+ // media, playing web audio, waiting in a video call conference room.
+ return !IsActive() && GetPageAwakeRequestCount() == 0;
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_TouchEventsOverrideInternal>,
+ dom::TouchEventsOverride, ContentParent* aSource) {
+ return XRE_IsParentProcess() && !aSource;
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_TouchEventsOverrideInternal>,
+ dom::TouchEventsOverride&& aOldValue) {
+ if (GetTouchEventsOverrideInternal() == aOldValue) {
+ return;
+ }
+ WalkPresContexts([&](nsPresContext* aPc) {
+ aPc->MediaFeatureValuesChanged(
+ {MediaFeatureChangeReason::SystemMetricsChange},
+ // We're already iterating through sub documents, so we don't need to
+ // propagate the change again.
+ MediaFeatureChangePropagation::JustThisDocument);
+ });
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_EmbedderColorSchemes>,
+ EmbedderColorSchemes&& aOldValue) {
+ if (GetEmbedderColorSchemes() == aOldValue) {
+ return;
+ }
+ PresContextAffectingFieldChanged();
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_PrefersColorSchemeOverride>,
+ dom::PrefersColorSchemeOverride aOldValue) {
+ MOZ_ASSERT(IsTop());
+ if (PrefersColorSchemeOverride() == aOldValue) {
+ return;
+ }
+ PresContextAffectingFieldChanged();
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_MediumOverride>,
+ nsString&& aOldValue) {
+ MOZ_ASSERT(IsTop());
+ if (GetMediumOverride() == aOldValue) {
+ return;
+ }
+ PresContextAffectingFieldChanged();
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_DisplayMode>,
+ enum DisplayMode aOldValue) {
+ MOZ_ASSERT(IsTop());
+
+ if (GetDisplayMode() == aOldValue) {
+ return;
+ }
+
+ WalkPresContexts([&](nsPresContext* aPc) {
+ aPc->MediaFeatureValuesChanged(
+ {MediaFeatureChangeReason::DisplayModeChange},
+ // We're already iterating through sub documents, so we don't need
+ // to propagate the change again.
+ //
+ // Images and other resources don't change their display-mode
+ // evaluation, display-mode is a property of the browsing context.
+ MediaFeatureChangePropagation::JustThisDocument);
+ });
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_Muted>) {
+ MOZ_ASSERT(IsTop(), "Set muted flag on non top-level context!");
+ USER_ACTIVATION_LOG("Set audio muted %d for %s browsing context 0x%08" PRIx64,
+ GetMuted(), XRE_IsParentProcess() ? "Parent" : "Child",
+ Id());
+ PreOrderWalk([&](BrowsingContext* aContext) {
+ nsPIDOMWindowOuter* win = aContext->GetDOMWindow();
+ if (win) {
+ win->RefreshMediaElementsVolume();
+ }
+ });
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_IsAppTab>, const bool& aValue,
+ ContentParent* aSource) {
+ return XRE_IsParentProcess() && !aSource && IsTop();
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_HasSiblings>, const bool& aValue,
+ ContentParent* aSource) {
+ return XRE_IsParentProcess() && !aSource && IsTop();
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_ShouldDelayMediaFromStart>,
+ const bool& aValue, ContentParent* aSource) {
+ return IsTop();
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_ShouldDelayMediaFromStart>,
+ bool aOldValue) {
+ MOZ_ASSERT(IsTop(), "Set attribute on non top-level context!");
+ if (aOldValue == GetShouldDelayMediaFromStart()) {
+ return;
+ }
+ if (!GetShouldDelayMediaFromStart()) {
+ PreOrderWalk([&](BrowsingContext* aContext) {
+ if (nsPIDOMWindowOuter* win = aContext->GetDOMWindow()) {
+ win->ActivateMediaComponents();
+ }
+ });
+ }
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_OverrideDPPX>, const float& aValue,
+ ContentParent* aSource) {
+ return XRE_IsParentProcess() && !aSource && IsTop();
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_OverrideDPPX>, float aOldValue) {
+ MOZ_ASSERT(IsTop());
+ if (GetOverrideDPPX() == aOldValue) {
+ return;
+ }
+ PresContextAffectingFieldChanged();
+}
+
+void BrowsingContext::SetCustomUserAgent(const nsAString& aUserAgent,
+ ErrorResult& aRv) {
+ Top()->SetUserAgentOverride(aUserAgent, aRv);
+}
+
+nsresult BrowsingContext::SetCustomUserAgent(const nsAString& aUserAgent) {
+ return Top()->SetUserAgentOverride(aUserAgent);
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_UserAgentOverride>) {
+ MOZ_ASSERT(IsTop());
+
+ PreOrderWalk([&](BrowsingContext* aContext) {
+ nsIDocShell* shell = aContext->GetDocShell();
+ if (shell) {
+ shell->ClearCachedUserAgent();
+ }
+ });
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_IsInBFCache>, bool,
+ ContentParent* aSource) {
+ return IsTop() && !aSource && mozilla::BFCacheInParent();
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_IsInBFCache>) {
+ MOZ_RELEASE_ASSERT(mozilla::BFCacheInParent());
+ MOZ_DIAGNOSTIC_ASSERT(IsTop());
+
+ const bool isInBFCache = GetIsInBFCache();
+ if (!isInBFCache) {
+ UpdateCurrentTopByBrowserId(this);
+ PreOrderWalk([&](BrowsingContext* aContext) {
+ aContext->mIsInBFCache = false;
+ nsCOMPtr<nsIDocShell> shell = aContext->GetDocShell();
+ if (shell) {
+ nsDocShell::Cast(shell)->ThawFreezeNonRecursive(true);
+ }
+ });
+ }
+
+ if (isInBFCache && XRE_IsContentProcess() && mDocShell) {
+ nsDocShell::Cast(mDocShell)->MaybeDisconnectChildListenersOnPageHide();
+ }
+
+ if (isInBFCache) {
+ PreOrderWalk([&](BrowsingContext* aContext) {
+ nsCOMPtr<nsIDocShell> shell = aContext->GetDocShell();
+ if (shell) {
+ nsDocShell::Cast(shell)->FirePageHideShowNonRecursive(false);
+ }
+ });
+ } else {
+ PostOrderWalk([&](BrowsingContext* aContext) {
+ nsCOMPtr<nsIDocShell> shell = aContext->GetDocShell();
+ if (shell) {
+ nsDocShell::Cast(shell)->FirePageHideShowNonRecursive(true);
+ }
+ });
+ }
+
+ if (isInBFCache) {
+ PreOrderWalk([&](BrowsingContext* aContext) {
+ nsCOMPtr<nsIDocShell> shell = aContext->GetDocShell();
+ if (shell) {
+ nsDocShell::Cast(shell)->ThawFreezeNonRecursive(false);
+ if (nsPresContext* pc = shell->GetPresContext()) {
+ pc->EventStateManager()->ResetHoverState();
+ }
+ }
+ aContext->mIsInBFCache = true;
+ Document* doc = aContext->GetDocument();
+ if (doc) {
+ // Notifying needs to happen after mIsInBFCache is set to true.
+ doc->NotifyActivityChanged();
+ }
+ });
+
+ if (XRE_IsParentProcess()) {
+ if (mCurrentWindowContext &&
+ mCurrentWindowContext->Canonical()->Fullscreen()) {
+ mCurrentWindowContext->Canonical()->ExitTopChromeDocumentFullscreen();
+ }
+ }
+ }
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_SyntheticDocumentContainer>) {
+ if (WindowContext* parentWindowContext = GetParentWindowContext()) {
+ parentWindowContext->UpdateChildSynthetic(this,
+ GetSyntheticDocumentContainer());
+ }
+}
+
+void BrowsingContext::SetCustomPlatform(const nsAString& aPlatform,
+ ErrorResult& aRv) {
+ Top()->SetPlatformOverride(aPlatform, aRv);
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_PlatformOverride>) {
+ MOZ_ASSERT(IsTop());
+
+ PreOrderWalk([&](BrowsingContext* aContext) {
+ nsIDocShell* shell = aContext->GetDocShell();
+ if (shell) {
+ shell->ClearCachedPlatform();
+ }
+ });
+}
+
+auto BrowsingContext::LegacyRevertIfNotOwningOrParentProcess(
+ ContentParent* aSource) -> CanSetResult {
+ if (aSource) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (!Canonical()->IsOwnedByProcess(aSource->ChildID())) {
+ return CanSetResult::Revert;
+ }
+ } else if (!IsInProcess() && !XRE_IsParentProcess()) {
+ // Don't allow this to be set from content processes that
+ // don't own the BrowsingContext.
+ return CanSetResult::Deny;
+ }
+
+ return CanSetResult::Allow;
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_IsActiveBrowserWindowInternal>,
+ const bool& aValue, ContentParent* aSource) {
+ // Should only be set in the parent process.
+ return XRE_IsParentProcess() && !aSource && IsTop();
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_IsActiveBrowserWindowInternal>,
+ bool aOldValue) {
+ bool isActivateEvent = GetIsActiveBrowserWindowInternal();
+ // The browser window containing this context has changed
+ // activation state so update window inactive document states
+ // for all in-process documents.
+ PreOrderWalk([isActivateEvent](BrowsingContext* aContext) {
+ if (RefPtr<Document> doc = aContext->GetExtantDocument()) {
+ doc->UpdateDocumentStates(DocumentState::WINDOW_INACTIVE, true);
+
+ RefPtr<nsPIDOMWindowInner> win = doc->GetInnerWindow();
+ if (win) {
+ RefPtr<MediaDevices> devices;
+ if (isActivateEvent && (devices = win->GetExtantMediaDevices())) {
+ devices->BrowserWindowBecameActive();
+ }
+
+ if (XRE_IsContentProcess() &&
+ (!aContext->GetParent() || !aContext->GetParent()->IsInProcess())) {
+ // Send the inner window an activate/deactivate event if
+ // the context is the top of a sub-tree of in-process
+ // contexts.
+ nsContentUtils::DispatchEventOnlyToChrome(
+ doc, win, isActivateEvent ? u"activate"_ns : u"deactivate"_ns,
+ CanBubble::eYes, Cancelable::eYes, nullptr);
+ }
+ }
+ }
+ });
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_OpenerPolicy>,
+ nsILoadInfo::CrossOriginOpenerPolicy aPolicy,
+ ContentParent* aSource) {
+ // A potentially cross-origin isolated BC can't change opener policy, nor can
+ // a BC become potentially cross-origin isolated. An unchanged policy is
+ // always OK.
+ return GetOpenerPolicy() == aPolicy ||
+ (GetOpenerPolicy() !=
+ nsILoadInfo::
+ OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP &&
+ aPolicy !=
+ nsILoadInfo::
+ OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP);
+}
+
+auto BrowsingContext::CanSet(FieldIndex<IDX_AllowContentRetargeting>,
+ const bool& aAllowContentRetargeting,
+ ContentParent* aSource) -> CanSetResult {
+ return LegacyRevertIfNotOwningOrParentProcess(aSource);
+}
+
+auto BrowsingContext::CanSet(FieldIndex<IDX_AllowContentRetargetingOnChildren>,
+ const bool& aAllowContentRetargetingOnChildren,
+ ContentParent* aSource) -> CanSetResult {
+ return LegacyRevertIfNotOwningOrParentProcess(aSource);
+}
+
+auto BrowsingContext::CanSet(FieldIndex<IDX_AllowPlugins>,
+ const bool& aAllowPlugins, ContentParent* aSource)
+ -> CanSetResult {
+ return LegacyRevertIfNotOwningOrParentProcess(aSource);
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_FullscreenAllowedByOwner>,
+ const bool& aAllowed, ContentParent* aSource) {
+ return CheckOnlyEmbedderCanSet(aSource);
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_UseErrorPages>,
+ const bool& aUseErrorPages,
+ ContentParent* aSource) {
+ return CheckOnlyEmbedderCanSet(aSource);
+}
+
+TouchEventsOverride BrowsingContext::TouchEventsOverride() const {
+ for (const auto* bc = this; bc; bc = bc->GetParent()) {
+ auto tev = bc->GetTouchEventsOverrideInternal();
+ if (tev != dom::TouchEventsOverride::None) {
+ return tev;
+ }
+ }
+ return dom::TouchEventsOverride::None;
+}
+
+bool BrowsingContext::TargetTopLevelLinkClicksToBlank() const {
+ return Top()->GetTargetTopLevelLinkClicksToBlankInternal();
+}
+
+// We map `watchedByDevTools` WebIDL attribute to `watchedByDevToolsInternal`
+// BC field. And we map it to the top level BrowsingContext.
+bool BrowsingContext::WatchedByDevTools() {
+ return Top()->GetWatchedByDevToolsInternal();
+}
+
+// Enforce that the watchedByDevTools BC field can only be set on the top level
+// Browsing Context.
+bool BrowsingContext::CanSet(FieldIndex<IDX_WatchedByDevToolsInternal>,
+ const bool& aWatchedByDevTools,
+ ContentParent* aSource) {
+ return IsTop();
+}
+void BrowsingContext::SetWatchedByDevTools(bool aWatchedByDevTools,
+ ErrorResult& aRv) {
+ if (!IsTop()) {
+ aRv.ThrowInvalidModificationError(
+ "watchedByDevTools can only be set on top BrowsingContext");
+ return;
+ }
+ SetWatchedByDevToolsInternal(aWatchedByDevTools, aRv);
+}
+
+auto BrowsingContext::CanSet(FieldIndex<IDX_DefaultLoadFlags>,
+ const uint32_t& aDefaultLoadFlags,
+ ContentParent* aSource) -> CanSetResult {
+ // Bug 1623565 - Are these flags only used by the debugger, which makes it
+ // possible that this field can only be settable by the parent process?
+ return LegacyRevertIfNotOwningOrParentProcess(aSource);
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_DefaultLoadFlags>) {
+ auto loadFlags = GetDefaultLoadFlags();
+ if (GetDocShell()) {
+ nsDocShell::Cast(GetDocShell())->SetLoadGroupDefaultLoadFlags(loadFlags);
+ }
+
+ if (XRE_IsParentProcess()) {
+ PreOrderWalk([&](BrowsingContext* aContext) {
+ if (aContext != this) {
+ // Setting load flags on a discarded context has no effect.
+ Unused << aContext->SetDefaultLoadFlags(loadFlags);
+ }
+ });
+ }
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_UseGlobalHistory>,
+ const bool& aUseGlobalHistory,
+ ContentParent* aSource) {
+ // Should only be set in the parent process.
+ // return XRE_IsParentProcess() && !aSource;
+ return true;
+}
+
+auto BrowsingContext::CanSet(FieldIndex<IDX_UserAgentOverride>,
+ const nsString& aUserAgent, ContentParent* aSource)
+ -> CanSetResult {
+ if (!IsTop()) {
+ return CanSetResult::Deny;
+ }
+
+ return LegacyRevertIfNotOwningOrParentProcess(aSource);
+}
+
+auto BrowsingContext::CanSet(FieldIndex<IDX_PlatformOverride>,
+ const nsString& aPlatform, ContentParent* aSource)
+ -> CanSetResult {
+ if (!IsTop()) {
+ return CanSetResult::Deny;
+ }
+
+ return LegacyRevertIfNotOwningOrParentProcess(aSource);
+}
+
+bool BrowsingContext::CheckOnlyEmbedderCanSet(ContentParent* aSource) {
+ if (XRE_IsParentProcess()) {
+ uint64_t childId = aSource ? aSource->ChildID() : 0;
+ return Canonical()->IsEmbeddedInProcess(childId);
+ }
+ return mEmbeddedByThisProcess;
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_EmbedderInnerWindowId>,
+ const uint64_t& aValue, ContentParent* aSource) {
+ // If we have a parent window, our embedder inner window ID must match it.
+ if (mParentWindow) {
+ return mParentWindow->Id() == aValue;
+ }
+
+ // For toplevel BrowsingContext instances, this value may only be set by the
+ // parent process, or initialized to `0`.
+ return CheckOnlyEmbedderCanSet(aSource);
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_EmbedderElementType>,
+ const Maybe<nsString>&, ContentParent* aSource) {
+ return CheckOnlyEmbedderCanSet(aSource);
+}
+
+auto BrowsingContext::CanSet(FieldIndex<IDX_CurrentInnerWindowId>,
+ const uint64_t& aValue, ContentParent* aSource)
+ -> CanSetResult {
+ // Generally allow clearing this. We may want to be more precise about this
+ // check in the future.
+ if (aValue == 0) {
+ return CanSetResult::Allow;
+ }
+
+ // We must have access to the specified context.
+ RefPtr<WindowContext> window = WindowContext::GetById(aValue);
+ if (!window || window->GetBrowsingContext() != this) {
+ return CanSetResult::Deny;
+ }
+
+ if (aSource) {
+ // If the sending process is no longer the current owner, revert
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (!Canonical()->IsOwnedByProcess(aSource->ChildID())) {
+ return CanSetResult::Revert;
+ }
+ } else if (XRE_IsContentProcess() && !IsOwnedByProcess()) {
+ return CanSetResult::Deny;
+ }
+
+ return CanSetResult::Allow;
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_ParentInitiatedNavigationEpoch>,
+ const uint64_t& aValue, ContentParent* aSource) {
+ return XRE_IsParentProcess() && !aSource;
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_CurrentInnerWindowId>) {
+ RefPtr<WindowContext> prevWindowContext = mCurrentWindowContext.forget();
+ mCurrentWindowContext = WindowContext::GetById(GetCurrentInnerWindowId());
+ MOZ_ASSERT(
+ !mCurrentWindowContext || mWindowContexts.Contains(mCurrentWindowContext),
+ "WindowContext not registered?");
+
+ // Clear our cached `children` value, to ensure that JS sees the up-to-date
+ // value.
+ BrowsingContext_Binding::ClearCachedChildrenValue(this);
+
+ if (XRE_IsParentProcess()) {
+ if (prevWindowContext != mCurrentWindowContext) {
+ if (prevWindowContext) {
+ prevWindowContext->Canonical()->DidBecomeCurrentWindowGlobal(false);
+ }
+ if (mCurrentWindowContext) {
+ // We set a timer when we set the current inner window. This
+ // will then flush the session storage to session store to
+ // make sure that we don't miss to store session storage to
+ // session store that is a result of navigation. This is due
+ // to Bug 1700623. We wish to fix this in Bug 1711886, where
+ // making sure to store everything would make this timer
+ // unnecessary.
+ Canonical()->MaybeScheduleSessionStoreUpdate();
+ mCurrentWindowContext->Canonical()->DidBecomeCurrentWindowGlobal(true);
+ }
+ }
+ BrowserParent::UpdateFocusFromBrowsingContext();
+ }
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_IsPopupSpam>, const bool& aValue,
+ ContentParent* aSource) {
+ // Ensure that we only mark a browsing context as popup spam once and never
+ // unmark it.
+ return aValue && !GetIsPopupSpam();
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_IsPopupSpam>) {
+ if (GetIsPopupSpam()) {
+ PopupBlocker::RegisterOpenPopupSpam();
+ }
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_MessageManagerGroup>,
+ const nsString& aMessageManagerGroup,
+ ContentParent* aSource) {
+ // Should only be set in the parent process on toplevel.
+ return XRE_IsParentProcess() && !aSource && IsTopContent();
+}
+
+bool BrowsingContext::CanSet(
+ FieldIndex<IDX_OrientationLock>,
+ const mozilla::hal::ScreenOrientation& aOrientationLock,
+ ContentParent* aSource) {
+ return IsTop();
+}
+
+bool BrowsingContext::IsLoading() {
+ if (GetLoading()) {
+ return true;
+ }
+
+ // If we're in the same process as the page, we're possibly just
+ // updating the flag.
+ nsIDocShell* shell = GetDocShell();
+ if (shell) {
+ Document* doc = shell->GetDocument();
+ return doc && doc->GetReadyStateEnum() < Document::READYSTATE_COMPLETE;
+ }
+
+ return false;
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_Loading>) {
+ if (mFields.Get<IDX_Loading>()) {
+ return;
+ }
+
+ while (!mDeprioritizedLoadRunner.isEmpty()) {
+ nsCOMPtr<nsIRunnable> runner = mDeprioritizedLoadRunner.popFirst();
+ NS_DispatchToCurrentThread(runner.forget());
+ }
+
+ if (StaticPrefs::dom_separate_event_queue_for_post_message_enabled() &&
+ Top() == this) {
+ Group()->FlushPostMessageEvents();
+ }
+}
+
+// Inform the Document for this context of the (potential) change in
+// loading state
+void BrowsingContext::DidSet(FieldIndex<IDX_AncestorLoading>) {
+ nsPIDOMWindowOuter* outer = GetDOMWindow();
+ if (!outer) {
+ MOZ_LOG(gTimeoutDeferralLog, mozilla::LogLevel::Debug,
+ ("DidSetAncestorLoading BC: %p -- No outer window", (void*)this));
+ return;
+ }
+ Document* document = nsGlobalWindowOuter::Cast(outer)->GetExtantDoc();
+ if (document) {
+ MOZ_LOG(gTimeoutDeferralLog, mozilla::LogLevel::Debug,
+ ("DidSetAncestorLoading BC: %p -- NotifyLoading(%d, %d, %d)",
+ (void*)this, GetAncestorLoading(), document->GetReadyStateEnum(),
+ document->GetReadyStateEnum()));
+ document->NotifyLoading(GetAncestorLoading(), document->GetReadyStateEnum(),
+ document->GetReadyStateEnum());
+ }
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_AuthorStyleDisabledDefault>) {
+ MOZ_ASSERT(IsTop(),
+ "Should only set AuthorStyleDisabledDefault in the top "
+ "browsing context");
+
+ // We don't need to handle changes to this field, since PageStyleChild.sys.mjs
+ // will respond to the PageStyle:Disable message in all content processes.
+ //
+ // But we store the state here on the top BrowsingContext so that the
+ // docshell has somewhere to look for the current author style disabling
+ // state when new iframes are inserted.
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_TextZoom>, float aOldValue) {
+ if (GetTextZoom() == aOldValue) {
+ return;
+ }
+
+ if (IsInProcess()) {
+ if (nsIDocShell* shell = GetDocShell()) {
+ if (nsPresContext* pc = shell->GetPresContext()) {
+ pc->RecomputeBrowsingContextDependentData();
+ }
+ }
+
+ for (BrowsingContext* child : Children()) {
+ // Setting text zoom on a discarded context has no effect.
+ Unused << child->SetTextZoom(GetTextZoom());
+ }
+ }
+
+ if (IsTop() && XRE_IsParentProcess()) {
+ if (Element* element = GetEmbedderElement()) {
+ AsyncEventDispatcher::RunDOMEventWhenSafe(*element, u"TextZoomChange"_ns,
+ CanBubble::eYes,
+ ChromeOnlyDispatch::eYes);
+ }
+ }
+}
+
+// TODO(emilio): It'd be potentially nicer and cheaper to allow to set this only
+// on the Top() browsing context, but there are a lot of tests that rely on
+// zooming a subframe so...
+void BrowsingContext::DidSet(FieldIndex<IDX_FullZoom>, float aOldValue) {
+ if (GetFullZoom() == aOldValue) {
+ return;
+ }
+
+ if (IsInProcess()) {
+ if (nsIDocShell* shell = GetDocShell()) {
+ if (nsPresContext* pc = shell->GetPresContext()) {
+ pc->RecomputeBrowsingContextDependentData();
+ }
+ }
+
+ for (BrowsingContext* child : Children()) {
+ // Setting full zoom on a discarded context has no effect.
+ Unused << child->SetFullZoom(GetFullZoom());
+ }
+ }
+
+ if (IsTop() && XRE_IsParentProcess()) {
+ if (Element* element = GetEmbedderElement()) {
+ AsyncEventDispatcher::RunDOMEventWhenSafe(*element, u"FullZoomChange"_ns,
+ CanBubble::eYes,
+ ChromeOnlyDispatch::eYes);
+ }
+ }
+}
+
+void BrowsingContext::AddDeprioritizedLoadRunner(nsIRunnable* aRunner) {
+ MOZ_ASSERT(IsLoading());
+ MOZ_ASSERT(Top() == this);
+
+ RefPtr<DeprioritizedLoadRunner> runner = new DeprioritizedLoadRunner(aRunner);
+ mDeprioritizedLoadRunner.insertBack(runner);
+ NS_DispatchToCurrentThreadQueue(
+ runner.forget(), StaticPrefs::page_load_deprioritization_period(),
+ EventQueuePriority::Idle);
+}
+
+bool BrowsingContext::IsDynamic() const {
+ const BrowsingContext* current = this;
+ do {
+ if (current->CreatedDynamically()) {
+ return true;
+ }
+ } while ((current = current->GetParent()));
+
+ return false;
+}
+
+bool BrowsingContext::GetOffsetPath(nsTArray<uint32_t>& aPath) const {
+ for (const BrowsingContext* current = this; current && current->GetParent();
+ current = current->GetParent()) {
+ if (current->CreatedDynamically()) {
+ return false;
+ }
+ aPath.AppendElement(current->ChildOffset());
+ }
+ return true;
+}
+
+void BrowsingContext::GetHistoryID(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aVal,
+ ErrorResult& aError) {
+ if (!xpc::ID2JSValue(aCx, GetHistoryID(), aVal)) {
+ aError.Throw(NS_ERROR_OUT_OF_MEMORY);
+ }
+}
+
+void BrowsingContext::InitSessionHistory() {
+ MOZ_ASSERT(!IsDiscarded());
+ MOZ_ASSERT(IsTop());
+ MOZ_ASSERT(EverAttached());
+
+ if (!GetHasSessionHistory()) {
+ MOZ_ALWAYS_SUCCEEDS(SetHasSessionHistory(true));
+ }
+}
+
+ChildSHistory* BrowsingContext::GetChildSessionHistory() {
+ if (!mozilla::SessionHistoryInParent()) {
+ // For now we're checking that the session history object for the child
+ // process is available before returning the ChildSHistory object, because
+ // it is the actual implementation that ChildSHistory forwards to. This can
+ // be removed once session history is stored exclusively in the parent
+ // process.
+ return mChildSessionHistory && mChildSessionHistory->IsInProcess()
+ ? mChildSessionHistory.get()
+ : nullptr;
+ }
+
+ return mChildSessionHistory;
+}
+
+void BrowsingContext::CreateChildSHistory() {
+ MOZ_ASSERT(IsTop());
+ MOZ_ASSERT(GetHasSessionHistory());
+ MOZ_DIAGNOSTIC_ASSERT(!mChildSessionHistory);
+
+ // Because session history is global in a browsing context tree, every process
+ // that has access to a browsing context tree needs access to its session
+ // history. That is why we create the ChildSHistory object in every process
+ // where we have access to this browsing context (which is the top one).
+ mChildSessionHistory = new ChildSHistory(this);
+
+ // If the top browsing context (this one) is loaded in this process then we
+ // also create the session history implementation for the child process.
+ // This can be removed once session history is stored exclusively in the
+ // parent process.
+ mChildSessionHistory->SetIsInProcess(IsInProcess());
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_HasSessionHistory>,
+ bool aOldValue) {
+ MOZ_ASSERT(GetHasSessionHistory() || !aOldValue,
+ "We don't support turning off session history.");
+
+ if (GetHasSessionHistory() && !aOldValue) {
+ CreateChildSHistory();
+ }
+}
+
+bool BrowsingContext::CanSet(
+ FieldIndex<IDX_TargetTopLevelLinkClicksToBlankInternal>,
+ const bool& aTargetTopLevelLinkClicksToBlankInternal,
+ ContentParent* aSource) {
+ return XRE_IsParentProcess() && !aSource && IsTop();
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_BrowserId>, const uint32_t& aValue,
+ ContentParent* aSource) {
+ // We should only be able to set this for toplevel contexts which don't have
+ // an ID yet.
+ return GetBrowserId() == 0 && IsTop() && Children().IsEmpty();
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_PendingInitialization>,
+ bool aNewValue, ContentParent* aSource) {
+ // Can only be cleared from `true` to `false`, and should only ever be set on
+ // the toplevel BrowsingContext.
+ return IsTop() && GetPendingInitialization() && !aNewValue;
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_HasRestoreData>, bool aNewValue,
+ ContentParent* aSource) {
+ return IsTop();
+}
+
+bool BrowsingContext::CanSet(FieldIndex<IDX_IsUnderHiddenEmbedderElement>,
+ const bool& aIsUnderHiddenEmbedderElement,
+ ContentParent* aSource) {
+ return true;
+}
+
+void BrowsingContext::DidSet(FieldIndex<IDX_IsUnderHiddenEmbedderElement>,
+ bool aOldValue) {
+ nsIDocShell* shell = GetDocShell();
+ if (!shell) {
+ return;
+ }
+
+ const bool newValue = IsUnderHiddenEmbedderElement();
+ if (NS_WARN_IF(aOldValue == newValue)) {
+ return;
+ }
+ if (PresShell* presShell = shell->GetPresShell()) {
+ presShell->SetIsUnderHiddenEmbedderElement(newValue);
+ }
+
+ // Propagate to children.
+ for (BrowsingContext* child : Children()) {
+ Element* embedderElement = child->GetEmbedderElement();
+ if (!embedderElement) {
+ // TODO: We shouldn't need to null check here since `child` and the
+ // element returned by `child->GetEmbedderElement()` are in our
+ // process (the actual browsing context represented by `child` may not
+ // be, but that doesn't matter). However, there are currently a very
+ // small number of crashes due to `embedderElement` being null, somehow
+ // - see bug 1551241. For now we wallpaper the crash.
+ continue;
+ }
+
+ bool embedderFrameIsHidden = true;
+ if (auto embedderFrame = embedderElement->GetPrimaryFrame()) {
+ embedderFrameIsHidden = !embedderFrame->StyleVisibility()->IsVisible();
+ }
+
+ bool hidden = IsUnderHiddenEmbedderElement() || embedderFrameIsHidden;
+ if (child->IsUnderHiddenEmbedderElement() != hidden) {
+ Unused << child->SetIsUnderHiddenEmbedderElement(hidden);
+ }
+ }
+}
+
+bool BrowsingContext::IsPopupAllowed() {
+ for (auto* context = GetCurrentWindowContext(); context;
+ context = context->GetParentWindowContext()) {
+ if (context->CanShowPopup()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/* static */
+bool BrowsingContext::ShouldAddEntryForRefresh(
+ nsIURI* aPreviousURI, const SessionHistoryInfo& aInfo) {
+ return ShouldAddEntryForRefresh(aPreviousURI, aInfo.GetURI(),
+ aInfo.HasPostData());
+}
+
+/* static */
+bool BrowsingContext::ShouldAddEntryForRefresh(nsIURI* aPreviousURI,
+ nsIURI* aNewURI,
+ bool aHasPostData) {
+ if (aHasPostData) {
+ return true;
+ }
+
+ bool equalsURI = false;
+ if (aPreviousURI) {
+ aPreviousURI->Equals(aNewURI, &equalsURI);
+ }
+ return !equalsURI;
+}
+
+void BrowsingContext::SessionHistoryCommit(
+ const LoadingSessionHistoryInfo& aInfo, uint32_t aLoadType,
+ nsIURI* aPreviousURI, SessionHistoryInfo* aPreviousActiveEntry,
+ bool aPersist, bool aCloneEntryChildren, bool aChannelExpired,
+ uint32_t aCacheKey) {
+ nsID changeID = {};
+ if (XRE_IsContentProcess()) {
+ RefPtr<ChildSHistory> rootSH = Top()->GetChildSessionHistory();
+ if (rootSH) {
+ if (!aInfo.mLoadIsFromSessionHistory) {
+ // We try to mimic as closely as possible what will happen in
+ // CanonicalBrowsingContext::SessionHistoryCommit. We'll be
+ // incrementing the session history length if we're not replacing,
+ // this is a top-level load or it's not the initial load in an iframe,
+ // ShouldUpdateSessionHistory(loadType) returns true and it's not a
+ // refresh for which ShouldAddEntryForRefresh returns false.
+ // It is possible that this leads to wrong length temporarily, but
+ // so would not having the check for replace.
+ // Note that nsSHistory::AddEntry does a replace load if the current
+ // entry is not marked as a persisted entry. The child process does
+ // not have access to the current entry, so we use the previous active
+ // entry as the best approximation. When that's not the current entry
+ // then the length might be wrong briefly, until the parent process
+ // commits the actual length.
+ if (!LOAD_TYPE_HAS_FLAGS(
+ aLoadType, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY) &&
+ (IsTop()
+ ? (!aPreviousActiveEntry || aPreviousActiveEntry->GetPersist())
+ : !!aPreviousActiveEntry) &&
+ ShouldUpdateSessionHistory(aLoadType) &&
+ (!LOAD_TYPE_HAS_FLAGS(aLoadType,
+ nsIWebNavigation::LOAD_FLAGS_IS_REFRESH) ||
+ ShouldAddEntryForRefresh(aPreviousURI, aInfo.mInfo))) {
+ changeID = rootSH->AddPendingHistoryChange();
+ }
+ } else {
+ // History load doesn't change the length, only index.
+ changeID = rootSH->AddPendingHistoryChange(aInfo.mOffset, 0);
+ }
+ }
+ ContentChild* cc = ContentChild::GetSingleton();
+ mozilla::Unused << cc->SendHistoryCommit(
+ this, aInfo.mLoadId, changeID, aLoadType, aPersist, aCloneEntryChildren,
+ aChannelExpired, aCacheKey);
+ } else {
+ Canonical()->SessionHistoryCommit(aInfo.mLoadId, changeID, aLoadType,
+ aPersist, aCloneEntryChildren,
+ aChannelExpired, aCacheKey);
+ }
+}
+
+void BrowsingContext::SetActiveSessionHistoryEntry(
+ const Maybe<nsPoint>& aPreviousScrollPos, SessionHistoryInfo* aInfo,
+ uint32_t aLoadType, uint32_t aUpdatedCacheKey, bool aUpdateLength) {
+ if (XRE_IsContentProcess()) {
+ // XXX Why we update cache key only in content process case?
+ if (aUpdatedCacheKey != 0) {
+ aInfo->SetCacheKey(aUpdatedCacheKey);
+ }
+
+ nsID changeID = {};
+ if (aUpdateLength) {
+ RefPtr<ChildSHistory> shistory = Top()->GetChildSessionHistory();
+ if (shistory) {
+ changeID = shistory->AddPendingHistoryChange();
+ }
+ }
+ ContentChild::GetSingleton()->SendSetActiveSessionHistoryEntry(
+ this, aPreviousScrollPos, *aInfo, aLoadType, aUpdatedCacheKey,
+ changeID);
+ } else {
+ Canonical()->SetActiveSessionHistoryEntry(
+ aPreviousScrollPos, aInfo, aLoadType, aUpdatedCacheKey, nsID());
+ }
+}
+
+void BrowsingContext::ReplaceActiveSessionHistoryEntry(
+ SessionHistoryInfo* aInfo) {
+ if (XRE_IsContentProcess()) {
+ ContentChild::GetSingleton()->SendReplaceActiveSessionHistoryEntry(this,
+ *aInfo);
+ } else {
+ Canonical()->ReplaceActiveSessionHistoryEntry(aInfo);
+ }
+}
+
+void BrowsingContext::RemoveDynEntriesFromActiveSessionHistoryEntry() {
+ if (XRE_IsContentProcess()) {
+ ContentChild::GetSingleton()
+ ->SendRemoveDynEntriesFromActiveSessionHistoryEntry(this);
+ } else {
+ Canonical()->RemoveDynEntriesFromActiveSessionHistoryEntry();
+ }
+}
+
+void BrowsingContext::RemoveFromSessionHistory(const nsID& aChangeID) {
+ if (XRE_IsContentProcess()) {
+ ContentChild::GetSingleton()->SendRemoveFromSessionHistory(this, aChangeID);
+ } else {
+ Canonical()->RemoveFromSessionHistory(aChangeID);
+ }
+}
+
+void BrowsingContext::HistoryGo(
+ int32_t aOffset, uint64_t aHistoryEpoch, bool aRequireUserInteraction,
+ bool aUserActivation, std::function<void(Maybe<int32_t>&&)>&& aResolver) {
+ if (XRE_IsContentProcess()) {
+ ContentChild::GetSingleton()->SendHistoryGo(
+ this, aOffset, aHistoryEpoch, aRequireUserInteraction, aUserActivation,
+ std::move(aResolver),
+ [](mozilla::ipc::
+ ResponseRejectReason) { /* FIXME Is ignoring this fine? */ });
+ } else {
+ RefPtr<CanonicalBrowsingContext> self = Canonical();
+ aResolver(self->HistoryGo(
+ aOffset, aHistoryEpoch, aRequireUserInteraction, aUserActivation,
+ Canonical()->GetContentParent()
+ ? Some(Canonical()->GetContentParent()->ChildID())
+ : Nothing()));
+ }
+}
+
+void BrowsingContext::SetChildSHistory(ChildSHistory* aChildSHistory) {
+ mChildSessionHistory = aChildSHistory;
+ mChildSessionHistory->SetBrowsingContext(this);
+ mFields.SetWithoutSyncing<IDX_HasSessionHistory>(true);
+}
+
+bool BrowsingContext::ShouldUpdateSessionHistory(uint32_t aLoadType) {
+ // We don't update session history on reload unless we're loading
+ // an iframe in shift-reload case.
+ return nsDocShell::ShouldUpdateGlobalHistory(aLoadType) &&
+ (!(aLoadType & nsIDocShell::LOAD_CMD_RELOAD) ||
+ (IsForceReloadType(aLoadType) && IsSubframe()));
+}
+
+nsresult BrowsingContext::CheckLocationChangeRateLimit(CallerType aCallerType) {
+ // We only rate limit non system callers
+ if (aCallerType == CallerType::System) {
+ return NS_OK;
+ }
+
+ // Fetch rate limiting preferences
+ uint32_t limitCount =
+ StaticPrefs::dom_navigation_locationChangeRateLimit_count();
+ uint32_t timeSpanSeconds =
+ StaticPrefs::dom_navigation_locationChangeRateLimit_timespan();
+
+ // Disable throttling if either of the preferences is set to 0.
+ if (limitCount == 0 || timeSpanSeconds == 0) {
+ return NS_OK;
+ }
+
+ TimeDuration throttleSpan = TimeDuration::FromSeconds(timeSpanSeconds);
+
+ if (mLocationChangeRateLimitSpanStart.IsNull() ||
+ ((TimeStamp::Now() - mLocationChangeRateLimitSpanStart) > throttleSpan)) {
+ // Initial call or timespan exceeded, reset counter and timespan.
+ mLocationChangeRateLimitSpanStart = TimeStamp::Now();
+ mLocationChangeRateLimitCount = 1;
+ return NS_OK;
+ }
+
+ if (mLocationChangeRateLimitCount >= limitCount) {
+ // Rate limit reached
+
+ Document* doc = GetDocument();
+ if (doc) {
+ nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, "DOM"_ns, doc,
+ nsContentUtils::eDOM_PROPERTIES,
+ "LocChangeFloodingPrevented");
+ }
+
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ mLocationChangeRateLimitCount++;
+ return NS_OK;
+}
+
+void BrowsingContext::ResetLocationChangeRateLimit() {
+ // Resetting the timestamp object will cause the check function to
+ // init again and reset the rate limit.
+ mLocationChangeRateLimitSpanStart = TimeStamp();
+}
+
+} // namespace dom
+
+namespace ipc {
+
+void IPDLParamTraits<dom::MaybeDiscarded<dom::BrowsingContext>>::Write(
+ IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const dom::MaybeDiscarded<dom::BrowsingContext>& aParam) {
+ MOZ_DIAGNOSTIC_ASSERT(!aParam.GetMaybeDiscarded() ||
+ aParam.GetMaybeDiscarded()->EverAttached());
+ uint64_t id = aParam.ContextId();
+ WriteIPDLParam(aWriter, aActor, id);
+}
+
+bool IPDLParamTraits<dom::MaybeDiscarded<dom::BrowsingContext>>::Read(
+ IPC::MessageReader* aReader, IProtocol* aActor,
+ dom::MaybeDiscarded<dom::BrowsingContext>* aResult) {
+ uint64_t id = 0;
+ if (!ReadIPDLParam(aReader, aActor, &id)) {
+ return false;
+ }
+
+ if (id == 0) {
+ *aResult = nullptr;
+ } else if (RefPtr<dom::BrowsingContext> bc = dom::BrowsingContext::Get(id)) {
+ *aResult = std::move(bc);
+ } else {
+ aResult->SetDiscarded(id);
+ }
+ return true;
+}
+
+void IPDLParamTraits<dom::BrowsingContext::IPCInitializer>::Write(
+ IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const dom::BrowsingContext::IPCInitializer& aInit) {
+ // Write actor ID parameters.
+ WriteIPDLParam(aWriter, aActor, aInit.mId);
+ WriteIPDLParam(aWriter, aActor, aInit.mParentId);
+ WriteIPDLParam(aWriter, aActor, aInit.mWindowless);
+ WriteIPDLParam(aWriter, aActor, aInit.mUseRemoteTabs);
+ WriteIPDLParam(aWriter, aActor, aInit.mUseRemoteSubframes);
+ WriteIPDLParam(aWriter, aActor, aInit.mCreatedDynamically);
+ WriteIPDLParam(aWriter, aActor, aInit.mChildOffset);
+ WriteIPDLParam(aWriter, aActor, aInit.mOriginAttributes);
+ WriteIPDLParam(aWriter, aActor, aInit.mRequestContextId);
+ WriteIPDLParam(aWriter, aActor, aInit.mSessionHistoryIndex);
+ WriteIPDLParam(aWriter, aActor, aInit.mSessionHistoryCount);
+ WriteIPDLParam(aWriter, aActor, aInit.mFields);
+}
+
+bool IPDLParamTraits<dom::BrowsingContext::IPCInitializer>::Read(
+ IPC::MessageReader* aReader, IProtocol* aActor,
+ dom::BrowsingContext::IPCInitializer* aInit) {
+ // Read actor ID parameters.
+ if (!ReadIPDLParam(aReader, aActor, &aInit->mId) ||
+ !ReadIPDLParam(aReader, aActor, &aInit->mParentId) ||
+ !ReadIPDLParam(aReader, aActor, &aInit->mWindowless) ||
+ !ReadIPDLParam(aReader, aActor, &aInit->mUseRemoteTabs) ||
+ !ReadIPDLParam(aReader, aActor, &aInit->mUseRemoteSubframes) ||
+ !ReadIPDLParam(aReader, aActor, &aInit->mCreatedDynamically) ||
+ !ReadIPDLParam(aReader, aActor, &aInit->mChildOffset) ||
+ !ReadIPDLParam(aReader, aActor, &aInit->mOriginAttributes) ||
+ !ReadIPDLParam(aReader, aActor, &aInit->mRequestContextId) ||
+ !ReadIPDLParam(aReader, aActor, &aInit->mSessionHistoryIndex) ||
+ !ReadIPDLParam(aReader, aActor, &aInit->mSessionHistoryCount) ||
+ !ReadIPDLParam(aReader, aActor, &aInit->mFields)) {
+ return false;
+ }
+ return true;
+}
+
+template struct IPDLParamTraits<dom::BrowsingContext::BaseTransaction>;
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/docshell/base/BrowsingContext.h b/docshell/base/BrowsingContext.h
new file mode 100644
index 0000000000..4c245337b7
--- /dev/null
+++ b/docshell/base/BrowsingContext.h
@@ -0,0 +1,1458 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_BrowsingContext_h
+#define mozilla_dom_BrowsingContext_h
+
+#include <tuple>
+#include "GVAutoplayRequestUtils.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/HalScreenConfiguration.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Span.h"
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/LocationBase.h"
+#include "mozilla/dom/MaybeDiscarded.h"
+#include "mozilla/dom/PopupBlocker.h"
+#include "mozilla/dom/UserActivation.h"
+#include "mozilla/dom/BrowsingContextBinding.h"
+#include "mozilla/dom/ScreenOrientationBinding.h"
+#include "mozilla/dom/SyncedContext.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIDocShell.h"
+#include "nsTArray.h"
+#include "nsWrapperCache.h"
+#include "nsILoadInfo.h"
+#include "nsILoadContext.h"
+#include "nsThreadUtils.h"
+
+class nsDocShellLoadState;
+class nsGlobalWindowInner;
+class nsGlobalWindowOuter;
+class nsIPrincipal;
+class nsOuterWindowProxy;
+struct nsPoint;
+class PickleIterator;
+
+namespace IPC {
+class Message;
+class MessageReader;
+class MessageWriter;
+} // namespace IPC
+
+namespace mozilla {
+
+class ErrorResult;
+class LogModule;
+
+namespace ipc {
+class IProtocol;
+class IPCResult;
+
+template <typename T>
+struct IPDLParamTraits;
+} // namespace ipc
+
+namespace dom {
+class BrowsingContent;
+class BrowsingContextGroup;
+class CanonicalBrowsingContext;
+class ChildSHistory;
+class ContentParent;
+class Element;
+struct LoadingSessionHistoryInfo;
+template <typename>
+struct Nullable;
+template <typename T>
+class Sequence;
+class SessionHistoryInfo;
+class SessionStorageManager;
+class StructuredCloneHolder;
+class WindowContext;
+class WindowGlobalChild;
+struct WindowPostMessageOptions;
+class WindowProxyHolder;
+
+enum class ExplicitActiveStatus : uint8_t {
+ None,
+ Active,
+ Inactive,
+ EndGuard_,
+};
+
+struct EmbedderColorSchemes {
+ PrefersColorSchemeOverride mUsed{};
+ PrefersColorSchemeOverride mPreferred{};
+
+ bool operator==(const EmbedderColorSchemes& aOther) const {
+ return mUsed == aOther.mUsed && mPreferred == aOther.mPreferred;
+ }
+
+ bool operator!=(const EmbedderColorSchemes& aOther) const {
+ return !(*this == aOther);
+ }
+};
+
+// Fields are, by default, settable by any process and readable by any process.
+// Racy sets will be resolved as-if they occurred in the order the parent
+// process finds out about them.
+//
+// The `DidSet` and `CanSet` methods may be overloaded to provide different
+// behavior for a specific field.
+// * `DidSet` is called to run code in every process whenever the value is
+// updated (This currently occurs even if the value didn't change, though
+// this may change in the future).
+// * `CanSet` is called before attempting to set the value, in both the process
+// which calls `Set`, and the parent process, and will kill the misbehaving
+// process if it fails.
+#define MOZ_EACH_BC_FIELD(FIELD) \
+ FIELD(Name, nsString) \
+ FIELD(Closed, bool) \
+ FIELD(ExplicitActive, ExplicitActiveStatus) \
+ /* Top()-only. If true, new-playing media will be suspended when in an \
+ * inactive browsing context. */ \
+ FIELD(SuspendMediaWhenInactive, bool) \
+ /* If true, we're within the nested event loop in window.open, and this \
+ * context may not be used as the target of a load */ \
+ FIELD(PendingInitialization, bool) \
+ /* Indicates if the browser window is active for the purpose of the \
+ * :-moz-window-inactive pseudoclass. Only read from or set on the \
+ * top BrowsingContext. */ \
+ FIELD(IsActiveBrowserWindowInternal, bool) \
+ FIELD(OpenerPolicy, nsILoadInfo::CrossOriginOpenerPolicy) \
+ /* Current opener for the BrowsingContext. Weak reference */ \
+ FIELD(OpenerId, uint64_t) \
+ FIELD(OnePermittedSandboxedNavigatorId, uint64_t) \
+ /* WindowID of the inner window which embeds this BC */ \
+ FIELD(EmbedderInnerWindowId, uint64_t) \
+ FIELD(CurrentInnerWindowId, uint64_t) \
+ FIELD(HadOriginalOpener, bool) \
+ FIELD(IsPopupSpam, bool) \
+ /* Hold the audio muted state and should be used on top level browsing \
+ * contexts only */ \
+ FIELD(Muted, bool) \
+ /* Hold the pinned/app-tab state and should be used on top level browsing \
+ * contexts only */ \
+ FIELD(IsAppTab, bool) \
+ /* Whether there's more than 1 tab / toplevel browsing context in this \
+ * parent window. Used to determine if a given BC is allowed to resize \
+ * and/or move the window or not. */ \
+ FIELD(HasSiblings, bool) \
+ /* Indicate that whether we should delay media playback, which would only \
+ be done on an unvisited tab. And this should only be used on the top \
+ level browsing contexts */ \
+ FIELD(ShouldDelayMediaFromStart, bool) \
+ /* See nsSandboxFlags.h for the possible flags. */ \
+ FIELD(SandboxFlags, uint32_t) \
+ /* The value of SandboxFlags when the BrowsingContext is first created. \
+ * Used for sandboxing the initial about:blank document. */ \
+ FIELD(InitialSandboxFlags, uint32_t) \
+ /* A non-zero unique identifier for the browser element that is hosting \
+ * this \
+ * BrowsingContext tree. Every BrowsingContext in the element's tree will \
+ * return the same ID in all processes and it will remain stable \
+ * regardless of process changes. When a browser element's frameloader is \
+ * switched to another browser element this ID will remain the same but \
+ * hosted under the under the new browser element. */ \
+ FIELD(BrowserId, uint64_t) \
+ FIELD(HistoryID, nsID) \
+ FIELD(InRDMPane, bool) \
+ FIELD(Loading, bool) \
+ /* A field only set on top browsing contexts, which indicates that either: \
+ * \
+ * * This is a browsing context created explicitly for printing or print \
+ * preview (thus hosting static documents). \
+ * \
+ * * This is a browsing context where something in this tree is calling \
+ * window.print() (and thus showing a modal dialog). \
+ * \
+ * We use it exclusively to block navigation for both of these cases. */ \
+ FIELD(IsPrinting, bool) \
+ FIELD(AncestorLoading, bool) \
+ FIELD(AllowPlugins, bool) \
+ FIELD(AllowContentRetargeting, bool) \
+ FIELD(AllowContentRetargetingOnChildren, bool) \
+ FIELD(ForceEnableTrackingProtection, bool) \
+ FIELD(UseGlobalHistory, bool) \
+ FIELD(TargetTopLevelLinkClicksToBlankInternal, bool) \
+ FIELD(FullscreenAllowedByOwner, bool) \
+ FIELD(ForceDesktopViewport, bool) \
+ /* \
+ * "is popup" in the spec. \
+ * Set only on top browsing contexts. \
+ * This doesn't indicate whether this is actually a popup or not, \
+ * but whether this browsing context is created by requesting popup or not. \
+ * See also: nsWindowWatcher::ShouldOpenPopup. \
+ */ \
+ FIELD(IsPopupRequested, bool) \
+ /* These field are used to store the states of autoplay media request on \
+ * GeckoView only, and it would only be modified on the top level browsing \
+ * context. */ \
+ FIELD(GVAudibleAutoplayRequestStatus, GVAutoplayRequestStatus) \
+ FIELD(GVInaudibleAutoplayRequestStatus, GVAutoplayRequestStatus) \
+ /* ScreenOrientation-related APIs */ \
+ FIELD(CurrentOrientationAngle, float) \
+ FIELD(CurrentOrientationType, mozilla::dom::OrientationType) \
+ FIELD(OrientationLock, mozilla::hal::ScreenOrientation) \
+ FIELD(UserAgentOverride, nsString) \
+ FIELD(TouchEventsOverrideInternal, mozilla::dom::TouchEventsOverride) \
+ FIELD(EmbedderElementType, Maybe<nsString>) \
+ FIELD(MessageManagerGroup, nsString) \
+ FIELD(MaxTouchPointsOverride, uint8_t) \
+ FIELD(FullZoom, float) \
+ FIELD(WatchedByDevToolsInternal, bool) \
+ FIELD(TextZoom, float) \
+ FIELD(OverrideDPPX, float) \
+ /* The current in-progress load. */ \
+ FIELD(CurrentLoadIdentifier, Maybe<uint64_t>) \
+ /* See nsIRequest for possible flags. */ \
+ FIELD(DefaultLoadFlags, uint32_t) \
+ /* Signals that session history is enabled for this browsing context tree. \
+ * This is only ever set to true on the top BC, so consumers need to get \
+ * the value from the top BC! */ \
+ FIELD(HasSessionHistory, bool) \
+ /* Tracks if this context is the only top-level document in the session \
+ * history of the context. */ \
+ FIELD(IsSingleToplevelInHistory, bool) \
+ FIELD(UseErrorPages, bool) \
+ FIELD(PlatformOverride, nsString) \
+ /* Specifies if this BC has loaded documents besides the initial \
+ * about:blank document. about:privatebrowsing, about:home, about:newtab \
+ * and non-initial about:blank are not considered to be initial \
+ * documents. */ \
+ FIELD(HasLoadedNonInitialDocument, bool) \
+ /* Default value for nsIContentViewer::authorStyleDisabled in any new \
+ * browsing contexts created as a descendant of this one. Valid only for \
+ * top BCs. */ \
+ FIELD(AuthorStyleDisabledDefault, bool) \
+ FIELD(ServiceWorkersTestingEnabled, bool) \
+ FIELD(MediumOverride, nsString) \
+ /* DevTools override for prefers-color-scheme */ \
+ FIELD(PrefersColorSchemeOverride, dom::PrefersColorSchemeOverride) \
+ /* prefers-color-scheme override based on the color-scheme style of our \
+ * <browser> embedder element. */ \
+ FIELD(EmbedderColorSchemes, EmbedderColorSchemes) \
+ FIELD(DisplayMode, dom::DisplayMode) \
+ /* The number of entries added to the session history because of this \
+ * browsing context. */ \
+ FIELD(HistoryEntryCount, uint32_t) \
+ /* Don't use the getter of the field, but IsInBFCache() method */ \
+ FIELD(IsInBFCache, bool) \
+ FIELD(HasRestoreData, bool) \
+ FIELD(SessionStoreEpoch, uint32_t) \
+ /* Whether we can execute scripts in this BrowsingContext. Has no effect \
+ * unless scripts are also allowed in the parent WindowContext. */ \
+ FIELD(AllowJavascript, bool) \
+ /* The count of request that are used to prevent the browsing context tree \
+ * from being suspended, which would ONLY be modified on the top level \
+ * context in the chrome process because that's a non-atomic counter */ \
+ FIELD(PageAwakeRequestCount, uint32_t) \
+ /* This field only gets incrememented when we start navigations in the \
+ * parent process. This is used for keeping track of the racing navigations \
+ * between the parent and content processes. */ \
+ FIELD(ParentInitiatedNavigationEpoch, uint64_t) \
+ /* This browsing context is for a synthetic image document wrapping an \
+ * image embedded in <object> or <embed>. */ \
+ FIELD(SyntheticDocumentContainer, bool) \
+ /* If true, this document is embedded within a content document, either \
+ * loaded in the parent (e.g. about:addons or the devtools toolbox), or in \
+ * a content process. */ \
+ FIELD(EmbeddedInContentDocument, bool) \
+ /* If true, this browsing context is within a hidden embedded document. */ \
+ FIELD(IsUnderHiddenEmbedderElement, bool)
+
+// BrowsingContext, in this context, is the cross process replicated
+// environment in which information about documents is stored. In
+// particular the tree structure of nested browsing contexts is
+// represented by the tree of BrowsingContexts.
+//
+// The tree of BrowsingContexts is created in step with its
+// corresponding nsDocShell, and when nsDocShells are connected
+// through a parent/child relationship, so are BrowsingContexts. The
+// major difference is that BrowsingContexts are replicated (synced)
+// to the parent process, making it possible to traverse the
+// BrowsingContext tree for a tab, in both the parent and the child
+// process.
+//
+// Trees of BrowsingContexts should only ever contain nodes of the
+// same BrowsingContext::Type. This is enforced by asserts in the
+// BrowsingContext::Create* methods.
+class BrowsingContext : public nsILoadContext, public nsWrapperCache {
+ MOZ_DECL_SYNCED_CONTEXT(BrowsingContext, MOZ_EACH_BC_FIELD)
+
+ public:
+ enum class Type { Chrome, Content };
+
+ static void Init();
+ static LogModule* GetLog();
+ static LogModule* GetSyncLog();
+
+ // Look up a BrowsingContext in the current process by ID.
+ static already_AddRefed<BrowsingContext> Get(uint64_t aId);
+ static already_AddRefed<BrowsingContext> Get(GlobalObject&, uint64_t aId) {
+ return Get(aId);
+ }
+ // Look up the top-level BrowsingContext by BrowserID.
+ static already_AddRefed<BrowsingContext> GetCurrentTopByBrowserId(
+ uint64_t aBrowserId);
+ static already_AddRefed<BrowsingContext> GetCurrentTopByBrowserId(
+ GlobalObject&, uint64_t aId) {
+ return GetCurrentTopByBrowserId(aId);
+ }
+
+ static void UpdateCurrentTopByBrowserId(BrowsingContext* aNewBrowsingContext);
+
+ static already_AddRefed<BrowsingContext> GetFromWindow(
+ WindowProxyHolder& aProxy);
+ static already_AddRefed<BrowsingContext> GetFromWindow(
+ GlobalObject&, WindowProxyHolder& aProxy) {
+ return GetFromWindow(aProxy);
+ }
+
+ static void DiscardFromContentParent(ContentParent* aCP);
+
+ // Create a brand-new toplevel BrowsingContext with no relationships to other
+ // BrowsingContexts, and which is not embedded within any <browser> or frame
+ // element.
+ //
+ // This BrowsingContext is immediately attached, and cannot have LoadContext
+ // flags customized unless it is of `Type::Chrome`.
+ //
+ // The process which created this BrowsingContext is responsible for detaching
+ // it.
+ static already_AddRefed<BrowsingContext> CreateIndependent(Type aType);
+
+ // Create a brand-new BrowsingContext object, but does not immediately attach
+ // it. State such as OriginAttributes and PrivateBrowsingId may be customized
+ // to configure the BrowsingContext before it is attached.
+ //
+ // `EnsureAttached()` must be called before the BrowsingContext is used for a
+ // DocShell, BrowserParent, or BrowserBridgeChild.
+ static already_AddRefed<BrowsingContext> CreateDetached(
+ nsGlobalWindowInner* aParent, BrowsingContext* aOpener,
+ BrowsingContextGroup* aSpecificGroup, const nsAString& aName, Type aType,
+ bool aIsPopupRequested, bool aCreatedDynamically = false);
+
+ void EnsureAttached();
+
+ bool EverAttached() const { return mEverAttached; }
+
+ // Cast this object to a canonical browsing context, and return it.
+ CanonicalBrowsingContext* Canonical();
+
+ // Is the most recent Document in this BrowsingContext loaded within this
+ // process? This may be true with a null mDocShell after the Window has been
+ // closed.
+ bool IsInProcess() const { return mIsInProcess; }
+
+ bool IsOwnedByProcess() const;
+
+ bool CanHaveRemoteOuterProxies() const {
+ return !mIsInProcess || mDanglingRemoteOuterProxies;
+ }
+
+ // Has this BrowsingContext been discarded. A discarded browsing context has
+ // been destroyed, and may not be available on the other side of an IPC
+ // message.
+ bool IsDiscarded() const { return mIsDiscarded; }
+
+ // Returns true if none of the BrowsingContext's ancestor BrowsingContexts or
+ // WindowContexts are discarded or cached.
+ bool AncestorsAreCurrent() const;
+
+ bool Windowless() const { return mWindowless; }
+
+ // Get the DocShell for this BrowsingContext if it is in-process, or
+ // null if it's not.
+ nsIDocShell* GetDocShell() const { return mDocShell; }
+ void SetDocShell(nsIDocShell* aDocShell);
+ void ClearDocShell() { mDocShell = nullptr; }
+
+ // Get the Document for this BrowsingContext if it is in-process, or
+ // null if it's not.
+ Document* GetDocument() const {
+ return mDocShell ? mDocShell->GetDocument() : nullptr;
+ }
+ Document* GetExtantDocument() const {
+ return mDocShell ? mDocShell->GetExtantDocument() : nullptr;
+ }
+
+ // This cleans up remote outer window proxies that might have been left behind
+ // when the browsing context went from being remote to local. It does this by
+ // turning them into cross-compartment wrappers to aOuter. If there is already
+ // a remote proxy in the compartment of aOuter, then aOuter will get swapped
+ // to it and the value of aOuter will be set to the object that used to be the
+ // remote proxy and is now an OuterWindowProxy.
+ void CleanUpDanglingRemoteOuterWindowProxies(
+ JSContext* aCx, JS::MutableHandle<JSObject*> aOuter);
+
+ // Get the embedder element for this BrowsingContext if the embedder is
+ // in-process, or null if it's not.
+ Element* GetEmbedderElement() const { return mEmbedderElement; }
+ void SetEmbedderElement(Element* aEmbedder);
+
+ // Return true if the type of the embedder element is either object
+ // or embed, false otherwise.
+ bool IsEmbedderTypeObjectOrEmbed();
+
+ // Called after the BrowingContext has been embedded in a FrameLoader. This
+ // happens after `SetEmbedderElement` is called on the BrowsingContext and
+ // after the BrowsingContext has been set on the FrameLoader.
+ void Embed();
+
+ // Get the outer window object for this BrowsingContext if it is in-process
+ // and still has a docshell, or null otherwise.
+ nsPIDOMWindowOuter* GetDOMWindow() const {
+ return mDocShell ? mDocShell->GetWindow() : nullptr;
+ }
+
+ uint64_t GetRequestContextId() const { return mRequestContextId; }
+
+ // Detach the current BrowsingContext from its parent, in both the
+ // child and the parent process.
+ void Detach(bool aFromIPC = false);
+
+ // Prepare this BrowsingContext to leave the current process.
+ void PrepareForProcessChange();
+
+ // Triggers a load in the process which currently owns this BrowsingContext.
+ nsresult LoadURI(nsDocShellLoadState* aLoadState,
+ bool aSetNavigating = false);
+
+ nsresult InternalLoad(nsDocShellLoadState* aLoadState);
+
+ // Removes the root document for this BrowsingContext tree from the BFCache,
+ // if it is cached, and returns true if it was.
+ bool RemoveRootFromBFCacheSync();
+
+ // If the load state includes a source BrowsingContext has been passed, check
+ // to see if we are sandboxed from it as the result of an iframe or CSP
+ // sandbox.
+ nsresult CheckSandboxFlags(nsDocShellLoadState* aLoadState);
+
+ void DisplayLoadError(const nsAString& aURI);
+
+ // Check that this browsing context is targetable for navigations (i.e. that
+ // it is neither closed, cached, nor discarded).
+ bool IsTargetable() const;
+
+ // True if this browsing context is inactive and is able to be suspended.
+ bool InactiveForSuspend() const;
+
+ const nsString& Name() const { return GetName(); }
+ void GetName(nsAString& aName) { aName = GetName(); }
+ bool NameEquals(const nsAString& aName) { return GetName().Equals(aName); }
+
+ Type GetType() const { return mType; }
+ bool IsContent() const { return mType == Type::Content; }
+ bool IsChrome() const { return !IsContent(); }
+
+ bool IsTop() const { return !GetParent(); }
+ bool IsSubframe() const { return !IsTop(); }
+
+ bool IsTopContent() const { return IsContent() && IsTop(); }
+
+ bool IsInSubtreeOf(BrowsingContext* aContext);
+
+ bool IsContentSubframe() const { return IsContent() && IsSubframe(); }
+
+ // non-zero
+ uint64_t Id() const { return mBrowsingContextId; }
+
+ BrowsingContext* GetParent() const;
+ BrowsingContext* Top();
+ const BrowsingContext* Top() const;
+
+ int32_t IndexOf(BrowsingContext* aChild);
+
+ // NOTE: Unlike `GetEmbedderWindowGlobal`, `GetParentWindowContext` does not
+ // cross toplevel content browser boundaries.
+ WindowContext* GetParentWindowContext() const { return mParentWindow; }
+ WindowContext* GetTopWindowContext() const;
+
+ already_AddRefed<BrowsingContext> GetOpener() const {
+ RefPtr<BrowsingContext> opener(Get(GetOpenerId()));
+ if (!mIsDiscarded && opener && !opener->mIsDiscarded) {
+ MOZ_DIAGNOSTIC_ASSERT(opener->mType == mType);
+ return opener.forget();
+ }
+ return nullptr;
+ }
+ void SetOpener(BrowsingContext* aOpener) {
+ MOZ_DIAGNOSTIC_ASSERT(!aOpener || aOpener->Group() == Group());
+ MOZ_DIAGNOSTIC_ASSERT(!aOpener || aOpener->mType == mType);
+
+ MOZ_ALWAYS_SUCCEEDS(SetOpenerId(aOpener ? aOpener->Id() : 0));
+ }
+
+ bool HasOpener() const;
+
+ bool HadOriginalOpener() const { return GetHadOriginalOpener(); }
+
+ // Returns true if the browsing context and top context are same origin
+ bool SameOriginWithTop();
+
+ /**
+ * When a new browsing context is opened by a sandboxed document, it needs to
+ * keep track of the browsing context that opened it, so that it can be
+ * navigated by it. This is the "one permitted sandboxed navigator".
+ */
+ already_AddRefed<BrowsingContext> GetOnePermittedSandboxedNavigator() const {
+ return Get(GetOnePermittedSandboxedNavigatorId());
+ }
+ [[nodiscard]] nsresult SetOnePermittedSandboxedNavigator(
+ BrowsingContext* aNavigator) {
+ if (GetOnePermittedSandboxedNavigatorId()) {
+ MOZ_ASSERT(false,
+ "One Permitted Sandboxed Navigator should only be set once.");
+ return NS_ERROR_FAILURE;
+ } else {
+ return SetOnePermittedSandboxedNavigatorId(aNavigator ? aNavigator->Id()
+ : 0);
+ }
+ }
+
+ uint32_t SandboxFlags() const { return GetSandboxFlags(); }
+
+ Span<RefPtr<BrowsingContext>> Children() const;
+ void GetChildren(nsTArray<RefPtr<BrowsingContext>>& aChildren);
+
+ Span<RefPtr<BrowsingContext>> NonSyntheticChildren() const;
+
+ const nsTArray<RefPtr<WindowContext>>& GetWindowContexts() {
+ return mWindowContexts;
+ }
+ void GetWindowContexts(nsTArray<RefPtr<WindowContext>>& aWindows);
+
+ void RegisterWindowContext(WindowContext* aWindow);
+ void UnregisterWindowContext(WindowContext* aWindow);
+ WindowContext* GetCurrentWindowContext() const {
+ return mCurrentWindowContext;
+ }
+
+ // Helpers to traverse this BrowsingContext subtree. Note that these will only
+ // traverse active contexts, and will ignore ones in the BFCache.
+ enum class WalkFlag {
+ Next,
+ Skip,
+ Stop,
+ };
+
+ /**
+ * Walk the browsing context tree in pre-order and call `aCallback`
+ * for every node in the tree. PreOrderWalk accepts two types of
+ * callbacks, either of the type `void(BrowsingContext*)` or
+ * `WalkFlag(BrowsingContext*)`. The former traverses the entire
+ * tree, but the latter let's you control if a sub-tree should be
+ * skipped by returning `WalkFlag::Skip`, completely abort traversal
+ * by returning `WalkFlag::Stop` or continue as normal with
+ * `WalkFlag::Next`.
+ */
+ template <typename F>
+ void PreOrderWalk(F&& aCallback) {
+ if constexpr (std::is_void_v<
+ typename std::invoke_result_t<F, BrowsingContext*>>) {
+ PreOrderWalkVoid(std::forward<F>(aCallback));
+ } else {
+ PreOrderWalkFlag(std::forward<F>(aCallback));
+ }
+ }
+
+ void PreOrderWalkVoid(const std::function<void(BrowsingContext*)>& aCallback);
+ WalkFlag PreOrderWalkFlag(
+ const std::function<WalkFlag(BrowsingContext*)>& aCallback);
+
+ void PostOrderWalk(const std::function<void(BrowsingContext*)>& aCallback);
+
+ void GetAllBrowsingContextsInSubtree(
+ nsTArray<RefPtr<BrowsingContext>>& aBrowsingContexts);
+
+ BrowsingContextGroup* Group() { return mGroup; }
+
+ // WebIDL bindings for nsILoadContext
+ Nullable<WindowProxyHolder> GetAssociatedWindow();
+ Nullable<WindowProxyHolder> GetTopWindow();
+ Element* GetTopFrameElement();
+ bool GetIsContent() { return IsContent(); }
+ void SetUsePrivateBrowsing(bool aUsePrivateBrowsing, ErrorResult& aError);
+ // Needs a different name to disambiguate from the xpidl method with
+ // the same signature but different return value.
+ void SetUseTrackingProtectionWebIDL(bool aUseTrackingProtection,
+ ErrorResult& aRv);
+ bool UseTrackingProtectionWebIDL() { return UseTrackingProtection(); }
+ void GetOriginAttributes(JSContext* aCx, JS::MutableHandle<JS::Value> aVal,
+ ErrorResult& aError);
+
+ bool InRDMPane() const { return GetInRDMPane(); }
+
+ bool WatchedByDevTools();
+ void SetWatchedByDevTools(bool aWatchedByDevTools, ErrorResult& aRv);
+
+ dom::TouchEventsOverride TouchEventsOverride() const;
+ bool TargetTopLevelLinkClicksToBlank() const;
+
+ bool FullscreenAllowed() const;
+
+ float FullZoom() const { return GetFullZoom(); }
+ float TextZoom() const { return GetTextZoom(); }
+
+ float OverrideDPPX() const { return Top()->GetOverrideDPPX(); }
+
+ bool SuspendMediaWhenInactive() const {
+ return GetSuspendMediaWhenInactive();
+ }
+
+ bool IsActive() const;
+ void SetIsActive(bool aIsActive, mozilla::ErrorResult& aRv) {
+ SetExplicitActive(aIsActive ? ExplicitActiveStatus::Active
+ : ExplicitActiveStatus::Inactive,
+ aRv);
+ }
+
+ bool ForceDesktopViewport() const { return GetForceDesktopViewport(); }
+
+ bool AuthorStyleDisabledDefault() const {
+ return GetAuthorStyleDisabledDefault();
+ }
+
+ bool UseGlobalHistory() const { return GetUseGlobalHistory(); }
+
+ bool GetIsActiveBrowserWindow();
+
+ void SetIsActiveBrowserWindow(bool aActive);
+
+ uint64_t BrowserId() const { return GetBrowserId(); }
+
+ bool IsLoading();
+
+ void GetEmbedderElementType(nsString& aElementType) {
+ if (GetEmbedderElementType().isSome()) {
+ aElementType = GetEmbedderElementType().value();
+ }
+ }
+
+ bool IsLoadingIdentifier(uint64_t aLoadIdentifer) {
+ if (GetCurrentLoadIdentifier() &&
+ *GetCurrentLoadIdentifier() == aLoadIdentifer) {
+ return true;
+ }
+ return false;
+ }
+
+ // ScreenOrientation related APIs
+ [[nodiscard]] nsresult SetCurrentOrientation(OrientationType aType,
+ float aAngle) {
+ Transaction txn;
+ txn.SetCurrentOrientationType(aType);
+ txn.SetCurrentOrientationAngle(aAngle);
+ return txn.Commit(this);
+ }
+
+ void SetRDMPaneOrientation(OrientationType aType, float aAngle,
+ ErrorResult& aRv) {
+ if (InRDMPane()) {
+ if (NS_FAILED(SetCurrentOrientation(aType, aAngle))) {
+ aRv.ThrowInvalidStateError("Browsing context is discarded");
+ }
+ }
+ }
+
+ void SetRDMPaneMaxTouchPoints(uint8_t aMaxTouchPoints, ErrorResult& aRv) {
+ if (InRDMPane()) {
+ SetMaxTouchPointsOverride(aMaxTouchPoints, aRv);
+ }
+ }
+
+ // Find a browsing context in this context's list of
+ // children. Doesn't consider the special names, '_self', '_parent',
+ // '_top', or '_blank'. Performs access control checks with regard to
+ // 'this'.
+ BrowsingContext* FindChildWithName(const nsAString& aName,
+ WindowGlobalChild& aRequestingWindow);
+
+ // Find a browsing context in the subtree rooted at 'this' Doesn't
+ // consider the special names, '_self', '_parent', '_top', or
+ // '_blank'.
+ //
+ // If passed, performs access control checks with regard to
+ // 'aRequestingContext', otherwise performs no access checks.
+ BrowsingContext* FindWithNameInSubtree(const nsAString& aName,
+ WindowGlobalChild* aRequestingWindow);
+
+ // Find the special browsing context if aName is '_self', '_parent',
+ // '_top', but not '_blank'. The latter is handled in FindWithName
+ BrowsingContext* FindWithSpecialName(const nsAString& aName,
+ WindowGlobalChild& aRequestingWindow);
+
+ nsISupports* GetParentObject() const;
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // Return the window proxy object that corresponds to this browsing context.
+ inline JSObject* GetWindowProxy() const { return mWindowProxy; }
+ inline JSObject* GetUnbarrieredWindowProxy() const {
+ return mWindowProxy.unbarrieredGet();
+ }
+
+ // Set the window proxy object that corresponds to this browsing context.
+ void SetWindowProxy(JS::Handle<JSObject*> aWindowProxy) {
+ mWindowProxy = aWindowProxy;
+ }
+
+ Nullable<WindowProxyHolder> GetWindow();
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(BrowsingContext)
+ NS_DECL_NSILOADCONTEXT
+
+ // Window APIs that are cross-origin-accessible (from the HTML spec).
+ WindowProxyHolder Window();
+ BrowsingContext* GetBrowsingContext() { return this; };
+ BrowsingContext* Self() { return this; }
+ void Location(JSContext* aCx, JS::MutableHandle<JSObject*> aLocation,
+ ErrorResult& aError);
+ void Close(CallerType aCallerType, ErrorResult& aError);
+ bool GetClosed(ErrorResult&) { return GetClosed(); }
+ void Focus(CallerType aCallerType, ErrorResult& aError);
+ void Blur(CallerType aCallerType, ErrorResult& aError);
+ WindowProxyHolder GetFrames(ErrorResult& aError);
+ int32_t Length() const { return Children().Length(); }
+ Nullable<WindowProxyHolder> GetTop(ErrorResult& aError);
+ void GetOpener(JSContext* aCx, JS::MutableHandle<JS::Value> aOpener,
+ ErrorResult& aError) const;
+ Nullable<WindowProxyHolder> GetParent(ErrorResult& aError);
+ void PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const nsAString& aTargetOrigin,
+ const Sequence<JSObject*>& aTransfer,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aError);
+ void PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+ const WindowPostMessageOptions& aOptions,
+ nsIPrincipal& aSubjectPrincipal, ErrorResult& aError);
+
+ void GetCustomUserAgent(nsAString& aUserAgent) {
+ aUserAgent = Top()->GetUserAgentOverride();
+ }
+ nsresult SetCustomUserAgent(const nsAString& aUserAgent);
+ void SetCustomUserAgent(const nsAString& aUserAgent, ErrorResult& aRv);
+
+ void GetCustomPlatform(nsAString& aPlatform) {
+ aPlatform = Top()->GetPlatformOverride();
+ }
+ void SetCustomPlatform(const nsAString& aPlatform, ErrorResult& aRv);
+
+ JSObject* WrapObject(JSContext* aCx);
+
+ static JSObject* ReadStructuredClone(JSContext* aCx,
+ JSStructuredCloneReader* aReader,
+ StructuredCloneHolder* aHolder);
+ bool WriteStructuredClone(JSContext* aCx, JSStructuredCloneWriter* aWriter,
+ StructuredCloneHolder* aHolder);
+
+ void StartDelayedAutoplayMediaComponents();
+
+ [[nodiscard]] nsresult ResetGVAutoplayRequestStatus();
+
+ /**
+ * Information required to initialize a BrowsingContext in another process.
+ * This object may be serialized over IPC.
+ */
+ struct IPCInitializer {
+ uint64_t mId = 0;
+
+ // IDs are used for Parent and Opener to allow for this object to be
+ // deserialized before other BrowsingContext in the BrowsingContextGroup
+ // have been initialized.
+ uint64_t mParentId = 0;
+ already_AddRefed<WindowContext> GetParent();
+ already_AddRefed<BrowsingContext> GetOpener();
+
+ uint64_t GetOpenerId() const { return mFields.Get<IDX_OpenerId>(); }
+
+ bool mWindowless = false;
+ bool mUseRemoteTabs = false;
+ bool mUseRemoteSubframes = false;
+ bool mCreatedDynamically = false;
+ int32_t mChildOffset = 0;
+ int32_t mSessionHistoryIndex = -1;
+ int32_t mSessionHistoryCount = 0;
+ OriginAttributes mOriginAttributes;
+ uint64_t mRequestContextId = 0;
+
+ FieldValues mFields;
+ };
+
+ // Create an IPCInitializer object for this BrowsingContext.
+ IPCInitializer GetIPCInitializer();
+
+ // Create a BrowsingContext object from over IPC.
+ static mozilla::ipc::IPCResult CreateFromIPC(IPCInitializer&& aInitializer,
+ BrowsingContextGroup* aGroup,
+ ContentParent* aOriginProcess);
+
+ bool IsSandboxedFrom(BrowsingContext* aTarget);
+
+ // The runnable will be called once there is idle time, or the top level
+ // page has been loaded or if a timeout has fired.
+ // Must be called only on the top level BrowsingContext.
+ void AddDeprioritizedLoadRunner(nsIRunnable* aRunner);
+
+ RefPtr<SessionStorageManager> GetSessionStorageManager();
+
+ // Set PendingInitialization on this BrowsingContext before the context has
+ // been attached.
+ void InitPendingInitialization(bool aPendingInitialization) {
+ MOZ_ASSERT(!EverAttached());
+ mFields.SetWithoutSyncing<IDX_PendingInitialization>(
+ aPendingInitialization);
+ }
+
+ bool CreatedDynamically() const { return mCreatedDynamically; }
+
+ // Returns true if this browsing context, or any ancestor to this browsing
+ // context was created dynamically. See also `CreatedDynamically`.
+ bool IsDynamic() const;
+
+ int32_t ChildOffset() const { return mChildOffset; }
+
+ bool GetOffsetPath(nsTArray<uint32_t>& aPath) const;
+
+ const OriginAttributes& OriginAttributesRef() { return mOriginAttributes; }
+ nsresult SetOriginAttributes(const OriginAttributes& aAttrs);
+
+ void GetHistoryID(JSContext* aCx, JS::MutableHandle<JS::Value> aVal,
+ ErrorResult& aError);
+
+ // This should only be called on the top browsing context.
+ void InitSessionHistory();
+
+ // This will only ever return a non-null value if called on the top browsing
+ // context.
+ ChildSHistory* GetChildSessionHistory();
+
+ bool CrossOriginIsolated();
+
+ // Check if it is allowed to open a popup from the current browsing
+ // context or any of its ancestors.
+ bool IsPopupAllowed();
+
+ // aCurrentURI is only required to be non-null if the load type contains the
+ // nsIWebNavigation::LOAD_FLAGS_IS_REFRESH flag and aInfo is for a refresh to
+ // the current URI.
+ void SessionHistoryCommit(const LoadingSessionHistoryInfo& aInfo,
+ uint32_t aLoadType, nsIURI* aCurrentURI,
+ SessionHistoryInfo* aPreviousActiveEntry,
+ bool aPersist, bool aCloneEntryChildren,
+ bool aChannelExpired, uint32_t aCacheKey);
+
+ // Set a new active entry on this browsing context. This is used for
+ // implementing history.pushState/replaceState and same document navigations.
+ // The new active entry will be linked to the current active entry through
+ // its shared state.
+ // aPreviousScrollPos is the scroll position that needs to be saved on the
+ // previous active entry.
+ // aUpdatedCacheKey is the cache key to set on the new active entry. If
+ // aUpdatedCacheKey is 0 then it will be ignored.
+ void SetActiveSessionHistoryEntry(const Maybe<nsPoint>& aPreviousScrollPos,
+ SessionHistoryInfo* aInfo,
+ uint32_t aLoadType,
+ uint32_t aUpdatedCacheKey,
+ bool aUpdateLength = true);
+
+ // Replace the active entry for this browsing context. This is used for
+ // implementing history.replaceState and same document navigations.
+ void ReplaceActiveSessionHistoryEntry(SessionHistoryInfo* aInfo);
+
+ // Removes dynamic child entries of the active entry.
+ void RemoveDynEntriesFromActiveSessionHistoryEntry();
+
+ // Removes entries corresponding to this BrowsingContext from session history.
+ void RemoveFromSessionHistory(const nsID& aChangeID);
+
+ void SetTriggeringAndInheritPrincipals(nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ uint64_t aLoadIdentifier);
+
+ // Return mTriggeringPrincipal and mPrincipalToInherit if the load id
+ // saved with the principal matches the current load identifier of this BC.
+ std::tuple<nsCOMPtr<nsIPrincipal>, nsCOMPtr<nsIPrincipal>>
+ GetTriggeringAndInheritPrincipalsForCurrentLoad();
+
+ void HistoryGo(int32_t aOffset, uint64_t aHistoryEpoch,
+ bool aRequireUserInteraction, bool aUserActivation,
+ std::function<void(Maybe<int32_t>&&)>&& aResolver);
+
+ bool ShouldUpdateSessionHistory(uint32_t aLoadType);
+
+ // Checks if we reached the rate limit for calls to Location and History API.
+ // The rate limit is controlled by the
+ // "dom.navigation.locationChangeRateLimit" prefs.
+ // Rate limit applies per BrowsingContext.
+ // Returns NS_OK if we are below the rate limit and increments the counter.
+ // Returns NS_ERROR_DOM_SECURITY_ERR if limit is reached.
+ nsresult CheckLocationChangeRateLimit(CallerType aCallerType);
+
+ void ResetLocationChangeRateLimit();
+
+ mozilla::dom::DisplayMode DisplayMode() { return Top()->GetDisplayMode(); }
+
+ // Returns canFocus, isActive
+ std::tuple<bool, bool> CanFocusCheck(CallerType aCallerType);
+
+ bool CanBlurCheck(CallerType aCallerType);
+
+ PopupBlocker::PopupControlState RevisePopupAbuseLevel(
+ PopupBlocker::PopupControlState aControl);
+
+ void IncrementHistoryEntryCountForBrowsingContext();
+
+ bool ServiceWorkersTestingEnabled() const {
+ return GetServiceWorkersTestingEnabled();
+ }
+
+ void GetMediumOverride(nsAString& aOverride) const {
+ aOverride = GetMediumOverride();
+ }
+
+ dom::PrefersColorSchemeOverride PrefersColorSchemeOverride() const {
+ return GetPrefersColorSchemeOverride();
+ }
+
+ bool IsInBFCache() const;
+
+ bool AllowJavascript() const { return GetAllowJavascript(); }
+ bool CanExecuteScripts() const { return mCanExecuteScripts; }
+
+ uint32_t DefaultLoadFlags() const { return GetDefaultLoadFlags(); }
+
+ // When request for page awake, it would increase a count that is used to
+ // prevent whole browsing context tree from being suspended. The request can
+ // be called multiple times. When calling the revoke, it would decrease the
+ // count and once the count reaches to zero, the browsing context tree could
+ // be suspended when the tree is inactive.
+ void RequestForPageAwake();
+ void RevokeForPageAwake();
+
+ void AddDiscardListener(std::function<void(uint64_t)>&& aListener);
+
+ bool IsAppTab() { return GetIsAppTab(); }
+ bool HasSiblings() { return GetHasSiblings(); }
+
+ bool IsUnderHiddenEmbedderElement() const {
+ return GetIsUnderHiddenEmbedderElement();
+ }
+
+ protected:
+ virtual ~BrowsingContext();
+ BrowsingContext(WindowContext* aParentWindow, BrowsingContextGroup* aGroup,
+ uint64_t aBrowsingContextId, Type aType, FieldValues&& aInit);
+
+ void SetChildSHistory(ChildSHistory* aChildSHistory);
+ already_AddRefed<ChildSHistory> ForgetChildSHistory() {
+ // FIXME Do we need to unset mHasSessionHistory?
+ return mChildSessionHistory.forget();
+ }
+
+ static bool ShouldAddEntryForRefresh(nsIURI* aCurrentURI,
+ const SessionHistoryInfo& aInfo);
+ static bool ShouldAddEntryForRefresh(nsIURI* aCurrentURI, nsIURI* aNewURI,
+ bool aHasPostData);
+
+ private:
+ mozilla::ipc::IPCResult Attach(bool aFromIPC, ContentParent* aOriginProcess);
+
+ // Recomputes whether we can execute scripts in this BrowsingContext based on
+ // the value of AllowJavascript() and whether scripts are allowed in the
+ // parent WindowContext. Called whenever the AllowJavascript() flag or the
+ // parent WC changes.
+ void RecomputeCanExecuteScripts();
+
+ // Is it early enough in the BrowsingContext's lifecycle that it is still
+ // OK to set OriginAttributes?
+ bool CanSetOriginAttributes();
+
+ void AssertOriginAttributesMatchPrivateBrowsing();
+
+ // Assert that the BrowsingContext's LoadContext flags appear coherent
+ // relative to related BrowsingContexts.
+ void AssertCoherentLoadContext();
+
+ friend class ::nsOuterWindowProxy;
+ friend class ::nsGlobalWindowOuter;
+ friend class WindowContext;
+
+ // Update the window proxy object that corresponds to this browsing context.
+ // This should be called from the window proxy object's objectMoved hook, if
+ // the object mWindowProxy points to was moved by the JS GC.
+ void UpdateWindowProxy(JSObject* obj, JSObject* old) {
+ if (mWindowProxy) {
+ MOZ_ASSERT(mWindowProxy == old);
+ mWindowProxy = obj;
+ }
+ }
+ // Clear the window proxy object that corresponds to this browsing context.
+ // This should be called if the window proxy object is finalized, or it can't
+ // reach its browsing context anymore.
+ void ClearWindowProxy() { mWindowProxy = nullptr; }
+
+ friend class Location;
+ friend class RemoteLocationProxy;
+ /**
+ * LocationProxy is the class for the native object stored as a private in a
+ * RemoteLocationProxy proxy representing a Location object in a different
+ * process. It forwards all operations to its BrowsingContext and aggregates
+ * its refcount to that BrowsingContext.
+ */
+ class LocationProxy final : public LocationBase {
+ public:
+ MozExternalRefCountType AddRef() { return GetBrowsingContext()->AddRef(); }
+ MozExternalRefCountType Release() {
+ return GetBrowsingContext()->Release();
+ }
+
+ protected:
+ friend class RemoteLocationProxy;
+ BrowsingContext* GetBrowsingContext() override {
+ return reinterpret_cast<BrowsingContext*>(
+ uintptr_t(this) - offsetof(BrowsingContext, mLocation));
+ }
+
+ already_AddRefed<nsIDocShell> GetDocShell() override { return nullptr; }
+ };
+
+ // Send a given `BaseTransaction` object to the correct remote.
+ void SendCommitTransaction(ContentParent* aParent,
+ const BaseTransaction& aTxn, uint64_t aEpoch);
+ void SendCommitTransaction(ContentChild* aChild, const BaseTransaction& aTxn,
+ uint64_t aEpoch);
+
+ bool CanSet(FieldIndex<IDX_SessionStoreEpoch>, uint32_t aEpoch,
+ ContentParent* aSource) {
+ return IsTop() && !aSource;
+ }
+
+ void DidSet(FieldIndex<IDX_SessionStoreEpoch>, uint32_t aOldValue);
+
+ using CanSetResult = syncedcontext::CanSetResult;
+
+ // Ensure that opener is in the same BrowsingContextGroup.
+ bool CanSet(FieldIndex<IDX_OpenerId>, const uint64_t& aValue,
+ ContentParent* aSource) {
+ if (aValue != 0) {
+ RefPtr<BrowsingContext> opener = Get(aValue);
+ return opener && opener->Group() == Group();
+ }
+ return true;
+ }
+
+ bool CanSet(FieldIndex<IDX_OpenerPolicy>,
+ nsILoadInfo::CrossOriginOpenerPolicy, ContentParent*);
+
+ bool CanSet(FieldIndex<IDX_ServiceWorkersTestingEnabled>, bool,
+ ContentParent*) {
+ return IsTop();
+ }
+
+ bool CanSet(FieldIndex<IDX_MediumOverride>, const nsString&, ContentParent*) {
+ return IsTop();
+ }
+
+ bool CanSet(FieldIndex<IDX_EmbedderColorSchemes>, const EmbedderColorSchemes&,
+ ContentParent* aSource) {
+ return CheckOnlyEmbedderCanSet(aSource);
+ }
+
+ bool CanSet(FieldIndex<IDX_PrefersColorSchemeOverride>,
+ dom::PrefersColorSchemeOverride, ContentParent*) {
+ return IsTop();
+ }
+
+ void DidSet(FieldIndex<IDX_InRDMPane>, bool aOldValue);
+
+ void DidSet(FieldIndex<IDX_EmbedderColorSchemes>,
+ EmbedderColorSchemes&& aOldValue);
+
+ void DidSet(FieldIndex<IDX_PrefersColorSchemeOverride>,
+ dom::PrefersColorSchemeOverride aOldValue);
+
+ template <typename Callback>
+ void WalkPresContexts(Callback&&);
+ void PresContextAffectingFieldChanged();
+
+ void DidSet(FieldIndex<IDX_MediumOverride>, nsString&& aOldValue);
+
+ bool CanSet(FieldIndex<IDX_SuspendMediaWhenInactive>, bool, ContentParent*) {
+ return IsTop();
+ }
+
+ bool CanSet(FieldIndex<IDX_TouchEventsOverrideInternal>,
+ dom::TouchEventsOverride aTouchEventsOverride,
+ ContentParent* aSource);
+ void DidSet(FieldIndex<IDX_TouchEventsOverrideInternal>,
+ dom::TouchEventsOverride&& aOldValue);
+
+ bool CanSet(FieldIndex<IDX_DisplayMode>, const enum DisplayMode& aDisplayMode,
+ ContentParent* aSource) {
+ return IsTop();
+ }
+
+ void DidSet(FieldIndex<IDX_DisplayMode>, enum DisplayMode aOldValue);
+
+ void DidSet(FieldIndex<IDX_ExplicitActive>, ExplicitActiveStatus aOldValue);
+
+ bool CanSet(FieldIndex<IDX_IsActiveBrowserWindowInternal>, const bool& aValue,
+ ContentParent* aSource);
+ void DidSet(FieldIndex<IDX_IsActiveBrowserWindowInternal>, bool aOldValue);
+
+ // Ensure that we only set the flag on the top level browsingContext.
+ // And then, we do a pre-order walk in the tree to refresh the
+ // volume of all media elements.
+ void DidSet(FieldIndex<IDX_Muted>);
+
+ bool CanSet(FieldIndex<IDX_IsAppTab>, const bool& aValue,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_HasSiblings>, const bool& aValue,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_ShouldDelayMediaFromStart>, const bool& aValue,
+ ContentParent* aSource);
+ void DidSet(FieldIndex<IDX_ShouldDelayMediaFromStart>, bool aOldValue);
+
+ bool CanSet(FieldIndex<IDX_OverrideDPPX>, const float& aValue,
+ ContentParent* aSource);
+ void DidSet(FieldIndex<IDX_OverrideDPPX>, float aOldValue);
+
+ bool CanSet(FieldIndex<IDX_EmbedderInnerWindowId>, const uint64_t& aValue,
+ ContentParent* aSource);
+
+ CanSetResult CanSet(FieldIndex<IDX_CurrentInnerWindowId>,
+ const uint64_t& aValue, ContentParent* aSource);
+
+ void DidSet(FieldIndex<IDX_CurrentInnerWindowId>);
+
+ bool CanSet(FieldIndex<IDX_ParentInitiatedNavigationEpoch>,
+ const uint64_t& aValue, ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_IsPopupSpam>, const bool& aValue,
+ ContentParent* aSource);
+
+ void DidSet(FieldIndex<IDX_IsPopupSpam>);
+
+ void DidSet(FieldIndex<IDX_GVAudibleAutoplayRequestStatus>);
+ void DidSet(FieldIndex<IDX_GVInaudibleAutoplayRequestStatus>);
+
+ void DidSet(FieldIndex<IDX_Loading>);
+
+ void DidSet(FieldIndex<IDX_AncestorLoading>);
+
+ void DidSet(FieldIndex<IDX_PlatformOverride>);
+ CanSetResult CanSet(FieldIndex<IDX_PlatformOverride>,
+ const nsString& aPlatformOverride,
+ ContentParent* aSource);
+
+ void DidSet(FieldIndex<IDX_UserAgentOverride>);
+ CanSetResult CanSet(FieldIndex<IDX_UserAgentOverride>,
+ const nsString& aUserAgent, ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_OrientationLock>,
+ const mozilla::hal::ScreenOrientation& aOrientationLock,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_EmbedderElementType>,
+ const Maybe<nsString>& aInitiatorType, ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_MessageManagerGroup>,
+ const nsString& aMessageManagerGroup, ContentParent* aSource);
+
+ CanSetResult CanSet(FieldIndex<IDX_AllowContentRetargeting>,
+ const bool& aAllowContentRetargeting,
+ ContentParent* aSource);
+ CanSetResult CanSet(FieldIndex<IDX_AllowContentRetargetingOnChildren>,
+ const bool& aAllowContentRetargetingOnChildren,
+ ContentParent* aSource);
+ CanSetResult CanSet(FieldIndex<IDX_AllowPlugins>, const bool& aAllowPlugins,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_FullscreenAllowedByOwner>, const bool&,
+ ContentParent*);
+ bool CanSet(FieldIndex<IDX_WatchedByDevToolsInternal>,
+ const bool& aWatchedByDevToolsInternal, ContentParent* aSource);
+
+ CanSetResult CanSet(FieldIndex<IDX_DefaultLoadFlags>,
+ const uint32_t& aDefaultLoadFlags,
+ ContentParent* aSource);
+ void DidSet(FieldIndex<IDX_DefaultLoadFlags>);
+
+ bool CanSet(FieldIndex<IDX_UseGlobalHistory>, const bool& aUseGlobalHistory,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_TargetTopLevelLinkClicksToBlankInternal>,
+ const bool& aTargetTopLevelLinkClicksToBlankInternal,
+ ContentParent* aSource);
+
+ void DidSet(FieldIndex<IDX_HasSessionHistory>, bool aOldValue);
+
+ bool CanSet(FieldIndex<IDX_BrowserId>, const uint32_t& aValue,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_UseErrorPages>, const bool& aUseErrorPages,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_PendingInitialization>, bool aNewValue,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_PageAwakeRequestCount>, uint32_t aNewValue,
+ ContentParent* aSource);
+ void DidSet(FieldIndex<IDX_PageAwakeRequestCount>, uint32_t aOldValue);
+
+ CanSetResult CanSet(FieldIndex<IDX_AllowJavascript>, bool aValue,
+ ContentParent* aSource);
+ void DidSet(FieldIndex<IDX_AllowJavascript>, bool aOldValue);
+
+ bool CanSet(FieldIndex<IDX_ForceDesktopViewport>, bool aValue,
+ ContentParent* aSource) {
+ return IsTop() && XRE_IsParentProcess();
+ }
+
+ // TODO(emilio): Maybe handle the flag being set dynamically without
+ // navigating? The previous code didn't do it tho, and a reload is probably
+ // worth it regardless.
+ // void DidSet(FieldIndex<IDX_ForceDesktopViewport>, bool aOldValue);
+
+ bool CanSet(FieldIndex<IDX_HasRestoreData>, bool aNewValue,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_IsUnderHiddenEmbedderElement>,
+ const bool& aIsUnderHiddenEmbedderElement,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_EmbeddedInContentDocument>, bool,
+ ContentParent* aSource) {
+ return CheckOnlyEmbedderCanSet(aSource);
+ }
+
+ template <size_t I, typename T>
+ bool CanSet(FieldIndex<I>, const T&, ContentParent*) {
+ return true;
+ }
+
+ // Overload `DidSet` to get notifications for a particular field being set.
+ //
+ // You can also overload the variant that gets the old value if you need it.
+ template <size_t I>
+ void DidSet(FieldIndex<I>) {}
+ template <size_t I, typename T>
+ void DidSet(FieldIndex<I>, T&& aOldValue) {}
+
+ void DidSet(FieldIndex<IDX_FullZoom>, float aOldValue);
+ void DidSet(FieldIndex<IDX_TextZoom>, float aOldValue);
+ void DidSet(FieldIndex<IDX_AuthorStyleDisabledDefault>);
+
+ bool CanSet(FieldIndex<IDX_IsInBFCache>, bool, ContentParent* aSource);
+ void DidSet(FieldIndex<IDX_IsInBFCache>);
+
+ void DidSet(FieldIndex<IDX_SyntheticDocumentContainer>);
+
+ void DidSet(FieldIndex<IDX_IsUnderHiddenEmbedderElement>, bool aOldValue);
+
+ // Allow if the process attemping to set field is the same as the owning
+ // process. Deprecated. New code that might use this should generally be moved
+ // to WindowContext or be settable only by the parent process.
+ CanSetResult LegacyRevertIfNotOwningOrParentProcess(ContentParent* aSource);
+
+ // True if the process attempting to set field is the same as the embedder's
+ // process.
+ bool CheckOnlyEmbedderCanSet(ContentParent* aSource);
+
+ void CreateChildSHistory();
+
+ using PrincipalWithLoadIdentifierTuple =
+ std::tuple<nsCOMPtr<nsIPrincipal>, uint64_t>;
+
+ nsIPrincipal* GetSavedPrincipal(
+ Maybe<PrincipalWithLoadIdentifierTuple> aPrincipalTuple);
+
+ // Type of BrowsingContent
+ const Type mType;
+
+ // Unique id identifying BrowsingContext
+ const uint64_t mBrowsingContextId;
+
+ RefPtr<BrowsingContextGroup> mGroup;
+ RefPtr<WindowContext> mParentWindow;
+ nsCOMPtr<nsIDocShell> mDocShell;
+
+ RefPtr<Element> mEmbedderElement;
+
+ nsTArray<RefPtr<WindowContext>> mWindowContexts;
+ RefPtr<WindowContext> mCurrentWindowContext;
+
+ // This is not a strong reference, but using a JS::Heap for that should be
+ // fine. The JSObject stored in here should be a proxy with a
+ // nsOuterWindowProxy handler, which will update the pointer from its
+ // objectMoved hook and clear it from its finalize hook.
+ JS::Heap<JSObject*> mWindowProxy;
+ LocationProxy mLocation;
+
+ // OriginAttributes for this BrowsingContext. May not be changed after this
+ // BrowsingContext is attached.
+ OriginAttributes mOriginAttributes;
+
+ // The network request context id, representing the nsIRequestContext
+ // associated with this BrowsingContext, and LoadGroups created for it.
+ uint64_t mRequestContextId = 0;
+
+ // Determines if private browsing should be used. May not be changed after
+ // this BrowsingContext is attached. This field matches mOriginAttributes in
+ // content Browsing Contexts. Currently treated as a binary value: 1 - in
+ // private mode, 0 - not private mode.
+ uint32_t mPrivateBrowsingId;
+
+ // True if Attach() has been called on this BrowsingContext already.
+ bool mEverAttached : 1;
+
+ // Is the most recent Document in this BrowsingContext loaded within this
+ // process? This may be true with a null mDocShell after the Window has been
+ // closed.
+ bool mIsInProcess : 1;
+
+ // Has this browsing context been discarded? BrowsingContexts should
+ // only be discarded once.
+ bool mIsDiscarded : 1;
+
+ // True if this BrowsingContext has no associated visible window, and is owned
+ // by whichever process created it, even if top-level.
+ bool mWindowless : 1;
+
+ // This is true if the BrowsingContext was out of process, but is now in
+ // process, and might have remote window proxies that need to be cleaned up.
+ bool mDanglingRemoteOuterProxies : 1;
+
+ // True if this BrowsingContext has been embedded in a element in this
+ // process.
+ bool mEmbeddedByThisProcess : 1;
+
+ // Determines if remote (out-of-process) tabs should be used. May not be
+ // changed after this BrowsingContext is attached.
+ bool mUseRemoteTabs : 1;
+
+ // Determines if out-of-process iframes should be used. May not be changed
+ // after this BrowsingContext is attached.
+ bool mUseRemoteSubframes : 1;
+
+ // True if this BrowsingContext is for a frame that was added dynamically.
+ bool mCreatedDynamically : 1;
+
+ // Set to true if the browsing context is in the bfcache and pagehide has been
+ // dispatched. When coming out from the bfcache, the value is set to false
+ // before dispatching pageshow.
+ bool mIsInBFCache : 1;
+
+ // Determines if we can execute scripts in this BrowsingContext. True if
+ // AllowJavascript() is true and script execution is allowed in the parent
+ // WindowContext.
+ bool mCanExecuteScripts : 1;
+
+ // The original offset of this context in its container. This property is -1
+ // if this BrowsingContext is for a frame that was added dynamically.
+ int32_t mChildOffset;
+
+ // The start time of user gesture, this is only available if the browsing
+ // context is in process.
+ TimeStamp mUserGestureStart;
+
+ // Triggering principal and principal to inherit need to point to original
+ // principal instances if the document is loaded in the same process as the
+ // process that initiated the load. When the load starts we save the
+ // principals along with the current load id.
+ // These principals correspond to the most recent load that took place within
+ // the process of this browsing context.
+ Maybe<PrincipalWithLoadIdentifierTuple> mTriggeringPrincipal;
+ Maybe<PrincipalWithLoadIdentifierTuple> mPrincipalToInherit;
+
+ class DeprioritizedLoadRunner
+ : public mozilla::Runnable,
+ public mozilla::LinkedListElement<DeprioritizedLoadRunner> {
+ public:
+ explicit DeprioritizedLoadRunner(nsIRunnable* aInner)
+ : Runnable("DeprioritizedLoadRunner"), mInner(aInner) {}
+
+ NS_IMETHOD Run() override {
+ if (mInner) {
+ RefPtr<nsIRunnable> inner = std::move(mInner);
+ inner->Run();
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsIRunnable> mInner;
+ };
+
+ mozilla::LinkedList<DeprioritizedLoadRunner> mDeprioritizedLoadRunner;
+
+ RefPtr<SessionStorageManager> mSessionStorageManager;
+ RefPtr<ChildSHistory> mChildSessionHistory;
+
+ nsTArray<std::function<void(uint64_t)>> mDiscardListeners;
+
+ // Counter and time span for rate limiting Location and History API calls.
+ // Used by CheckLocationChangeRateLimit. Do not apply cross-process.
+ uint32_t mLocationChangeRateLimitCount;
+ mozilla::TimeStamp mLocationChangeRateLimitSpanStart;
+};
+
+/**
+ * Gets a WindowProxy object for a BrowsingContext that lives in a different
+ * process (creating the object if it doesn't already exist). The WindowProxy
+ * object will be in the compartment that aCx is currently in. This should only
+ * be called if aContext doesn't hold a docshell, otherwise the BrowsingContext
+ * lives in this process, and a same-process WindowProxy should be used (see
+ * nsGlobalWindowOuter). This should only be called by bindings code, ToJSValue
+ * is the right API to get a WindowProxy for a BrowsingContext.
+ *
+ * If aTransplantTo is non-null, then the WindowProxy object will eventually be
+ * transplanted onto it. Therefore it should be used as the value in the remote
+ * proxy map.
+ */
+extern bool GetRemoteOuterWindowProxy(JSContext* aCx, BrowsingContext* aContext,
+ JS::Handle<JSObject*> aTransplantTo,
+ JS::MutableHandle<JSObject*> aRetVal);
+
+using BrowsingContextTransaction = BrowsingContext::BaseTransaction;
+using BrowsingContextInitializer = BrowsingContext::IPCInitializer;
+using MaybeDiscardedBrowsingContext = MaybeDiscarded<BrowsingContext>;
+
+// Specialize the transaction object for every translation unit it's used in.
+extern template class syncedcontext::Transaction<BrowsingContext>;
+
+} // namespace dom
+
+// Allow sending BrowsingContext objects over IPC.
+namespace ipc {
+template <>
+struct IPDLParamTraits<dom::MaybeDiscarded<dom::BrowsingContext>> {
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const dom::MaybeDiscarded<dom::BrowsingContext>& aParam);
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ dom::MaybeDiscarded<dom::BrowsingContext>* aResult);
+};
+
+template <>
+struct IPDLParamTraits<dom::BrowsingContext::IPCInitializer> {
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const dom::BrowsingContext::IPCInitializer& aInitializer);
+
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ dom::BrowsingContext::IPCInitializer* aInitializer);
+};
+} // namespace ipc
+} // namespace mozilla
+
+#endif // !defined(mozilla_dom_BrowsingContext_h)
diff --git a/docshell/base/BrowsingContextGroup.cpp b/docshell/base/BrowsingContextGroup.cpp
new file mode 100644
index 0000000000..66ef235271
--- /dev/null
+++ b/docshell/base/BrowsingContextGroup.cpp
@@ -0,0 +1,579 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/BrowsingContextGroup.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/InputTaskManager.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/BrowsingContextBinding.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/ThrottledEventQueue.h"
+#include "nsFocusManager.h"
+#include "nsTHashMap.h"
+
+namespace mozilla {
+namespace dom {
+
+// Maximum number of successive dialogs before we prompt users to disable
+// dialogs for this window.
+#define MAX_SUCCESSIVE_DIALOG_COUNT 5
+
+static StaticRefPtr<BrowsingContextGroup> sChromeGroup;
+
+static StaticAutoPtr<nsTHashMap<uint64_t, RefPtr<BrowsingContextGroup>>>
+ sBrowsingContextGroups;
+
+already_AddRefed<BrowsingContextGroup> BrowsingContextGroup::GetOrCreate(
+ uint64_t aId) {
+ if (!sBrowsingContextGroups) {
+ sBrowsingContextGroups =
+ new nsTHashMap<nsUint64HashKey, RefPtr<BrowsingContextGroup>>();
+ ClearOnShutdown(&sBrowsingContextGroups);
+ }
+
+ return do_AddRef(sBrowsingContextGroups->LookupOrInsertWith(
+ aId, [&aId] { return do_AddRef(new BrowsingContextGroup(aId)); }));
+}
+
+already_AddRefed<BrowsingContextGroup> BrowsingContextGroup::GetExisting(
+ uint64_t aId) {
+ if (sBrowsingContextGroups) {
+ return do_AddRef(sBrowsingContextGroups->Get(aId));
+ }
+ return nullptr;
+}
+
+// Only use 53 bits for the BrowsingContextGroup ID.
+static constexpr uint64_t kBrowsingContextGroupIdTotalBits = 53;
+static constexpr uint64_t kBrowsingContextGroupIdProcessBits = 22;
+static constexpr uint64_t kBrowsingContextGroupIdFlagBits = 1;
+static constexpr uint64_t kBrowsingContextGroupIdBits =
+ kBrowsingContextGroupIdTotalBits - kBrowsingContextGroupIdProcessBits -
+ kBrowsingContextGroupIdFlagBits;
+
+// IDs for the relevant flags
+static constexpr uint64_t kPotentiallyCrossOriginIsolatedFlag = 0x1;
+
+// The next ID value which will be used.
+static uint64_t sNextBrowsingContextGroupId = 1;
+
+// Generate the next ID with the given flags.
+static uint64_t GenerateBrowsingContextGroupId(uint64_t aFlags) {
+ MOZ_RELEASE_ASSERT(aFlags < (uint64_t(1) << kBrowsingContextGroupIdFlagBits));
+ uint64_t childId = XRE_IsContentProcess()
+ ? ContentChild::GetSingleton()->GetID()
+ : uint64_t(0);
+ MOZ_RELEASE_ASSERT(childId <
+ (uint64_t(1) << kBrowsingContextGroupIdProcessBits));
+ uint64_t id = sNextBrowsingContextGroupId++;
+ MOZ_RELEASE_ASSERT(id < (uint64_t(1) << kBrowsingContextGroupIdBits));
+
+ return (childId << (kBrowsingContextGroupIdBits +
+ kBrowsingContextGroupIdFlagBits)) |
+ (id << kBrowsingContextGroupIdFlagBits) | aFlags;
+}
+
+// Extract flags from the given ID.
+static uint64_t GetBrowsingContextGroupIdFlags(uint64_t aId) {
+ return aId & ((uint64_t(1) << kBrowsingContextGroupIdFlagBits) - 1);
+}
+
+uint64_t BrowsingContextGroup::CreateId(bool aPotentiallyCrossOriginIsolated) {
+ // We encode the potentially cross-origin isolated bit within the ID so that
+ // the information can be recovered whenever the group needs to be re-created
+ // due to e.g. being garbage-collected.
+ //
+ // In the future if we end up needing more complex information stored within
+ // the ID, we can consider converting it to a more complex type, like a
+ // string.
+ uint64_t flags =
+ aPotentiallyCrossOriginIsolated ? kPotentiallyCrossOriginIsolatedFlag : 0;
+ uint64_t id = GenerateBrowsingContextGroupId(flags);
+ MOZ_ASSERT(GetBrowsingContextGroupIdFlags(id) == flags);
+ return id;
+}
+
+already_AddRefed<BrowsingContextGroup> BrowsingContextGroup::Create(
+ bool aPotentiallyCrossOriginIsolated) {
+ return GetOrCreate(CreateId(aPotentiallyCrossOriginIsolated));
+}
+
+BrowsingContextGroup::BrowsingContextGroup(uint64_t aId) : mId(aId) {
+ mTimerEventQueue = ThrottledEventQueue::Create(
+ GetMainThreadSerialEventTarget(), "BrowsingContextGroup timer queue");
+
+ mWorkerEventQueue = ThrottledEventQueue::Create(
+ GetMainThreadSerialEventTarget(), "BrowsingContextGroup worker queue");
+}
+
+void BrowsingContextGroup::Register(nsISupports* aContext) {
+ MOZ_DIAGNOSTIC_ASSERT(!mDestroyed);
+ MOZ_DIAGNOSTIC_ASSERT(aContext);
+ mContexts.Insert(aContext);
+}
+
+void BrowsingContextGroup::Unregister(nsISupports* aContext) {
+ MOZ_DIAGNOSTIC_ASSERT(!mDestroyed);
+ MOZ_DIAGNOSTIC_ASSERT(aContext);
+ mContexts.Remove(aContext);
+
+ MaybeDestroy();
+}
+
+void BrowsingContextGroup::EnsureHostProcess(ContentParent* aProcess) {
+ MOZ_DIAGNOSTIC_ASSERT(!mDestroyed);
+ MOZ_DIAGNOSTIC_ASSERT(this != sChromeGroup,
+ "cannot have content host for chrome group");
+ MOZ_DIAGNOSTIC_ASSERT(aProcess->GetRemoteType() != PREALLOC_REMOTE_TYPE,
+ "cannot use preallocated process as host");
+ MOZ_DIAGNOSTIC_ASSERT(!aProcess->GetRemoteType().IsEmpty(),
+ "host process must have remote type");
+
+ // XXX: The diagnostic crashes in bug 1816025 seemed to come through caller
+ // ContentParent::GetNewOrUsedLaunchingBrowserProcess where we already
+ // did AssertAlive, so IsDead should be irrelevant here. Still it reads
+ // wrong that we ever might do AddBrowsingContextGroup if aProcess->IsDead().
+ if (aProcess->IsDead() ||
+ mHosts.WithEntryHandle(aProcess->GetRemoteType(), [&](auto&& entry) {
+ if (entry) {
+ // We know from bug 1816025 that this happens quite often and we have
+ // bug 1815480 on file that should harden the entire flow. But in the
+ // meantime we can just live with NOT replacing the found host
+ // process with a new one here if it is still alive.
+ MOZ_ASSERT(
+ entry.Data() == aProcess,
+ "There's already another host process for this remote type");
+ if (!entry.Data()->IsShuttingDown()) {
+ return false;
+ }
+ }
+
+ // This process wasn't already marked as our host, so insert it (or
+ // update if the old process is shutting down), and begin subscribing,
+ // unless the process is still launching.
+ entry.InsertOrUpdate(do_AddRef(aProcess));
+
+ return true;
+ })) {
+ aProcess->AddBrowsingContextGroup(this);
+ }
+}
+
+void BrowsingContextGroup::RemoveHostProcess(ContentParent* aProcess) {
+ MOZ_DIAGNOSTIC_ASSERT(aProcess);
+ MOZ_DIAGNOSTIC_ASSERT(aProcess->GetRemoteType() != PREALLOC_REMOTE_TYPE);
+ auto entry = mHosts.Lookup(aProcess->GetRemoteType());
+ if (entry && entry.Data() == aProcess) {
+ entry.Remove();
+ }
+}
+
+static void CollectContextInitializers(
+ Span<RefPtr<BrowsingContext>> aContexts,
+ nsTArray<SyncedContextInitializer>& aInits) {
+ // The order that we record these initializers is important, as it will keep
+ // the order that children are attached to their parent in the newly connected
+ // content process consistent.
+ for (auto& context : aContexts) {
+ aInits.AppendElement(context->GetIPCInitializer());
+ for (const auto& window : context->GetWindowContexts()) {
+ aInits.AppendElement(window->GetIPCInitializer());
+ CollectContextInitializers(window->Children(), aInits);
+ }
+ }
+}
+
+void BrowsingContextGroup::Subscribe(ContentParent* aProcess) {
+ MOZ_DIAGNOSTIC_ASSERT(!mDestroyed);
+ MOZ_DIAGNOSTIC_ASSERT(aProcess && !aProcess->IsLaunching());
+ MOZ_DIAGNOSTIC_ASSERT(aProcess->GetRemoteType() != PREALLOC_REMOTE_TYPE);
+
+ // Check if we're already subscribed to this process.
+ if (!mSubscribers.EnsureInserted(aProcess)) {
+ return;
+ }
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ // If the process is already marked as dead, we won't be the host, but may
+ // still need to subscribe to the process due to creating a popup while
+ // shutting down.
+ if (!aProcess->IsDead()) {
+ auto hostEntry = mHosts.Lookup(aProcess->GetRemoteType());
+ MOZ_DIAGNOSTIC_ASSERT(hostEntry && hostEntry.Data() == aProcess,
+ "Cannot subscribe a non-host process");
+ }
+#endif
+
+ // FIXME: This won't send non-discarded children of discarded BCs, but those
+ // BCs will be in the process of being destroyed anyway.
+ // FIXME: Prevent that situation from occuring.
+ nsTArray<SyncedContextInitializer> inits(mContexts.Count());
+ CollectContextInitializers(mToplevels, inits);
+
+ // Send all of our contexts to the target content process.
+ Unused << aProcess->SendRegisterBrowsingContextGroup(Id(), inits);
+
+ // If the focused or active BrowsingContexts belong in this group, tell the
+ // newly subscribed process.
+ if (nsFocusManager* fm = nsFocusManager::GetFocusManager()) {
+ BrowsingContext* focused = fm->GetFocusedBrowsingContextInChrome();
+ if (focused && focused->Group() != this) {
+ focused = nullptr;
+ }
+ BrowsingContext* active = fm->GetActiveBrowsingContextInChrome();
+ if (active && active->Group() != this) {
+ active = nullptr;
+ }
+
+ if (focused || active) {
+ Unused << aProcess->SendSetupFocusedAndActive(
+ focused, fm->GetActionIdForFocusedBrowsingContextInChrome(), active,
+ fm->GetActionIdForActiveBrowsingContextInChrome());
+ }
+ }
+}
+
+void BrowsingContextGroup::Unsubscribe(ContentParent* aProcess) {
+ MOZ_DIAGNOSTIC_ASSERT(aProcess);
+ MOZ_DIAGNOSTIC_ASSERT(aProcess->GetRemoteType() != PREALLOC_REMOTE_TYPE);
+ mSubscribers.Remove(aProcess);
+ aProcess->RemoveBrowsingContextGroup(this);
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ auto hostEntry = mHosts.Lookup(aProcess->GetRemoteType());
+ MOZ_DIAGNOSTIC_ASSERT(!hostEntry || hostEntry.Data() != aProcess,
+ "Unsubscribing existing host entry");
+#endif
+}
+
+ContentParent* BrowsingContextGroup::GetHostProcess(
+ const nsACString& aRemoteType) {
+ return mHosts.GetWeak(aRemoteType);
+}
+
+void BrowsingContextGroup::UpdateToplevelsSuspendedIfNeeded() {
+ if (!StaticPrefs::dom_suspend_inactive_enabled()) {
+ return;
+ }
+
+ mToplevelsSuspended = ShouldSuspendAllTopLevelContexts();
+ for (const auto& context : mToplevels) {
+ nsPIDOMWindowOuter* outer = context->GetDOMWindow();
+ if (!outer) {
+ continue;
+ }
+ nsCOMPtr<nsPIDOMWindowInner> inner = outer->GetCurrentInnerWindow();
+ if (!inner) {
+ continue;
+ }
+ if (mToplevelsSuspended && !inner->GetWasSuspendedByGroup()) {
+ inner->Suspend();
+ inner->SetWasSuspendedByGroup(true);
+ } else if (!mToplevelsSuspended && inner->GetWasSuspendedByGroup()) {
+ inner->Resume();
+ inner->SetWasSuspendedByGroup(false);
+ }
+ }
+}
+
+bool BrowsingContextGroup::ShouldSuspendAllTopLevelContexts() const {
+ for (const auto& context : mToplevels) {
+ if (!context->InactiveForSuspend()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+BrowsingContextGroup::~BrowsingContextGroup() { Destroy(); }
+
+void BrowsingContextGroup::Destroy() {
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ if (mDestroyed) {
+ MOZ_DIAGNOSTIC_ASSERT(mHosts.Count() == 0);
+ MOZ_DIAGNOSTIC_ASSERT(mSubscribers.Count() == 0);
+ MOZ_DIAGNOSTIC_ASSERT_IF(sBrowsingContextGroups,
+ !sBrowsingContextGroups->Contains(Id()) ||
+ *sBrowsingContextGroups->Lookup(Id()) != this);
+ }
+ mDestroyed = true;
+#endif
+
+ // Make sure to call `RemoveBrowsingContextGroup` for every entry in both
+ // `mHosts` and `mSubscribers`. This will visit most entries twice, but
+ // `RemoveBrowsingContextGroup` is safe to call multiple times.
+ for (const auto& entry : mHosts.Values()) {
+ entry->RemoveBrowsingContextGroup(this);
+ }
+ for (const auto& key : mSubscribers) {
+ key->RemoveBrowsingContextGroup(this);
+ }
+ mHosts.Clear();
+ mSubscribers.Clear();
+
+ if (sBrowsingContextGroups) {
+ sBrowsingContextGroups->Remove(Id());
+ }
+}
+
+void BrowsingContextGroup::AddKeepAlive() {
+ MOZ_DIAGNOSTIC_ASSERT(!mDestroyed);
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+ mKeepAliveCount++;
+}
+
+void BrowsingContextGroup::RemoveKeepAlive() {
+ MOZ_DIAGNOSTIC_ASSERT(!mDestroyed);
+ MOZ_DIAGNOSTIC_ASSERT(mKeepAliveCount > 0);
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+ mKeepAliveCount--;
+
+ MaybeDestroy();
+}
+
+auto BrowsingContextGroup::MakeKeepAlivePtr() -> KeepAlivePtr {
+ AddKeepAlive();
+ return KeepAlivePtr{do_AddRef(this).take()};
+}
+
+void BrowsingContextGroup::MaybeDestroy() {
+ // Once there are no synced contexts referencing a `BrowsingContextGroup`, we
+ // can clear subscribers and destroy this group. We only do this in the parent
+ // process, as it will orchestrate destruction of BCGs in content processes.
+ if (XRE_IsParentProcess() && mContexts.IsEmpty() && mKeepAliveCount == 0 &&
+ this != sChromeGroup) {
+ Destroy();
+
+ // We may have been deleted here, as `Destroy()` will clear references. Do
+ // not access any members at this point.
+ }
+}
+
+void BrowsingContextGroup::ChildDestroy() {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess());
+ MOZ_DIAGNOSTIC_ASSERT(!mDestroyed);
+ MOZ_DIAGNOSTIC_ASSERT(mContexts.IsEmpty());
+ Destroy();
+}
+
+nsISupports* BrowsingContextGroup::GetParentObject() const {
+ return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
+}
+
+JSObject* BrowsingContextGroup::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return BrowsingContextGroup_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsresult BrowsingContextGroup::QueuePostMessageEvent(
+ already_AddRefed<nsIRunnable>&& aRunnable) {
+ if (StaticPrefs::dom_separate_event_queue_for_post_message_enabled()) {
+ if (!mPostMessageEventQueue) {
+ nsCOMPtr<nsISerialEventTarget> target = GetMainThreadSerialEventTarget();
+ mPostMessageEventQueue = ThrottledEventQueue::Create(
+ target, "PostMessage Queue",
+ nsIRunnablePriority::PRIORITY_DEFERRED_TIMERS);
+ nsresult rv = mPostMessageEventQueue->SetIsPaused(false);
+ MOZ_ALWAYS_SUCCEEDS(rv);
+ }
+
+ // Ensure the queue is enabled
+ if (mPostMessageEventQueue->IsPaused()) {
+ nsresult rv = mPostMessageEventQueue->SetIsPaused(false);
+ MOZ_ALWAYS_SUCCEEDS(rv);
+ }
+
+ if (mPostMessageEventQueue) {
+ mPostMessageEventQueue->Dispatch(std::move(aRunnable),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+void BrowsingContextGroup::FlushPostMessageEvents() {
+ if (StaticPrefs::dom_separate_event_queue_for_post_message_enabled()) {
+ if (mPostMessageEventQueue) {
+ nsresult rv = mPostMessageEventQueue->SetIsPaused(true);
+ MOZ_ALWAYS_SUCCEEDS(rv);
+ nsCOMPtr<nsIRunnable> event;
+ while ((event = mPostMessageEventQueue->GetEvent())) {
+ NS_DispatchToMainThread(event.forget());
+ }
+ }
+ }
+}
+
+bool BrowsingContextGroup::HasActiveBC() {
+ for (auto& topLevelBC : Toplevels()) {
+ if (topLevelBC->IsActive()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void BrowsingContextGroup::IncInputEventSuspensionLevel() {
+ MOZ_ASSERT(StaticPrefs::dom_input_events_canSuspendInBCG_enabled());
+ if (!mHasIncreasedInputTaskManagerSuspensionLevel && HasActiveBC()) {
+ IncInputTaskManagerSuspensionLevel();
+ }
+ ++mInputEventSuspensionLevel;
+}
+
+void BrowsingContextGroup::DecInputEventSuspensionLevel() {
+ MOZ_ASSERT(StaticPrefs::dom_input_events_canSuspendInBCG_enabled());
+ --mInputEventSuspensionLevel;
+ if (!mInputEventSuspensionLevel &&
+ mHasIncreasedInputTaskManagerSuspensionLevel) {
+ DecInputTaskManagerSuspensionLevel();
+ }
+}
+
+void BrowsingContextGroup::DecInputTaskManagerSuspensionLevel() {
+ MOZ_ASSERT(StaticPrefs::dom_input_events_canSuspendInBCG_enabled());
+ MOZ_ASSERT(mHasIncreasedInputTaskManagerSuspensionLevel);
+
+ InputTaskManager::Get()->DecSuspensionLevel();
+ mHasIncreasedInputTaskManagerSuspensionLevel = false;
+}
+
+void BrowsingContextGroup::IncInputTaskManagerSuspensionLevel() {
+ MOZ_ASSERT(StaticPrefs::dom_input_events_canSuspendInBCG_enabled());
+ MOZ_ASSERT(!mHasIncreasedInputTaskManagerSuspensionLevel);
+ MOZ_ASSERT(HasActiveBC());
+
+ InputTaskManager::Get()->IncSuspensionLevel();
+ mHasIncreasedInputTaskManagerSuspensionLevel = true;
+}
+
+void BrowsingContextGroup::UpdateInputTaskManagerIfNeeded(bool aIsActive) {
+ MOZ_ASSERT(StaticPrefs::dom_input_events_canSuspendInBCG_enabled());
+ if (!aIsActive) {
+ if (mHasIncreasedInputTaskManagerSuspensionLevel) {
+ MOZ_ASSERT(mInputEventSuspensionLevel > 0);
+ if (!HasActiveBC()) {
+ DecInputTaskManagerSuspensionLevel();
+ }
+ }
+ } else {
+ if (mInputEventSuspensionLevel &&
+ !mHasIncreasedInputTaskManagerSuspensionLevel) {
+ IncInputTaskManagerSuspensionLevel();
+ }
+ }
+}
+
+/* static */
+BrowsingContextGroup* BrowsingContextGroup::GetChromeGroup() {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+ if (!sChromeGroup && XRE_IsParentProcess()) {
+ sChromeGroup = BrowsingContextGroup::Create();
+ ClearOnShutdown(&sChromeGroup);
+ }
+
+ return sChromeGroup;
+}
+
+void BrowsingContextGroup::GetDocGroups(nsTArray<DocGroup*>& aDocGroups) {
+ MOZ_ASSERT(NS_IsMainThread());
+ AppendToArray(aDocGroups, mDocGroups.Values());
+}
+
+already_AddRefed<DocGroup> BrowsingContextGroup::AddDocument(
+ const nsACString& aKey, Document* aDocument) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<DocGroup>& docGroup = mDocGroups.LookupOrInsertWith(
+ aKey, [&] { return DocGroup::Create(this, aKey); });
+
+ docGroup->AddDocument(aDocument);
+ return do_AddRef(docGroup);
+}
+
+void BrowsingContextGroup::RemoveDocument(Document* aDocument,
+ DocGroup* aDocGroup) {
+ MOZ_ASSERT(NS_IsMainThread());
+ RefPtr<DocGroup> docGroup = aDocGroup;
+ // Removing the last document in DocGroup might decrement the
+ // DocGroup BrowsingContextGroup's refcount to 0.
+ RefPtr<BrowsingContextGroup> kungFuDeathGrip(this);
+ docGroup->RemoveDocument(aDocument);
+
+ if (docGroup->IsEmpty()) {
+ mDocGroups.Remove(docGroup->GetKey());
+ }
+}
+
+already_AddRefed<BrowsingContextGroup> BrowsingContextGroup::Select(
+ WindowContext* aParent, BrowsingContext* aOpener) {
+ if (aParent) {
+ return do_AddRef(aParent->Group());
+ }
+ if (aOpener) {
+ return do_AddRef(aOpener->Group());
+ }
+ return Create();
+}
+
+void BrowsingContextGroup::GetAllGroups(
+ nsTArray<RefPtr<BrowsingContextGroup>>& aGroups) {
+ aGroups.Clear();
+ if (!sBrowsingContextGroups) {
+ return;
+ }
+
+ aGroups = ToArray(sBrowsingContextGroups->Values());
+}
+
+// For tests only.
+void BrowsingContextGroup::ResetDialogAbuseState() {
+ mDialogAbuseCount = 0;
+ // Reset the timer.
+ mLastDialogQuitTime =
+ TimeStamp::Now() -
+ TimeDuration::FromSeconds(DEFAULT_SUCCESSIVE_DIALOG_TIME_LIMIT);
+}
+
+bool BrowsingContextGroup::DialogsAreBeingAbused() {
+ if (mLastDialogQuitTime.IsNull() || nsContentUtils::IsCallerChrome()) {
+ return false;
+ }
+
+ TimeDuration dialogInterval(TimeStamp::Now() - mLastDialogQuitTime);
+ if (dialogInterval.ToSeconds() <
+ Preferences::GetInt("dom.successive_dialog_time_limit",
+ DEFAULT_SUCCESSIVE_DIALOG_TIME_LIMIT)) {
+ mDialogAbuseCount++;
+
+ return PopupBlocker::GetPopupControlState() > PopupBlocker::openAllowed ||
+ mDialogAbuseCount > MAX_SUCCESSIVE_DIALOG_COUNT;
+ }
+
+ // Reset the abuse counter
+ mDialogAbuseCount = 0;
+
+ return false;
+}
+
+bool BrowsingContextGroup::IsPotentiallyCrossOriginIsolated() {
+ return GetBrowsingContextGroupIdFlags(mId) &
+ kPotentiallyCrossOriginIsolatedFlag;
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(BrowsingContextGroup, mContexts,
+ mToplevels, mHosts, mSubscribers,
+ mTimerEventQueue, mWorkerEventQueue,
+ mDocGroups)
+
+} // namespace dom
+} // namespace mozilla
diff --git a/docshell/base/BrowsingContextGroup.h b/docshell/base/BrowsingContextGroup.h
new file mode 100644
index 0000000000..fb1f2e528c
--- /dev/null
+++ b/docshell/base/BrowsingContextGroup.h
@@ -0,0 +1,318 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_BrowsingContextGroup_h
+#define mozilla_dom_BrowsingContextGroup_h
+
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/FunctionRef.h"
+#include "nsRefPtrHashtable.h"
+#include "nsHashKeys.h"
+#include "nsTArray.h"
+#include "nsTHashSet.h"
+#include "nsWrapperCache.h"
+#include "nsXULAppAPI.h"
+
+namespace mozilla {
+class ThrottledEventQueue;
+
+namespace dom {
+
+// Amount of time allowed between alert/prompt/confirm before enabling
+// the stop dialog checkbox.
+#define DEFAULT_SUCCESSIVE_DIALOG_TIME_LIMIT 3 // 3 sec
+
+class BrowsingContext;
+class WindowContext;
+class ContentParent;
+class DocGroup;
+
+// A BrowsingContextGroup represents the Unit of Related Browsing Contexts in
+// the standard.
+//
+// A BrowsingContext may not hold references to other BrowsingContext objects
+// which are not in the same BrowsingContextGroup.
+class BrowsingContextGroup final : public nsWrapperCache {
+ public:
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(BrowsingContextGroup)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(BrowsingContextGroup)
+
+ // Interact with the list of synced contexts. This controls the lifecycle of
+ // the BrowsingContextGroup and contexts loaded within them.
+ void Register(nsISupports* aContext);
+ void Unregister(nsISupports* aContext);
+
+ // Control which processes will be used to host documents loaded in this
+ // BrowsingContextGroup. There should only ever be one host process per remote
+ // type.
+ //
+ // A new host process will be subscribed to the BrowsingContextGroup unless it
+ // is still launching, in which case it will subscribe itself when it is done
+ // launching.
+ void EnsureHostProcess(ContentParent* aProcess);
+
+ // A removed host process will no longer be used to host documents loaded in
+ // this BrowsingContextGroup.
+ void RemoveHostProcess(ContentParent* aProcess);
+
+ // Synchronize the current BrowsingContextGroup state down to the given
+ // content process, and continue updating it.
+ //
+ // You rarely need to call this directly, as it's automatically called by
+ // |EnsureHostProcess| as needed.
+ void Subscribe(ContentParent* aProcess);
+
+ // Stop synchronizing the current BrowsingContextGroup state down to a given
+ // content process. The content process must no longer be a host process.
+ void Unsubscribe(ContentParent* aProcess);
+
+ // Look up the process which should be used to host documents with this
+ // RemoteType. This will be a non-dead process associated with this
+ // BrowsingContextGroup, if possible.
+ ContentParent* GetHostProcess(const nsACString& aRemoteType);
+
+ // When a BrowsingContext is being discarded, we may want to keep the
+ // corresponding BrowsingContextGroup alive until the other process
+ // acknowledges that the BrowsingContext has been discarded. A `KeepAlive`
+ // will be added to the `BrowsingContextGroup`, delaying destruction.
+ void AddKeepAlive();
+ void RemoveKeepAlive();
+
+ // A `KeepAlivePtr` will hold both a strong reference to the
+ // `BrowsingContextGroup` and holds a `KeepAlive`. When the pointer is
+ // dropped, it will release both the strong reference and the keepalive.
+ struct KeepAliveDeleter {
+ void operator()(BrowsingContextGroup* aPtr) {
+ if (RefPtr<BrowsingContextGroup> ptr = already_AddRefed(aPtr)) {
+ ptr->RemoveKeepAlive();
+ }
+ }
+ };
+ using KeepAlivePtr = UniquePtr<BrowsingContextGroup, KeepAliveDeleter>;
+ KeepAlivePtr MakeKeepAlivePtr();
+
+ // Call when we want to check if we should suspend or resume all top level
+ // contexts.
+ void UpdateToplevelsSuspendedIfNeeded();
+
+ // Get a reference to the list of toplevel contexts in this
+ // BrowsingContextGroup.
+ nsTArray<RefPtr<BrowsingContext>>& Toplevels() { return mToplevels; }
+ void GetToplevels(nsTArray<RefPtr<BrowsingContext>>& aToplevels) {
+ aToplevels.AppendElements(mToplevels);
+ }
+
+ uint64_t Id() { return mId; }
+
+ nsISupports* GetParentObject() const;
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // Get or create a BrowsingContextGroup with the given ID.
+ static already_AddRefed<BrowsingContextGroup> GetOrCreate(uint64_t aId);
+ static already_AddRefed<BrowsingContextGroup> GetExisting(uint64_t aId);
+ static already_AddRefed<BrowsingContextGroup> Create(
+ bool aPotentiallyCrossOriginIsolated = false);
+ static already_AddRefed<BrowsingContextGroup> Select(
+ WindowContext* aParent, BrowsingContext* aOpener);
+
+ // Like `Create` but only generates and reserves a new ID without actually
+ // creating the BrowsingContextGroup object.
+ static uint64_t CreateId(bool aPotentiallyCrossOriginIsolated = false);
+
+ // For each 'ContentParent', except for 'aExcludedParent',
+ // associated with this group call 'aCallback'.
+ template <typename Func>
+ void EachOtherParent(ContentParent* aExcludedParent, Func&& aCallback) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+ for (const auto& key : mSubscribers) {
+ if (key != aExcludedParent) {
+ aCallback(key);
+ }
+ }
+ }
+
+ // For each 'ContentParent' associated with
+ // this group call 'aCallback'.
+ template <typename Func>
+ void EachParent(Func&& aCallback) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+ for (const auto& key : mSubscribers) {
+ aCallback(key);
+ }
+ }
+
+ nsresult QueuePostMessageEvent(already_AddRefed<nsIRunnable>&& aRunnable);
+
+ void FlushPostMessageEvents();
+
+ // Increase or decrease the suspension level in InputTaskManager
+ void UpdateInputTaskManagerIfNeeded(bool aIsActive);
+
+ static BrowsingContextGroup* GetChromeGroup();
+
+ void GetDocGroups(nsTArray<DocGroup*>& aDocGroups);
+
+ // Called by Document when a Document needs to be added to a DocGroup.
+ already_AddRefed<DocGroup> AddDocument(const nsACString& aKey,
+ Document* aDocument);
+
+ // Called by Document when a Document needs to be removed from a DocGroup.
+ // aDocGroup should be from aDocument. This is done to avoid the assert
+ // in GetDocGroup() which can crash when called during unlinking.
+ void RemoveDocument(Document* aDocument, DocGroup* aDocGroup);
+
+ mozilla::ThrottledEventQueue* GetTimerEventQueue() const {
+ return mTimerEventQueue;
+ }
+
+ mozilla::ThrottledEventQueue* GetWorkerEventQueue() const {
+ return mWorkerEventQueue;
+ }
+
+ void SetAreDialogsEnabled(bool aAreDialogsEnabled) {
+ mAreDialogsEnabled = aAreDialogsEnabled;
+ }
+
+ bool GetAreDialogsEnabled() { return mAreDialogsEnabled; }
+
+ bool GetDialogAbuseCount() { return mDialogAbuseCount; }
+
+ // For tests only.
+ void ResetDialogAbuseState();
+
+ bool DialogsAreBeingAbused();
+
+ TimeStamp GetLastDialogQuitTime() { return mLastDialogQuitTime; }
+
+ void SetLastDialogQuitTime(TimeStamp aLastDialogQuitTime) {
+ mLastDialogQuitTime = aLastDialogQuitTime;
+ }
+
+ // Whether all toplevel documents loaded in this group are allowed to be
+ // Cross-Origin Isolated.
+ //
+ // This does not reflect the actual value of `crossOriginIsolated`, as that
+ // also requires that the document is loaded within a `webCOOP+COEP` content
+ // process.
+ bool IsPotentiallyCrossOriginIsolated();
+
+ static void GetAllGroups(nsTArray<RefPtr<BrowsingContextGroup>>& aGroups);
+
+ void IncInputEventSuspensionLevel();
+ void DecInputEventSuspensionLevel();
+
+ void ChildDestroy();
+
+ private:
+ friend class CanonicalBrowsingContext;
+
+ explicit BrowsingContextGroup(uint64_t aId);
+ ~BrowsingContextGroup();
+
+ void MaybeDestroy();
+ void Destroy();
+
+ bool ShouldSuspendAllTopLevelContexts() const;
+
+ bool HasActiveBC();
+ void DecInputTaskManagerSuspensionLevel();
+ void IncInputTaskManagerSuspensionLevel();
+
+ uint64_t mId;
+
+ uint32_t mKeepAliveCount = 0;
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ bool mDestroyed = false;
+#endif
+
+ // A BrowsingContextGroup contains a series of {Browsing,Window}Context
+ // objects. They are addressed using a hashtable to avoid linear lookup when
+ // adding or removing elements from the set.
+ //
+ // FIXME: This list is only required over a counter to keep nested
+ // non-discarded contexts within discarded contexts alive. It should be
+ // removed in the future.
+ // FIXME: Consider introducing a better common base than `nsISupports`?
+ nsTHashSet<nsRefPtrHashKey<nsISupports>> mContexts;
+
+ // The set of toplevel browsing contexts in the current BrowsingContextGroup.
+ nsTArray<RefPtr<BrowsingContext>> mToplevels;
+
+ // Whether or not all toplevels in this group should be suspended
+ bool mToplevelsSuspended = false;
+
+ // DocGroups are thread-safe, and not able to be cycle collected,
+ // but we still keep strong pointers. When all Documents are removed
+ // from DocGroup (by the BrowsingContextGroup), the DocGroup is
+ // removed from here.
+ nsRefPtrHashtable<nsCStringHashKey, DocGroup> mDocGroups;
+
+ // The content process which will host documents in this BrowsingContextGroup
+ // which need to be loaded with a given remote type.
+ //
+ // A non-launching host process must also be a subscriber, though a launching
+ // host process may not yet be subscribed, and a subscriber need not be a host
+ // process.
+ nsRefPtrHashtable<nsCStringHashKey, ContentParent> mHosts;
+
+ nsTHashSet<nsRefPtrHashKey<ContentParent>> mSubscribers;
+
+ // A queue to store postMessage events during page load, the queue will be
+ // flushed once the page is loaded
+ RefPtr<mozilla::ThrottledEventQueue> mPostMessageEventQueue;
+
+ RefPtr<mozilla::ThrottledEventQueue> mTimerEventQueue;
+ RefPtr<mozilla::ThrottledEventQueue> mWorkerEventQueue;
+
+ // A counter to keep track of the input event suspension level of this BCG
+ //
+ // We use BrowsingContextGroup to emulate process isolation in Fission, so
+ // documents within the same the same BCG will behave like they share
+ // the same input task queue.
+ uint32_t mInputEventSuspensionLevel = 0;
+ // Whether this BCG has increased the suspension level in InputTaskManager
+ bool mHasIncreasedInputTaskManagerSuspensionLevel = false;
+
+ // This flag keeps track of whether dialogs are
+ // currently enabled for windows of this group.
+ // It's OK to have these local to each process only because even if
+ // frames from two/three different sites (and thus, processes) coordinate a
+ // dialog abuse attack, they would only the double/triple number of dialogs,
+ // as it is still limited per-site.
+ bool mAreDialogsEnabled = true;
+
+ // This counts the number of windows that have been opened in rapid succession
+ // (i.e. within dom.successive_dialog_time_limit of each other). It is reset
+ // to 0 once a dialog is opened after dom.successive_dialog_time_limit seconds
+ // have elapsed without any other dialogs.
+ // See comment for mAreDialogsEnabled as to why it's ok to have this local to
+ // each process.
+ uint32_t mDialogAbuseCount = 0;
+
+ // This holds the time when the last modal dialog was shown. If more than
+ // MAX_DIALOG_LIMIT dialogs are shown within the time span defined by
+ // dom.successive_dialog_time_limit, we show a checkbox or confirmation prompt
+ // to allow disabling of further dialogs from windows in this BC group.
+ TimeStamp mLastDialogQuitTime;
+};
+} // namespace dom
+} // namespace mozilla
+
+inline void ImplCycleCollectionUnlink(
+ mozilla::dom::BrowsingContextGroup::KeepAlivePtr& aField) {
+ aField = nullptr;
+}
+
+inline void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& aCallback,
+ mozilla::dom::BrowsingContextGroup::KeepAlivePtr& aField, const char* aName,
+ uint32_t aFlags = 0) {
+ CycleCollectionNoteChild(aCallback, aField.get(), aName, aFlags);
+}
+
+#endif // !defined(mozilla_dom_BrowsingContextGroup_h)
diff --git a/docshell/base/BrowsingContextWebProgress.cpp b/docshell/base/BrowsingContextWebProgress.cpp
new file mode 100644
index 0000000000..2c6baf4fa8
--- /dev/null
+++ b/docshell/base/BrowsingContextWebProgress.cpp
@@ -0,0 +1,432 @@
+/* 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 "BrowsingContextWebProgress.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/ErrorNames.h"
+#include "mozilla/Logging.h"
+#include "nsCOMPtr.h"
+#include "nsIWebProgressListener.h"
+#include "nsString.h"
+#include "nsPrintfCString.h"
+#include "nsIChannel.h"
+#include "xptinfo.h"
+
+namespace mozilla {
+namespace dom {
+
+static mozilla::LazyLogModule gBCWebProgressLog("BCWebProgress");
+
+static nsCString DescribeBrowsingContext(CanonicalBrowsingContext* aContext);
+static nsCString DescribeWebProgress(nsIWebProgress* aWebProgress);
+static nsCString DescribeRequest(nsIRequest* aRequest);
+static nsCString DescribeWebProgressFlags(uint32_t aFlags,
+ const nsACString& aPrefix);
+static nsCString DescribeError(nsresult aError);
+
+NS_IMPL_CYCLE_COLLECTION(BrowsingContextWebProgress, mCurrentBrowsingContext)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(BrowsingContextWebProgress)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(BrowsingContextWebProgress)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BrowsingContextWebProgress)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebProgress)
+ NS_INTERFACE_MAP_ENTRY(nsIWebProgress)
+ NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
+NS_INTERFACE_MAP_END
+
+BrowsingContextWebProgress::BrowsingContextWebProgress(
+ CanonicalBrowsingContext* aBrowsingContext)
+ : mCurrentBrowsingContext(aBrowsingContext) {}
+
+BrowsingContextWebProgress::~BrowsingContextWebProgress() = default;
+
+NS_IMETHODIMP BrowsingContextWebProgress::AddProgressListener(
+ nsIWebProgressListener* aListener, uint32_t aNotifyMask) {
+ nsWeakPtr listener = do_GetWeakReference(aListener);
+ if (!listener) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (mListenerInfoList.Contains(listener)) {
+ // The listener is already registered!
+ return NS_ERROR_FAILURE;
+ }
+
+ mListenerInfoList.AppendElement(ListenerInfo(listener, aNotifyMask));
+ return NS_OK;
+}
+
+NS_IMETHODIMP BrowsingContextWebProgress::RemoveProgressListener(
+ nsIWebProgressListener* aListener) {
+ nsWeakPtr listener = do_GetWeakReference(aListener);
+ if (!listener) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return mListenerInfoList.RemoveElement(listener) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP BrowsingContextWebProgress::GetBrowsingContextXPCOM(
+ BrowsingContext** aBrowsingContext) {
+ NS_IF_ADDREF(*aBrowsingContext = mCurrentBrowsingContext);
+ return NS_OK;
+}
+
+BrowsingContext* BrowsingContextWebProgress::GetBrowsingContext() {
+ return mCurrentBrowsingContext;
+}
+
+NS_IMETHODIMP BrowsingContextWebProgress::GetDOMWindow(
+ mozIDOMWindowProxy** aDOMWindow) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP BrowsingContextWebProgress::GetIsTopLevel(bool* aIsTopLevel) {
+ *aIsTopLevel = mCurrentBrowsingContext->IsTop();
+ return NS_OK;
+}
+
+NS_IMETHODIMP BrowsingContextWebProgress::GetIsLoadingDocument(
+ bool* aIsLoadingDocument) {
+ *aIsLoadingDocument = mIsLoadingDocument;
+ return NS_OK;
+}
+
+NS_IMETHODIMP BrowsingContextWebProgress::GetLoadType(uint32_t* aLoadType) {
+ *aLoadType = mLoadType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP BrowsingContextWebProgress::GetTarget(nsIEventTarget** aTarget) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP BrowsingContextWebProgress::SetTarget(nsIEventTarget* aTarget) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void BrowsingContextWebProgress::UpdateAndNotifyListeners(
+ uint32_t aFlag,
+ const std::function<void(nsIWebProgressListener*)>& aCallback) {
+ RefPtr<BrowsingContextWebProgress> kungFuDeathGrip = this;
+
+ ListenerArray::ForwardIterator iter(mListenerInfoList);
+ while (iter.HasMore()) {
+ ListenerInfo& info = iter.GetNext();
+ if (!(info.mNotifyMask & aFlag)) {
+ continue;
+ }
+
+ nsCOMPtr<nsIWebProgressListener> listener =
+ do_QueryReferent(info.mWeakListener);
+ if (!listener) {
+ mListenerInfoList.RemoveElement(info);
+ continue;
+ }
+
+ aCallback(listener);
+ }
+
+ mListenerInfoList.Compact();
+
+ // Notify the parent BrowsingContextWebProgress of the event to continue
+ // propagating.
+ auto* parent = mCurrentBrowsingContext->GetParent();
+ if (parent && parent->GetWebProgress()) {
+ aCallback(parent->GetWebProgress());
+ }
+}
+
+void BrowsingContextWebProgress::ContextDiscarded() {
+ if (!mIsLoadingDocument) {
+ return;
+ }
+
+ // If our BrowsingContext is being discarded while still loading a document,
+ // fire a synthetic `STATE_STOP` to end the ongoing load.
+ MOZ_LOG(gBCWebProgressLog, LogLevel::Info,
+ ("Discarded while loading %s",
+ DescribeBrowsingContext(mCurrentBrowsingContext).get()));
+
+ // This matches what nsDocLoader::doStopDocumentLoad does, except we don't
+ // bother notifying for `STATE_STOP | STATE_IS_DOCUMENT`,
+ // nsBrowserStatusFilter would filter it out before it gets to the parent
+ // process.
+ nsCOMPtr<nsIRequest> request = mLoadingDocumentRequest;
+ OnStateChange(this, request, STATE_STOP | STATE_IS_WINDOW | STATE_IS_NETWORK,
+ NS_ERROR_ABORT);
+}
+
+void BrowsingContextWebProgress::ContextReplaced(
+ CanonicalBrowsingContext* aNewContext) {
+ mCurrentBrowsingContext = aNewContext;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsIWebProgressListener
+
+NS_IMETHODIMP
+BrowsingContextWebProgress::OnStateChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aStateFlags,
+ nsresult aStatus) {
+ MOZ_LOG(
+ gBCWebProgressLog, LogLevel::Info,
+ ("OnStateChange(%s, %s, %s, %s) on %s",
+ DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
+ DescribeWebProgressFlags(aStateFlags, "STATE_"_ns).get(),
+ DescribeError(aStatus).get(),
+ DescribeBrowsingContext(mCurrentBrowsingContext).get()));
+
+ bool targetIsThis = aWebProgress == this;
+
+ // We may receive a request from an in-process nsDocShell which doesn't have
+ // `aWebProgress == this` which we should still consider as targeting
+ // ourselves.
+ if (nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aWebProgress);
+ docShell && docShell->GetBrowsingContext() == mCurrentBrowsingContext) {
+ targetIsThis = true;
+ aWebProgress->GetLoadType(&mLoadType);
+ }
+
+ // Track `mIsLoadingDocument` based on the notifications we've received so far
+ // if the nsIWebProgress being targeted is this one.
+ if (targetIsThis) {
+ constexpr uint32_t startFlags = nsIWebProgressListener::STATE_START |
+ nsIWebProgressListener::STATE_IS_DOCUMENT |
+ nsIWebProgressListener::STATE_IS_REQUEST |
+ nsIWebProgressListener::STATE_IS_WINDOW |
+ nsIWebProgressListener::STATE_IS_NETWORK;
+ constexpr uint32_t stopFlags = nsIWebProgressListener::STATE_STOP |
+ nsIWebProgressListener::STATE_IS_WINDOW;
+ constexpr uint32_t redirectFlags =
+ nsIWebProgressListener::STATE_IS_REDIRECTED_DOCUMENT;
+ if ((aStateFlags & startFlags) == startFlags) {
+ if (mIsLoadingDocument) {
+ // We received a duplicate `STATE_START` notification, silence this
+ // notification until we receive the matching `STATE_STOP` to not fire
+ // duplicate `STATE_START` notifications into frontend on process
+ // switches.
+ return NS_OK;
+ }
+ mIsLoadingDocument = true;
+
+ // Record the request we started the load with, so we can emit a synthetic
+ // `STATE_STOP` notification if the BrowsingContext is discarded before
+ // the notification arrives.
+ mLoadingDocumentRequest = aRequest;
+ } else if ((aStateFlags & stopFlags) == stopFlags) {
+ // We've received a `STATE_STOP` notification targeting this web progress,
+ // clear our loading document flag.
+ mIsLoadingDocument = false;
+ mLoadingDocumentRequest = nullptr;
+ } else if (mIsLoadingDocument &&
+ (aStateFlags & redirectFlags) == redirectFlags) {
+ // If we see a redirected document load, update the loading request which
+ // we'll emit the synthetic STATE_STOP notification with.
+ mLoadingDocumentRequest = aRequest;
+ }
+ }
+
+ UpdateAndNotifyListeners(
+ ((aStateFlags >> 16) & nsIWebProgress::NOTIFY_STATE_ALL),
+ [&](nsIWebProgressListener* listener) {
+ listener->OnStateChange(aWebProgress, aRequest, aStateFlags, aStatus);
+ });
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BrowsingContextWebProgress::OnProgressChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress) {
+ MOZ_LOG(
+ gBCWebProgressLog, LogLevel::Info,
+ ("OnProgressChange(%s, %s, %d, %d, %d, %d) on %s",
+ DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
+ aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress,
+ DescribeBrowsingContext(mCurrentBrowsingContext).get()));
+ UpdateAndNotifyListeners(
+ nsIWebProgress::NOTIFY_PROGRESS, [&](nsIWebProgressListener* listener) {
+ listener->OnProgressChange(aWebProgress, aRequest, aCurSelfProgress,
+ aMaxSelfProgress, aCurTotalProgress,
+ aMaxTotalProgress);
+ });
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BrowsingContextWebProgress::OnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsIURI* aLocation,
+ uint32_t aFlags) {
+ MOZ_LOG(
+ gBCWebProgressLog, LogLevel::Info,
+ ("OnLocationChange(%s, %s, %s, %s) on %s",
+ DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
+ aLocation ? aLocation->GetSpecOrDefault().get() : "<null>",
+ DescribeWebProgressFlags(aFlags, "LOCATION_CHANGE_"_ns).get(),
+ DescribeBrowsingContext(mCurrentBrowsingContext).get()));
+ UpdateAndNotifyListeners(
+ nsIWebProgress::NOTIFY_LOCATION, [&](nsIWebProgressListener* listener) {
+ listener->OnLocationChange(aWebProgress, aRequest, aLocation, aFlags);
+ });
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BrowsingContextWebProgress::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsresult aStatus,
+ const char16_t* aMessage) {
+ MOZ_LOG(
+ gBCWebProgressLog, LogLevel::Info,
+ ("OnStatusChange(%s, %s, %s, \"%s\") on %s",
+ DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
+ DescribeError(aStatus).get(), NS_ConvertUTF16toUTF8(aMessage).get(),
+ DescribeBrowsingContext(mCurrentBrowsingContext).get()));
+ UpdateAndNotifyListeners(
+ nsIWebProgress::NOTIFY_STATUS, [&](nsIWebProgressListener* listener) {
+ listener->OnStatusChange(aWebProgress, aRequest, aStatus, aMessage);
+ });
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BrowsingContextWebProgress::OnSecurityChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aState) {
+ MOZ_LOG(
+ gBCWebProgressLog, LogLevel::Info,
+ ("OnSecurityChange(%s, %s, %x) on %s",
+ DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
+ aState, DescribeBrowsingContext(mCurrentBrowsingContext).get()));
+ UpdateAndNotifyListeners(
+ nsIWebProgress::NOTIFY_SECURITY, [&](nsIWebProgressListener* listener) {
+ listener->OnSecurityChange(aWebProgress, aRequest, aState);
+ });
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BrowsingContextWebProgress::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aEvent) {
+ MOZ_LOG(
+ gBCWebProgressLog, LogLevel::Info,
+ ("OnContentBlockingEvent(%s, %s, %x) on %s",
+ DescribeWebProgress(aWebProgress).get(), DescribeRequest(aRequest).get(),
+ aEvent, DescribeBrowsingContext(mCurrentBrowsingContext).get()));
+ UpdateAndNotifyListeners(nsIWebProgress::NOTIFY_CONTENT_BLOCKING,
+ [&](nsIWebProgressListener* listener) {
+ listener->OnContentBlockingEvent(aWebProgress,
+ aRequest, aEvent);
+ });
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BrowsingContextWebProgress::GetDocumentRequest(nsIRequest** aRequest) {
+ NS_IF_ADDREF(*aRequest = mLoadingDocumentRequest);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Helper methods for notification logging
+
+static nsCString DescribeBrowsingContext(CanonicalBrowsingContext* aContext) {
+ if (!aContext) {
+ return "<null>"_ns;
+ }
+
+ nsCOMPtr<nsIURI> currentURI = aContext->GetCurrentURI();
+ return nsPrintfCString(
+ "{top:%d, id:%" PRIx64 ", url:%s}", aContext->IsTop(), aContext->Id(),
+ currentURI ? currentURI->GetSpecOrDefault().get() : "<null>");
+}
+
+static nsCString DescribeWebProgress(nsIWebProgress* aWebProgress) {
+ if (!aWebProgress) {
+ return "<null>"_ns;
+ }
+
+ bool isTopLevel = false;
+ aWebProgress->GetIsTopLevel(&isTopLevel);
+ bool isLoadingDocument = false;
+ aWebProgress->GetIsLoadingDocument(&isLoadingDocument);
+ return nsPrintfCString("{isTopLevel:%d, isLoadingDocument:%d}", isTopLevel,
+ isLoadingDocument);
+}
+
+static nsCString DescribeRequest(nsIRequest* aRequest) {
+ if (!aRequest) {
+ return "<null>"_ns;
+ }
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ if (!channel) {
+ return "<non-channel>"_ns;
+ }
+
+ nsCOMPtr<nsIURI> originalURI;
+ channel->GetOriginalURI(getter_AddRefs(originalURI));
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+
+ return nsPrintfCString(
+ "{URI:%s, originalURI:%s}",
+ uri ? uri->GetSpecOrDefault().get() : "<null>",
+ originalURI ? originalURI->GetSpecOrDefault().get() : "<null>");
+}
+
+static nsCString DescribeWebProgressFlags(uint32_t aFlags,
+ const nsACString& aPrefix) {
+ nsCString flags;
+ uint32_t remaining = aFlags;
+
+ // Hackily fetch the names of each constant from the XPT information used for
+ // reflecting it into JS. This doesn't need to be reliable and just exists as
+ // a logging aid.
+ //
+ // If a change to xpt in the future breaks this code, just delete it and
+ // replace it with a normal hex log.
+ if (const auto* ifaceInfo =
+ nsXPTInterfaceInfo::ByIID(NS_GET_IID(nsIWebProgressListener))) {
+ for (uint16_t i = 0; i < ifaceInfo->ConstantCount(); ++i) {
+ const auto& constInfo = ifaceInfo->Constant(i);
+ nsDependentCString name(constInfo.Name());
+ if (!StringBeginsWith(name, aPrefix)) {
+ continue;
+ }
+
+ if (remaining & constInfo.mValue) {
+ remaining &= ~constInfo.mValue;
+ if (!flags.IsEmpty()) {
+ flags.AppendLiteral("|");
+ }
+ flags.Append(name);
+ }
+ }
+ }
+ if (remaining != 0 || flags.IsEmpty()) {
+ if (!flags.IsEmpty()) {
+ flags.AppendLiteral("|");
+ }
+ flags.AppendInt(remaining, 16);
+ }
+ return flags;
+}
+
+static nsCString DescribeError(nsresult aError) {
+ nsCString name;
+ GetErrorName(aError, name);
+ return name;
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/docshell/base/BrowsingContextWebProgress.h b/docshell/base/BrowsingContextWebProgress.h
new file mode 100644
index 0000000000..b39fb2545d
--- /dev/null
+++ b/docshell/base/BrowsingContextWebProgress.h
@@ -0,0 +1,98 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_BrowsingContextWebProgress_h
+#define mozilla_dom_BrowsingContextWebProgress_h
+
+#include "nsIWebProgress.h"
+#include "nsIWebProgressListener.h"
+#include "nsTObserverArray.h"
+#include "nsWeakReference.h"
+#include "nsCycleCollectionParticipant.h"
+
+namespace mozilla::dom {
+
+class CanonicalBrowsingContext;
+
+/// Object acting as the nsIWebProgress instance for a BrowsingContext over its
+/// lifetime.
+///
+/// An active toplevel CanonicalBrowsingContext will always have a
+/// BrowsingContextWebProgress, which will be moved between contexts as
+/// BrowsingContextGroup-changing loads are performed.
+///
+/// Subframes will only have a `BrowsingContextWebProgress` if they are loaded
+/// in a content process, and will use the nsDocShell instead if they are loaded
+/// in the parent process, as parent process documents cannot have or be
+/// out-of-process iframes.
+class BrowsingContextWebProgress final : public nsIWebProgress,
+ public nsIWebProgressListener {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(BrowsingContextWebProgress,
+ nsIWebProgress)
+ NS_DECL_NSIWEBPROGRESS
+ NS_DECL_NSIWEBPROGRESSLISTENER
+
+ explicit BrowsingContextWebProgress(
+ CanonicalBrowsingContext* aBrowsingContext);
+
+ struct ListenerInfo {
+ ListenerInfo(nsIWeakReference* aListener, unsigned long aNotifyMask)
+ : mWeakListener(aListener), mNotifyMask(aNotifyMask) {}
+
+ bool operator==(const ListenerInfo& aOther) const {
+ return mWeakListener == aOther.mWeakListener;
+ }
+ bool operator==(const nsWeakPtr& aOther) const {
+ return mWeakListener == aOther;
+ }
+
+ // Weak pointer for the nsIWebProgressListener...
+ nsWeakPtr mWeakListener;
+
+ // Mask indicating which notifications the listener wants to receive.
+ unsigned long mNotifyMask;
+ };
+
+ void ContextDiscarded();
+ void ContextReplaced(CanonicalBrowsingContext* aNewContext);
+
+ void SetLoadType(uint32_t aLoadType) { mLoadType = aLoadType; }
+
+ private:
+ virtual ~BrowsingContextWebProgress();
+
+ void UpdateAndNotifyListeners(
+ uint32_t aFlag,
+ const std::function<void(nsIWebProgressListener*)>& aCallback);
+
+ using ListenerArray = nsAutoTObserverArray<ListenerInfo, 4>;
+ ListenerArray mListenerInfoList;
+
+ // The current BrowsingContext which owns this BrowsingContextWebProgress.
+ // This context may change during navigations and may not be fully attached at
+ // all times.
+ RefPtr<CanonicalBrowsingContext> mCurrentBrowsingContext;
+
+ // The current request being actively loaded by the BrowsingContext. Only set
+ // while mIsLoadingDocument is true, and is used to fire STATE_STOP
+ // notifications if the BrowsingContext is discarded while the load is
+ // ongoing.
+ nsCOMPtr<nsIRequest> mLoadingDocumentRequest;
+
+ // The most recent load type observed for this BrowsingContextWebProgress.
+ uint32_t mLoadType = 0;
+
+ // Are we currently in the process of loading a document? This is true between
+ // the `STATE_START` notification from content and the `STATE_STOP`
+ // notification being received. Duplicate `STATE_START` events may be
+ // discarded while loading a document to avoid noise caused by process
+ // switches.
+ bool mIsLoadingDocument = false;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_BrowsingContextWebProgress_h
diff --git a/docshell/base/CanonicalBrowsingContext.cpp b/docshell/base/CanonicalBrowsingContext.cpp
new file mode 100644
index 0000000000..b6bedf7ba9
--- /dev/null
+++ b/docshell/base/CanonicalBrowsingContext.cpp
@@ -0,0 +1,3069 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+
+#include "ErrorList.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/BrowsingContextBinding.h"
+#include "mozilla/dom/BrowsingContextGroup.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/EventTarget.h"
+#include "mozilla/dom/PBrowserParent.h"
+#include "mozilla/dom/PBackgroundSessionStorageCache.h"
+#include "mozilla/dom/PWindowGlobalParent.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/dom/ContentProcessManager.h"
+#include "mozilla/dom/MediaController.h"
+#include "mozilla/dom/MediaControlService.h"
+#include "mozilla/dom/ContentPlaybackController.h"
+#include "mozilla/dom/SessionStorageManager.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#ifdef NS_PRINTING
+# include "mozilla/layout/RemotePrintJobParent.h"
+#endif
+#include "mozilla/net/DocumentLoadListener.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_docshell.h"
+#include "mozilla/StaticPrefs_fission.h"
+#include "mozilla/Telemetry.h"
+#include "nsILayoutHistoryState.h"
+#include "nsIPrintSettings.h"
+#include "nsIPrintSettingsService.h"
+#include "nsISupports.h"
+#include "nsIWebNavigation.h"
+#include "mozilla/MozPromiseInlines.h"
+#include "nsDocShell.h"
+#include "nsFrameLoader.h"
+#include "nsFrameLoaderOwner.h"
+#include "nsGlobalWindowOuter.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsIXULRuntime.h"
+#include "nsNetUtil.h"
+#include "nsSHistory.h"
+#include "nsSecureBrowserUI.h"
+#include "nsQueryObject.h"
+#include "nsBrowserStatusFilter.h"
+#include "nsIBrowser.h"
+#include "nsTHashSet.h"
+#include "SessionStoreFunctions.h"
+#include "nsIXPConnect.h"
+#include "nsImportModule.h"
+#include "UnitTransforms.h"
+
+using namespace mozilla::ipc;
+
+extern mozilla::LazyLogModule gAutoplayPermissionLog;
+extern mozilla::LazyLogModule gSHLog;
+extern mozilla::LazyLogModule gSHIPBFCacheLog;
+
+#define AUTOPLAY_LOG(msg, ...) \
+ MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
+
+static mozilla::LazyLogModule sPBContext("PBContext");
+
+// Global count of canonical browsing contexts with the private attribute set
+static uint32_t gNumberOfPrivateContexts = 0;
+
+// Current parent process epoch for parent initiated navigations
+static uint64_t gParentInitiatedNavigationEpoch = 0;
+
+static void IncreasePrivateCount() {
+ gNumberOfPrivateContexts++;
+ MOZ_LOG(sPBContext, mozilla::LogLevel::Debug,
+ ("%s: Private browsing context count %d -> %d", __func__,
+ gNumberOfPrivateContexts - 1, gNumberOfPrivateContexts));
+ if (gNumberOfPrivateContexts > 1) {
+ return;
+ }
+
+ static bool sHasSeenPrivateContext = false;
+ if (!sHasSeenPrivateContext) {
+ sHasSeenPrivateContext = true;
+ mozilla::Telemetry::ScalarSet(
+ mozilla::Telemetry::ScalarID::DOM_PARENTPROCESS_PRIVATE_WINDOW_USED,
+ true);
+ }
+}
+
+static void DecreasePrivateCount() {
+ MOZ_ASSERT(gNumberOfPrivateContexts > 0);
+ gNumberOfPrivateContexts--;
+
+ MOZ_LOG(sPBContext, mozilla::LogLevel::Debug,
+ ("%s: Private browsing context count %d -> %d", __func__,
+ gNumberOfPrivateContexts + 1, gNumberOfPrivateContexts));
+ if (!gNumberOfPrivateContexts &&
+ !mozilla::StaticPrefs::browser_privatebrowsing_autostart()) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ MOZ_LOG(sPBContext, mozilla::LogLevel::Debug,
+ ("%s: last-pb-context-exited fired", __func__));
+ observerService->NotifyObservers(nullptr, "last-pb-context-exited",
+ nullptr);
+ }
+ }
+}
+
+namespace mozilla {
+namespace dom {
+
+extern mozilla::LazyLogModule gUserInteractionPRLog;
+
+#define USER_ACTIVATION_LOG(msg, ...) \
+ MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
+
+CanonicalBrowsingContext::CanonicalBrowsingContext(WindowContext* aParentWindow,
+ BrowsingContextGroup* aGroup,
+ uint64_t aBrowsingContextId,
+ uint64_t aOwnerProcessId,
+ uint64_t aEmbedderProcessId,
+ BrowsingContext::Type aType,
+ FieldValues&& aInit)
+ : BrowsingContext(aParentWindow, aGroup, aBrowsingContextId, aType,
+ std::move(aInit)),
+ mProcessId(aOwnerProcessId),
+ mEmbedderProcessId(aEmbedderProcessId),
+ mPermanentKey(JS::NullValue()) {
+ // You are only ever allowed to create CanonicalBrowsingContexts in the
+ // parent process.
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
+
+ // The initial URI in a BrowsingContext is always "about:blank".
+ MOZ_ALWAYS_SUCCEEDS(
+ NS_NewURI(getter_AddRefs(mCurrentRemoteURI), "about:blank"));
+
+ mozilla::HoldJSObjects(this);
+}
+
+CanonicalBrowsingContext::~CanonicalBrowsingContext() {
+ mPermanentKey.setNull();
+
+ mozilla::DropJSObjects(this);
+
+ if (mSessionHistory) {
+ mSessionHistory->SetBrowsingContext(nullptr);
+ }
+}
+
+/* static */
+already_AddRefed<CanonicalBrowsingContext> CanonicalBrowsingContext::Get(
+ uint64_t aId) {
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
+ return BrowsingContext::Get(aId).downcast<CanonicalBrowsingContext>();
+}
+
+/* static */
+CanonicalBrowsingContext* CanonicalBrowsingContext::Cast(
+ BrowsingContext* aContext) {
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
+ return static_cast<CanonicalBrowsingContext*>(aContext);
+}
+
+/* static */
+const CanonicalBrowsingContext* CanonicalBrowsingContext::Cast(
+ const BrowsingContext* aContext) {
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
+ return static_cast<const CanonicalBrowsingContext*>(aContext);
+}
+
+already_AddRefed<CanonicalBrowsingContext> CanonicalBrowsingContext::Cast(
+ already_AddRefed<BrowsingContext>&& aContext) {
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
+ return aContext.downcast<CanonicalBrowsingContext>();
+}
+
+ContentParent* CanonicalBrowsingContext::GetContentParent() const {
+ if (mProcessId == 0) {
+ return nullptr;
+ }
+
+ ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
+ if (!cpm) {
+ return nullptr;
+ }
+ return cpm->GetContentProcessById(ContentParentId(mProcessId));
+}
+
+void CanonicalBrowsingContext::GetCurrentRemoteType(nsACString& aRemoteType,
+ ErrorResult& aRv) const {
+ // If we're in the parent process, dump out the void string.
+ if (mProcessId == 0) {
+ aRemoteType = NOT_REMOTE_TYPE;
+ return;
+ }
+
+ ContentParent* cp = GetContentParent();
+ if (!cp) {
+ aRv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ aRemoteType = cp->GetRemoteType();
+}
+
+void CanonicalBrowsingContext::SetOwnerProcessId(uint64_t aProcessId) {
+ MOZ_LOG(GetLog(), LogLevel::Debug,
+ ("SetOwnerProcessId for 0x%08" PRIx64 " (0x%08" PRIx64
+ " -> 0x%08" PRIx64 ")",
+ Id(), mProcessId, aProcessId));
+
+ mProcessId = aProcessId;
+}
+
+nsISecureBrowserUI* CanonicalBrowsingContext::GetSecureBrowserUI() {
+ if (!IsTop()) {
+ return nullptr;
+ }
+ if (!mSecureBrowserUI) {
+ mSecureBrowserUI = new nsSecureBrowserUI(this);
+ }
+ return mSecureBrowserUI;
+}
+
+namespace {
+// The DocShellProgressBridge is attached to a root content docshell loaded in
+// the parent process. Notifications are paired up with the docshell which they
+// came from, so that they can be fired to the correct
+// BrowsingContextWebProgress and bubble through this tree separately.
+//
+// Notifications are filtered by a nsBrowserStatusFilter before being received
+// by the DocShellProgressBridge.
+class DocShellProgressBridge : public nsIWebProgressListener {
+ public:
+ NS_DECL_ISUPPORTS
+ // NOTE: This relies in the expansion of `NS_FORWARD_SAFE` and all listener
+ // methods accepting an `aWebProgress` argument. If this changes in the
+ // future, this may need to be written manually.
+ NS_FORWARD_SAFE_NSIWEBPROGRESSLISTENER(GetTargetContext(aWebProgress))
+
+ explicit DocShellProgressBridge(uint64_t aTopContextId)
+ : mTopContextId(aTopContextId) {}
+
+ private:
+ virtual ~DocShellProgressBridge() = default;
+
+ nsIWebProgressListener* GetTargetContext(nsIWebProgress* aWebProgress) {
+ RefPtr<CanonicalBrowsingContext> context;
+ if (nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aWebProgress)) {
+ context = docShell->GetBrowsingContext()->Canonical();
+ } else {
+ context = CanonicalBrowsingContext::Get(mTopContextId);
+ }
+ return context && !context->IsDiscarded() ? context->GetWebProgress()
+ : nullptr;
+ }
+
+ uint64_t mTopContextId = 0;
+};
+
+NS_IMPL_ISUPPORTS(DocShellProgressBridge, nsIWebProgressListener)
+} // namespace
+
+void CanonicalBrowsingContext::MaybeAddAsProgressListener(
+ nsIWebProgress* aWebProgress) {
+ // Only add as a listener if the created docshell is a toplevel content
+ // docshell. We'll get notifications for all of our subframes through a single
+ // listener.
+ if (!IsTopContent()) {
+ return;
+ }
+
+ if (!mDocShellProgressBridge) {
+ mDocShellProgressBridge = new DocShellProgressBridge(Id());
+ mStatusFilter = new nsBrowserStatusFilter();
+ mStatusFilter->AddProgressListener(mDocShellProgressBridge,
+ nsIWebProgress::NOTIFY_ALL);
+ }
+
+ aWebProgress->AddProgressListener(mStatusFilter, nsIWebProgress::NOTIFY_ALL);
+}
+
+void CanonicalBrowsingContext::ReplacedBy(
+ CanonicalBrowsingContext* aNewContext,
+ const NavigationIsolationOptions& aRemotenessOptions) {
+ MOZ_ASSERT(!aNewContext->mWebProgress);
+ MOZ_ASSERT(!aNewContext->mSessionHistory);
+ MOZ_ASSERT(IsTop() && aNewContext->IsTop());
+
+ mIsReplaced = true;
+ aNewContext->mIsReplaced = false;
+
+ if (mStatusFilter) {
+ mStatusFilter->RemoveProgressListener(mDocShellProgressBridge);
+ mStatusFilter = nullptr;
+ }
+
+ mWebProgress->ContextReplaced(aNewContext);
+ aNewContext->mWebProgress = std::move(mWebProgress);
+
+ // Use the Transaction for the fields which need to be updated whether or not
+ // the new context has been attached before.
+ // SetWithoutSyncing can be used if context hasn't been attached.
+ Transaction txn;
+ txn.SetBrowserId(GetBrowserId());
+ txn.SetIsAppTab(GetIsAppTab());
+ txn.SetHasSiblings(GetHasSiblings());
+ txn.SetHistoryID(GetHistoryID());
+ txn.SetExplicitActive(GetExplicitActive());
+ txn.SetEmbedderColorSchemes(GetEmbedderColorSchemes());
+ txn.SetHasRestoreData(GetHasRestoreData());
+ txn.SetShouldDelayMediaFromStart(GetShouldDelayMediaFromStart());
+
+ // Propagate some settings on BrowsingContext replacement so they're not lost
+ // on bfcached navigations. These are important for GeckoView (see bug
+ // 1781936).
+ txn.SetAllowJavascript(GetAllowJavascript());
+ txn.SetForceEnableTrackingProtection(GetForceEnableTrackingProtection());
+ txn.SetUserAgentOverride(GetUserAgentOverride());
+ txn.SetSuspendMediaWhenInactive(GetSuspendMediaWhenInactive());
+ txn.SetDisplayMode(GetDisplayMode());
+ txn.SetForceDesktopViewport(GetForceDesktopViewport());
+ txn.SetIsUnderHiddenEmbedderElement(GetIsUnderHiddenEmbedderElement());
+
+ // Propagate the default load flags so that the TRR mode flags are forwarded
+ // to the new browsing context. See bug 1828643.
+ txn.SetDefaultLoadFlags(GetDefaultLoadFlags());
+
+ // As this is a different BrowsingContext, set InitialSandboxFlags to the
+ // current flags in the new context so that they also apply to any initial
+ // about:blank documents created in it.
+ txn.SetSandboxFlags(GetSandboxFlags());
+ txn.SetInitialSandboxFlags(GetSandboxFlags());
+ txn.SetTargetTopLevelLinkClicksToBlankInternal(
+ TargetTopLevelLinkClicksToBlank());
+ if (aNewContext->EverAttached()) {
+ MOZ_ALWAYS_SUCCEEDS(txn.Commit(aNewContext));
+ } else {
+ txn.CommitWithoutSyncing(aNewContext);
+ }
+
+ aNewContext->mRestoreState = mRestoreState.forget();
+ MOZ_ALWAYS_SUCCEEDS(SetHasRestoreData(false));
+
+ // XXXBFCache name handling is still a bit broken in Fission in general,
+ // at least in case name should be cleared.
+ if (aRemotenessOptions.mTryUseBFCache) {
+ MOZ_ASSERT(!aNewContext->EverAttached());
+ aNewContext->mFields.SetWithoutSyncing<IDX_Name>(GetName());
+ // We don't copy over HasLoadedNonInitialDocument here, we'll actually end
+ // up loading a new initial document at this point, before the real load.
+ // The real load will then end up setting HasLoadedNonInitialDocument to
+ // true.
+ }
+
+ if (mSessionHistory) {
+ mSessionHistory->SetBrowsingContext(aNewContext);
+ // At this point we will be creating a new ChildSHistory in the child.
+ // That means that the child's epoch will be reset, so it makes sense to
+ // reset the epoch in the parent too.
+ mSessionHistory->SetEpoch(0, Nothing());
+ mSessionHistory.swap(aNewContext->mSessionHistory);
+ RefPtr<ChildSHistory> childSHistory = ForgetChildSHistory();
+ aNewContext->SetChildSHistory(childSHistory);
+ }
+
+ if (mozilla::SessionHistoryInParent()) {
+ BackgroundSessionStorageManager::PropagateManager(Id(), aNewContext->Id());
+ }
+
+ // Transfer the ownership of the priority active status from the old context
+ // to the new context.
+ aNewContext->mPriorityActive = mPriorityActive;
+ mPriorityActive = false;
+
+ MOZ_ASSERT(aNewContext->mLoadingEntries.IsEmpty());
+ mLoadingEntries.SwapElements(aNewContext->mLoadingEntries);
+ MOZ_ASSERT(!aNewContext->mActiveEntry);
+ mActiveEntry.swap(aNewContext->mActiveEntry);
+
+ aNewContext->mPermanentKey = mPermanentKey;
+ mPermanentKey.setNull();
+}
+
+void CanonicalBrowsingContext::UpdateSecurityState() {
+ if (mSecureBrowserUI) {
+ mSecureBrowserUI->RecomputeSecurityFlags();
+ }
+}
+
+void CanonicalBrowsingContext::GetWindowGlobals(
+ nsTArray<RefPtr<WindowGlobalParent>>& aWindows) {
+ aWindows.SetCapacity(GetWindowContexts().Length());
+ for (auto& window : GetWindowContexts()) {
+ aWindows.AppendElement(static_cast<WindowGlobalParent*>(window.get()));
+ }
+}
+
+WindowGlobalParent* CanonicalBrowsingContext::GetCurrentWindowGlobal() const {
+ return static_cast<WindowGlobalParent*>(GetCurrentWindowContext());
+}
+
+WindowGlobalParent* CanonicalBrowsingContext::GetParentWindowContext() {
+ return static_cast<WindowGlobalParent*>(
+ BrowsingContext::GetParentWindowContext());
+}
+
+WindowGlobalParent* CanonicalBrowsingContext::GetTopWindowContext() {
+ return static_cast<WindowGlobalParent*>(
+ BrowsingContext::GetTopWindowContext());
+}
+
+already_AddRefed<nsIWidget>
+CanonicalBrowsingContext::GetParentProcessWidgetContaining() {
+ // If our document is loaded in-process, such as chrome documents, get the
+ // widget directly from our outer window. Otherwise, try to get the widget
+ // from the toplevel content's browser's element.
+ nsCOMPtr<nsIWidget> widget;
+ if (nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(GetDOMWindow())) {
+ widget = window->GetNearestWidget();
+ } else if (Element* topEmbedder = Top()->GetEmbedderElement()) {
+ widget = nsContentUtils::WidgetForContent(topEmbedder);
+ if (!widget) {
+ widget = nsContentUtils::WidgetForDocument(topEmbedder->OwnerDoc());
+ }
+ }
+
+ if (widget) {
+ widget = widget->GetTopLevelWidget();
+ }
+
+ return widget.forget();
+}
+
+already_AddRefed<nsIBrowserDOMWindow>
+CanonicalBrowsingContext::GetBrowserDOMWindow() {
+ RefPtr<CanonicalBrowsingContext> chromeTop = TopCrossChromeBoundary();
+ if (nsCOMPtr<nsIDOMChromeWindow> chromeWin =
+ do_QueryInterface(chromeTop->GetDOMWindow())) {
+ nsCOMPtr<nsIBrowserDOMWindow> bdw;
+ if (NS_SUCCEEDED(chromeWin->GetBrowserDOMWindow(getter_AddRefs(bdw)))) {
+ return bdw.forget();
+ }
+ }
+ return nullptr;
+}
+
+already_AddRefed<WindowGlobalParent>
+CanonicalBrowsingContext::GetEmbedderWindowGlobal() const {
+ uint64_t windowId = GetEmbedderInnerWindowId();
+ if (windowId == 0) {
+ return nullptr;
+ }
+
+ return WindowGlobalParent::GetByInnerWindowId(windowId);
+}
+
+already_AddRefed<CanonicalBrowsingContext>
+CanonicalBrowsingContext::GetParentCrossChromeBoundary() {
+ if (GetParent()) {
+ return do_AddRef(Cast(GetParent()));
+ }
+ if (GetEmbedderElement()) {
+ return do_AddRef(
+ Cast(GetEmbedderElement()->OwnerDoc()->GetBrowsingContext()));
+ }
+ return nullptr;
+}
+
+already_AddRefed<CanonicalBrowsingContext>
+CanonicalBrowsingContext::TopCrossChromeBoundary() {
+ RefPtr<CanonicalBrowsingContext> bc(this);
+ while (RefPtr<CanonicalBrowsingContext> parent =
+ bc->GetParentCrossChromeBoundary()) {
+ bc = parent.forget();
+ }
+ return bc.forget();
+}
+
+Nullable<WindowProxyHolder> CanonicalBrowsingContext::GetTopChromeWindow() {
+ RefPtr<CanonicalBrowsingContext> bc = TopCrossChromeBoundary();
+ if (bc->IsChrome()) {
+ return WindowProxyHolder(bc.forget());
+ }
+ return nullptr;
+}
+
+nsISHistory* CanonicalBrowsingContext::GetSessionHistory() {
+ if (!IsTop()) {
+ return Cast(Top())->GetSessionHistory();
+ }
+
+ // Check GetChildSessionHistory() to make sure that this BrowsingContext has
+ // session history enabled.
+ if (!mSessionHistory && GetChildSessionHistory()) {
+ mSessionHistory = new nsSHistory(this);
+ }
+
+ return mSessionHistory;
+}
+
+SessionHistoryEntry* CanonicalBrowsingContext::GetActiveSessionHistoryEntry() {
+ return mActiveEntry;
+}
+
+void CanonicalBrowsingContext::SetActiveSessionHistoryEntry(
+ SessionHistoryEntry* aEntry) {
+ mActiveEntry = aEntry;
+}
+
+bool CanonicalBrowsingContext::HasHistoryEntry(nsISHEntry* aEntry) {
+ // XXX Should we check also loading entries?
+ return aEntry && mActiveEntry == aEntry;
+}
+
+void CanonicalBrowsingContext::SwapHistoryEntries(nsISHEntry* aOldEntry,
+ nsISHEntry* aNewEntry) {
+ // XXX Should we check also loading entries?
+ if (mActiveEntry == aOldEntry) {
+ nsCOMPtr<SessionHistoryEntry> newEntry = do_QueryInterface(aNewEntry);
+ mActiveEntry = newEntry.forget();
+ }
+}
+
+void CanonicalBrowsingContext::AddLoadingSessionHistoryEntry(
+ uint64_t aLoadId, SessionHistoryEntry* aEntry) {
+ Unused << SetHistoryID(aEntry->DocshellID());
+ mLoadingEntries.AppendElement(LoadingSessionHistoryEntry{aLoadId, aEntry});
+}
+
+void CanonicalBrowsingContext::GetLoadingSessionHistoryInfoFromParent(
+ Maybe<LoadingSessionHistoryInfo>& aLoadingInfo) {
+ nsISHistory* shistory = GetSessionHistory();
+ if (!shistory || !GetParent()) {
+ return;
+ }
+
+ SessionHistoryEntry* parentSHE =
+ GetParent()->Canonical()->GetActiveSessionHistoryEntry();
+ if (parentSHE) {
+ int32_t index = -1;
+ for (BrowsingContext* sibling : GetParent()->Children()) {
+ ++index;
+ if (sibling == this) {
+ nsCOMPtr<nsISHEntry> shEntry;
+ parentSHE->GetChildSHEntryIfHasNoDynamicallyAddedChild(
+ index, getter_AddRefs(shEntry));
+ nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(shEntry);
+ if (she) {
+ aLoadingInfo.emplace(she);
+ mLoadingEntries.AppendElement(LoadingSessionHistoryEntry{
+ aLoadingInfo.value().mLoadId, she.get()});
+ Unused << SetHistoryID(she->DocshellID());
+ }
+ break;
+ }
+ }
+ }
+}
+
+UniquePtr<LoadingSessionHistoryInfo>
+CanonicalBrowsingContext::CreateLoadingSessionHistoryEntryForLoad(
+ nsDocShellLoadState* aLoadState, SessionHistoryEntry* existingEntry,
+ nsIChannel* aChannel) {
+ RefPtr<SessionHistoryEntry> entry;
+ const LoadingSessionHistoryInfo* existingLoadingInfo =
+ aLoadState->GetLoadingSessionHistoryInfo();
+ MOZ_ASSERT_IF(!existingLoadingInfo, !existingEntry);
+ if (existingLoadingInfo) {
+ if (existingEntry) {
+ entry = existingEntry;
+ } else {
+ MOZ_ASSERT(!existingLoadingInfo->mLoadIsFromSessionHistory);
+
+ SessionHistoryEntry::LoadingEntry* loadingEntry =
+ SessionHistoryEntry::GetByLoadId(existingLoadingInfo->mLoadId);
+ MOZ_LOG(gSHLog, LogLevel::Verbose,
+ ("SHEntry::GetByLoadId(%" PRIu64 ") -> %p",
+ existingLoadingInfo->mLoadId, entry.get()));
+ if (!loadingEntry) {
+ return nullptr;
+ }
+ entry = loadingEntry->mEntry;
+ }
+
+ // If the entry was updated, update also the LoadingSessionHistoryInfo.
+ UniquePtr<LoadingSessionHistoryInfo> lshi =
+ MakeUnique<LoadingSessionHistoryInfo>(entry, existingLoadingInfo);
+ aLoadState->SetLoadingSessionHistoryInfo(std::move(lshi));
+ existingLoadingInfo = aLoadState->GetLoadingSessionHistoryInfo();
+ Unused << SetHistoryEntryCount(entry->BCHistoryLength());
+ } else if (aLoadState->LoadType() == LOAD_REFRESH &&
+ !ShouldAddEntryForRefresh(aLoadState->URI(),
+ aLoadState->PostDataStream()) &&
+ mActiveEntry) {
+ entry = mActiveEntry;
+ } else {
+ entry = new SessionHistoryEntry(aLoadState, aChannel);
+ if (IsTop()) {
+ // Only top level pages care about Get/SetPersist.
+ entry->SetPersist(
+ nsDocShell::ShouldAddToSessionHistory(aLoadState->URI(), aChannel));
+ } else if (mActiveEntry || !mLoadingEntries.IsEmpty()) {
+ entry->SetIsSubFrame(true);
+ }
+ entry->SetDocshellID(GetHistoryID());
+ entry->SetIsDynamicallyAdded(CreatedDynamically());
+ entry->SetForInitialLoad(true);
+ }
+ MOZ_DIAGNOSTIC_ASSERT(entry);
+
+ UniquePtr<LoadingSessionHistoryInfo> loadingInfo;
+ if (existingLoadingInfo) {
+ loadingInfo = MakeUnique<LoadingSessionHistoryInfo>(*existingLoadingInfo);
+ } else {
+ loadingInfo = MakeUnique<LoadingSessionHistoryInfo>(entry);
+ mLoadingEntries.AppendElement(
+ LoadingSessionHistoryEntry{loadingInfo->mLoadId, entry});
+ }
+
+ MOZ_ASSERT(SessionHistoryEntry::GetByLoadId(loadingInfo->mLoadId)->mEntry ==
+ entry);
+
+ return loadingInfo;
+}
+
+UniquePtr<LoadingSessionHistoryInfo>
+CanonicalBrowsingContext::ReplaceLoadingSessionHistoryEntryForLoad(
+ LoadingSessionHistoryInfo* aInfo, nsIChannel* aNewChannel) {
+ MOZ_ASSERT(aInfo);
+ MOZ_ASSERT(aNewChannel);
+
+ SessionHistoryInfo newInfo = SessionHistoryInfo(
+ aNewChannel, aInfo->mInfo.LoadType(),
+ aInfo->mInfo.GetPartitionedPrincipalToInherit(), aInfo->mInfo.GetCsp());
+
+ for (size_t i = 0; i < mLoadingEntries.Length(); ++i) {
+ if (mLoadingEntries[i].mLoadId == aInfo->mLoadId) {
+ RefPtr<SessionHistoryEntry> loadingEntry = mLoadingEntries[i].mEntry;
+ loadingEntry->SetInfo(&newInfo);
+
+ if (IsTop()) {
+ // Only top level pages care about Get/SetPersist.
+ nsCOMPtr<nsIURI> uri;
+ aNewChannel->GetURI(getter_AddRefs(uri));
+ loadingEntry->SetPersist(
+ nsDocShell::ShouldAddToSessionHistory(uri, aNewChannel));
+ } else {
+ loadingEntry->SetIsSubFrame(aInfo->mInfo.IsSubFrame());
+ }
+ loadingEntry->SetDocshellID(GetHistoryID());
+ loadingEntry->SetIsDynamicallyAdded(CreatedDynamically());
+ return MakeUnique<LoadingSessionHistoryInfo>(loadingEntry, aInfo);
+ }
+ }
+ return nullptr;
+}
+
+using PrintPromise = CanonicalBrowsingContext::PrintPromise;
+#ifdef NS_PRINTING
+class PrintListenerAdapter final : public nsIWebProgressListener {
+ public:
+ explicit PrintListenerAdapter(PrintPromise::Private* aPromise)
+ : mPromise(aPromise) {}
+
+ NS_DECL_ISUPPORTS
+
+ // NS_DECL_NSIWEBPROGRESSLISTENER
+ NS_IMETHOD OnStateChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ uint32_t aStateFlags, nsresult aStatus) override {
+ if (aStateFlags & nsIWebProgressListener::STATE_STOP &&
+ aStateFlags & nsIWebProgressListener::STATE_IS_DOCUMENT && mPromise) {
+ mPromise->Resolve(true, __func__);
+ mPromise = nullptr;
+ }
+ return NS_OK;
+ }
+ NS_IMETHOD OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ nsresult aStatus,
+ const char16_t* aMessage) override {
+ if (aStatus != NS_OK && mPromise) {
+ mPromise->Reject(aStatus, __func__);
+ mPromise = nullptr;
+ }
+ return NS_OK;
+ }
+ NS_IMETHOD OnProgressChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress) override {
+ return NS_OK;
+ }
+ NS_IMETHOD OnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, nsIURI* aLocation,
+ uint32_t aFlags) override {
+ return NS_OK;
+ }
+ NS_IMETHOD OnSecurityChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, uint32_t aState) override {
+ return NS_OK;
+ }
+ NS_IMETHOD OnContentBlockingEvent(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aEvent) override {
+ return NS_OK;
+ }
+
+ private:
+ ~PrintListenerAdapter() = default;
+
+ RefPtr<PrintPromise::Private> mPromise;
+};
+
+NS_IMPL_ISUPPORTS(PrintListenerAdapter, nsIWebProgressListener)
+#endif
+
+already_AddRefed<Promise> CanonicalBrowsingContext::PrintJS(
+ nsIPrintSettings* aPrintSettings, ErrorResult& aRv) {
+ RefPtr<Promise> promise = Promise::Create(GetIncumbentGlobal(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return promise.forget();
+ }
+
+ Print(aPrintSettings)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise](bool) { promise->MaybeResolveWithUndefined(); },
+ [promise](nsresult aResult) { promise->MaybeReject(aResult); });
+ return promise.forget();
+}
+
+RefPtr<PrintPromise> CanonicalBrowsingContext::Print(
+ nsIPrintSettings* aPrintSettings) {
+#ifndef NS_PRINTING
+ return PrintPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
+#else
+
+ auto promise = MakeRefPtr<PrintPromise::Private>(__func__);
+ auto listener = MakeRefPtr<PrintListenerAdapter>(promise);
+ if (IsInProcess()) {
+ RefPtr<nsGlobalWindowOuter> outerWindow =
+ nsGlobalWindowOuter::Cast(GetDOMWindow());
+ if (NS_WARN_IF(!outerWindow)) {
+ promise->Reject(NS_ERROR_FAILURE, __func__);
+ return promise;
+ }
+
+ ErrorResult rv;
+ outerWindow->Print(aPrintSettings,
+ /* aRemotePrintJob = */ nullptr, listener,
+ /* aDocShellToCloneInto = */ nullptr,
+ nsGlobalWindowOuter::IsPreview::No,
+ nsGlobalWindowOuter::IsForWindowDotPrint::No,
+ /* aPrintPreviewCallback = */ nullptr, rv);
+ if (rv.Failed()) {
+ promise->Reject(rv.StealNSResult(), __func__);
+ }
+ return promise;
+ }
+
+ auto* browserParent = GetBrowserParent();
+ if (NS_WARN_IF(!browserParent)) {
+ promise->Reject(NS_ERROR_FAILURE, __func__);
+ return promise;
+ }
+
+ nsCOMPtr<nsIPrintSettingsService> printSettingsSvc =
+ do_GetService("@mozilla.org/gfx/printsettings-service;1");
+ if (NS_WARN_IF(!printSettingsSvc)) {
+ promise->Reject(NS_ERROR_FAILURE, __func__);
+ return promise;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIPrintSettings> printSettings = aPrintSettings;
+ if (!printSettings) {
+ rv =
+ printSettingsSvc->CreateNewPrintSettings(getter_AddRefs(printSettings));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ promise->Reject(rv, __func__);
+ return promise;
+ }
+ }
+
+ embedding::PrintData printData;
+ rv = printSettingsSvc->SerializeToPrintData(printSettings, &printData);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ promise->Reject(rv, __func__);
+ return promise;
+ }
+
+ layout::RemotePrintJobParent* remotePrintJob =
+ new layout::RemotePrintJobParent(printSettings);
+ printData.remotePrintJob() =
+ browserParent->Manager()->SendPRemotePrintJobConstructor(remotePrintJob);
+
+ if (listener) {
+ remotePrintJob->RegisterListener(listener);
+ }
+
+ if (NS_WARN_IF(!browserParent->SendPrint(this, printData))) {
+ promise->Reject(NS_ERROR_FAILURE, __func__);
+ }
+ return promise.forget();
+#endif
+}
+
+void CanonicalBrowsingContext::CallOnAllTopDescendants(
+ const std::function<mozilla::CallState(CanonicalBrowsingContext*)>&
+ aCallback) {
+#ifdef DEBUG
+ RefPtr<CanonicalBrowsingContext> parent = GetParentCrossChromeBoundary();
+ MOZ_ASSERT(!parent, "Should only call on top chrome BC");
+#endif
+
+ nsTArray<RefPtr<BrowsingContextGroup>> groups;
+ BrowsingContextGroup::GetAllGroups(groups);
+ for (auto& browsingContextGroup : groups) {
+ for (auto& bc : browsingContextGroup->Toplevels()) {
+ if (bc == this) {
+ // Cannot be a descendent of myself so skip.
+ continue;
+ }
+
+ RefPtr<CanonicalBrowsingContext> top =
+ bc->Canonical()->TopCrossChromeBoundary();
+ if (top == this) {
+ if (aCallback(bc->Canonical()) == CallState::Stop) {
+ return;
+ }
+ }
+ }
+ }
+}
+
+void CanonicalBrowsingContext::SessionHistoryCommit(
+ uint64_t aLoadId, const nsID& aChangeID, uint32_t aLoadType, bool aPersist,
+ bool aCloneEntryChildren, bool aChannelExpired, uint32_t aCacheKey) {
+ MOZ_LOG(gSHLog, LogLevel::Verbose,
+ ("CanonicalBrowsingContext::SessionHistoryCommit %p %" PRIu64, this,
+ aLoadId));
+ MOZ_ASSERT(aLoadId != UINT64_MAX,
+ "Must not send special about:blank loadinfo to parent.");
+ for (size_t i = 0; i < mLoadingEntries.Length(); ++i) {
+ if (mLoadingEntries[i].mLoadId == aLoadId) {
+ nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory());
+ if (!shistory) {
+ SessionHistoryEntry::RemoveLoadId(aLoadId);
+ mLoadingEntries.RemoveElementAt(i);
+ return;
+ }
+
+ RefPtr<SessionHistoryEntry> newActiveEntry = mLoadingEntries[i].mEntry;
+ if (aCacheKey != 0) {
+ newActiveEntry->SetCacheKey(aCacheKey);
+ }
+
+ if (aChannelExpired) {
+ newActiveEntry->SharedInfo()->mExpired = true;
+ }
+
+ bool loadFromSessionHistory = !newActiveEntry->ForInitialLoad();
+ newActiveEntry->SetForInitialLoad(false);
+ SessionHistoryEntry::RemoveLoadId(aLoadId);
+ mLoadingEntries.RemoveElementAt(i);
+
+ int32_t indexOfHistoryLoad = -1;
+ if (loadFromSessionHistory) {
+ nsCOMPtr<nsISHEntry> root = nsSHistory::GetRootSHEntry(newActiveEntry);
+ indexOfHistoryLoad = shistory->GetIndexOfEntry(root);
+ if (indexOfHistoryLoad < 0) {
+ // Entry has been removed from the session history.
+ return;
+ }
+ }
+
+ CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory);
+
+ // If there is a name in the new entry, clear the name of all contiguous
+ // entries. This is for https://html.spec.whatwg.org/#history-traversal
+ // Step 4.4.2.
+ nsAutoString nameOfNewEntry;
+ newActiveEntry->GetName(nameOfNewEntry);
+ if (!nameOfNewEntry.IsEmpty()) {
+ nsSHistory::WalkContiguousEntries(
+ newActiveEntry,
+ [](nsISHEntry* aEntry) { aEntry->SetName(EmptyString()); });
+ }
+
+ bool addEntry = ShouldUpdateSessionHistory(aLoadType);
+ if (IsTop()) {
+ if (mActiveEntry && !mActiveEntry->GetFrameLoader()) {
+ bool sharesDocument = true;
+ mActiveEntry->SharesDocumentWith(newActiveEntry, &sharesDocument);
+ if (!sharesDocument) {
+ // If the old page won't be in the bfcache,
+ // clear the dynamic entries.
+ RemoveDynEntriesFromActiveSessionHistoryEntry();
+ }
+ }
+
+ if (LOAD_TYPE_HAS_FLAGS(aLoadType,
+ nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY)) {
+ // Replace the current entry with the new entry.
+ int32_t index = shistory->GetIndexForReplace();
+
+ // If we're trying to replace an inexistant shistory entry then we
+ // should append instead.
+ addEntry = index < 0;
+ if (!addEntry) {
+ shistory->ReplaceEntry(index, newActiveEntry);
+ }
+ mActiveEntry = newActiveEntry;
+ } else if (LOAD_TYPE_HAS_FLAGS(
+ aLoadType, nsIWebNavigation::LOAD_FLAGS_IS_REFRESH) &&
+ !ShouldAddEntryForRefresh(newActiveEntry) && mActiveEntry) {
+ addEntry = false;
+ mActiveEntry->ReplaceWith(*newActiveEntry);
+ } else {
+ mActiveEntry = newActiveEntry;
+ }
+
+ if (loadFromSessionHistory) {
+ // XXX Synchronize browsing context tree and session history tree?
+ shistory->InternalSetRequestedIndex(indexOfHistoryLoad);
+ shistory->UpdateIndex();
+
+ if (IsTop()) {
+ mActiveEntry->SetWireframe(Nothing());
+ }
+ } else if (addEntry) {
+ shistory->AddEntry(mActiveEntry, aPersist);
+ shistory->InternalSetRequestedIndex(-1);
+ }
+ } else {
+ // FIXME The old implementations adds it to the parent's mLSHE if there
+ // is one, need to figure out if that makes sense here (peterv
+ // doesn't think it would).
+ if (loadFromSessionHistory) {
+ if (mActiveEntry) {
+ // mActiveEntry is null if we're loading iframes from session
+ // history while also parent page is loading from session history.
+ // In that case there isn't anything to sync.
+ mActiveEntry->SyncTreesForSubframeNavigation(newActiveEntry, Top(),
+ this);
+ }
+ mActiveEntry = newActiveEntry;
+
+ shistory->InternalSetRequestedIndex(indexOfHistoryLoad);
+ // FIXME UpdateIndex() here may update index too early (but even the
+ // old implementation seems to have similar issues).
+ shistory->UpdateIndex();
+ } else if (addEntry) {
+ if (mActiveEntry) {
+ if (LOAD_TYPE_HAS_FLAGS(
+ aLoadType, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY) ||
+ (LOAD_TYPE_HAS_FLAGS(aLoadType,
+ nsIWebNavigation::LOAD_FLAGS_IS_REFRESH) &&
+ !ShouldAddEntryForRefresh(newActiveEntry))) {
+ // FIXME We need to make sure that when we create the info we
+ // make a copy of the shared state.
+ mActiveEntry->ReplaceWith(*newActiveEntry);
+ } else {
+ // AddChildSHEntryHelper does update the index of the session
+ // history!
+ shistory->AddChildSHEntryHelper(mActiveEntry, newActiveEntry,
+ Top(), aCloneEntryChildren);
+ mActiveEntry = newActiveEntry;
+ }
+ } else {
+ SessionHistoryEntry* parentEntry = GetParent()->mActiveEntry;
+ // XXX What should happen if parent doesn't have mActiveEntry?
+ // Or can that even happen ever?
+ if (parentEntry) {
+ mActiveEntry = newActiveEntry;
+ // FIXME Using IsInProcess for aUseRemoteSubframes isn't quite
+ // right, but aUseRemoteSubframes should be going away.
+ parentEntry->AddChild(
+ mActiveEntry,
+ CreatedDynamically() ? -1 : GetParent()->IndexOf(this),
+ IsInProcess());
+ }
+ }
+ shistory->InternalSetRequestedIndex(-1);
+ }
+ }
+
+ ResetSHEntryHasUserInteractionCache();
+
+ HistoryCommitIndexAndLength(aChangeID, caller);
+
+ shistory->LogHistory();
+
+ return;
+ }
+ // XXX Should the loading entries before [i] be removed?
+ }
+ // FIXME Should we throw an error if we don't find an entry for
+ // aSessionHistoryEntryId?
+}
+
+already_AddRefed<nsDocShellLoadState> CanonicalBrowsingContext::CreateLoadInfo(
+ SessionHistoryEntry* aEntry) {
+ const SessionHistoryInfo& info = aEntry->Info();
+ RefPtr<nsDocShellLoadState> loadState(new nsDocShellLoadState(info.GetURI()));
+ info.FillLoadInfo(*loadState);
+ UniquePtr<LoadingSessionHistoryInfo> loadingInfo;
+ loadingInfo = MakeUnique<LoadingSessionHistoryInfo>(aEntry);
+ mLoadingEntries.AppendElement(
+ LoadingSessionHistoryEntry{loadingInfo->mLoadId, aEntry});
+ loadState->SetLoadingSessionHistoryInfo(std::move(loadingInfo));
+
+ return loadState.forget();
+}
+
+void CanonicalBrowsingContext::NotifyOnHistoryReload(
+ bool aForceReload, bool& aCanReload,
+ Maybe<NotNull<RefPtr<nsDocShellLoadState>>>& aLoadState,
+ Maybe<bool>& aReloadActiveEntry) {
+ MOZ_DIAGNOSTIC_ASSERT(!aLoadState);
+
+ aCanReload = true;
+ nsISHistory* shistory = GetSessionHistory();
+ NS_ENSURE_TRUE_VOID(shistory);
+
+ shistory->NotifyOnHistoryReload(&aCanReload);
+ if (!aCanReload) {
+ return;
+ }
+
+ if (mActiveEntry) {
+ aLoadState.emplace(WrapMovingNotNull(RefPtr{CreateLoadInfo(mActiveEntry)}));
+ aReloadActiveEntry.emplace(true);
+ if (aForceReload) {
+ shistory->RemoveFrameEntries(mActiveEntry);
+ }
+ } else if (!mLoadingEntries.IsEmpty()) {
+ const LoadingSessionHistoryEntry& loadingEntry =
+ mLoadingEntries.LastElement();
+ uint64_t loadId = loadingEntry.mLoadId;
+ aLoadState.emplace(
+ WrapMovingNotNull(RefPtr{CreateLoadInfo(loadingEntry.mEntry)}));
+ aReloadActiveEntry.emplace(false);
+ if (aForceReload) {
+ SessionHistoryEntry::LoadingEntry* entry =
+ SessionHistoryEntry::GetByLoadId(loadId);
+ if (entry) {
+ shistory->RemoveFrameEntries(entry->mEntry);
+ }
+ }
+ }
+
+ if (aLoadState) {
+ // Use 0 as the offset, since aLoadState will be be used for reload.
+ aLoadState.ref()->SetLoadIsFromSessionHistory(0,
+ aReloadActiveEntry.value());
+ }
+ // If we don't have an active entry and we don't have a loading entry then
+ // the nsDocShell will create a load state based on its document.
+}
+
+void CanonicalBrowsingContext::SetActiveSessionHistoryEntry(
+ const Maybe<nsPoint>& aPreviousScrollPos, SessionHistoryInfo* aInfo,
+ uint32_t aLoadType, uint32_t aUpdatedCacheKey, const nsID& aChangeID) {
+ nsISHistory* shistory = GetSessionHistory();
+ if (!shistory) {
+ return;
+ }
+ CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory);
+
+ RefPtr<SessionHistoryEntry> oldActiveEntry = mActiveEntry;
+ if (aPreviousScrollPos.isSome() && oldActiveEntry) {
+ oldActiveEntry->SetScrollPosition(aPreviousScrollPos.ref().x,
+ aPreviousScrollPos.ref().y);
+ }
+ mActiveEntry = new SessionHistoryEntry(aInfo);
+ mActiveEntry->SetDocshellID(GetHistoryID());
+ mActiveEntry->AdoptBFCacheEntry(oldActiveEntry);
+ if (aUpdatedCacheKey != 0) {
+ mActiveEntry->SharedInfo()->mCacheKey = aUpdatedCacheKey;
+ }
+
+ if (IsTop()) {
+ Maybe<int32_t> previousEntryIndex, loadedEntryIndex;
+ shistory->AddToRootSessionHistory(
+ true, oldActiveEntry, this, mActiveEntry, aLoadType,
+ nsDocShell::ShouldAddToSessionHistory(aInfo->GetURI(), nullptr),
+ &previousEntryIndex, &loadedEntryIndex);
+ } else {
+ if (oldActiveEntry) {
+ shistory->AddChildSHEntryHelper(oldActiveEntry, mActiveEntry, Top(),
+ true);
+ } else if (GetParent() && GetParent()->mActiveEntry) {
+ GetParent()->mActiveEntry->AddChild(
+ mActiveEntry, CreatedDynamically() ? -1 : GetParent()->IndexOf(this),
+ UseRemoteSubframes());
+ }
+ }
+
+ ResetSHEntryHasUserInteractionCache();
+
+ shistory->InternalSetRequestedIndex(-1);
+
+ // FIXME Need to do the equivalent of EvictContentViewersOrReplaceEntry.
+ HistoryCommitIndexAndLength(aChangeID, caller);
+
+ static_cast<nsSHistory*>(shistory)->LogHistory();
+}
+
+void CanonicalBrowsingContext::ReplaceActiveSessionHistoryEntry(
+ SessionHistoryInfo* aInfo) {
+ if (!mActiveEntry) {
+ return;
+ }
+
+ mActiveEntry->SetInfo(aInfo);
+ // Notify children of the update
+ nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory());
+ if (shistory) {
+ shistory->NotifyOnHistoryReplaceEntry();
+ shistory->UpdateRootBrowsingContextState();
+ }
+
+ ResetSHEntryHasUserInteractionCache();
+
+ if (IsTop()) {
+ mActiveEntry->SetWireframe(Nothing());
+ }
+
+ // FIXME Need to do the equivalent of EvictContentViewersOrReplaceEntry.
+}
+
+void CanonicalBrowsingContext::RemoveDynEntriesFromActiveSessionHistoryEntry() {
+ nsISHistory* shistory = GetSessionHistory();
+ // In theory shistory can be null here if the method is called right after
+ // CanonicalBrowsingContext::ReplacedBy call.
+ NS_ENSURE_TRUE_VOID(shistory);
+ nsCOMPtr<nsISHEntry> root = nsSHistory::GetRootSHEntry(mActiveEntry);
+ shistory->RemoveDynEntries(shistory->GetIndexOfEntry(root), mActiveEntry);
+}
+
+void CanonicalBrowsingContext::RemoveFromSessionHistory(const nsID& aChangeID) {
+ nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory());
+ if (shistory) {
+ CallerWillNotifyHistoryIndexAndLengthChanges caller(shistory);
+ nsCOMPtr<nsISHEntry> root = nsSHistory::GetRootSHEntry(mActiveEntry);
+ bool didRemove;
+ AutoTArray<nsID, 16> ids({GetHistoryID()});
+ shistory->RemoveEntries(ids, shistory->GetIndexOfEntry(root), &didRemove);
+ if (didRemove) {
+ RefPtr<BrowsingContext> rootBC = shistory->GetBrowsingContext();
+ if (rootBC) {
+ if (!rootBC->IsInProcess()) {
+ if (ContentParent* cp = rootBC->Canonical()->GetContentParent()) {
+ Unused << cp->SendDispatchLocationChangeEvent(rootBC);
+ }
+ } else if (rootBC->GetDocShell()) {
+ rootBC->GetDocShell()->DispatchLocationChangeEvent();
+ }
+ }
+ }
+ HistoryCommitIndexAndLength(aChangeID, caller);
+ }
+}
+
+Maybe<int32_t> CanonicalBrowsingContext::HistoryGo(
+ int32_t aOffset, uint64_t aHistoryEpoch, bool aRequireUserInteraction,
+ bool aUserActivation, Maybe<ContentParentId> aContentId) {
+ if (aRequireUserInteraction && aOffset != -1 && aOffset != 1) {
+ NS_ERROR(
+ "aRequireUserInteraction may only be used with an offset of -1 or 1");
+ return Nothing();
+ }
+
+ nsSHistory* shistory = static_cast<nsSHistory*>(GetSessionHistory());
+ if (!shistory) {
+ return Nothing();
+ }
+
+ CheckedInt<int32_t> index = shistory->GetRequestedIndex() >= 0
+ ? shistory->GetRequestedIndex()
+ : shistory->Index();
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("HistoryGo(%d->%d) epoch %" PRIu64 "/id %" PRIu64, aOffset,
+ (index + aOffset).value(), aHistoryEpoch,
+ (uint64_t)(aContentId.isSome() ? aContentId.value() : 0)));
+
+ while (true) {
+ index += aOffset;
+ if (!index.isValid()) {
+ MOZ_LOG(gSHLog, LogLevel::Debug, ("Invalid index"));
+ return Nothing();
+ }
+
+ // Check for user interaction if desired, except for the first and last
+ // history entries. We compare with >= to account for the case where
+ // aOffset >= length.
+ if (!aRequireUserInteraction || index.value() >= shistory->Length() - 1 ||
+ index.value() <= 0) {
+ break;
+ }
+ if (shistory->HasUserInteractionAtIndex(index.value())) {
+ break;
+ }
+ }
+
+ // Implement aborting additional history navigations from within the same
+ // event spin of the content process.
+
+ uint64_t epoch;
+ bool sameEpoch = false;
+ Maybe<ContentParentId> id;
+ shistory->GetEpoch(epoch, id);
+
+ if (aContentId == id && epoch >= aHistoryEpoch) {
+ sameEpoch = true;
+ MOZ_LOG(gSHLog, LogLevel::Debug, ("Same epoch/id"));
+ }
+ // Don't update the epoch until we know if the target index is valid
+
+ // GoToIndex checks that index is >= 0 and < length.
+ nsTArray<nsSHistory::LoadEntryResult> loadResults;
+ nsresult rv = shistory->GotoIndex(index.value(), loadResults, sameEpoch,
+ aOffset == 0, aUserActivation);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("Dropping HistoryGo - bad index or same epoch (not in same doc)"));
+ return Nothing();
+ }
+ if (epoch < aHistoryEpoch || aContentId != id) {
+ MOZ_LOG(gSHLog, LogLevel::Debug, ("Set epoch"));
+ shistory->SetEpoch(aHistoryEpoch, aContentId);
+ }
+ int32_t requestedIndex = shistory->GetRequestedIndex();
+ nsSHistory::LoadURIs(loadResults);
+ return Some(requestedIndex);
+}
+
+JSObject* CanonicalBrowsingContext::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return CanonicalBrowsingContext_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void CanonicalBrowsingContext::DispatchWheelZoomChange(bool aIncrease) {
+ Element* element = Top()->GetEmbedderElement();
+ if (!element) {
+ return;
+ }
+
+ auto event = aIncrease ? u"DoZoomEnlargeBy10"_ns : u"DoZoomReduceBy10"_ns;
+ auto dispatcher = MakeRefPtr<AsyncEventDispatcher>(
+ element, event, CanBubble::eYes, ChromeOnlyDispatch::eYes);
+ dispatcher->PostDOMEvent();
+}
+
+void CanonicalBrowsingContext::CanonicalDiscard() {
+ if (mTabMediaController) {
+ mTabMediaController->Shutdown();
+ mTabMediaController = nullptr;
+ }
+
+ if (mCurrentLoad) {
+ mCurrentLoad->Cancel(NS_BINDING_ABORTED,
+ "CanonicalBrowsingContext::CanonicalDiscard"_ns);
+ }
+
+ if (mWebProgress) {
+ RefPtr<BrowsingContextWebProgress> progress = mWebProgress;
+ progress->ContextDiscarded();
+ }
+
+ if (IsTop()) {
+ BackgroundSessionStorageManager::RemoveManager(Id());
+ }
+
+ CancelSessionStoreUpdate();
+
+ if (UsePrivateBrowsing() && EverAttached() && IsContent()) {
+ DecreasePrivateCount();
+ }
+}
+
+void CanonicalBrowsingContext::CanonicalAttach() {
+ if (UsePrivateBrowsing() && IsContent()) {
+ IncreasePrivateCount();
+ }
+}
+
+void CanonicalBrowsingContext::AddPendingDiscard() {
+ MOZ_ASSERT(!mFullyDiscarded);
+ mPendingDiscards++;
+}
+
+void CanonicalBrowsingContext::RemovePendingDiscard() {
+ mPendingDiscards--;
+ if (!mPendingDiscards) {
+ mFullyDiscarded = true;
+ auto listeners = std::move(mFullyDiscardedListeners);
+ for (const auto& listener : listeners) {
+ listener(Id());
+ }
+ }
+}
+
+void CanonicalBrowsingContext::AddFinalDiscardListener(
+ std::function<void(uint64_t)>&& aListener) {
+ if (mFullyDiscarded) {
+ aListener(Id());
+ return;
+ }
+ mFullyDiscardedListeners.AppendElement(std::move(aListener));
+}
+
+void CanonicalBrowsingContext::SetForceAppWindowActive(bool aForceActive,
+ ErrorResult& aRv) {
+ MOZ_DIAGNOSTIC_ASSERT(IsChrome());
+ MOZ_DIAGNOSTIC_ASSERT(IsTop());
+ if (!IsChrome() || !IsTop()) {
+ return aRv.ThrowNotAllowedError(
+ "You shouldn't need to force this BrowsingContext to be active, use "
+ ".isActive instead");
+ }
+ if (mForceAppWindowActive == aForceActive) {
+ return;
+ }
+ mForceAppWindowActive = aForceActive;
+ RecomputeAppWindowVisibility();
+}
+
+void CanonicalBrowsingContext::RecomputeAppWindowVisibility() {
+ MOZ_RELEASE_ASSERT(IsChrome());
+ MOZ_RELEASE_ASSERT(IsTop());
+
+ const bool isActive = [&] {
+ if (ForceAppWindowActive()) {
+ return true;
+ }
+ auto* docShell = GetDocShell();
+ if (NS_WARN_IF(!docShell)) {
+ return false;
+ }
+ nsCOMPtr<nsIWidget> widget;
+ nsDocShell::Cast(docShell)->GetMainWidget(getter_AddRefs(widget));
+ if (NS_WARN_IF(!widget)) {
+ return false;
+ }
+ if (widget->IsFullyOccluded() ||
+ widget->SizeMode() == nsSizeMode_Minimized) {
+ return false;
+ }
+ return true;
+ }();
+ SetIsActive(isActive, IgnoreErrors());
+}
+
+void CanonicalBrowsingContext::AdjustPrivateBrowsingCount(
+ bool aPrivateBrowsing) {
+ if (IsDiscarded() || !EverAttached() || IsChrome()) {
+ return;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(aPrivateBrowsing == UsePrivateBrowsing());
+ if (aPrivateBrowsing) {
+ IncreasePrivateCount();
+ } else {
+ DecreasePrivateCount();
+ }
+}
+
+void CanonicalBrowsingContext::NotifyStartDelayedAutoplayMedia() {
+ WindowContext* windowContext = GetCurrentWindowContext();
+ if (!windowContext) {
+ return;
+ }
+
+ // As this function would only be called when user click the play icon on the
+ // tab bar. That's clear user intent to play, so gesture activate the window
+ // context so that the block-autoplay logic allows the media to autoplay.
+ windowContext->NotifyUserGestureActivation();
+ AUTOPLAY_LOG("NotifyStartDelayedAutoplayMedia for chrome bc 0x%08" PRIx64,
+ Id());
+ StartDelayedAutoplayMediaComponents();
+ // Notfiy all content browsing contexts which are related with the canonical
+ // browsing content tree to start delayed autoplay media.
+
+ Group()->EachParent([&](ContentParent* aParent) {
+ Unused << aParent->SendStartDelayedAutoplayMediaComponents(this);
+ });
+}
+
+void CanonicalBrowsingContext::NotifyMediaMutedChanged(bool aMuted,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(!GetParent(),
+ "Notify media mute change on non top-level context!");
+ SetMuted(aMuted, aRv);
+}
+
+uint32_t CanonicalBrowsingContext::CountSiteOrigins(
+ GlobalObject& aGlobal,
+ const Sequence<OwningNonNull<BrowsingContext>>& aRoots) {
+ nsTHashSet<nsCString> uniqueSiteOrigins;
+
+ for (const auto& root : aRoots) {
+ root->PreOrderWalk([&](BrowsingContext* aContext) {
+ WindowGlobalParent* windowGlobalParent =
+ aContext->Canonical()->GetCurrentWindowGlobal();
+ if (windowGlobalParent) {
+ nsIPrincipal* documentPrincipal =
+ windowGlobalParent->DocumentPrincipal();
+
+ bool isContentPrincipal = documentPrincipal->GetIsContentPrincipal();
+ if (isContentPrincipal) {
+ nsCString siteOrigin;
+ documentPrincipal->GetSiteOrigin(siteOrigin);
+ uniqueSiteOrigins.Insert(siteOrigin);
+ }
+ }
+ });
+ }
+
+ return uniqueSiteOrigins.Count();
+}
+
+/* static */
+bool CanonicalBrowsingContext::IsPrivateBrowsingActive() {
+ return gNumberOfPrivateContexts > 0;
+}
+
+void CanonicalBrowsingContext::UpdateMediaControlAction(
+ const MediaControlAction& aAction) {
+ if (IsDiscarded()) {
+ return;
+ }
+ ContentMediaControlKeyHandler::HandleMediaControlAction(this, aAction);
+ Group()->EachParent([&](ContentParent* aParent) {
+ Unused << aParent->SendUpdateMediaControlAction(this, aAction);
+ });
+}
+
+void CanonicalBrowsingContext::LoadURI(nsIURI* aURI,
+ const LoadURIOptions& aOptions,
+ ErrorResult& aError) {
+ RefPtr<nsDocShellLoadState> loadState;
+ nsresult rv = nsDocShellLoadState::CreateFromLoadURIOptions(
+ this, aURI, aOptions, getter_AddRefs(loadState));
+ MOZ_ASSERT(rv != NS_ERROR_MALFORMED_URI);
+
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ return;
+ }
+
+ LoadURI(loadState, true);
+}
+
+void CanonicalBrowsingContext::FixupAndLoadURIString(
+ const nsAString& aURI, const LoadURIOptions& aOptions,
+ ErrorResult& aError) {
+ RefPtr<nsDocShellLoadState> loadState;
+ nsresult rv = nsDocShellLoadState::CreateFromLoadURIOptions(
+ this, aURI, aOptions, getter_AddRefs(loadState));
+
+ if (rv == NS_ERROR_MALFORMED_URI) {
+ DisplayLoadError(aURI);
+ return;
+ }
+
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ return;
+ }
+
+ LoadURI(loadState, true);
+}
+
+void CanonicalBrowsingContext::GoBack(
+ const Optional<int32_t>& aCancelContentJSEpoch,
+ bool aRequireUserInteraction, bool aUserActivation) {
+ if (IsDiscarded()) {
+ return;
+ }
+
+ // Stop any known network loads if necessary.
+ if (mCurrentLoad) {
+ mCurrentLoad->Cancel(NS_BINDING_CANCELLED_OLD_LOAD, ""_ns);
+ }
+
+ if (RefPtr<nsDocShell> docShell = nsDocShell::Cast(GetDocShell())) {
+ if (aCancelContentJSEpoch.WasPassed()) {
+ docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value());
+ }
+ docShell->GoBack(aRequireUserInteraction, aUserActivation);
+ } else if (ContentParent* cp = GetContentParent()) {
+ Maybe<int32_t> cancelContentJSEpoch;
+ if (aCancelContentJSEpoch.WasPassed()) {
+ cancelContentJSEpoch = Some(aCancelContentJSEpoch.Value());
+ }
+ Unused << cp->SendGoBack(this, cancelContentJSEpoch,
+ aRequireUserInteraction, aUserActivation);
+ }
+}
+void CanonicalBrowsingContext::GoForward(
+ const Optional<int32_t>& aCancelContentJSEpoch,
+ bool aRequireUserInteraction, bool aUserActivation) {
+ if (IsDiscarded()) {
+ return;
+ }
+
+ // Stop any known network loads if necessary.
+ if (mCurrentLoad) {
+ mCurrentLoad->Cancel(NS_BINDING_CANCELLED_OLD_LOAD, ""_ns);
+ }
+
+ if (RefPtr<nsDocShell> docShell = nsDocShell::Cast(GetDocShell())) {
+ if (aCancelContentJSEpoch.WasPassed()) {
+ docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value());
+ }
+ docShell->GoForward(aRequireUserInteraction, aUserActivation);
+ } else if (ContentParent* cp = GetContentParent()) {
+ Maybe<int32_t> cancelContentJSEpoch;
+ if (aCancelContentJSEpoch.WasPassed()) {
+ cancelContentJSEpoch.emplace(aCancelContentJSEpoch.Value());
+ }
+ Unused << cp->SendGoForward(this, cancelContentJSEpoch,
+ aRequireUserInteraction, aUserActivation);
+ }
+}
+void CanonicalBrowsingContext::GoToIndex(
+ int32_t aIndex, const Optional<int32_t>& aCancelContentJSEpoch,
+ bool aUserActivation) {
+ if (IsDiscarded()) {
+ return;
+ }
+
+ // Stop any known network loads if necessary.
+ if (mCurrentLoad) {
+ mCurrentLoad->Cancel(NS_BINDING_CANCELLED_OLD_LOAD, ""_ns);
+ }
+
+ if (RefPtr<nsDocShell> docShell = nsDocShell::Cast(GetDocShell())) {
+ if (aCancelContentJSEpoch.WasPassed()) {
+ docShell->SetCancelContentJSEpoch(aCancelContentJSEpoch.Value());
+ }
+ docShell->GotoIndex(aIndex, aUserActivation);
+ } else if (ContentParent* cp = GetContentParent()) {
+ Maybe<int32_t> cancelContentJSEpoch;
+ if (aCancelContentJSEpoch.WasPassed()) {
+ cancelContentJSEpoch.emplace(aCancelContentJSEpoch.Value());
+ }
+ Unused << cp->SendGoToIndex(this, aIndex, cancelContentJSEpoch,
+ aUserActivation);
+ }
+}
+void CanonicalBrowsingContext::Reload(uint32_t aReloadFlags) {
+ if (IsDiscarded()) {
+ return;
+ }
+
+ // Stop any known network loads if necessary.
+ if (mCurrentLoad) {
+ mCurrentLoad->Cancel(NS_BINDING_CANCELLED_OLD_LOAD, ""_ns);
+ }
+
+ if (RefPtr<nsDocShell> docShell = nsDocShell::Cast(GetDocShell())) {
+ docShell->Reload(aReloadFlags);
+ } else if (ContentParent* cp = GetContentParent()) {
+ Unused << cp->SendReload(this, aReloadFlags);
+ }
+}
+
+void CanonicalBrowsingContext::Stop(uint32_t aStopFlags) {
+ if (IsDiscarded()) {
+ return;
+ }
+
+ // Stop any known network loads if necessary.
+ if (mCurrentLoad && (aStopFlags & nsIWebNavigation::STOP_NETWORK)) {
+ mCurrentLoad->Cancel(NS_BINDING_ABORTED,
+ "CanonicalBrowsingContext::Stop"_ns);
+ }
+
+ // Ask the docshell to stop to handle loads that haven't
+ // yet reached here, as well as non-network activity.
+ if (auto* docShell = nsDocShell::Cast(GetDocShell())) {
+ docShell->Stop(aStopFlags);
+ } else if (ContentParent* cp = GetContentParent()) {
+ Unused << cp->SendStopLoad(this, aStopFlags);
+ }
+}
+
+void CanonicalBrowsingContext::PendingRemotenessChange::ProcessLaunched() {
+ if (!mPromise) {
+ return;
+ }
+
+ if (mContentParent) {
+ // If our new content process is still unloading from a previous process
+ // switch, wait for that unload to complete before continuing.
+ auto found = mTarget->FindUnloadingHost(mContentParent->ChildID());
+ if (found != mTarget->mUnloadingHosts.end()) {
+ found->mCallbacks.AppendElement(
+ [self = RefPtr{this}]() { self->ProcessReady(); });
+ return;
+ }
+ }
+
+ ProcessReady();
+}
+
+void CanonicalBrowsingContext::PendingRemotenessChange::ProcessReady() {
+ if (!mPromise) {
+ return;
+ }
+
+ MOZ_ASSERT(!mProcessReady);
+ mProcessReady = true;
+ MaybeFinish();
+}
+
+void CanonicalBrowsingContext::PendingRemotenessChange::MaybeFinish() {
+ if (!mPromise) {
+ return;
+ }
+
+ if (!mProcessReady || mWaitingForPrepareToChange) {
+ return;
+ }
+
+ // If this BrowsingContext is embedded within the parent process, perform the
+ // process switch directly.
+ nsresult rv = mTarget->IsTopContent() ? FinishTopContent() : FinishSubframe();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Error finishing PendingRemotenessChange!");
+ Cancel(rv);
+ } else {
+ Clear();
+ }
+}
+
+// Logic for finishing a toplevel process change embedded within the parent
+// process. Due to frontend integration the logic differs substantially from
+// subframe process switches, and is handled separately.
+nsresult CanonicalBrowsingContext::PendingRemotenessChange::FinishTopContent() {
+ MOZ_DIAGNOSTIC_ASSERT(mTarget->IsTop(),
+ "We shouldn't be trying to change the remoteness of "
+ "non-remote iframes");
+
+ // Abort if our ContentParent died while process switching.
+ if (mContentParent && NS_WARN_IF(mContentParent->IsShuttingDown())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // While process switching, we need to check if any of our ancestors are
+ // discarded or no longer current, in which case the process switch needs to
+ // be aborted.
+ RefPtr<CanonicalBrowsingContext> target(mTarget);
+ if (target->IsDiscarded() || !target->AncestorsAreCurrent()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ Element* browserElement = target->GetEmbedderElement();
+ if (!browserElement) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIBrowser> browser = browserElement->AsBrowser();
+ if (!browser) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<nsFrameLoaderOwner> frameLoaderOwner = do_QueryObject(browserElement);
+ MOZ_RELEASE_ASSERT(frameLoaderOwner,
+ "embedder browser must be nsFrameLoaderOwner");
+
+ // If we're process switching a browsing context in private browsing
+ // mode we might decrease the private browsing count to '0', which
+ // would make us fire "last-pb-context-exited" and drop the private
+ // session. To prevent that we artificially increment the number of
+ // private browsing contexts with '1' until the process switch is done.
+ bool usePrivateBrowsing = mTarget->UsePrivateBrowsing();
+ if (usePrivateBrowsing) {
+ IncreasePrivateCount();
+ }
+
+ auto restorePrivateCount = MakeScopeExit([usePrivateBrowsing]() {
+ if (usePrivateBrowsing) {
+ DecreasePrivateCount();
+ }
+ });
+
+ // Tell frontend code that this browser element is about to change process.
+ nsresult rv = browser->BeforeChangeRemoteness();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Some frontend code checks the value of the `remote` attribute on the
+ // browser to determine if it is remote, so update the value.
+ browserElement->SetAttr(kNameSpaceID_None, nsGkAtoms::remote,
+ mContentParent ? u"true"_ns : u"false"_ns,
+ /* notify */ true);
+
+ // The process has been created, hand off to nsFrameLoaderOwner to finish
+ // the process switch.
+ ErrorResult error;
+ frameLoaderOwner->ChangeRemotenessToProcess(mContentParent, mOptions,
+ mSpecificGroup, error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+
+ // Tell frontend the load is done.
+ bool loadResumed = false;
+ rv = browser->FinishChangeRemoteness(mPendingSwitchId, &loadResumed);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // We did it! The process switch is complete.
+ RefPtr<nsFrameLoader> frameLoader = frameLoaderOwner->GetFrameLoader();
+ RefPtr<BrowserParent> newBrowser = frameLoader->GetBrowserParent();
+ if (!newBrowser) {
+ if (mContentParent) {
+ // Failed to create the BrowserParent somehow! Abort the process switch
+ // attempt.
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (!loadResumed) {
+ RefPtr<nsDocShell> newDocShell = frameLoader->GetDocShell(error);
+ if (error.Failed()) {
+ return error.StealNSResult();
+ }
+
+ rv = newDocShell->ResumeRedirectedLoad(mPendingSwitchId,
+ /* aHistoryIndex */ -1);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ } else if (!loadResumed) {
+ newBrowser->ResumeLoad(mPendingSwitchId);
+ }
+
+ mPromise->Resolve(newBrowser, __func__);
+ return NS_OK;
+}
+
+nsresult CanonicalBrowsingContext::PendingRemotenessChange::FinishSubframe() {
+ MOZ_DIAGNOSTIC_ASSERT(!mOptions.mReplaceBrowsingContext,
+ "Cannot replace BC for subframe");
+ MOZ_DIAGNOSTIC_ASSERT(!mTarget->IsTop());
+
+ // While process switching, we need to check if any of our ancestors are
+ // discarded or no longer current, in which case the process switch needs to
+ // be aborted.
+ RefPtr<CanonicalBrowsingContext> target(mTarget);
+ if (target->IsDiscarded() || !target->AncestorsAreCurrent()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_WARN_IF(!mContentParent)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<WindowGlobalParent> embedderWindow = target->GetParentWindowContext();
+ if (NS_WARN_IF(!embedderWindow) || NS_WARN_IF(!embedderWindow->CanSend())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<BrowserParent> embedderBrowser = embedderWindow->GetBrowserParent();
+ if (NS_WARN_IF(!embedderBrowser)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If we're creating a new remote browser, and the host process is already
+ // dead, abort the process switch.
+ if (mContentParent != embedderBrowser->Manager() &&
+ NS_WARN_IF(mContentParent->IsShuttingDown())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<BrowserParent> oldBrowser = target->GetBrowserParent();
+ target->SetCurrentBrowserParent(nullptr);
+
+ // If we were in a remote frame, trigger unloading of the remote window. The
+ // previous BrowserParent is registered in `mUnloadingHosts` and will only be
+ // cleared when the BrowserParent is fully destroyed.
+ bool wasRemote = oldBrowser && oldBrowser->GetBrowsingContext() == target;
+ if (wasRemote) {
+ MOZ_DIAGNOSTIC_ASSERT(oldBrowser != embedderBrowser);
+ MOZ_DIAGNOSTIC_ASSERT(oldBrowser->IsDestroyed() ||
+ oldBrowser->GetBrowserBridgeParent());
+
+ // `oldBrowser` will clear the `UnloadingHost` status once the actor has
+ // been destroyed.
+ if (oldBrowser->CanSend()) {
+ target->StartUnloadingHost(oldBrowser->Manager()->ChildID());
+ Unused << oldBrowser->SendWillChangeProcess();
+ oldBrowser->Destroy();
+ }
+ }
+
+ // Update which process is considered the current owner
+ target->SetOwnerProcessId(mContentParent->ChildID());
+
+ // If we're switching from remote to local, we don't need to create a
+ // BrowserBridge, and can instead perform the switch directly.
+ if (mContentParent == embedderBrowser->Manager()) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ mPendingSwitchId,
+ "We always have a PendingSwitchId, except for print-preview loads, "
+ "which will never perform a process-switch to being in-process with "
+ "their embedder");
+ MOZ_DIAGNOSTIC_ASSERT(wasRemote,
+ "Attempt to process-switch from local to local?");
+
+ target->SetCurrentBrowserParent(embedderBrowser);
+ Unused << embedderWindow->SendMakeFrameLocal(target, mPendingSwitchId);
+ mPromise->Resolve(embedderBrowser, __func__);
+ return NS_OK;
+ }
+
+ // The BrowsingContext will be remote, either as an already-remote frame
+ // changing processes, or as a local frame becoming remote. Construct a new
+ // BrowserBridgeParent to host the remote content.
+ target->SetCurrentBrowserParent(nullptr);
+
+ MOZ_DIAGNOSTIC_ASSERT(target->UseRemoteTabs() && target->UseRemoteSubframes(),
+ "Not supported without fission");
+ uint32_t chromeFlags = nsIWebBrowserChrome::CHROME_REMOTE_WINDOW |
+ nsIWebBrowserChrome::CHROME_FISSION_WINDOW;
+ if (target->UsePrivateBrowsing()) {
+ chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW;
+ }
+
+ nsCOMPtr<nsIPrincipal> initialPrincipal =
+ NullPrincipal::Create(target->OriginAttributesRef());
+ WindowGlobalInit windowInit =
+ WindowGlobalActor::AboutBlankInitializer(target, initialPrincipal);
+
+ // Create and initialize our new BrowserBridgeParent.
+ TabId tabId(nsContentUtils::GenerateTabId());
+ RefPtr<BrowserBridgeParent> bridge = new BrowserBridgeParent();
+ nsresult rv = bridge->InitWithProcess(embedderBrowser, mContentParent,
+ windowInit, chromeFlags, tabId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ // If we've already destroyed our previous document, make a best-effort
+ // attempt to recover from this failure and show the crashed tab UI. We only
+ // do this in the previously-remote case, as previously in-process frames
+ // will have their navigation cancelled, and will remain visible.
+ if (wasRemote) {
+ target->ShowSubframeCrashedUI(oldBrowser->GetBrowserBridgeParent());
+ }
+ return rv;
+ }
+
+ // Tell the embedder process a remoteness change is in-process. When this is
+ // acknowledged, reset the in-flight ID if it used to be an in-process load.
+ RefPtr<BrowserParent> newBrowser = bridge->GetBrowserParent();
+ {
+ // If we weren't remote, mark our embedder window browser as unloading until
+ // our embedder process has acked our MakeFrameRemote message.
+ Maybe<uint64_t> clearChildID;
+ if (!wasRemote) {
+ clearChildID = Some(embedderBrowser->Manager()->ChildID());
+ target->StartUnloadingHost(*clearChildID);
+ }
+ auto callback = [target, clearChildID](auto&&) {
+ if (clearChildID) {
+ target->ClearUnloadingHost(*clearChildID);
+ }
+ };
+
+ ManagedEndpoint<PBrowserBridgeChild> endpoint =
+ embedderBrowser->OpenPBrowserBridgeEndpoint(bridge);
+ MOZ_DIAGNOSTIC_ASSERT(endpoint.IsValid());
+ embedderWindow->SendMakeFrameRemote(target, std::move(endpoint), tabId,
+ newBrowser->GetLayersId(), callback,
+ callback);
+ }
+
+ // Resume the pending load in our new process.
+ if (mPendingSwitchId) {
+ newBrowser->ResumeLoad(mPendingSwitchId);
+ }
+
+ // We did it! The process switch is complete.
+ mPromise->Resolve(newBrowser, __func__);
+ return NS_OK;
+}
+
+void CanonicalBrowsingContext::PendingRemotenessChange::Cancel(nsresult aRv) {
+ if (!mPromise) {
+ return;
+ }
+
+ mPromise->Reject(aRv, __func__);
+ Clear();
+}
+
+void CanonicalBrowsingContext::PendingRemotenessChange::Clear() {
+ // Make sure we don't die while we're doing cleanup.
+ RefPtr<PendingRemotenessChange> kungFuDeathGrip(this);
+ if (mTarget) {
+ MOZ_DIAGNOSTIC_ASSERT(mTarget->mPendingRemotenessChange == this);
+ mTarget->mPendingRemotenessChange = nullptr;
+ }
+
+ // When this PendingRemotenessChange was created, it was given a
+ // `mContentParent`.
+ if (mContentParent) {
+ mContentParent->RemoveKeepAlive();
+ mContentParent = nullptr;
+ }
+
+ // If we were given a specific group, stop keeping that group alive manually.
+ if (mSpecificGroup) {
+ mSpecificGroup->RemoveKeepAlive();
+ mSpecificGroup = nullptr;
+ }
+
+ mPromise = nullptr;
+ mTarget = nullptr;
+}
+
+CanonicalBrowsingContext::PendingRemotenessChange::PendingRemotenessChange(
+ CanonicalBrowsingContext* aTarget, RemotenessPromise::Private* aPromise,
+ uint64_t aPendingSwitchId, const NavigationIsolationOptions& aOptions)
+ : mTarget(aTarget),
+ mPromise(aPromise),
+ mPendingSwitchId(aPendingSwitchId),
+ mOptions(aOptions) {}
+
+CanonicalBrowsingContext::PendingRemotenessChange::~PendingRemotenessChange() {
+ MOZ_ASSERT(!mPromise && !mTarget && !mContentParent && !mSpecificGroup,
+ "should've already been Cancel() or Complete()-ed");
+}
+
+BrowserParent* CanonicalBrowsingContext::GetBrowserParent() const {
+ return mCurrentBrowserParent;
+}
+
+void CanonicalBrowsingContext::SetCurrentBrowserParent(
+ BrowserParent* aBrowserParent) {
+ MOZ_DIAGNOSTIC_ASSERT(!mCurrentBrowserParent || !aBrowserParent,
+ "BrowsingContext already has a current BrowserParent!");
+ MOZ_DIAGNOSTIC_ASSERT_IF(aBrowserParent, aBrowserParent->CanSend());
+ MOZ_DIAGNOSTIC_ASSERT_IF(aBrowserParent,
+ aBrowserParent->Manager()->ChildID() == mProcessId);
+
+ // BrowserParent must either be directly for this BrowsingContext, or the
+ // manager out our embedder WindowGlobal.
+ MOZ_DIAGNOSTIC_ASSERT_IF(
+ aBrowserParent && aBrowserParent->GetBrowsingContext() != this,
+ GetParentWindowContext() &&
+ GetParentWindowContext()->Manager() == aBrowserParent);
+
+ mCurrentBrowserParent = aBrowserParent;
+}
+
+RefPtr<CanonicalBrowsingContext::RemotenessPromise>
+CanonicalBrowsingContext::ChangeRemoteness(
+ const NavigationIsolationOptions& aOptions, uint64_t aPendingSwitchId) {
+ MOZ_DIAGNOSTIC_ASSERT(IsContent(),
+ "cannot change the process of chrome contexts");
+ MOZ_DIAGNOSTIC_ASSERT(
+ IsTop() == IsEmbeddedInProcess(0),
+ "toplevel content must be embedded in the parent process");
+ MOZ_DIAGNOSTIC_ASSERT(!aOptions.mReplaceBrowsingContext || IsTop(),
+ "Cannot replace BrowsingContext for subframes");
+ MOZ_DIAGNOSTIC_ASSERT(
+ aOptions.mSpecificGroupId == 0 || aOptions.mReplaceBrowsingContext,
+ "Cannot specify group ID unless replacing BC");
+ MOZ_DIAGNOSTIC_ASSERT(aPendingSwitchId || !IsTop(),
+ "Should always have aPendingSwitchId for top-level "
+ "frames");
+
+ if (!AncestorsAreCurrent()) {
+ NS_WARNING("An ancestor context is no longer current");
+ return RemotenessPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ // Ensure our embedder hasn't been destroyed or asked to shutdown already.
+ RefPtr<WindowGlobalParent> embedderWindowGlobal = GetEmbedderWindowGlobal();
+ if (!embedderWindowGlobal) {
+ NS_WARNING("Non-embedded BrowsingContext");
+ return RemotenessPromise::CreateAndReject(NS_ERROR_UNEXPECTED, __func__);
+ }
+
+ if (!embedderWindowGlobal->CanSend()) {
+ NS_WARNING("Embedder already been destroyed.");
+ return RemotenessPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
+ }
+
+ RefPtr<BrowserParent> embedderBrowser =
+ embedderWindowGlobal->GetBrowserParent();
+ if (embedderBrowser && embedderBrowser->Manager()->IsShuttingDown()) {
+ NS_WARNING("Embedder already asked to shutdown.");
+ return RemotenessPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__);
+ }
+
+ if (aOptions.mRemoteType.IsEmpty() && (!IsTop() || !GetEmbedderElement())) {
+ NS_WARNING("Cannot load non-remote subframes");
+ return RemotenessPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ // Cancel ongoing remoteness changes.
+ if (mPendingRemotenessChange) {
+ mPendingRemotenessChange->Cancel(NS_ERROR_ABORT);
+ MOZ_DIAGNOSTIC_ASSERT(!mPendingRemotenessChange, "Should have cleared");
+ }
+
+ auto promise = MakeRefPtr<RemotenessPromise::Private>(__func__);
+ promise->UseDirectTaskDispatch(__func__);
+
+ RefPtr<PendingRemotenessChange> change =
+ new PendingRemotenessChange(this, promise, aPendingSwitchId, aOptions);
+ mPendingRemotenessChange = change;
+
+ // If a specific BrowsingContextGroup ID was specified for this load, make
+ // sure to keep it alive until the process switch is completed.
+ if (aOptions.mSpecificGroupId) {
+ change->mSpecificGroup =
+ BrowsingContextGroup::GetOrCreate(aOptions.mSpecificGroupId);
+ change->mSpecificGroup->AddKeepAlive();
+ }
+
+ // Call `prepareToChangeRemoteness` in parallel with starting a new process
+ // for <browser> loads.
+ if (IsTop() && GetEmbedderElement()) {
+ nsCOMPtr<nsIBrowser> browser = GetEmbedderElement()->AsBrowser();
+ if (!browser) {
+ change->Cancel(NS_ERROR_FAILURE);
+ return promise.forget();
+ }
+
+ RefPtr<Promise> blocker;
+ nsresult rv = browser->PrepareToChangeRemoteness(getter_AddRefs(blocker));
+ if (NS_FAILED(rv)) {
+ change->Cancel(rv);
+ return promise.forget();
+ }
+
+ // Mark prepareToChange as unresolved, and wait for it to become resolved.
+ if (blocker && blocker->State() != Promise::PromiseState::Resolved) {
+ change->mWaitingForPrepareToChange = true;
+ RefPtr<DomPromiseListener> listener = new DomPromiseListener(
+ [change](JSContext* aCx, JS::Handle<JS::Value> aValue) {
+ change->mWaitingForPrepareToChange = false;
+ change->MaybeFinish();
+ },
+ [change](nsresult aRv) { change->Cancel(aRv); });
+ blocker->AppendNativeHandler(listener);
+ }
+ }
+
+ // Switching a subframe to be local within it's embedding process.
+ if (embedderBrowser &&
+ aOptions.mRemoteType == embedderBrowser->Manager()->GetRemoteType()) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ aPendingSwitchId,
+ "We always have a PendingSwitchId, except for print-preview loads, "
+ "which will never perform a process-switch to being in-process with "
+ "their embedder");
+ MOZ_DIAGNOSTIC_ASSERT(!aOptions.mReplaceBrowsingContext);
+ MOZ_DIAGNOSTIC_ASSERT(!aOptions.mRemoteType.IsEmpty());
+ MOZ_DIAGNOSTIC_ASSERT(!change->mWaitingForPrepareToChange);
+ MOZ_DIAGNOSTIC_ASSERT(!change->mSpecificGroup);
+
+ // Switching to local, so we don't need to create a new process, and will
+ // instead use our embedder process.
+ change->mContentParent = embedderBrowser->Manager();
+ change->mContentParent->AddKeepAlive();
+ change->ProcessLaunched();
+ return promise.forget();
+ }
+
+ // Switching to the parent process.
+ if (aOptions.mRemoteType.IsEmpty()) {
+ change->ProcessLaunched();
+ return promise.forget();
+ }
+
+ // If we're aiming to end up in a new process of the same type as our old
+ // process, and then putting our previous document in the BFCache, try to stay
+ // in the same process to avoid creating new processes unnecessarily.
+ RefPtr<ContentParent> existingProcess = GetContentParent();
+ if (existingProcess && !existingProcess->IsShuttingDown() &&
+ aOptions.mReplaceBrowsingContext &&
+ aOptions.mRemoteType == existingProcess->GetRemoteType()) {
+ change->mContentParent = existingProcess;
+ change->mContentParent->AddKeepAlive();
+ change->ProcessLaunched();
+ return promise.forget();
+ }
+
+ // Try to predict which BrowsingContextGroup will be used for the final load
+ // in this BrowsingContext. This has to be accurate if switching into an
+ // existing group, as it will control what pool of processes will be used
+ // for process selection.
+ //
+ // It's _technically_ OK to provide a group here if we're actually going to
+ // switch into a brand new group, though it's sub-optimal, as it can
+ // restrict the set of processes we're using.
+ BrowsingContextGroup* finalGroup =
+ aOptions.mReplaceBrowsingContext ? change->mSpecificGroup.get() : Group();
+
+ bool preferUsed =
+ StaticPrefs::browser_tabs_remote_subframesPreferUsed() && !IsTop();
+
+ change->mContentParent = ContentParent::GetNewOrUsedLaunchingBrowserProcess(
+ /* aRemoteType = */ aOptions.mRemoteType,
+ /* aGroup = */ finalGroup,
+ /* aPriority = */ hal::PROCESS_PRIORITY_FOREGROUND,
+ /* aPreferUsed = */ preferUsed);
+ if (!change->mContentParent) {
+ change->Cancel(NS_ERROR_FAILURE);
+ return promise.forget();
+ }
+
+ // Add a KeepAlive used by this ContentParent, which will be cleared when
+ // the change is complete. This should prevent the process dying before
+ // we're ready to use it.
+ change->mContentParent->AddKeepAlive();
+ if (change->mContentParent->IsLaunching()) {
+ change->mContentParent->WaitForLaunchAsync()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [change](ContentParent*) { change->ProcessLaunched(); },
+ [change]() { change->Cancel(NS_ERROR_FAILURE); });
+ } else {
+ change->ProcessLaunched();
+ }
+ return promise.forget();
+}
+
+void CanonicalBrowsingContext::MaybeSetPermanentKey(Element* aEmbedder) {
+ MOZ_DIAGNOSTIC_ASSERT(IsTop());
+
+ if (aEmbedder) {
+ if (nsCOMPtr<nsIBrowser> browser = aEmbedder->AsBrowser()) {
+ JS::Rooted<JS::Value> key(RootingCx());
+ if (NS_SUCCEEDED(browser->GetPermanentKey(&key)) && key.isObject()) {
+ mPermanentKey = key;
+ }
+ }
+ }
+}
+
+MediaController* CanonicalBrowsingContext::GetMediaController() {
+ // We would only create one media controller per tab, so accessing the
+ // controller via the top-level browsing context.
+ if (GetParent()) {
+ return Cast(Top())->GetMediaController();
+ }
+
+ MOZ_ASSERT(!GetParent(),
+ "Must access the controller from the top-level browsing context!");
+ // Only content browsing context can create media controller, we won't create
+ // controller for chrome document, such as the browser UI.
+ if (!mTabMediaController && !IsDiscarded() && IsContent()) {
+ mTabMediaController = new MediaController(Id());
+ }
+ return mTabMediaController;
+}
+
+bool CanonicalBrowsingContext::HasCreatedMediaController() const {
+ return !!mTabMediaController;
+}
+
+bool CanonicalBrowsingContext::SupportsLoadingInParent(
+ nsDocShellLoadState* aLoadState, uint64_t* aOuterWindowId) {
+ // We currently don't support initiating loads in the parent when they are
+ // watched by devtools. This is because devtools tracks loads using content
+ // process notifications, which happens after the load is initiated in this
+ // case. Devtools clears all prior requests when it detects a new navigation,
+ // so it drops the main document load that happened here.
+ if (WatchedByDevTools()) {
+ return false;
+ }
+
+ // Session-history-in-parent implementation relies currently on getting a
+ // round trip through a child process.
+ if (aLoadState->LoadIsFromSessionHistory()) {
+ return false;
+ }
+
+ // DocumentChannel currently only supports connecting channels into the
+ // content process, so we can only support schemes that will always be loaded
+ // there for now. Restrict to just http(s) for simplicity.
+ if (!net::SchemeIsHTTP(aLoadState->URI()) &&
+ !net::SchemeIsHTTPS(aLoadState->URI())) {
+ return false;
+ }
+
+ if (WindowGlobalParent* global = GetCurrentWindowGlobal()) {
+ nsCOMPtr<nsIURI> currentURI = global->GetDocumentURI();
+ if (currentURI) {
+ bool newURIHasRef = false;
+ aLoadState->URI()->GetHasRef(&newURIHasRef);
+ bool equalsExceptRef = false;
+ aLoadState->URI()->EqualsExceptRef(currentURI, &equalsExceptRef);
+
+ if (equalsExceptRef && newURIHasRef) {
+ // This navigation is same-doc WRT the current one, we should pass it
+ // down to the docshell to be handled.
+ return false;
+ }
+ }
+ // If the current document has a beforeunload listener, then we need to
+ // start the load in that process after we fire the event.
+ if (global->HasBeforeUnload()) {
+ return false;
+ }
+
+ *aOuterWindowId = global->OuterWindowId();
+ }
+ return true;
+}
+
+bool CanonicalBrowsingContext::LoadInParent(nsDocShellLoadState* aLoadState,
+ bool aSetNavigating) {
+ // We currently only support starting loads directly from the
+ // CanonicalBrowsingContext for top-level BCs.
+ // We currently only support starting loads directly from the
+ // CanonicalBrowsingContext for top-level BCs.
+ if (!IsTopContent() || !GetContentParent() ||
+ !StaticPrefs::browser_tabs_documentchannel_parent_controlled()) {
+ return false;
+ }
+
+ uint64_t outerWindowId = 0;
+ if (!SupportsLoadingInParent(aLoadState, &outerWindowId)) {
+ return false;
+ }
+
+ MOZ_ASSERT(!net::SchemeIsJavascript(aLoadState->URI()));
+
+ MOZ_ALWAYS_SUCCEEDS(
+ SetParentInitiatedNavigationEpoch(++gParentInitiatedNavigationEpoch));
+ // Note: If successful, this will recurse into StartDocumentLoad and
+ // set mCurrentLoad to the DocumentLoadListener instance created.
+ // Ideally in the future we will only start loads from here, and we can
+ // just set this directly instead.
+ return net::DocumentLoadListener::LoadInParent(this, aLoadState,
+ aSetNavigating);
+}
+
+bool CanonicalBrowsingContext::AttemptSpeculativeLoadInParent(
+ nsDocShellLoadState* aLoadState) {
+ // We currently only support starting loads directly from the
+ // CanonicalBrowsingContext for top-level BCs.
+ // We currently only support starting loads directly from the
+ // CanonicalBrowsingContext for top-level BCs.
+ if (!IsTopContent() || !GetContentParent() ||
+ (StaticPrefs::browser_tabs_documentchannel_parent_controlled())) {
+ return false;
+ }
+
+ uint64_t outerWindowId = 0;
+ if (!SupportsLoadingInParent(aLoadState, &outerWindowId)) {
+ return false;
+ }
+
+ // If we successfully open the DocumentChannel, then it'll register
+ // itself using aLoadIdentifier and be kept alive until it completes
+ // loading.
+ return net::DocumentLoadListener::SpeculativeLoadInParent(this, aLoadState);
+}
+
+bool CanonicalBrowsingContext::StartDocumentLoad(
+ net::DocumentLoadListener* aLoad) {
+ // If we're controlling loads from the parent, then starting a new load means
+ // that we need to cancel any existing ones.
+ if (StaticPrefs::browser_tabs_documentchannel_parent_controlled() &&
+ mCurrentLoad) {
+ // Make sure we are not loading a javascript URI.
+ MOZ_ASSERT(!aLoad->IsLoadingJSURI());
+
+ // If we want to do a download, don't cancel the current navigation.
+ if (!aLoad->IsDownload()) {
+ mCurrentLoad->Cancel(NS_BINDING_CANCELLED_OLD_LOAD, ""_ns);
+ }
+ }
+ mCurrentLoad = aLoad;
+
+ if (NS_FAILED(SetCurrentLoadIdentifier(Some(aLoad->GetLoadIdentifier())))) {
+ mCurrentLoad = nullptr;
+ return false;
+ }
+
+ return true;
+}
+
+void CanonicalBrowsingContext::EndDocumentLoad(bool aContinueNavigating) {
+ mCurrentLoad = nullptr;
+
+ if (!aContinueNavigating) {
+ // Resetting the current load identifier on a discarded context
+ // has no effect when a document load has finished.
+ Unused << SetCurrentLoadIdentifier(Nothing());
+ }
+}
+
+already_AddRefed<nsIURI> CanonicalBrowsingContext::GetCurrentURI() const {
+ nsCOMPtr<nsIURI> currentURI;
+ if (nsIDocShell* docShell = GetDocShell()) {
+ MOZ_ALWAYS_SUCCEEDS(
+ nsDocShell::Cast(docShell)->GetCurrentURI(getter_AddRefs(currentURI)));
+ } else {
+ currentURI = mCurrentRemoteURI;
+ }
+ return currentURI.forget();
+}
+
+void CanonicalBrowsingContext::SetCurrentRemoteURI(nsIURI* aCurrentRemoteURI) {
+ MOZ_ASSERT(!GetDocShell());
+ mCurrentRemoteURI = aCurrentRemoteURI;
+}
+
+void CanonicalBrowsingContext::ResetSHEntryHasUserInteractionCache() {
+ WindowContext* topWc = GetTopWindowContext();
+ if (topWc && !topWc->IsDiscarded()) {
+ MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(false));
+ }
+}
+
+void CanonicalBrowsingContext::HistoryCommitIndexAndLength() {
+ nsID changeID = {};
+ CallerWillNotifyHistoryIndexAndLengthChanges caller(nullptr);
+ HistoryCommitIndexAndLength(changeID, caller);
+}
+void CanonicalBrowsingContext::HistoryCommitIndexAndLength(
+ const nsID& aChangeID,
+ const CallerWillNotifyHistoryIndexAndLengthChanges& aProofOfCaller) {
+ if (!IsTop()) {
+ Cast(Top())->HistoryCommitIndexAndLength(aChangeID, aProofOfCaller);
+ return;
+ }
+
+ nsISHistory* shistory = GetSessionHistory();
+ if (!shistory) {
+ return;
+ }
+ int32_t index = 0;
+ shistory->GetIndex(&index);
+ int32_t length = shistory->GetCount();
+
+ GetChildSessionHistory()->SetIndexAndLength(index, length, aChangeID);
+
+ shistory->EvictOutOfRangeContentViewers(index);
+
+ Group()->EachParent([&](ContentParent* aParent) {
+ Unused << aParent->SendHistoryCommitIndexAndLength(this, index, length,
+ aChangeID);
+ });
+}
+
+void CanonicalBrowsingContext::SynchronizeLayoutHistoryState() {
+ if (mActiveEntry) {
+ if (IsInProcess()) {
+ nsIDocShell* docShell = GetDocShell();
+ if (docShell) {
+ docShell->PersistLayoutHistoryState();
+
+ nsCOMPtr<nsILayoutHistoryState> state;
+ docShell->GetLayoutHistoryState(getter_AddRefs(state));
+ if (state) {
+ mActiveEntry->SetLayoutHistoryState(state);
+ }
+ }
+ } else if (ContentParent* cp = GetContentParent()) {
+ cp->SendGetLayoutHistoryState(this)->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [activeEntry = mActiveEntry](
+ const std::tuple<RefPtr<nsILayoutHistoryState>, Maybe<Wireframe>>&
+ aResult) {
+ if (std::get<0>(aResult)) {
+ activeEntry->SetLayoutHistoryState(std::get<0>(aResult));
+ }
+ if (std::get<1>(aResult)) {
+ activeEntry->SetWireframe(std::get<1>(aResult));
+ }
+ },
+ []() {});
+ }
+ }
+}
+
+void CanonicalBrowsingContext::ResetScalingZoom() {
+ // This currently only ever gets called in the parent process, and we
+ // pass the message on to the WindowGlobalChild for the rootmost browsing
+ // context.
+ if (WindowGlobalParent* topWindow = GetTopWindowContext()) {
+ Unused << topWindow->SendResetScalingZoom();
+ }
+}
+
+void CanonicalBrowsingContext::SetRestoreData(SessionStoreRestoreData* aData,
+ ErrorResult& aError) {
+ MOZ_DIAGNOSTIC_ASSERT(aData);
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+ RefPtr<Promise> promise = Promise::Create(global, aError);
+ if (aError.Failed()) {
+ return;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(SetHasRestoreData(true)))) {
+ aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ mRestoreState = new RestoreState();
+ mRestoreState->mData = aData;
+ mRestoreState->mPromise = promise;
+}
+
+already_AddRefed<Promise> CanonicalBrowsingContext::GetRestorePromise() {
+ if (mRestoreState) {
+ return do_AddRef(mRestoreState->mPromise);
+ }
+ return nullptr;
+}
+
+void CanonicalBrowsingContext::ClearRestoreState() {
+ if (!mRestoreState) {
+ MOZ_DIAGNOSTIC_ASSERT(!GetHasRestoreData());
+ return;
+ }
+ if (mRestoreState->mPromise) {
+ mRestoreState->mPromise->MaybeRejectWithUndefined();
+ }
+ mRestoreState = nullptr;
+ MOZ_ALWAYS_SUCCEEDS(SetHasRestoreData(false));
+}
+
+void CanonicalBrowsingContext::RequestRestoreTabContent(
+ WindowGlobalParent* aWindow) {
+ MOZ_DIAGNOSTIC_ASSERT(IsTop());
+
+ if (IsDiscarded() || !mRestoreState || !mRestoreState->mData) {
+ return;
+ }
+
+ CanonicalBrowsingContext* context = aWindow->GetBrowsingContext();
+ MOZ_DIAGNOSTIC_ASSERT(!context->IsDiscarded());
+
+ RefPtr<SessionStoreRestoreData> data =
+ mRestoreState->mData->FindDataForChild(context);
+
+ if (context->IsTop()) {
+ MOZ_DIAGNOSTIC_ASSERT(context == this);
+
+ // We need to wait until the appropriate load event has fired before we
+ // can "complete" the restore process, so if we're holding an empty data
+ // object, just resolve the promise immediately.
+ if (mRestoreState->mData->IsEmpty()) {
+ MOZ_DIAGNOSTIC_ASSERT(!data || data->IsEmpty());
+ mRestoreState->Resolve();
+ ClearRestoreState();
+ return;
+ }
+
+ // Since we're following load event order, we'll only arrive here for a
+ // toplevel context after we've already sent down data for all child frames,
+ // so it's safe to clear this reference now. The completion callback below
+ // relies on the mData field being null to determine if all requests have
+ // been sent out.
+ mRestoreState->ClearData();
+ MOZ_ALWAYS_SUCCEEDS(SetHasRestoreData(false));
+ }
+
+ if (data && !data->IsEmpty()) {
+ auto onTabRestoreComplete = [self = RefPtr{this},
+ state = RefPtr{mRestoreState}](auto) {
+ state->mResolves++;
+ if (!state->mData && state->mRequests == state->mResolves) {
+ state->Resolve();
+ if (state == self->mRestoreState) {
+ self->ClearRestoreState();
+ }
+ }
+ };
+
+ mRestoreState->mRequests++;
+
+ if (data->CanRestoreInto(aWindow->GetDocumentURI())) {
+ if (!aWindow->IsInProcess()) {
+ aWindow->SendRestoreTabContent(WrapNotNull(data.get()),
+ onTabRestoreComplete,
+ onTabRestoreComplete);
+ return;
+ }
+ data->RestoreInto(context);
+ }
+
+ // This must be called both when we're doing an in-process restore, and when
+ // we didn't do a restore at all due to a URL mismatch.
+ onTabRestoreComplete(true);
+ }
+}
+
+void CanonicalBrowsingContext::RestoreState::Resolve() {
+ MOZ_DIAGNOSTIC_ASSERT(mPromise);
+ mPromise->MaybeResolveWithUndefined();
+ mPromise = nullptr;
+}
+
+nsresult CanonicalBrowsingContext::WriteSessionStorageToSessionStore(
+ const nsTArray<SSCacheCopy>& aSesssionStorage, uint32_t aEpoch) {
+ nsCOMPtr<nsISessionStoreFunctions> funcs = do_ImportESModule(
+ "resource://gre/modules/SessionStoreFunctions.sys.mjs", fallible);
+ if (!funcs) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIXPConnectWrappedJS> wrapped = do_QueryInterface(funcs);
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(wrapped->GetJSObjectGlobal())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JS::Rooted<JS::Value> key(jsapi.cx(), Top()->PermanentKey());
+
+ Record<nsCString, Record<nsString, nsString>> storage;
+ JS::Rooted<JS::Value> update(jsapi.cx());
+
+ if (!aSesssionStorage.IsEmpty()) {
+ SessionStoreUtils::ConstructSessionStorageValues(this, aSesssionStorage,
+ storage);
+ if (!ToJSValue(jsapi.cx(), storage, &update)) {
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ update.setNull();
+ }
+
+ return funcs->UpdateSessionStoreForStorage(Top()->GetEmbedderElement(), this,
+ key, aEpoch, update);
+}
+
+void CanonicalBrowsingContext::UpdateSessionStoreSessionStorage(
+ const std::function<void()>& aDone) {
+ if (!StaticPrefs::browser_sessionstore_collect_session_storage_AtStartup()) {
+ aDone();
+ return;
+ }
+
+ using DataPromise = BackgroundSessionStorageManager::DataPromise;
+ BackgroundSessionStorageManager::GetData(
+ this, StaticPrefs::browser_sessionstore_dom_storage_limit(),
+ /* aClearSessionStoreTimer = */ true)
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr{this}, aDone, epoch = GetSessionStoreEpoch()](
+ const DataPromise::ResolveOrRejectValue& valueList) {
+ if (valueList.IsResolve()) {
+ self->WriteSessionStorageToSessionStore(
+ valueList.ResolveValue(), epoch);
+ }
+ aDone();
+ });
+}
+
+/* static */
+void CanonicalBrowsingContext::UpdateSessionStoreForStorage(
+ uint64_t aBrowsingContextId) {
+ RefPtr<CanonicalBrowsingContext> browsingContext = Get(aBrowsingContextId);
+
+ if (!browsingContext) {
+ return;
+ }
+
+ browsingContext->UpdateSessionStoreSessionStorage([]() {});
+}
+
+void CanonicalBrowsingContext::MaybeScheduleSessionStoreUpdate() {
+ if (!StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) {
+ return;
+ }
+
+ if (!IsTop()) {
+ Top()->MaybeScheduleSessionStoreUpdate();
+ return;
+ }
+
+ if (IsInBFCache()) {
+ return;
+ }
+
+ if (mSessionStoreSessionStorageUpdateTimer) {
+ return;
+ }
+
+ if (!StaticPrefs::browser_sessionstore_debug_no_auto_updates()) {
+ auto result = NS_NewTimerWithFuncCallback(
+ [](nsITimer*, void* aClosure) {
+ auto* context = static_cast<CanonicalBrowsingContext*>(aClosure);
+ context->UpdateSessionStoreSessionStorage([]() {});
+ },
+ this, StaticPrefs::browser_sessionstore_interval(),
+ nsITimer::TYPE_ONE_SHOT,
+ "CanonicalBrowsingContext::MaybeScheduleSessionStoreUpdate");
+
+ if (result.isErr()) {
+ return;
+ }
+
+ mSessionStoreSessionStorageUpdateTimer = result.unwrap();
+ }
+}
+
+void CanonicalBrowsingContext::CancelSessionStoreUpdate() {
+ if (mSessionStoreSessionStorageUpdateTimer) {
+ mSessionStoreSessionStorageUpdateTimer->Cancel();
+ mSessionStoreSessionStorageUpdateTimer = nullptr;
+ }
+}
+
+void CanonicalBrowsingContext::SetContainerFeaturePolicy(
+ FeaturePolicy* aContainerFeaturePolicy) {
+ mContainerFeaturePolicy = aContainerFeaturePolicy;
+
+ if (WindowGlobalParent* current = GetCurrentWindowGlobal()) {
+ Unused << current->SendSetContainerFeaturePolicy(mContainerFeaturePolicy);
+ }
+}
+
+void CanonicalBrowsingContext::SetCrossGroupOpenerId(uint64_t aOpenerId) {
+ MOZ_DIAGNOSTIC_ASSERT(IsTopContent());
+ MOZ_DIAGNOSTIC_ASSERT(mCrossGroupOpenerId == 0,
+ "Can only set CrossGroupOpenerId once");
+ mCrossGroupOpenerId = aOpenerId;
+}
+
+void CanonicalBrowsingContext::SetCrossGroupOpener(
+ CanonicalBrowsingContext& aCrossGroupOpener, ErrorResult& aRv) {
+ if (!IsTopContent()) {
+ aRv.ThrowNotAllowedError(
+ "Can only set crossGroupOpener on toplevel content");
+ return;
+ }
+ if (mCrossGroupOpenerId != 0) {
+ aRv.ThrowNotAllowedError("Can only set crossGroupOpener once");
+ return;
+ }
+
+ SetCrossGroupOpenerId(aCrossGroupOpener.Id());
+}
+
+auto CanonicalBrowsingContext::FindUnloadingHost(uint64_t aChildID)
+ -> nsTArray<UnloadingHost>::iterator {
+ return std::find_if(
+ mUnloadingHosts.begin(), mUnloadingHosts.end(),
+ [&](const auto& host) { return host.mChildID == aChildID; });
+}
+
+void CanonicalBrowsingContext::ClearUnloadingHost(uint64_t aChildID) {
+ // Notify any callbacks which were waiting for the host to finish unloading
+ // that it has.
+ auto found = FindUnloadingHost(aChildID);
+ if (found != mUnloadingHosts.end()) {
+ auto callbacks = std::move(found->mCallbacks);
+ mUnloadingHosts.RemoveElementAt(found);
+ for (const auto& callback : callbacks) {
+ callback();
+ }
+ }
+}
+
+void CanonicalBrowsingContext::StartUnloadingHost(uint64_t aChildID) {
+ MOZ_DIAGNOSTIC_ASSERT(FindUnloadingHost(aChildID) == mUnloadingHosts.end());
+ mUnloadingHosts.AppendElement(UnloadingHost{aChildID, {}});
+}
+
+void CanonicalBrowsingContext::BrowserParentDestroyed(
+ BrowserParent* aBrowserParent, bool aAbnormalShutdown) {
+ ClearUnloadingHost(aBrowserParent->Manager()->ChildID());
+
+ // Handling specific to when the current BrowserParent has been destroyed.
+ if (mCurrentBrowserParent == aBrowserParent) {
+ mCurrentBrowserParent = nullptr;
+
+ // If this BrowserParent is for a subframe, attempt to recover from a
+ // subframe crash by rendering the subframe crashed page in the embedding
+ // content.
+ if (aAbnormalShutdown) {
+ ShowSubframeCrashedUI(aBrowserParent->GetBrowserBridgeParent());
+ }
+ }
+}
+
+void CanonicalBrowsingContext::ShowSubframeCrashedUI(
+ BrowserBridgeParent* aBridge) {
+ if (!aBridge || IsDiscarded() || !aBridge->CanSend()) {
+ return;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(!aBridge->GetBrowsingContext() ||
+ aBridge->GetBrowsingContext() == this);
+
+ // There is no longer a current inner window within this
+ // BrowsingContext, update the `CurrentInnerWindowId` field to reflect
+ // this.
+ MOZ_ALWAYS_SUCCEEDS(SetCurrentInnerWindowId(0));
+
+ // The owning process will now be the embedder to render the subframe
+ // crashed page, switch ownership back over.
+ SetOwnerProcessId(aBridge->Manager()->Manager()->ChildID());
+ SetCurrentBrowserParent(aBridge->Manager());
+
+ Unused << aBridge->SendSubFrameCrashed();
+}
+
+static void LogBFCacheBlockingForDoc(BrowsingContext* aBrowsingContext,
+ uint32_t aBFCacheCombo, bool aIsSubDoc) {
+ if (aIsSubDoc) {
+ nsAutoCString uri("[no uri]");
+ nsCOMPtr<nsIURI> currentURI =
+ aBrowsingContext->Canonical()->GetCurrentURI();
+ if (currentURI) {
+ uri = currentURI->GetSpecOrDefault();
+ }
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
+ (" ** Blocked for document %s", uri.get()));
+ }
+ if (aBFCacheCombo & BFCacheStatus::EVENT_HANDLING_SUPPRESSED) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
+ (" * event handling suppression"));
+ }
+ if (aBFCacheCombo & BFCacheStatus::SUSPENDED) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * suspended Window"));
+ }
+ if (aBFCacheCombo & BFCacheStatus::UNLOAD_LISTENER) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * unload listener"));
+ }
+ if (aBFCacheCombo & BFCacheStatus::REQUEST) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * requests in the loadgroup"));
+ }
+ if (aBFCacheCombo & BFCacheStatus::ACTIVE_GET_USER_MEDIA) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * GetUserMedia"));
+ }
+ if (aBFCacheCombo & BFCacheStatus::ACTIVE_PEER_CONNECTION) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * PeerConnection"));
+ }
+ if (aBFCacheCombo & BFCacheStatus::CONTAINS_EME_CONTENT) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * EME content"));
+ }
+ if (aBFCacheCombo & BFCacheStatus::CONTAINS_MSE_CONTENT) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * MSE use"));
+ }
+ if (aBFCacheCombo & BFCacheStatus::HAS_ACTIVE_SPEECH_SYNTHESIS) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * Speech use"));
+ }
+ if (aBFCacheCombo & BFCacheStatus::HAS_USED_VR) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * used VR"));
+ }
+ if (aBFCacheCombo & BFCacheStatus::BEFOREUNLOAD_LISTENER) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * beforeunload listener"));
+ }
+ if (aBFCacheCombo & BFCacheStatus::ACTIVE_LOCK) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * has active Web Locks"));
+ }
+}
+
+bool CanonicalBrowsingContext::AllowedInBFCache(
+ const Maybe<uint64_t>& aChannelId, nsIURI* aNewURI) {
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug))) {
+ nsAutoCString uri("[no uri]");
+ nsCOMPtr<nsIURI> currentURI = GetCurrentURI();
+ if (currentURI) {
+ uri = currentURI->GetSpecOrDefault();
+ }
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, ("Checking %s", uri.get()));
+ }
+
+ if (IsInProcess()) {
+ return false;
+ }
+
+ uint32_t bfcacheCombo = 0;
+ if (mRestoreState) {
+ bfcacheCombo |= BFCacheStatus::RESTORING;
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * during session restore"));
+ }
+
+ if (Group()->Toplevels().Length() > 1) {
+ bfcacheCombo |= BFCacheStatus::NOT_ONLY_TOPLEVEL_IN_BCG;
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
+ (" * auxiliary BrowsingContexts"));
+ }
+
+ // There are not a lot of about:* pages that are allowed to load in
+ // subframes, so it's OK to allow those few about:* pages enter BFCache.
+ MOZ_ASSERT(IsTop(), "Trying to put a non top level BC into BFCache");
+
+ WindowGlobalParent* wgp = GetCurrentWindowGlobal();
+ if (wgp && wgp->GetDocumentURI()) {
+ nsCOMPtr<nsIURI> currentURI = wgp->GetDocumentURI();
+ // Exempt about:* pages from bfcache, with the exception of about:blank
+ if (currentURI->SchemeIs("about") &&
+ !currentURI->GetSpecOrDefault().EqualsLiteral("about:blank")) {
+ bfcacheCombo |= BFCacheStatus::ABOUT_PAGE;
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug, (" * about:* page"));
+ }
+
+ if (aNewURI) {
+ bool equalUri = false;
+ aNewURI->Equals(currentURI, &equalUri);
+ if (equalUri) {
+ // When loading the same uri, disable bfcache so that
+ // nsDocShell::OnNewURI transforms the load to LOAD_NORMAL_REPLACE.
+ return false;
+ }
+ }
+ }
+
+ // For telemetry we're collecting all the flags for all the BCs hanging
+ // from this top-level BC.
+ PreOrderWalk([&](BrowsingContext* aBrowsingContext) {
+ WindowGlobalParent* wgp =
+ aBrowsingContext->Canonical()->GetCurrentWindowGlobal();
+ uint32_t subDocBFCacheCombo = wgp ? wgp->GetBFCacheStatus() : 0;
+ if (wgp) {
+ const Maybe<uint64_t>& singleChannelId = wgp->GetSingleChannelId();
+ if (singleChannelId.isSome()) {
+ if (singleChannelId.value() == 0 || aChannelId.isNothing() ||
+ singleChannelId.value() != aChannelId.value()) {
+ subDocBFCacheCombo |= BFCacheStatus::REQUEST;
+ }
+ }
+ }
+
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug))) {
+ LogBFCacheBlockingForDoc(aBrowsingContext, subDocBFCacheCombo,
+ aBrowsingContext != this);
+ }
+
+ bfcacheCombo |= subDocBFCacheCombo;
+ });
+
+ nsDocShell::ReportBFCacheComboTelemetry(bfcacheCombo);
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Debug))) {
+ nsAutoCString uri("[no uri]");
+ nsCOMPtr<nsIURI> currentURI = GetCurrentURI();
+ if (currentURI) {
+ uri = currentURI->GetSpecOrDefault();
+ }
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
+ (" +> %s %s be blocked from going into the BFCache", uri.get(),
+ bfcacheCombo == 0 ? "shouldn't" : "should"));
+ }
+
+ if (StaticPrefs::docshell_shistory_bfcache_allow_unload_listeners()) {
+ bfcacheCombo &= ~BFCacheStatus::UNLOAD_LISTENER;
+ }
+
+ return bfcacheCombo == 0;
+}
+
+void CanonicalBrowsingContext::SetTouchEventsOverride(
+ dom::TouchEventsOverride aOverride, ErrorResult& aRv) {
+ SetTouchEventsOverrideInternal(aOverride, aRv);
+}
+
+void CanonicalBrowsingContext::SetTargetTopLevelLinkClicksToBlank(
+ bool aTargetTopLevelLinkClicksToBlank, ErrorResult& aRv) {
+ SetTargetTopLevelLinkClicksToBlankInternal(aTargetTopLevelLinkClicksToBlank,
+ aRv);
+}
+
+void CanonicalBrowsingContext::AddPageAwakeRequest() {
+ MOZ_ASSERT(IsTop());
+ auto count = GetPageAwakeRequestCount();
+ MOZ_ASSERT(count < UINT32_MAX);
+ Unused << SetPageAwakeRequestCount(++count);
+}
+
+void CanonicalBrowsingContext::RemovePageAwakeRequest() {
+ MOZ_ASSERT(IsTop());
+ auto count = GetPageAwakeRequestCount();
+ MOZ_ASSERT(count > 0);
+ Unused << SetPageAwakeRequestCount(--count);
+}
+
+void CanonicalBrowsingContext::CloneDocumentTreeInto(
+ CanonicalBrowsingContext* aSource, const nsACString& aRemoteType,
+ embedding::PrintData&& aPrintData) {
+ NavigationIsolationOptions options;
+ options.mRemoteType = aRemoteType;
+
+ mClonePromise =
+ ChangeRemoteness(options, /* aPendingSwitchId = */ 0)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [source = MaybeDiscardedBrowsingContext{aSource},
+ data = std::move(aPrintData)](
+ BrowserParent* aBp) -> RefPtr<GenericNonExclusivePromise> {
+ RefPtr<BrowserBridgeParent> bridge =
+ aBp->GetBrowserBridgeParent();
+ return aBp->SendCloneDocumentTreeIntoSelf(source, data)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [bridge](
+ BrowserParent::CloneDocumentTreeIntoSelfPromise::
+ ResolveOrRejectValue&& aValue) {
+ // We're cloning a remote iframe, so we created a
+ // BrowserBridge which makes us register an OOP load
+ // (see Document::OOPChildLoadStarted), even though
+ // this isn't a real load. We call
+ // SendMaybeFireEmbedderLoadEvents here so that we do
+ // register the end of the load (see
+ // Document::OOPChildLoadDone).
+ if (bridge) {
+ Unused << bridge->SendMaybeFireEmbedderLoadEvents(
+ EmbedderElementEventType::NoEvent);
+ }
+ if (aValue.IsResolve() && aValue.ResolveValue()) {
+ return GenericNonExclusivePromise::CreateAndResolve(
+ true, __func__);
+ }
+ return GenericNonExclusivePromise::CreateAndReject(
+ NS_ERROR_FAILURE, __func__);
+ });
+ },
+ [](nsresult aRv) -> RefPtr<GenericNonExclusivePromise> {
+ NS_WARNING(
+ nsPrintfCString("Remote clone failed: %x\n", unsigned(aRv))
+ .get());
+ return GenericNonExclusivePromise::CreateAndReject(
+ NS_ERROR_FAILURE, __func__);
+ });
+
+ mClonePromise->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [self = RefPtr{this}]() { self->mClonePromise = nullptr; });
+}
+
+bool CanonicalBrowsingContext::StartApzAutoscroll(float aAnchorX,
+ float aAnchorY,
+ nsViewID aScrollId,
+ uint32_t aPresShellId) {
+ nsCOMPtr<nsIWidget> widget;
+ mozilla::layers::LayersId layersId{0};
+
+ if (IsInProcess()) {
+ nsCOMPtr<nsPIDOMWindowOuter> outer = GetDOMWindow();
+ if (!outer) {
+ return false;
+ }
+
+ widget = widget::WidgetUtils::DOMWindowToWidget(outer);
+ if (widget) {
+ layersId = widget->GetRootLayerTreeId();
+ }
+ } else {
+ RefPtr<BrowserParent> parent = GetBrowserParent();
+ if (!parent) {
+ return false;
+ }
+
+ widget = parent->GetWidget();
+ layersId = parent->GetLayersId();
+ }
+
+ if (!widget || !widget->AsyncPanZoomEnabled()) {
+ return false;
+ }
+
+ // The anchor coordinates that are passed in are relative to the origin of the
+ // screen, but we are sending them to APZ which only knows about coordinates
+ // relative to the widget, so convert them accordingly.
+ const LayoutDeviceIntPoint anchor =
+ RoundedToInt(LayoutDevicePoint(aAnchorX, aAnchorY)) -
+ widget->WidgetToScreenOffset();
+
+ mozilla::layers::ScrollableLayerGuid guid(layersId, aPresShellId, aScrollId);
+
+ return widget->StartAsyncAutoscroll(
+ ViewAs<ScreenPixel>(
+ anchor, PixelCastJustification::LayoutDeviceIsScreenForBounds),
+ guid);
+}
+
+void CanonicalBrowsingContext::StopApzAutoscroll(nsViewID aScrollId,
+ uint32_t aPresShellId) {
+ nsCOMPtr<nsIWidget> widget;
+ mozilla::layers::LayersId layersId{0};
+
+ if (IsInProcess()) {
+ nsCOMPtr<nsPIDOMWindowOuter> outer = GetDOMWindow();
+ if (!outer) {
+ return;
+ }
+
+ widget = widget::WidgetUtils::DOMWindowToWidget(outer);
+ if (widget) {
+ layersId = widget->GetRootLayerTreeId();
+ }
+ } else {
+ RefPtr<BrowserParent> parent = GetBrowserParent();
+ if (!parent) {
+ return;
+ }
+
+ widget = parent->GetWidget();
+ layersId = parent->GetLayersId();
+ }
+
+ if (!widget || !widget->AsyncPanZoomEnabled()) {
+ return;
+ }
+
+ mozilla::layers::ScrollableLayerGuid guid(layersId, aPresShellId, aScrollId);
+ widget->StopAsyncAutoscroll(guid);
+}
+
+already_AddRefed<nsISHEntry>
+CanonicalBrowsingContext::GetMostRecentLoadingSessionHistoryEntry() {
+ if (mLoadingEntries.IsEmpty()) {
+ return nullptr;
+ }
+
+ RefPtr<SessionHistoryEntry> entry = mLoadingEntries.LastElement().mEntry;
+ return entry.forget();
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(CanonicalBrowsingContext)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(CanonicalBrowsingContext,
+ BrowsingContext)
+ tmp->mPermanentKey.setNull();
+ if (tmp->mSessionHistory) {
+ tmp->mSessionHistory->SetBrowsingContext(nullptr);
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mSessionHistory, mContainerFeaturePolicy,
+ mCurrentBrowserParent, mWebProgress,
+ mSessionStoreSessionStorageUpdateTimer)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(CanonicalBrowsingContext,
+ BrowsingContext)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessionHistory, mContainerFeaturePolicy,
+ mCurrentBrowserParent, mWebProgress,
+ mSessionStoreSessionStorageUpdateTimer)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(CanonicalBrowsingContext,
+ BrowsingContext)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mPermanentKey)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_ADDREF_INHERITED(CanonicalBrowsingContext, BrowsingContext)
+NS_IMPL_RELEASE_INHERITED(CanonicalBrowsingContext, BrowsingContext)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CanonicalBrowsingContext)
+NS_INTERFACE_MAP_END_INHERITING(BrowsingContext)
+
+} // namespace dom
+} // namespace mozilla
diff --git a/docshell/base/CanonicalBrowsingContext.h b/docshell/base/CanonicalBrowsingContext.h
new file mode 100644
index 0000000000..9420c7e647
--- /dev/null
+++ b/docshell/base/CanonicalBrowsingContext.h
@@ -0,0 +1,601 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_CanonicalBrowsingContext_h
+#define mozilla_dom_CanonicalBrowsingContext_h
+
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/MediaControlKeySource.h"
+#include "mozilla/dom/BrowsingContextWebProgress.h"
+#include "mozilla/dom/ProcessIsolation.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/SessionHistoryEntry.h"
+#include "mozilla/dom/SessionStoreRestoreData.h"
+#include "mozilla/dom/SessionStoreUtils.h"
+#include "mozilla/dom/ipc/IdType.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/MozPromise.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "nsTArray.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+#include "nsISecureBrowserUI.h"
+
+class nsIBrowserDOMWindow;
+class nsISHistory;
+class nsIWidget;
+class nsIPrintSettings;
+class nsSHistory;
+class nsBrowserStatusFilter;
+class nsSecureBrowserUI;
+class CallerWillNotifyHistoryIndexAndLengthChanges;
+class nsITimer;
+
+namespace mozilla {
+enum class CallState;
+
+namespace embedding {
+class PrintData;
+}
+
+namespace net {
+class DocumentLoadListener;
+}
+
+namespace dom {
+
+class BrowserParent;
+class BrowserBridgeParent;
+class FeaturePolicy;
+struct LoadURIOptions;
+class MediaController;
+struct LoadingSessionHistoryInfo;
+class SSCacheCopy;
+class WindowGlobalParent;
+class SessionStoreFormData;
+class SessionStoreScrollData;
+
+// CanonicalBrowsingContext is a BrowsingContext living in the parent
+// process, with whatever extra data that a BrowsingContext in the
+// parent needs.
+class CanonicalBrowsingContext final : public BrowsingContext {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(
+ CanonicalBrowsingContext, BrowsingContext)
+
+ static already_AddRefed<CanonicalBrowsingContext> Get(uint64_t aId);
+ static CanonicalBrowsingContext* Cast(BrowsingContext* aContext);
+ static const CanonicalBrowsingContext* Cast(const BrowsingContext* aContext);
+ static already_AddRefed<CanonicalBrowsingContext> Cast(
+ already_AddRefed<BrowsingContext>&& aContext);
+
+ bool IsOwnedByProcess(uint64_t aProcessId) const {
+ return mProcessId == aProcessId;
+ }
+ bool IsEmbeddedInProcess(uint64_t aProcessId) const {
+ return mEmbedderProcessId == aProcessId;
+ }
+ uint64_t OwnerProcessId() const { return mProcessId; }
+ uint64_t EmbedderProcessId() const { return mEmbedderProcessId; }
+ ContentParent* GetContentParent() const;
+
+ void GetCurrentRemoteType(nsACString& aRemoteType, ErrorResult& aRv) const;
+
+ void SetOwnerProcessId(uint64_t aProcessId);
+
+ // The ID of the BrowsingContext which caused this BrowsingContext to be
+ // opened, or `0` if this is unknown.
+ // Only set for toplevel content BrowsingContexts, and may be from a different
+ // BrowsingContextGroup.
+ uint64_t GetCrossGroupOpenerId() const { return mCrossGroupOpenerId; }
+ void SetCrossGroupOpenerId(uint64_t aOpenerId);
+ void SetCrossGroupOpener(CanonicalBrowsingContext& aCrossGroupOpener,
+ ErrorResult& aRv);
+
+ void GetWindowGlobals(nsTArray<RefPtr<WindowGlobalParent>>& aWindows);
+
+ // The current active WindowGlobal.
+ WindowGlobalParent* GetCurrentWindowGlobal() const;
+
+ // Same as the methods on `BrowsingContext`, but with the types already cast
+ // to the parent process type.
+ CanonicalBrowsingContext* GetParent() {
+ return Cast(BrowsingContext::GetParent());
+ }
+ CanonicalBrowsingContext* Top() { return Cast(BrowsingContext::Top()); }
+ WindowGlobalParent* GetParentWindowContext();
+ WindowGlobalParent* GetTopWindowContext();
+
+ already_AddRefed<nsIWidget> GetParentProcessWidgetContaining();
+ already_AddRefed<nsIBrowserDOMWindow> GetBrowserDOMWindow();
+
+ // Same as `GetParentWindowContext`, but will also cross <browser> and
+ // content/chrome boundaries.
+ already_AddRefed<WindowGlobalParent> GetEmbedderWindowGlobal() const;
+
+ already_AddRefed<CanonicalBrowsingContext> GetParentCrossChromeBoundary();
+
+ already_AddRefed<CanonicalBrowsingContext> TopCrossChromeBoundary();
+ Nullable<WindowProxyHolder> GetTopChromeWindow();
+
+ nsISHistory* GetSessionHistory();
+ SessionHistoryEntry* GetActiveSessionHistoryEntry();
+ void SetActiveSessionHistoryEntry(SessionHistoryEntry* aEntry);
+
+ UniquePtr<LoadingSessionHistoryInfo> CreateLoadingSessionHistoryEntryForLoad(
+ nsDocShellLoadState* aLoadState, SessionHistoryEntry* aExistingEntry,
+ nsIChannel* aChannel);
+
+ UniquePtr<LoadingSessionHistoryInfo> ReplaceLoadingSessionHistoryEntryForLoad(
+ LoadingSessionHistoryInfo* aInfo, nsIChannel* aNewChannel);
+
+ using PrintPromise = MozPromise</* unused */ bool, nsresult, false>;
+ MOZ_CAN_RUN_SCRIPT RefPtr<PrintPromise> Print(nsIPrintSettings*);
+ MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> PrintJS(nsIPrintSettings*,
+ ErrorResult&);
+
+ // Call the given callback on all top-level descendant BrowsingContexts.
+ // Return Callstate::Stop from the callback to stop calling
+ // further children.
+ void CallOnAllTopDescendants(
+ const std::function<mozilla::CallState(CanonicalBrowsingContext*)>&
+ aCallback);
+
+ void SessionHistoryCommit(uint64_t aLoadId, const nsID& aChangeID,
+ uint32_t aLoadType, bool aPersist,
+ bool aCloneEntryChildren, bool aChannelExpired,
+ uint32_t aCacheKey);
+
+ // Calls the session history listeners' OnHistoryReload, storing the result in
+ // aCanReload. If aCanReload is set to true and we have an active or a loading
+ // entry then aLoadState will be initialized from that entry, and
+ // aReloadActiveEntry will be true if we have an active entry. If aCanReload
+ // is true and aLoadState and aReloadActiveEntry are not set then we should
+ // attempt to reload based on the current document in the docshell.
+ void NotifyOnHistoryReload(
+ bool aForceReload, bool& aCanReload,
+ Maybe<NotNull<RefPtr<nsDocShellLoadState>>>& aLoadState,
+ Maybe<bool>& aReloadActiveEntry);
+
+ // See BrowsingContext::SetActiveSessionHistoryEntry.
+ void SetActiveSessionHistoryEntry(const Maybe<nsPoint>& aPreviousScrollPos,
+ SessionHistoryInfo* aInfo,
+ uint32_t aLoadType,
+ uint32_t aUpdatedCacheKey,
+ const nsID& aChangeID);
+
+ void ReplaceActiveSessionHistoryEntry(SessionHistoryInfo* aInfo);
+
+ void RemoveDynEntriesFromActiveSessionHistoryEntry();
+
+ void RemoveFromSessionHistory(const nsID& aChangeID);
+
+ Maybe<int32_t> HistoryGo(int32_t aOffset, uint64_t aHistoryEpoch,
+ bool aRequireUserInteraction, bool aUserActivation,
+ Maybe<ContentParentId> aContentId);
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // Dispatches a wheel zoom change to the embedder element.
+ void DispatchWheelZoomChange(bool aIncrease);
+
+ // This function is used to start the autoplay media which are delayed to
+ // start. If needed, it would also notify the content browsing context which
+ // are related with the canonical browsing content tree to start delayed
+ // autoplay media.
+ void NotifyStartDelayedAutoplayMedia();
+
+ // This function is used to mute or unmute all media within a tab. It would
+ // set the media mute property for the top level window and propagate it to
+ // other top level windows in other processes.
+ void NotifyMediaMutedChanged(bool aMuted, ErrorResult& aRv);
+
+ // Return the number of unique site origins by iterating all given BCs,
+ // including their subtrees.
+ static uint32_t CountSiteOrigins(
+ GlobalObject& aGlobal,
+ const Sequence<mozilla::OwningNonNull<BrowsingContext>>& aRoots);
+
+ // Return true if a private browsing session is active.
+ static bool IsPrivateBrowsingActive();
+
+ // This function would propogate the action to its all child browsing contexts
+ // in content processes.
+ void UpdateMediaControlAction(const MediaControlAction& aAction);
+
+ // Triggers a load in the process
+ using BrowsingContext::LoadURI;
+ void FixupAndLoadURIString(const nsAString& aURI,
+ const LoadURIOptions& aOptions,
+ ErrorResult& aError);
+ void LoadURI(nsIURI* aURI, const LoadURIOptions& aOptions,
+ ErrorResult& aError);
+
+ void GoBack(const Optional<int32_t>& aCancelContentJSEpoch,
+ bool aRequireUserInteraction, bool aUserActivation);
+ void GoForward(const Optional<int32_t>& aCancelContentJSEpoch,
+ bool aRequireUserInteraction, bool aUserActivation);
+ void GoToIndex(int32_t aIndex, const Optional<int32_t>& aCancelContentJSEpoch,
+ bool aUserActivation);
+ void Reload(uint32_t aReloadFlags);
+ void Stop(uint32_t aStopFlags);
+
+ // Get the publicly exposed current URI loaded in this BrowsingContext.
+ already_AddRefed<nsIURI> GetCurrentURI() const;
+ void SetCurrentRemoteURI(nsIURI* aCurrentRemoteURI);
+
+ BrowserParent* GetBrowserParent() const;
+ void SetCurrentBrowserParent(BrowserParent* aBrowserParent);
+
+ // Internal method to change which process a BrowsingContext is being loaded
+ // in. The returned promise will resolve when the process switch is completed.
+ //
+ // A NOT_REMOTE_TYPE aRemoteType argument will perform a process switch into
+ // the parent process, and the method will resolve with a null BrowserParent.
+ using RemotenessPromise = MozPromise<RefPtr<BrowserParent>, nsresult, false>;
+ RefPtr<RemotenessPromise> ChangeRemoteness(
+ const NavigationIsolationOptions& aOptions, uint64_t aPendingSwitchId);
+
+ // Return a media controller from the top-level browsing context that can
+ // control all media belonging to this browsing context tree. Return nullptr
+ // if the top-level browsing context has been discarded.
+ MediaController* GetMediaController();
+ bool HasCreatedMediaController() const;
+
+ // Attempts to start loading the given load state in this BrowsingContext,
+ // without requiring any communication from a docshell. This will handle
+ // computing the right process to load in, and organising handoff to
+ // the right docshell when we get a response.
+ bool LoadInParent(nsDocShellLoadState* aLoadState, bool aSetNavigating);
+
+ // Attempts to start loading the given load state in this BrowsingContext,
+ // in parallel with a DocumentChannelChild being created in the docshell.
+ // Requires the DocumentChannel to connect with this load for it to
+ // complete successfully.
+ bool AttemptSpeculativeLoadInParent(nsDocShellLoadState* aLoadState);
+
+ // Get or create a secure browser UI for this BrowsingContext
+ nsISecureBrowserUI* GetSecureBrowserUI();
+
+ BrowsingContextWebProgress* GetWebProgress() { return mWebProgress; }
+
+ // Called when the current URI changes (from an
+ // nsIWebProgressListener::OnLocationChange event, so that we
+ // can update our security UI for the new location, or when the
+ // mixed content/https-only state for our current window is changed.
+ void UpdateSecurityState();
+
+ void MaybeAddAsProgressListener(nsIWebProgress* aWebProgress);
+
+ // Called when a navigation forces us to recreate our browsing
+ // context (for example, when switching in or out of the parent
+ // process).
+ // aNewContext is the newly created BrowsingContext that is replacing
+ // us.
+ void ReplacedBy(CanonicalBrowsingContext* aNewContext,
+ const NavigationIsolationOptions& aRemotenessOptions);
+
+ bool HasHistoryEntry(nsISHEntry* aEntry);
+ bool HasLoadingHistoryEntry(nsISHEntry* aEntry) {
+ for (const LoadingSessionHistoryEntry& loading : mLoadingEntries) {
+ if (loading.mEntry == aEntry) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void SwapHistoryEntries(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry);
+
+ void AddLoadingSessionHistoryEntry(uint64_t aLoadId,
+ SessionHistoryEntry* aEntry);
+
+ void GetLoadingSessionHistoryInfoFromParent(
+ Maybe<LoadingSessionHistoryInfo>& aLoadingInfo);
+
+ void HistoryCommitIndexAndLength();
+
+ void SynchronizeLayoutHistoryState();
+
+ void ResetScalingZoom();
+
+ void SetContainerFeaturePolicy(FeaturePolicy* aContainerFeaturePolicy);
+ FeaturePolicy* GetContainerFeaturePolicy() const {
+ return mContainerFeaturePolicy;
+ }
+
+ void SetRestoreData(SessionStoreRestoreData* aData, ErrorResult& aError);
+ void ClearRestoreState();
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void RequestRestoreTabContent(
+ WindowGlobalParent* aWindow);
+ already_AddRefed<Promise> GetRestorePromise();
+
+ nsresult WriteSessionStorageToSessionStore(
+ const nsTArray<SSCacheCopy>& aSesssionStorage, uint32_t aEpoch);
+
+ void UpdateSessionStoreSessionStorage(const std::function<void()>& aDone);
+
+ static void UpdateSessionStoreForStorage(uint64_t aBrowsingContextId);
+
+ // Called when a BrowserParent for this BrowsingContext has been fully
+ // destroyed (i.e. `ActorDestroy` was called).
+ void BrowserParentDestroyed(BrowserParent* aBrowserParent,
+ bool aAbnormalShutdown);
+
+ void StartUnloadingHost(uint64_t aChildID);
+ void ClearUnloadingHost(uint64_t aChildID);
+
+ bool AllowedInBFCache(const Maybe<uint64_t>& aChannelId, nsIURI* aNewURI);
+
+ // Methods for getting and setting the active state for top level
+ // browsing contexts, for the process priority manager.
+ bool IsPriorityActive() const {
+ MOZ_RELEASE_ASSERT(IsTop());
+ return mPriorityActive;
+ }
+ void SetPriorityActive(bool aIsActive) {
+ MOZ_RELEASE_ASSERT(IsTop());
+ mPriorityActive = aIsActive;
+ }
+
+ void SetTouchEventsOverride(dom::TouchEventsOverride, ErrorResult& aRv);
+ void SetTargetTopLevelLinkClicksToBlank(bool aTargetTopLevelLinkClicksToBlank,
+ ErrorResult& aRv);
+
+ bool IsReplaced() const { return mIsReplaced; }
+
+ const JS::Heap<JS::Value>& PermanentKey() { return mPermanentKey; }
+ void ClearPermanentKey() { mPermanentKey.setNull(); }
+ void MaybeSetPermanentKey(Element* aEmbedder);
+
+ // When request for page awake, it would increase a count that is used to
+ // prevent whole browsing context tree from being suspended. The request can
+ // be called multiple times. When calling the revoke, it would decrease the
+ // count and once the count reaches to zero, the browsing context tree could
+ // be suspended when the tree is inactive.
+ void AddPageAwakeRequest();
+ void RemovePageAwakeRequest();
+
+ void CloneDocumentTreeInto(CanonicalBrowsingContext* aSource,
+ const nsACString& aRemoteType,
+ embedding::PrintData&& aPrintData);
+
+ // Returns a Promise which resolves when cloning documents for printing
+ // finished if this browsing context is cloning document tree.
+ RefPtr<GenericNonExclusivePromise> GetClonePromise() const {
+ return mClonePromise;
+ }
+
+ bool StartApzAutoscroll(float aAnchorX, float aAnchorY, nsViewID aScrollId,
+ uint32_t aPresShellId);
+ void StopApzAutoscroll(nsViewID aScrollId, uint32_t aPresShellId);
+
+ void AddFinalDiscardListener(std::function<void(uint64_t)>&& aListener);
+
+ bool ForceAppWindowActive() const { return mForceAppWindowActive; }
+ void SetForceAppWindowActive(bool, ErrorResult&);
+ void RecomputeAppWindowVisibility();
+
+ already_AddRefed<nsISHEntry> GetMostRecentLoadingSessionHistoryEntry();
+
+ protected:
+ // Called when the browsing context is being discarded.
+ void CanonicalDiscard();
+
+ // Called when the browsing context is being attached.
+ void CanonicalAttach();
+
+ // Called when the browsing context private mode is changed after
+ // being attached, but before being discarded.
+ void AdjustPrivateBrowsingCount(bool aPrivateBrowsing);
+
+ using Type = BrowsingContext::Type;
+ CanonicalBrowsingContext(WindowContext* aParentWindow,
+ BrowsingContextGroup* aGroup,
+ uint64_t aBrowsingContextId,
+ uint64_t aOwnerProcessId,
+ uint64_t aEmbedderProcessId, Type aType,
+ FieldValues&& aInit);
+
+ private:
+ friend class BrowsingContext;
+
+ virtual ~CanonicalBrowsingContext();
+
+ class PendingRemotenessChange {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(PendingRemotenessChange)
+
+ PendingRemotenessChange(CanonicalBrowsingContext* aTarget,
+ RemotenessPromise::Private* aPromise,
+ uint64_t aPendingSwitchId,
+ const NavigationIsolationOptions& aOptions);
+
+ void Cancel(nsresult aRv);
+
+ private:
+ friend class CanonicalBrowsingContext;
+
+ ~PendingRemotenessChange();
+ void ProcessLaunched();
+ void ProcessReady();
+ void MaybeFinish();
+ void Clear();
+
+ nsresult FinishTopContent();
+ nsresult FinishSubframe();
+
+ RefPtr<CanonicalBrowsingContext> mTarget;
+ RefPtr<RemotenessPromise::Private> mPromise;
+ RefPtr<ContentParent> mContentParent;
+ RefPtr<BrowsingContextGroup> mSpecificGroup;
+
+ bool mProcessReady = false;
+ bool mWaitingForPrepareToChange = false;
+
+ uint64_t mPendingSwitchId;
+ NavigationIsolationOptions mOptions;
+ };
+
+ struct RestoreState {
+ NS_INLINE_DECL_REFCOUNTING(RestoreState)
+
+ void ClearData() { mData = nullptr; }
+ void Resolve();
+
+ RefPtr<SessionStoreRestoreData> mData;
+ RefPtr<Promise> mPromise;
+ uint32_t mRequests = 0;
+ uint32_t mResolves = 0;
+
+ private:
+ ~RestoreState() = default;
+ };
+
+ friend class net::DocumentLoadListener;
+ // Called when a DocumentLoadListener is created to start a load for
+ // this browsing context. Returns false if a higher priority load is
+ // already in-progress and the new one has been rejected.
+ bool StartDocumentLoad(net::DocumentLoadListener* aLoad);
+ // Called once DocumentLoadListener completes handling a load, and it
+ // is either complete, or handed off to the final channel to deliver
+ // data to the destination docshell.
+ // If aContinueNavigating it set, the reference to the DocumentLoadListener
+ // will be cleared to prevent it being cancelled, however the current load ID
+ // will be preserved until `EndDocumentLoad` is called again.
+ void EndDocumentLoad(bool aContinueNavigating);
+
+ bool SupportsLoadingInParent(nsDocShellLoadState* aLoadState,
+ uint64_t* aOuterWindowId);
+
+ void HistoryCommitIndexAndLength(
+ const nsID& aChangeID,
+ const CallerWillNotifyHistoryIndexAndLengthChanges& aProofOfCaller);
+
+ struct UnloadingHost {
+ uint64_t mChildID;
+ nsTArray<std::function<void()>> mCallbacks;
+ };
+ nsTArray<UnloadingHost>::iterator FindUnloadingHost(uint64_t aChildID);
+
+ // Called when we want to show the subframe crashed UI as our previous browser
+ // has become unloaded for one reason or another.
+ void ShowSubframeCrashedUI(BrowserBridgeParent* aBridge);
+
+ void MaybeScheduleSessionStoreUpdate();
+
+ void CancelSessionStoreUpdate();
+
+ void AddPendingDiscard();
+
+ void RemovePendingDiscard();
+
+ bool ShouldAddEntryForRefresh(const SessionHistoryEntry* aEntry) {
+ return ShouldAddEntryForRefresh(aEntry->Info().GetURI(),
+ aEntry->Info().HasPostData());
+ }
+ bool ShouldAddEntryForRefresh(nsIURI* aNewURI, bool aHasPostData) {
+ nsCOMPtr<nsIURI> currentURI = GetCurrentURI();
+ return BrowsingContext::ShouldAddEntryForRefresh(currentURI, aNewURI,
+ aHasPostData);
+ }
+
+ already_AddRefed<nsDocShellLoadState> CreateLoadInfo(
+ SessionHistoryEntry* aEntry);
+
+ // XXX(farre): Store a ContentParent pointer here rather than mProcessId?
+ // Indicates which process owns the docshell.
+ uint64_t mProcessId;
+
+ // Indicates which process owns the embedder element.
+ uint64_t mEmbedderProcessId;
+
+ uint64_t mCrossGroupOpenerId = 0;
+
+ // This function will make the top window context reset its
+ // "SHEntryHasUserInteraction" cache that prevents documents from repeatedly
+ // setting user interaction on SH entries. Should be called anytime SH
+ // entries are added or replaced.
+ void ResetSHEntryHasUserInteractionCache();
+
+ RefPtr<BrowserParent> mCurrentBrowserParent;
+
+ nsTArray<UnloadingHost> mUnloadingHosts;
+
+ // The current URI loaded in this BrowsingContext. This value is only set for
+ // BrowsingContexts loaded in content processes.
+ nsCOMPtr<nsIURI> mCurrentRemoteURI;
+
+ // The current remoteness change which is in a pending state.
+ RefPtr<PendingRemotenessChange> mPendingRemotenessChange;
+
+ RefPtr<nsSHistory> mSessionHistory;
+
+ // Tab media controller is used to control all media existing in the same
+ // browsing context tree, so it would only exist in the top level browsing
+ // context.
+ RefPtr<MediaController> mTabMediaController;
+
+ RefPtr<net::DocumentLoadListener> mCurrentLoad;
+
+ struct LoadingSessionHistoryEntry {
+ uint64_t mLoadId = 0;
+ RefPtr<SessionHistoryEntry> mEntry;
+ };
+ nsTArray<LoadingSessionHistoryEntry> mLoadingEntries;
+ RefPtr<SessionHistoryEntry> mActiveEntry;
+
+ RefPtr<nsSecureBrowserUI> mSecureBrowserUI;
+ RefPtr<BrowsingContextWebProgress> mWebProgress;
+
+ nsCOMPtr<nsIWebProgressListener> mDocShellProgressBridge;
+ RefPtr<nsBrowserStatusFilter> mStatusFilter;
+
+ RefPtr<FeaturePolicy> mContainerFeaturePolicy;
+
+ friend class BrowserSessionStore;
+ WeakPtr<SessionStoreFormData>& GetSessionStoreFormDataRef() {
+ return mFormdata;
+ }
+ WeakPtr<SessionStoreScrollData>& GetSessionStoreScrollDataRef() {
+ return mScroll;
+ }
+
+ WeakPtr<SessionStoreFormData> mFormdata;
+ WeakPtr<SessionStoreScrollData> mScroll;
+
+ RefPtr<RestoreState> mRestoreState;
+
+ nsCOMPtr<nsITimer> mSessionStoreSessionStorageUpdateTimer;
+
+ // If this is a top level context, this is true if our browser ID is marked as
+ // active in the process priority manager.
+ bool mPriorityActive = false;
+
+ // See CanonicalBrowsingContext.forceAppWindowActive.
+ bool mForceAppWindowActive = false;
+
+ bool mIsReplaced = false;
+
+ // A Promise created when cloning documents for printing.
+ RefPtr<GenericNonExclusivePromise> mClonePromise;
+
+ JS::Heap<JS::Value> mPermanentKey;
+
+ uint32_t mPendingDiscards = 0;
+
+ bool mFullyDiscarded = false;
+
+ nsTArray<std::function<void(uint64_t)>> mFullyDiscardedListeners;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // !defined(mozilla_dom_CanonicalBrowsingContext_h)
diff --git a/docshell/base/ChildProcessChannelListener.cpp b/docshell/base/ChildProcessChannelListener.cpp
new file mode 100644
index 0000000000..628d75abb1
--- /dev/null
+++ b/docshell/base/ChildProcessChannelListener.cpp
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ChildProcessChannelListener.h"
+
+#include "mozilla/ipc/Endpoint.h"
+#include "nsDocShellLoadState.h"
+
+namespace mozilla {
+namespace dom {
+
+static StaticRefPtr<ChildProcessChannelListener> sCPCLSingleton;
+
+void ChildProcessChannelListener::RegisterCallback(uint64_t aIdentifier,
+ Callback&& aCallback) {
+ if (auto args = mChannelArgs.Extract(aIdentifier)) {
+ nsresult rv =
+ aCallback(args->mLoadState, std::move(args->mStreamFilterEndpoints),
+ args->mTiming);
+ args->mResolver(rv);
+ } else {
+ mCallbacks.InsertOrUpdate(aIdentifier, std::move(aCallback));
+ }
+}
+
+void ChildProcessChannelListener::OnChannelReady(
+ nsDocShellLoadState* aLoadState, uint64_t aIdentifier,
+ nsTArray<Endpoint>&& aStreamFilterEndpoints, nsDOMNavigationTiming* aTiming,
+ Resolver&& aResolver) {
+ if (auto callback = mCallbacks.Extract(aIdentifier)) {
+ nsresult rv =
+ (*callback)(aLoadState, std::move(aStreamFilterEndpoints), aTiming);
+ aResolver(rv);
+ } else {
+ mChannelArgs.InsertOrUpdate(
+ aIdentifier, CallbackArgs{aLoadState, std::move(aStreamFilterEndpoints),
+ aTiming, std::move(aResolver)});
+ }
+}
+
+ChildProcessChannelListener::~ChildProcessChannelListener() {
+ for (const auto& args : mChannelArgs.Values()) {
+ args.mResolver(NS_ERROR_FAILURE);
+ }
+}
+
+already_AddRefed<ChildProcessChannelListener>
+ChildProcessChannelListener::GetSingleton() {
+ if (!sCPCLSingleton) {
+ sCPCLSingleton = new ChildProcessChannelListener();
+ ClearOnShutdown(&sCPCLSingleton);
+ }
+ RefPtr<ChildProcessChannelListener> cpcl = sCPCLSingleton;
+ return cpcl.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/docshell/base/ChildProcessChannelListener.h b/docshell/base/ChildProcessChannelListener.h
new file mode 100644
index 0000000000..c00c2ff5a7
--- /dev/null
+++ b/docshell/base/ChildProcessChannelListener.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ChildProcessChannelListener_h
+#define mozilla_dom_ChildProcessChannelListener_h
+
+#include <functional>
+
+#include "mozilla/extensions/StreamFilterParent.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsTHashMap.h"
+#include "nsIChannel.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+
+namespace mozilla::dom {
+
+class ChildProcessChannelListener final {
+ NS_INLINE_DECL_REFCOUNTING(ChildProcessChannelListener)
+
+ using Endpoint = mozilla::ipc::Endpoint<extensions::PStreamFilterParent>;
+ using Resolver = std::function<void(const nsresult&)>;
+ using Callback = std::function<nsresult(
+ nsDocShellLoadState*, nsTArray<Endpoint>&&, nsDOMNavigationTiming*)>;
+
+ void RegisterCallback(uint64_t aIdentifier, Callback&& aCallback);
+
+ void OnChannelReady(nsDocShellLoadState* aLoadState, uint64_t aIdentifier,
+ nsTArray<Endpoint>&& aStreamFilterEndpoints,
+ nsDOMNavigationTiming* aTiming, Resolver&& aResolver);
+
+ static already_AddRefed<ChildProcessChannelListener> GetSingleton();
+
+ private:
+ ChildProcessChannelListener() = default;
+ ~ChildProcessChannelListener();
+ struct CallbackArgs {
+ RefPtr<nsDocShellLoadState> mLoadState;
+ nsTArray<Endpoint> mStreamFilterEndpoints;
+ RefPtr<nsDOMNavigationTiming> mTiming;
+ Resolver mResolver;
+ };
+
+ // TODO Backtrack.
+ nsTHashMap<nsUint64HashKey, Callback> mCallbacks;
+ nsTHashMap<nsUint64HashKey, CallbackArgs> mChannelArgs;
+};
+
+} // namespace mozilla::dom
+
+#endif // !defined(mozilla_dom_ChildProcessChannelListener_h)
diff --git a/docshell/base/IHistory.h b/docshell/base/IHistory.h
new file mode 100644
index 0000000000..594ebdb3bb
--- /dev/null
+++ b/docshell/base/IHistory.h
@@ -0,0 +1,169 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_IHistory_h_
+#define mozilla_IHistory_h_
+
+#include "nsISupports.h"
+#include "nsURIHashKey.h"
+#include "nsTHashSet.h"
+#include "nsTObserverArray.h"
+
+class nsIURI;
+class nsIWidget;
+
+namespace mozilla {
+
+namespace dom {
+class ContentParent;
+class Document;
+class Link;
+} // namespace dom
+
+// 0057c9d3-b98e-4933-bdc5-0275d06705e1
+#define IHISTORY_IID \
+ { \
+ 0x0057c9d3, 0xb98e, 0x4933, { \
+ 0xbd, 0xc5, 0x02, 0x75, 0xd0, 0x67, 0x05, 0xe1 \
+ } \
+ }
+
+class IHistory : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(IHISTORY_IID)
+
+ using ContentParentSet = nsTHashSet<RefPtr<dom::ContentParent>>;
+
+ /**
+ * Registers the Link for notifications about the visited-ness of aURI.
+ * Consumers should assume that the URI is unvisited after calling this, and
+ * they will be notified if that state (unvisited) changes by having
+ * VisitedQueryFinished called on themselves. Note that it may call
+ * synchronously if the answer is already known.
+ *
+ * @note VisitedQueryFinished must not call RegisterVisitedCallback or
+ * UnregisterVisitedCallback.
+ *
+ * @pre aURI must not be null.
+ * @pre aLink may be null only in the parent (chrome) process.
+ *
+ * @param aURI
+ * The URI to check.
+ * @param aLink
+ * The link to update whenever the history status changes. The
+ * implementation will only hold onto a raw pointer, so if this
+ * object should be destroyed, be sure to call
+ * UnregisterVistedCallback first.
+ */
+ virtual void RegisterVisitedCallback(nsIURI* aURI, dom::Link* aLink) = 0;
+
+ /**
+ * Schedules a single visited query from a given child process.
+ *
+ * @param aURI the URI to query.
+ * @param ContentParent the process interested in knowing about the visited
+ * state of this URI.
+ */
+ virtual void ScheduleVisitedQuery(nsIURI* aURI, dom::ContentParent*) = 0;
+
+ /**
+ * Unregisters a previously registered Link object. This must be called
+ * before destroying the registered object, and asserts when misused.
+ *
+ * @pre aURI must not be null.
+ * @pre aLink must not be null.
+ *
+ * @param aURI
+ * The URI that aLink was registered for.
+ * @param aLink
+ * The link object to unregister for aURI.
+ */
+ virtual void UnregisterVisitedCallback(nsIURI* aURI, dom::Link* aLink) = 0;
+
+ enum class VisitedStatus : uint8_t {
+ Unknown,
+ Visited,
+ Unvisited,
+ };
+
+ /**
+ * Notifies about the visited status of a given URI. The visited status cannot
+ * be unknown, otherwise there's no point in notifying of anything.
+ *
+ * @param ContentParentSet a set of content processes that are interested on
+ * this change. If null, it is broadcasted to all
+ * child processes.
+ */
+ virtual void NotifyVisited(nsIURI*, VisitedStatus,
+ const ContentParentSet* = nullptr) = 0;
+
+ enum VisitFlags {
+ /**
+ * Indicates whether the URI was loaded in a top-level window.
+ */
+ TOP_LEVEL = 1 << 0,
+ /**
+ * Indicates whether the URI is the target of a permanent redirect.
+ */
+ REDIRECT_PERMANENT = 1 << 1,
+ /**
+ * Indicates whether the URI is the target of a temporary redirect.
+ */
+ REDIRECT_TEMPORARY = 1 << 2,
+ /**
+ * Indicates the URI will redirect (Response code 3xx).
+ */
+ REDIRECT_SOURCE = 1 << 3,
+ /**
+ * Indicates the URI caused an error that is unlikely fixable by a
+ * retry, like a not found or unfetchable page.
+ */
+ UNRECOVERABLE_ERROR = 1 << 4,
+ /**
+ * If REDIRECT_SOURCE is set, this indicates that the redirect is permanent.
+ * Note this differs from REDIRECT_PERMANENT because that one refers to how
+ * we reached the URI, while this is used when the URI itself redirects.
+ */
+ REDIRECT_SOURCE_PERMANENT = 1 << 5
+ };
+
+ /**
+ * Adds a history visit for the URI.
+ *
+ * @pre aURI must not be null.
+ *
+ * @param aWidget
+ * The widget for the DocShell.
+ * @param aURI
+ * The URI of the page being visited.
+ * @param aLastVisitedURI
+ * The URI of the last visit in the chain.
+ * @param aFlags
+ * The VisitFlags describing this visit.
+ * @param aBrowserId
+ * The id of browser used for this visit.
+ */
+ NS_IMETHOD VisitURI(nsIWidget* aWidget, nsIURI* aURI, nsIURI* aLastVisitedURI,
+ uint32_t aFlags, uint64_t aBrowserId) = 0;
+
+ /**
+ * Set the title of the URI.
+ *
+ * @pre aURI must not be null.
+ *
+ * @param aURI
+ * The URI to set the title for.
+ * @param aTitle
+ * The title string.
+ */
+ NS_IMETHOD SetURITitle(nsIURI* aURI, const nsAString& aTitle) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(IHistory, IHISTORY_IID)
+
+} // namespace mozilla
+
+#endif // mozilla_IHistory_h_
diff --git a/docshell/base/LoadContext.cpp b/docshell/base/LoadContext.cpp
new file mode 100644
index 0000000000..2e501be221
--- /dev/null
+++ b/docshell/base/LoadContext.cpp
@@ -0,0 +1,236 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Assertions.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/LoadContext.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ScriptSettings.h" // for AutoJSAPI
+#include "mozilla/dom/BrowsingContext.h"
+#include "nsContentUtils.h"
+#include "xpcpublic.h"
+
+namespace mozilla {
+
+NS_IMPL_ISUPPORTS(LoadContext, nsILoadContext, nsIInterfaceRequestor)
+
+LoadContext::LoadContext(const IPC::SerializedLoadContext& aToCopy,
+ dom::Element* aTopFrameElement,
+ OriginAttributes& aAttrs)
+ : mTopFrameElement(do_GetWeakReference(aTopFrameElement)),
+ mIsContent(aToCopy.mIsContent),
+ mUseRemoteTabs(aToCopy.mUseRemoteTabs),
+ mUseRemoteSubframes(aToCopy.mUseRemoteSubframes),
+ mUseTrackingProtection(aToCopy.mUseTrackingProtection),
+#ifdef DEBUG
+ mIsNotNull(aToCopy.mIsNotNull),
+#endif
+ mOriginAttributes(aAttrs) {
+}
+
+LoadContext::LoadContext(OriginAttributes& aAttrs)
+ : mTopFrameElement(nullptr),
+ mIsContent(false),
+ mUseRemoteTabs(false),
+ mUseRemoteSubframes(false),
+ mUseTrackingProtection(false),
+#ifdef DEBUG
+ mIsNotNull(true),
+#endif
+ mOriginAttributes(aAttrs) {
+}
+
+LoadContext::LoadContext(nsIPrincipal* aPrincipal,
+ nsILoadContext* aOptionalBase)
+ : mTopFrameElement(nullptr),
+ mIsContent(true),
+ mUseRemoteTabs(false),
+ mUseRemoteSubframes(false),
+ mUseTrackingProtection(false),
+#ifdef DEBUG
+ mIsNotNull(true),
+#endif
+ mOriginAttributes(aPrincipal->OriginAttributesRef()) {
+ if (!aOptionalBase) {
+ return;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(aOptionalBase->GetIsContent(&mIsContent));
+ MOZ_ALWAYS_SUCCEEDS(aOptionalBase->GetUseRemoteTabs(&mUseRemoteTabs));
+ MOZ_ALWAYS_SUCCEEDS(
+ aOptionalBase->GetUseRemoteSubframes(&mUseRemoteSubframes));
+ MOZ_ALWAYS_SUCCEEDS(
+ aOptionalBase->GetUseTrackingProtection(&mUseTrackingProtection));
+}
+
+LoadContext::~LoadContext() = default;
+
+//-----------------------------------------------------------------------------
+// LoadContext::nsILoadContext
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+LoadContext::GetAssociatedWindow(mozIDOMWindowProxy**) {
+ MOZ_ASSERT(mIsNotNull);
+
+ // can't support this in the parent process
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+LoadContext::GetTopWindow(mozIDOMWindowProxy**) {
+ MOZ_ASSERT(mIsNotNull);
+
+ // can't support this in the parent process
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+LoadContext::GetTopFrameElement(dom::Element** aElement) {
+ nsCOMPtr<dom::Element> element = do_QueryReferent(mTopFrameElement);
+ element.forget(aElement);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadContext::GetIsContent(bool* aIsContent) {
+ MOZ_ASSERT(mIsNotNull);
+
+ NS_ENSURE_ARG_POINTER(aIsContent);
+
+ *aIsContent = mIsContent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadContext::GetUsePrivateBrowsing(bool* aUsePrivateBrowsing) {
+ MOZ_ASSERT(mIsNotNull);
+
+ NS_ENSURE_ARG_POINTER(aUsePrivateBrowsing);
+
+ *aUsePrivateBrowsing = mOriginAttributes.mPrivateBrowsingId > 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadContext::SetUsePrivateBrowsing(bool aUsePrivateBrowsing) {
+ MOZ_ASSERT(mIsNotNull);
+
+ // We shouldn't need this on parent...
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+LoadContext::SetPrivateBrowsing(bool aUsePrivateBrowsing) {
+ MOZ_ASSERT(mIsNotNull);
+
+ // We shouldn't need this on parent...
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+LoadContext::GetUseRemoteTabs(bool* aUseRemoteTabs) {
+ MOZ_ASSERT(mIsNotNull);
+
+ NS_ENSURE_ARG_POINTER(aUseRemoteTabs);
+
+ *aUseRemoteTabs = mUseRemoteTabs;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadContext::SetRemoteTabs(bool aUseRemoteTabs) {
+ MOZ_ASSERT(mIsNotNull);
+
+ // We shouldn't need this on parent...
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+LoadContext::GetUseRemoteSubframes(bool* aUseRemoteSubframes) {
+ MOZ_ASSERT(mIsNotNull);
+
+ NS_ENSURE_ARG_POINTER(aUseRemoteSubframes);
+
+ *aUseRemoteSubframes = mUseRemoteSubframes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadContext::SetRemoteSubframes(bool aUseRemoteSubframes) {
+ MOZ_ASSERT(mIsNotNull);
+
+ // We shouldn't need this on parent...
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+LoadContext::GetScriptableOriginAttributes(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aAttrs) {
+ bool ok = ToJSValue(aCx, mOriginAttributes, aAttrs);
+ NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+LoadContext::GetOriginAttributes(mozilla::OriginAttributes& aAttrs) {
+ aAttrs = mOriginAttributes;
+}
+
+NS_IMETHODIMP
+LoadContext::GetUseTrackingProtection(bool* aUseTrackingProtection) {
+ MOZ_ASSERT(mIsNotNull);
+
+ NS_ENSURE_ARG_POINTER(aUseTrackingProtection);
+
+ *aUseTrackingProtection = mUseTrackingProtection;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadContext::SetUseTrackingProtection(bool aUseTrackingProtection) {
+ MOZ_ASSERT_UNREACHABLE("Should only be set through nsDocShell");
+
+ return NS_ERROR_UNEXPECTED;
+}
+
+//-----------------------------------------------------------------------------
+// LoadContext::nsIInterfaceRequestor
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+LoadContext::GetInterface(const nsIID& aIID, void** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = nullptr;
+
+ if (aIID.Equals(NS_GET_IID(nsILoadContext))) {
+ *aResult = static_cast<nsILoadContext*>(this);
+ NS_ADDREF_THIS();
+ return NS_OK;
+ }
+
+ return NS_NOINTERFACE;
+}
+
+static already_AddRefed<nsILoadContext> CreateInstance(bool aPrivate) {
+ OriginAttributes oa;
+ oa.mPrivateBrowsingId = aPrivate ? 1 : 0;
+
+ nsCOMPtr<nsILoadContext> lc = new LoadContext(oa);
+
+ return lc.forget();
+}
+
+already_AddRefed<nsILoadContext> CreateLoadContext() {
+ return CreateInstance(false);
+}
+
+already_AddRefed<nsILoadContext> CreatePrivateLoadContext() {
+ return CreateInstance(true);
+}
+
+} // namespace mozilla
diff --git a/docshell/base/LoadContext.h b/docshell/base/LoadContext.h
new file mode 100644
index 0000000000..5cb71ff347
--- /dev/null
+++ b/docshell/base/LoadContext.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef LoadContext_h
+#define LoadContext_h
+
+#include "SerializedLoadContext.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BasePrincipal.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsILoadContext.h"
+
+namespace mozilla::dom {
+class Element;
+}
+
+namespace mozilla {
+
+/**
+ * Class that provides nsILoadContext info in Parent process. Typically copied
+ * from Child via SerializedLoadContext.
+ *
+ * Note: this is not the "normal" or "original" nsILoadContext. That is
+ * typically provided by BrowsingContext. This is only used when the original
+ * docshell is in a different process and we need to copy certain values from
+ * it.
+ */
+
+class LoadContext final : public nsILoadContext, public nsIInterfaceRequestor {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSILOADCONTEXT
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ LoadContext(const IPC::SerializedLoadContext& aToCopy,
+ dom::Element* aTopFrameElement, OriginAttributes& aAttrs);
+
+ // Constructor taking reserved origin attributes.
+ explicit LoadContext(OriginAttributes& aAttrs);
+
+ // Constructor for creating a LoadContext with a given browser flag.
+ explicit LoadContext(nsIPrincipal* aPrincipal,
+ nsILoadContext* aOptionalBase = nullptr);
+
+ private:
+ ~LoadContext();
+
+ nsWeakPtr mTopFrameElement;
+ bool mIsContent;
+ bool mUseRemoteTabs;
+ bool mUseRemoteSubframes;
+ bool mUseTrackingProtection;
+#ifdef DEBUG
+ bool mIsNotNull;
+#endif
+ OriginAttributes mOriginAttributes;
+};
+
+already_AddRefed<nsILoadContext> CreateLoadContext();
+already_AddRefed<nsILoadContext> CreatePrivateLoadContext();
+
+} // namespace mozilla
+
+#endif // LoadContext_h
diff --git a/docshell/base/SerializedLoadContext.cpp b/docshell/base/SerializedLoadContext.cpp
new file mode 100644
index 0000000000..cb598b43fe
--- /dev/null
+++ b/docshell/base/SerializedLoadContext.cpp
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SerializedLoadContext.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+#include "nsILoadContext.h"
+#include "nsIPrivateBrowsingChannel.h"
+#include "nsIWebSocketChannel.h"
+
+namespace IPC {
+
+SerializedLoadContext::SerializedLoadContext(nsILoadContext* aLoadContext)
+ : mIsContent(false),
+ mUseRemoteTabs(false),
+ mUseRemoteSubframes(false),
+ mUseTrackingProtection(false) {
+ Init(aLoadContext);
+}
+
+SerializedLoadContext::SerializedLoadContext(nsIChannel* aChannel)
+ : mIsContent(false),
+ mUseRemoteTabs(false),
+ mUseRemoteSubframes(false),
+ mUseTrackingProtection(false) {
+ if (!aChannel) {
+ Init(nullptr);
+ return;
+ }
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(aChannel, loadContext);
+ Init(loadContext);
+
+ if (!loadContext) {
+ // Attempt to retrieve the private bit from the channel if it has been
+ // overriden.
+ bool isPrivate = false;
+ bool isOverriden = false;
+ nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(aChannel);
+ if (pbChannel &&
+ NS_SUCCEEDED(
+ pbChannel->IsPrivateModeOverriden(&isPrivate, &isOverriden)) &&
+ isOverriden) {
+ mIsPrivateBitValid = true;
+ }
+ mOriginAttributes.SyncAttributesWithPrivateBrowsing(isPrivate);
+ }
+}
+
+SerializedLoadContext::SerializedLoadContext(nsIWebSocketChannel* aChannel)
+ : mIsContent(false),
+ mUseRemoteTabs(false),
+ mUseRemoteSubframes(false),
+ mUseTrackingProtection(false) {
+ nsCOMPtr<nsILoadContext> loadContext;
+ if (aChannel) {
+ NS_QueryNotificationCallbacks(aChannel, loadContext);
+ }
+ Init(loadContext);
+}
+
+void SerializedLoadContext::Init(nsILoadContext* aLoadContext) {
+ if (aLoadContext) {
+ mIsNotNull = true;
+ mIsPrivateBitValid = true;
+ aLoadContext->GetIsContent(&mIsContent);
+ aLoadContext->GetUseRemoteTabs(&mUseRemoteTabs);
+ aLoadContext->GetUseRemoteSubframes(&mUseRemoteSubframes);
+ aLoadContext->GetUseTrackingProtection(&mUseTrackingProtection);
+ aLoadContext->GetOriginAttributes(mOriginAttributes);
+ } else {
+ mIsNotNull = false;
+ mIsPrivateBitValid = false;
+ // none of below values really matter when mIsNotNull == false:
+ // we won't be GetInterfaced to nsILoadContext
+ mIsContent = true;
+ mUseRemoteTabs = false;
+ mUseRemoteSubframes = false;
+ mUseTrackingProtection = false;
+ }
+}
+
+} // namespace IPC
diff --git a/docshell/base/SerializedLoadContext.h b/docshell/base/SerializedLoadContext.h
new file mode 100644
index 0000000000..d47ad08003
--- /dev/null
+++ b/docshell/base/SerializedLoadContext.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SerializedLoadContext_h
+#define SerializedLoadContext_h
+
+#include "base/basictypes.h"
+#include "ipc/IPCMessageUtils.h"
+#include "ipc/IPCMessageUtilsSpecializations.h"
+#include "mozilla/BasePrincipal.h"
+
+class nsILoadContext;
+
+/*
+ * This file contains the IPC::SerializedLoadContext class, which is used to
+ * copy data across IPDL from Child process contexts so it is available in the
+ * Parent.
+ */
+
+class nsIChannel;
+class nsIWebSocketChannel;
+
+namespace IPC {
+
+class SerializedLoadContext {
+ public:
+ SerializedLoadContext()
+ : mIsNotNull(false),
+ mIsPrivateBitValid(false),
+ mIsContent(false),
+ mUseRemoteTabs(false),
+ mUseRemoteSubframes(false),
+ mUseTrackingProtection(false) {
+ Init(nullptr);
+ }
+
+ explicit SerializedLoadContext(nsILoadContext* aLoadContext);
+ explicit SerializedLoadContext(nsIChannel* aChannel);
+ explicit SerializedLoadContext(nsIWebSocketChannel* aChannel);
+
+ void Init(nsILoadContext* aLoadContext);
+
+ bool IsNotNull() const { return mIsNotNull; }
+ bool IsPrivateBitValid() const { return mIsPrivateBitValid; }
+
+ // used to indicate if child-side LoadContext * was null.
+ bool mIsNotNull;
+ // used to indicate if child-side mUsePrivateBrowsing flag is valid, even if
+ // mIsNotNull is false, i.e., child LoadContext was null.
+ bool mIsPrivateBitValid;
+ bool mIsContent;
+ bool mUseRemoteTabs;
+ bool mUseRemoteSubframes;
+ bool mUseTrackingProtection;
+ mozilla::OriginAttributes mOriginAttributes;
+};
+
+// Function to serialize over IPDL
+template <>
+struct ParamTraits<SerializedLoadContext> {
+ typedef SerializedLoadContext paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ nsAutoCString suffix;
+ aParam.mOriginAttributes.CreateSuffix(suffix);
+
+ WriteParam(aWriter, aParam.mIsNotNull);
+ WriteParam(aWriter, aParam.mIsContent);
+ WriteParam(aWriter, aParam.mIsPrivateBitValid);
+ WriteParam(aWriter, aParam.mUseRemoteTabs);
+ WriteParam(aWriter, aParam.mUseRemoteSubframes);
+ WriteParam(aWriter, aParam.mUseTrackingProtection);
+ WriteParam(aWriter, suffix);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ nsAutoCString suffix;
+ if (!ReadParam(aReader, &aResult->mIsNotNull) ||
+ !ReadParam(aReader, &aResult->mIsContent) ||
+ !ReadParam(aReader, &aResult->mIsPrivateBitValid) ||
+ !ReadParam(aReader, &aResult->mUseRemoteTabs) ||
+ !ReadParam(aReader, &aResult->mUseRemoteSubframes) ||
+ !ReadParam(aReader, &aResult->mUseTrackingProtection) ||
+ !ReadParam(aReader, &suffix)) {
+ return false;
+ }
+ return aResult->mOriginAttributes.PopulateFromSuffix(suffix);
+ }
+};
+
+} // namespace IPC
+
+#endif // SerializedLoadContext_h
diff --git a/docshell/base/SyncedContext.h b/docshell/base/SyncedContext.h
new file mode 100644
index 0000000000..679e07edc2
--- /dev/null
+++ b/docshell/base/SyncedContext.h
@@ -0,0 +1,402 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_SyncedContext_h
+#define mozilla_dom_SyncedContext_h
+
+#include <array>
+#include <type_traits>
+#include <utility>
+#include "mozilla/Attributes.h"
+#include "mozilla/BitSet.h"
+#include "mozilla/EnumSet.h"
+#include "nsStringFwd.h"
+#include "nscore.h"
+
+// Referenced via macro definitions
+#include "mozilla/ErrorResult.h"
+
+class PickleIterator;
+
+namespace IPC {
+class Message;
+class MessageReader;
+class MessageWriter;
+} // namespace IPC
+
+namespace mozilla {
+namespace ipc {
+class IProtocol;
+class IPCResult;
+template <typename T>
+struct IPDLParamTraits;
+} // namespace ipc
+
+namespace dom {
+class ContentParent;
+class ContentChild;
+template <typename T>
+class MaybeDiscarded;
+
+namespace syncedcontext {
+
+template <size_t I>
+using Index = typename std::integral_constant<size_t, I>;
+
+// We're going to use the empty base optimization for synced fields of different
+// sizes, so we define an empty class for that purpose.
+template <size_t I, size_t S>
+struct Empty {};
+
+// A templated container for a synced field. I is the index and T is the type.
+template <size_t I, typename T>
+struct Field {
+ T mField{};
+};
+
+// SizedField is a Field with a helper to define either an "empty" field, or a
+// field of a given type.
+template <size_t I, typename T, size_t S>
+using SizedField = std::conditional_t<((sizeof(T) > 8) ? 8 : sizeof(T)) == S,
+ Field<I, T>, Empty<I, S>>;
+
+template <typename Context>
+class Transaction {
+ public:
+ using IndexSet = EnumSet<size_t, BitSet<Context::FieldValues::count>>;
+
+ // Set a field at the given index in this `Transaction`. Creating a
+ // `Transaction` object and setting multiple fields on it allows for
+ // multiple mutations to be performed atomically.
+ template <size_t I, typename U>
+ void Set(U&& aValue) {
+ mValues.Get(Index<I>{}) = std::forward<U>(aValue);
+ mModified += I;
+ }
+
+ // Apply the changes from this transaction to the specified Context in all
+ // processes. This method will call the correct `CanSet` and `DidSet` methods,
+ // as well as move the value.
+ //
+ // If the target has been discarded, changes will be ignored.
+ //
+ // NOTE: This method mutates `this`, clearing the modified field set.
+ [[nodiscard]] nsresult Commit(Context* aOwner);
+
+ // Called from `ContentParent` in response to a transaction from content.
+ mozilla::ipc::IPCResult CommitFromIPC(const MaybeDiscarded<Context>& aOwner,
+ ContentParent* aSource);
+
+ // Called from `ContentChild` in response to a transaction from the parent.
+ mozilla::ipc::IPCResult CommitFromIPC(const MaybeDiscarded<Context>& aOwner,
+ uint64_t aEpoch, ContentChild* aSource);
+
+ // Apply the changes from this transaction to the specified Context WITHOUT
+ // syncing the changes to other processes.
+ //
+ // Unlike `Commit`, this method will NOT call the corresponding `CanSet` or
+ // `DidSet` methods, and can be performed when the target context is
+ // unattached or discarded.
+ //
+ // NOTE: YOU PROBABLY DO NOT WANT TO USE THIS METHOD
+ void CommitWithoutSyncing(Context* aOwner);
+
+ private:
+ friend struct mozilla::ipc::IPDLParamTraits<Transaction<Context>>;
+
+ void Write(IPC::MessageWriter* aWriter,
+ mozilla::ipc::IProtocol* aActor) const;
+ bool Read(IPC::MessageReader* aReader, mozilla::ipc::IProtocol* aActor);
+
+ // You probably don't want to directly call this method - instead call
+ // `Commit`, which will perform the necessary synchronization.
+ //
+ // `Validate` must be called before calling this method.
+ void Apply(Context* aOwner, bool aFromIPC);
+
+ // Returns the set of fields which failed to validate, or an empty set if
+ // there were no validation errors.
+ //
+ // NOTE: This method mutates `this` if any changes were reverted.
+ IndexSet Validate(Context* aOwner, ContentParent* aSource);
+
+ template <typename F>
+ static void EachIndex(F&& aCallback) {
+ Context::FieldValues::EachIndex(aCallback);
+ }
+
+ template <size_t I>
+ static uint64_t& FieldEpoch(Index<I>, Context* aContext) {
+ return std::get<I>(aContext->mFields.mEpochs);
+ }
+
+ typename Context::FieldValues mValues;
+ IndexSet mModified;
+};
+
+template <typename Base, size_t Count>
+class FieldValues : public Base {
+ public:
+ // The number of fields stored by this type.
+ static constexpr size_t count = Count;
+
+ // The base type will define a series of `Get` methods for looking up a field
+ // by its field index.
+ using Base::Get;
+
+ // Calls a generic lambda with an `Index<I>` for each index less than the
+ // field count.
+ template <typename F>
+ static void EachIndex(F&& aCallback) {
+ EachIndexInner(std::make_index_sequence<count>(),
+ std::forward<F>(aCallback));
+ }
+
+ private:
+ friend struct mozilla::ipc::IPDLParamTraits<FieldValues<Base, Count>>;
+
+ void Write(IPC::MessageWriter* aWriter,
+ mozilla::ipc::IProtocol* aActor) const;
+ bool Read(IPC::MessageReader* aReader, mozilla::ipc::IProtocol* aActor);
+
+ template <typename F, size_t... Indexes>
+ static void EachIndexInner(std::index_sequence<Indexes...> aIndexes,
+ F&& aCallback) {
+ (aCallback(Index<Indexes>()), ...);
+ }
+};
+
+// Storage related to synchronized context fields. Contains both a tuple of
+// individual field values, and epoch information for field synchronization.
+template <typename Values>
+class FieldStorage {
+ public:
+ // Unsafely grab a reference directly to the internal values structure which
+ // can be modified without telling other processes about the change.
+ //
+ // This is only sound in specific code which is already messaging other
+ // processes, and doesn't need to worry about epochs or other properties of
+ // field synchronization.
+ Values& RawValues() { return mValues; }
+ const Values& RawValues() const { return mValues; }
+
+ // Get an individual field by index.
+ template <size_t I>
+ const auto& Get() const {
+ return RawValues().Get(Index<I>{});
+ }
+
+ // Set the value of a field without telling other processes about the change.
+ //
+ // This is only sound in specific code which is already messaging other
+ // processes, and doesn't need to worry about epochs or other properties of
+ // field synchronization.
+ template <size_t I, typename U>
+ void SetWithoutSyncing(U&& aValue) {
+ GetNonSyncingReference<I>() = std::move(aValue);
+ }
+
+ // Get a reference to a field that can modify without telling other
+ // processes about the change.
+ //
+ // This is only sound in specific code which is already messaging other
+ // processes, and doesn't need to worry about epochs or other properties of
+ // field synchronization.
+ template <size_t I>
+ auto& GetNonSyncingReference() {
+ return RawValues().Get(Index<I>{});
+ }
+
+ FieldStorage() = default;
+ explicit FieldStorage(Values&& aInit) : mValues(std::move(aInit)) {}
+
+ private:
+ template <typename Context>
+ friend class Transaction;
+
+ // Data Members
+ std::array<uint64_t, Values::count> mEpochs{};
+ Values mValues;
+};
+
+// Alternative return type enum for `CanSet` validators which allows specifying
+// more behaviour.
+enum class CanSetResult : uint8_t {
+ // The set attempt is denied. This is equivalent to returning `false`.
+ Deny,
+ // The set attempt is allowed. This is equivalent to returning `true`.
+ Allow,
+ // The set attempt is reverted non-fatally.
+ Revert,
+};
+
+// Helper type traits to use concrete types rather than generic forwarding
+// references for the `SetXXX` methods defined on the synced context type.
+//
+// This helps avoid potential issues where someone accidentally declares an
+// overload of these methods with slightly different types and different
+// behaviours. See bug 1659520.
+template <typename T>
+struct GetFieldSetterType {
+ using SetterArg = T;
+};
+template <>
+struct GetFieldSetterType<nsString> {
+ using SetterArg = const nsAString&;
+};
+template <>
+struct GetFieldSetterType<nsCString> {
+ using SetterArg = const nsACString&;
+};
+template <typename T>
+using FieldSetterType = typename GetFieldSetterType<T>::SetterArg;
+
+#define MOZ_DECL_SYNCED_CONTEXT_FIELD_INDEX(name, type) IDX_##name,
+
+#define MOZ_DECL_SYNCED_CONTEXT_FIELD_GETSET(name, type) \
+ const type& Get##name() const { return mFields.template Get<IDX_##name>(); } \
+ \
+ [[nodiscard]] nsresult Set##name( \
+ ::mozilla::dom::syncedcontext::FieldSetterType<type> aValue) { \
+ Transaction txn; \
+ txn.template Set<IDX_##name>(std::move(aValue)); \
+ return txn.Commit(this); \
+ } \
+ void Set##name(::mozilla::dom::syncedcontext::FieldSetterType<type> aValue, \
+ ErrorResult& aRv) { \
+ nsresult rv = this->Set##name(std::move(aValue)); \
+ if (NS_FAILED(rv)) { \
+ aRv.ThrowInvalidStateError("cannot set synced field '" #name \
+ "': context is discarded"); \
+ } \
+ }
+
+#define MOZ_DECL_SYNCED_CONTEXT_TRANSACTION_SET(name, type) \
+ template <typename U> \
+ void Set##name(U&& aValue) { \
+ this->template Set<IDX_##name>(std::forward<U>(aValue)); \
+ }
+#define MOZ_DECL_SYNCED_CONTEXT_INDEX_TO_NAME(name, type) \
+ case IDX_##name: \
+ return #name;
+
+#define MOZ_DECL_SYNCED_FIELD_INHERIT(name, type) \
+ public \
+ syncedcontext::SizedField<IDX_##name, type, Size>,
+
+#define MOZ_DECL_SYNCED_CONTEXT_BASE_FIELD_GETTER(name, type) \
+ type& Get(FieldIndex<IDX_##name>) { \
+ return Field<IDX_##name, type>::mField; \
+ } \
+ const type& Get(FieldIndex<IDX_##name>) const { \
+ return Field<IDX_##name, type>::mField; \
+ }
+
+// Declare a type as a synced context type.
+//
+// clazz is the name of the type being declared, and `eachfield` is a macro
+// which, when called with the name of the macro, will call that macro once for
+// each field in the synced context.
+#define MOZ_DECL_SYNCED_CONTEXT(clazz, eachfield) \
+ public: \
+ /* Index constants for referring to each field in generic code */ \
+ enum FieldIndexes { \
+ eachfield(MOZ_DECL_SYNCED_CONTEXT_FIELD_INDEX) SYNCED_FIELD_COUNT \
+ }; \
+ \
+ /* Helper for overloading methods like `CanSet` and `DidSet` */ \
+ template <size_t I> \
+ using FieldIndex = typename ::mozilla::dom::syncedcontext::Index<I>; \
+ \
+ /* Fields contain all synced fields defined by \
+ * `eachfield(MOZ_DECL_SYNCED_FIELD_INHERIT)`, but only those where the size \
+ * of the field is equal to size Size will be present. We use SizedField to \
+ * remove fields of the wrong size. */ \
+ template <size_t Size> \
+ struct Fields : eachfield(MOZ_DECL_SYNCED_FIELD_INHERIT) \
+ syncedcontext::Empty<SYNCED_FIELD_COUNT, Size> {}; \
+ \
+ /* Struct containing the data for all synced fields as members. We filter \
+ * sizes to lay out fields of size 1, then 2, then 4 and last 8 or greater. \
+ * This way we don't need to consider packing when defining fields, but \
+ * we'll just reorder them here. \
+ */ \
+ struct BaseFieldValues : public Fields<1>, \
+ public Fields<2>, \
+ public Fields<4>, \
+ public Fields<8> { \
+ template <size_t I> \
+ auto& Get() { \
+ return Get(FieldIndex<I>{}); \
+ } \
+ template <size_t I> \
+ const auto& Get() const { \
+ return Get(FieldIndex<I>{}); \
+ } \
+ eachfield(MOZ_DECL_SYNCED_CONTEXT_BASE_FIELD_GETTER) \
+ }; \
+ using FieldValues = \
+ typename ::mozilla::dom::syncedcontext::FieldValues<BaseFieldValues, \
+ SYNCED_FIELD_COUNT>; \
+ \
+ protected: \
+ friend class ::mozilla::dom::syncedcontext::Transaction<clazz>; \
+ ::mozilla::dom::syncedcontext::FieldStorage<FieldValues> mFields; \
+ \
+ public: \
+ /* Transaction types for bulk mutations */ \
+ using BaseTransaction = ::mozilla::dom::syncedcontext::Transaction<clazz>; \
+ class Transaction final : public BaseTransaction { \
+ public: \
+ eachfield(MOZ_DECL_SYNCED_CONTEXT_TRANSACTION_SET) \
+ }; \
+ \
+ /* Field name getter by field index */ \
+ static const char* FieldIndexToName(size_t aIndex) { \
+ switch (aIndex) { eachfield(MOZ_DECL_SYNCED_CONTEXT_INDEX_TO_NAME) } \
+ return "<unknown>"; \
+ } \
+ eachfield(MOZ_DECL_SYNCED_CONTEXT_FIELD_GETSET)
+
+} // namespace syncedcontext
+} // namespace dom
+
+namespace ipc {
+
+template <typename Context>
+struct IPDLParamTraits<dom::syncedcontext::Transaction<Context>> {
+ typedef dom::syncedcontext::Transaction<Context> paramType;
+
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const paramType& aParam) {
+ aParam.Write(aWriter, aActor);
+ }
+
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ paramType* aResult) {
+ return aResult->Read(aReader, aActor);
+ }
+};
+
+template <typename Base, size_t Count>
+struct IPDLParamTraits<dom::syncedcontext::FieldValues<Base, Count>> {
+ typedef dom::syncedcontext::FieldValues<Base, Count> paramType;
+
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const paramType& aParam) {
+ aParam.Write(aWriter, aActor);
+ }
+
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ paramType* aResult) {
+ return aResult->Read(aReader, aActor);
+ }
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // !defined(mozilla_dom_SyncedContext_h)
diff --git a/docshell/base/SyncedContextInlines.h b/docshell/base/SyncedContextInlines.h
new file mode 100644
index 0000000000..99141d630d
--- /dev/null
+++ b/docshell/base/SyncedContextInlines.h
@@ -0,0 +1,358 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_SyncedContextInlines_h
+#define mozilla_dom_SyncedContextInlines_h
+
+#include "mozilla/dom/SyncedContext.h"
+#include "mozilla/dom/BrowsingContextGroup.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/ContentChild.h"
+#include "nsReadableUtils.h"
+#include "mozilla/HalIPCUtils.h"
+
+namespace mozilla {
+namespace dom {
+namespace syncedcontext {
+
+template <typename T>
+struct IsMozillaMaybe : std::false_type {};
+template <typename T>
+struct IsMozillaMaybe<Maybe<T>> : std::true_type {};
+
+// A super-sketchy logging only function for generating a human-readable version
+// of the value of a synced context field.
+template <typename T>
+void FormatFieldValue(nsACString& aStr, const T& aValue) {
+ if constexpr (std::is_same_v<bool, T>) {
+ if (aValue) {
+ aStr.AppendLiteral("true");
+ } else {
+ aStr.AppendLiteral("false");
+ }
+ } else if constexpr (std::is_same_v<nsID, T>) {
+ aStr.Append(nsIDToCString(aValue).get());
+ } else if constexpr (std::is_integral_v<T>) {
+ aStr.AppendInt(aValue);
+ } else if constexpr (std::is_enum_v<T>) {
+ aStr.AppendInt(static_cast<std::underlying_type_t<T>>(aValue));
+ } else if constexpr (std::is_floating_point_v<T>) {
+ aStr.AppendFloat(aValue);
+ } else if constexpr (std::is_base_of_v<nsAString, T>) {
+ AppendUTF16toUTF8(aValue, aStr);
+ } else if constexpr (std::is_base_of_v<nsACString, T>) {
+ aStr.Append(aValue);
+ } else if constexpr (IsMozillaMaybe<T>::value) {
+ if (aValue) {
+ aStr.AppendLiteral("Some(");
+ FormatFieldValue(aStr, aValue.ref());
+ aStr.AppendLiteral(")");
+ } else {
+ aStr.AppendLiteral("Nothing");
+ }
+ } else {
+ aStr.AppendLiteral("???");
+ }
+}
+
+// Sketchy logging-only logic to generate a human-readable output of the actions
+// a synced context transaction is going to perform.
+template <typename Context>
+nsAutoCString FormatTransaction(
+ typename Transaction<Context>::IndexSet aModified,
+ const typename Context::FieldValues& aOldValues,
+ const typename Context::FieldValues& aNewValues) {
+ nsAutoCString result;
+ Context::FieldValues::EachIndex([&](auto idx) {
+ if (aModified.contains(idx)) {
+ result.Append(Context::FieldIndexToName(idx));
+ result.AppendLiteral("(");
+ FormatFieldValue(result, aOldValues.Get(idx));
+ result.AppendLiteral("->");
+ FormatFieldValue(result, aNewValues.Get(idx));
+ result.AppendLiteral(") ");
+ }
+ });
+ return result;
+}
+
+template <typename Context>
+nsCString FormatValidationError(
+ typename Transaction<Context>::IndexSet aFailedFields, const char* prefix) {
+ MOZ_ASSERT(!aFailedFields.isEmpty());
+ return nsDependentCString{prefix} +
+ StringJoin(", "_ns, aFailedFields,
+ [](nsACString& dest, const auto& idx) {
+ dest.Append(Context::FieldIndexToName(idx));
+ });
+}
+
+template <typename Context>
+nsresult Transaction<Context>::Commit(Context* aOwner) {
+ if (NS_WARN_IF(aOwner->IsDiscarded())) {
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ IndexSet failedFields = Validate(aOwner, nullptr);
+ if (!failedFields.isEmpty()) {
+ nsCString error = FormatValidationError<Context>(
+ failedFields, "CanSet failed for field(s): ");
+ MOZ_CRASH_UNSAFE_PRINTF("%s", error.get());
+ }
+
+ if (mModified.isEmpty()) {
+ return NS_OK;
+ }
+
+ if (XRE_IsContentProcess()) {
+ ContentChild* cc = ContentChild::GetSingleton();
+
+ // Increment the field epoch for fields affected by this transaction.
+ uint64_t epoch = cc->NextBrowsingContextFieldEpoch();
+ EachIndex([&](auto idx) {
+ if (mModified.contains(idx)) {
+ FieldEpoch(idx, aOwner) = epoch;
+ }
+ });
+
+ // Tell our derived class to send the correct "Commit" IPC message.
+ aOwner->SendCommitTransaction(cc, *this, epoch);
+ } else {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+
+ // Tell our derived class to send the correct "Commit" IPC messages.
+ BrowsingContextGroup* group = aOwner->Group();
+ group->EachParent([&](ContentParent* aParent) {
+ aOwner->SendCommitTransaction(aParent, *this,
+ aParent->GetBrowsingContextFieldEpoch());
+ });
+ }
+
+ Apply(aOwner, /* aFromIPC */ false);
+ return NS_OK;
+}
+
+template <typename Context>
+mozilla::ipc::IPCResult Transaction<Context>::CommitFromIPC(
+ const MaybeDiscarded<Context>& aOwner, ContentParent* aSource) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+ if (aOwner.IsNullOrDiscarded()) {
+ MOZ_LOG(Context::GetSyncLog(), LogLevel::Debug,
+ ("IPC: Trying to send a message to dead or detached context"));
+ return IPC_OK();
+ }
+ Context* owner = aOwner.get();
+
+ // Validate that the set from content is allowed before continuing.
+ IndexSet failedFields = Validate(owner, aSource);
+ if (!failedFields.isEmpty()) {
+ nsCString error = FormatValidationError<Context>(
+ failedFields,
+ "Invalid Transaction from Child - CanSet failed for field(s): ");
+ return IPC_FAIL(aSource, error.get());
+ }
+
+ // Validate may have dropped some fields from the transaction, check it's not
+ // empty before continuing.
+ if (mModified.isEmpty()) {
+ return IPC_OK();
+ }
+
+ BrowsingContextGroup* group = owner->Group();
+ group->EachOtherParent(aSource, [&](ContentParent* aParent) {
+ owner->SendCommitTransaction(aParent, *this,
+ aParent->GetBrowsingContextFieldEpoch());
+ });
+
+ Apply(owner, /* aFromIPC */ true);
+ return IPC_OK();
+}
+
+template <typename Context>
+mozilla::ipc::IPCResult Transaction<Context>::CommitFromIPC(
+ const MaybeDiscarded<Context>& aOwner, uint64_t aEpoch,
+ ContentChild* aSource) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess());
+ if (aOwner.IsNullOrDiscarded()) {
+ MOZ_LOG(Context::GetSyncLog(), LogLevel::Debug,
+ ("ChildIPC: Trying to send a message to dead or detached context"));
+ return IPC_OK();
+ }
+ Context* owner = aOwner.get();
+
+ // Clear any fields which have been obsoleted by the epoch.
+ EachIndex([&](auto idx) {
+ if (mModified.contains(idx) && FieldEpoch(idx, owner) > aEpoch) {
+ MOZ_LOG(
+ Context::GetSyncLog(), LogLevel::Debug,
+ ("Transaction::Obsoleted(#%" PRIx64 ", %" PRIu64 ">%" PRIu64 "): %s",
+ owner->Id(), FieldEpoch(idx, owner), aEpoch,
+ Context::FieldIndexToName(idx)));
+ mModified -= idx;
+ }
+ });
+
+ if (mModified.isEmpty()) {
+ return IPC_OK();
+ }
+
+ Apply(owner, /* aFromIPC */ true);
+ return IPC_OK();
+}
+
+template <typename Context>
+void Transaction<Context>::Apply(Context* aOwner, bool aFromIPC) {
+ MOZ_ASSERT(!mModified.isEmpty());
+
+ MOZ_LOG(
+ Context::GetSyncLog(), LogLevel::Debug,
+ ("Transaction::Apply(#%" PRIx64 ", %s): %s", aOwner->Id(),
+ aFromIPC ? "ipc" : "local",
+ FormatTransaction<Context>(mModified, aOwner->mFields.mValues, mValues)
+ .get()));
+
+ EachIndex([&](auto idx) {
+ if (mModified.contains(idx)) {
+ auto& txnField = mValues.Get(idx);
+ auto& ownerField = aOwner->mFields.mValues.Get(idx);
+ std::swap(ownerField, txnField);
+ aOwner->DidSet(idx);
+ aOwner->DidSet(idx, std::move(txnField));
+ }
+ });
+ mModified.clear();
+}
+
+template <typename Context>
+void Transaction<Context>::CommitWithoutSyncing(Context* aOwner) {
+ MOZ_LOG(
+ Context::GetSyncLog(), LogLevel::Debug,
+ ("Transaction::CommitWithoutSyncing(#%" PRIx64 "): %s", aOwner->Id(),
+ FormatTransaction<Context>(mModified, aOwner->mFields.mValues, mValues)
+ .get()));
+
+ EachIndex([&](auto idx) {
+ if (mModified.contains(idx)) {
+ aOwner->mFields.mValues.Get(idx) = std::move(mValues.Get(idx));
+ }
+ });
+ mModified.clear();
+}
+
+inline CanSetResult AsCanSetResult(CanSetResult aValue) { return aValue; }
+inline CanSetResult AsCanSetResult(bool aValue) {
+ return aValue ? CanSetResult::Allow : CanSetResult::Deny;
+}
+
+template <typename Context>
+typename Transaction<Context>::IndexSet Transaction<Context>::Validate(
+ Context* aOwner, ContentParent* aSource) {
+ IndexSet failedFields;
+ Transaction<Context> revertTxn;
+
+ EachIndex([&](auto idx) {
+ if (!mModified.contains(idx)) {
+ return;
+ }
+
+ switch (AsCanSetResult(aOwner->CanSet(idx, mValues.Get(idx), aSource))) {
+ case CanSetResult::Allow:
+ break;
+ case CanSetResult::Deny:
+ failedFields += idx;
+ break;
+ case CanSetResult::Revert:
+ revertTxn.mValues.Get(idx) = aOwner->mFields.mValues.Get(idx);
+ revertTxn.mModified += idx;
+ break;
+ }
+ });
+
+ // If any changes need to be reverted, log them, remove them from this
+ // transaction, and optionally send a message to the source process.
+ if (!revertTxn.mModified.isEmpty()) {
+ // NOTE: Logging with modified IndexSet from revert transaction, and values
+ // from this transaction, so we log the failed values we're going to revert.
+ MOZ_LOG(
+ Context::GetSyncLog(), LogLevel::Debug,
+ ("Transaction::PartialRevert(#%" PRIx64 ", pid %" PRIPID "): %s",
+ aOwner->Id(), aSource ? aSource->OtherPid() : base::kInvalidProcessId,
+ FormatTransaction<Context>(revertTxn.mModified, mValues,
+ revertTxn.mValues)
+ .get()));
+
+ mModified -= revertTxn.mModified;
+
+ if (aSource) {
+ aOwner->SendCommitTransaction(aSource, revertTxn,
+ aSource->GetBrowsingContextFieldEpoch());
+ }
+ }
+ return failedFields;
+}
+
+template <typename Context>
+void Transaction<Context>::Write(IPC::MessageWriter* aWriter,
+ mozilla::ipc::IProtocol* aActor) const {
+ // Record which field indices will be included, and then write those fields
+ // out.
+ typename IndexSet::serializedType modified = mModified.serialize();
+ WriteIPDLParam(aWriter, aActor, modified);
+ EachIndex([&](auto idx) {
+ if (mModified.contains(idx)) {
+ WriteIPDLParam(aWriter, aActor, mValues.Get(idx));
+ }
+ });
+}
+
+template <typename Context>
+bool Transaction<Context>::Read(IPC::MessageReader* aReader,
+ mozilla::ipc::IProtocol* aActor) {
+ // Read in which field indices were sent by the remote, followed by the fields
+ // identified by those indices.
+ typename IndexSet::serializedType modified =
+ typename IndexSet::serializedType{};
+ if (!ReadIPDLParam(aReader, aActor, &modified)) {
+ return false;
+ }
+ mModified.deserialize(modified);
+
+ bool ok = true;
+ EachIndex([&](auto idx) {
+ if (ok && mModified.contains(idx)) {
+ ok = ReadIPDLParam(aReader, aActor, &mValues.Get(idx));
+ }
+ });
+ return ok;
+}
+
+template <typename Base, size_t Count>
+void FieldValues<Base, Count>::Write(IPC::MessageWriter* aWriter,
+ mozilla::ipc::IProtocol* aActor) const {
+ // XXX The this-> qualification is necessary to work around a bug in older gcc
+ // versions causing an ICE.
+ EachIndex([&](auto idx) { WriteIPDLParam(aWriter, aActor, this->Get(idx)); });
+}
+
+template <typename Base, size_t Count>
+bool FieldValues<Base, Count>::Read(IPC::MessageReader* aReader,
+ mozilla::ipc::IProtocol* aActor) {
+ bool ok = true;
+ EachIndex([&](auto idx) {
+ if (ok) {
+ // XXX The this-> qualification is necessary to work around a bug in older
+ // gcc versions causing an ICE.
+ ok = ReadIPDLParam(aReader, aActor, &this->Get(idx));
+ }
+ });
+ return ok;
+}
+
+} // namespace syncedcontext
+} // namespace dom
+} // namespace mozilla
+
+#endif // !defined(mozilla_dom_SyncedContextInlines_h)
diff --git a/docshell/base/URIFixup.sys.mjs b/docshell/base/URIFixup.sys.mjs
new file mode 100644
index 0000000000..212bb0ddbf
--- /dev/null
+++ b/docshell/base/URIFixup.sys.mjs
@@ -0,0 +1,1303 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This component handles fixing up URIs, by correcting obvious typos and adding
+ * missing schemes.
+ * URI references:
+ * http://www.faqs.org/rfcs/rfc1738.html
+ * http://www.faqs.org/rfcs/rfc2396.html
+ */
+
+// TODO (Bug 1641220) getFixupURIInfo has a complex logic, that likely could be
+// simplified, but the risk of regressing its behavior is high.
+/* eslint complexity: ["error", 43] */
+
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+
+import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
+
+const lazy = {};
+
+XPCOMUtils.defineLazyServiceGetter(
+ lazy,
+ "externalProtocolService",
+ "@mozilla.org/uriloader/external-protocol-service;1",
+ "nsIExternalProtocolService"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ lazy,
+ "defaultProtocolHandler",
+ "@mozilla.org/network/protocol;1?name=default",
+ "nsIProtocolHandler"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ lazy,
+ "fileProtocolHandler",
+ "@mozilla.org/network/protocol;1?name=file",
+ "nsIFileProtocolHandler"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ lazy,
+ "handlerService",
+ "@mozilla.org/uriloader/handler-service;1",
+ "nsIHandlerService"
+);
+
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "fixupSchemeTypos",
+ "browser.fixup.typo.scheme",
+ true
+);
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "dnsFirstForSingleWords",
+ "browser.fixup.dns_first_for_single_words",
+ false
+);
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "keywordEnabled",
+ "keyword.enabled",
+ true
+);
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "alternateEnabled",
+ "browser.fixup.alternate.enabled",
+ false
+);
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "alternateProtocol",
+ "browser.fixup.alternate.protocol",
+ "https"
+);
+
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "dnsResolveFullyQualifiedNames",
+ "browser.urlbar.dnsResolveFullyQualifiedNames",
+ true
+);
+
+const {
+ FIXUP_FLAG_NONE,
+ FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP,
+ FIXUP_FLAGS_MAKE_ALTERNATE_URI,
+ FIXUP_FLAG_PRIVATE_CONTEXT,
+ FIXUP_FLAG_FIX_SCHEME_TYPOS,
+ FIXUP_FLAG_FORCE_ALTERNATE_URI,
+} = Ci.nsIURIFixup;
+
+const COMMON_PROTOCOLS = ["http", "https", "file"];
+
+// Regex used to identify user:password tokens in url strings.
+// This is not a strict valid characters check, because we try to fixup this
+// part of the url too.
+XPCOMUtils.defineLazyGetter(
+ lazy,
+ "userPasswordRegex",
+ () => /^([a-z+.-]+:\/{0,3})*([^\/@]+@).+/i
+);
+
+// Regex used to identify the string that starts with port expression.
+XPCOMUtils.defineLazyGetter(lazy, "portRegex", () => /^:\d{1,5}([?#/]|$)/);
+
+// Regex used to identify numbers.
+XPCOMUtils.defineLazyGetter(lazy, "numberRegex", () => /^[0-9]+(\.[0-9]+)?$/);
+
+// Regex used to identify tab separated content (having at least 2 tabs).
+XPCOMUtils.defineLazyGetter(lazy, "maxOneTabRegex", () => /^[^\t]*\t?[^\t]*$/);
+
+// Regex used to test if a string with a protocol might instead be a url
+// without a protocol but with a port:
+//
+// <hostname>:<port> or
+// <hostname>:<port>/
+//
+// Where <hostname> is a string of alphanumeric characters and dashes
+// separated by dots.
+// and <port> is a 5 or less digits. This actually breaks the rfc2396
+// definition of a scheme which allows dots in schemes.
+//
+// Note:
+// People expecting this to work with
+// <user>:<password>@<host>:<port>/<url-path> will be disappointed!
+//
+// Note: Parser could be a lot tighter, tossing out silly hostnames
+// such as those containing consecutive dots and so on.
+XPCOMUtils.defineLazyGetter(
+ lazy,
+ "possiblyHostPortRegex",
+ () => /^[a-z0-9-]+(\.[a-z0-9-]+)*:[0-9]{1,5}([/?#]|$)/i
+);
+
+// Regex used to strip newlines.
+XPCOMUtils.defineLazyGetter(lazy, "newLinesRegex", () => /[\r\n]/g);
+
+// Regex used to match a possible protocol.
+// This resembles the logic in Services.io.extractScheme, thus \t is admitted
+// and stripped later. We don't use Services.io.extractScheme because of
+// performance bottleneck caused by crossing XPConnect.
+XPCOMUtils.defineLazyGetter(
+ lazy,
+ "possibleProtocolRegex",
+ () => /^([a-z][a-z0-9.+\t-]*)(:|;)?(\/\/)?/i
+);
+
+// Regex used to match IPs. Note that these are not made to validate IPs, but
+// just to detect strings that look like an IP. They also skip protocol.
+// For IPv4 this also accepts a shorthand format with just 2 dots.
+XPCOMUtils.defineLazyGetter(
+ lazy,
+ "IPv4LikeRegex",
+ () => /^(?:[a-z+.-]+:\/*(?!\/))?(?:\d{1,3}\.){2,3}\d{1,3}(?::\d+|\/)?/i
+);
+XPCOMUtils.defineLazyGetter(
+ lazy,
+ "IPv6LikeRegex",
+ () =>
+ /^(?:[a-z+.-]+:\/*(?!\/))?\[(?:[0-9a-f]{0,4}:){0,7}[0-9a-f]{0,4}\]?(?::\d+|\/)?/i
+);
+
+// Cache of known domains.
+XPCOMUtils.defineLazyGetter(lazy, "knownDomains", () => {
+ const branch = "browser.fixup.domainwhitelist.";
+ let domains = new Set(
+ Services.prefs
+ .getChildList(branch)
+ .filter(p => Services.prefs.getBoolPref(p, false))
+ .map(p => p.substring(branch.length))
+ );
+ // Hold onto the observer to avoid it being GC-ed.
+ domains._observer = {
+ observe(subject, topic, data) {
+ let domain = data.substring(branch.length);
+ if (Services.prefs.getBoolPref(data, false)) {
+ domains.add(domain);
+ } else {
+ domains.delete(domain);
+ }
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIObserver",
+ "nsISupportsWeakReference",
+ ]),
+ };
+ Services.prefs.addObserver(branch, domains._observer, true);
+ return domains;
+});
+
+// Cache of known suffixes.
+// This works differently from the known domains, because when we examine a
+// domain we can't tell how many dot-separated parts constitute the suffix.
+// We create a Map keyed by the last dotted part, containing a Set of
+// all the suffixes ending with that part:
+// "two" => ["two"]
+// "three" => ["some.three", "three"]
+// When searching we can restrict the linear scan based on the last part.
+// The ideal structure for this would be a Directed Acyclic Word Graph, but
+// since we expect this list to be small it's not worth the complication.
+XPCOMUtils.defineLazyGetter(lazy, "knownSuffixes", () => {
+ const branch = "browser.fixup.domainsuffixwhitelist.";
+ let suffixes = new Map();
+ let prefs = Services.prefs
+ .getChildList(branch)
+ .filter(p => Services.prefs.getBoolPref(p, false));
+ for (let pref of prefs) {
+ let suffix = pref.substring(branch.length);
+ let lastPart = suffix.substr(suffix.lastIndexOf(".") + 1);
+ if (lastPart) {
+ let entries = suffixes.get(lastPart);
+ if (!entries) {
+ entries = new Set();
+ suffixes.set(lastPart, entries);
+ }
+ entries.add(suffix);
+ }
+ }
+ // Hold onto the observer to avoid it being GC-ed.
+ suffixes._observer = {
+ observe(subject, topic, data) {
+ let suffix = data.substring(branch.length);
+ let lastPart = suffix.substr(suffix.lastIndexOf(".") + 1);
+ let entries = suffixes.get(lastPart);
+ if (Services.prefs.getBoolPref(data, false)) {
+ // Add the suffix.
+ if (!entries) {
+ entries = new Set();
+ suffixes.set(lastPart, entries);
+ }
+ entries.add(suffix);
+ } else if (entries) {
+ // Remove the suffix.
+ entries.delete(suffix);
+ if (!entries.size) {
+ suffixes.delete(lastPart);
+ }
+ }
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIObserver",
+ "nsISupportsWeakReference",
+ ]),
+ };
+ Services.prefs.addObserver(branch, suffixes._observer, true);
+ return suffixes;
+});
+
+export function URIFixup() {
+ // There are cases that nsIExternalProtocolService.externalProtocolHandlerExists() does
+ // not work well and returns always true due to flatpak. In this case, in order to
+ // fallback to nsIHandlerService.exits(), we test whether can trust
+ // nsIExternalProtocolService here.
+ this._trustExternalProtocolService =
+ !lazy.externalProtocolService.externalProtocolHandlerExists(
+ `__dummy${Date.now()}__`
+ );
+}
+
+URIFixup.prototype = {
+ get FIXUP_FLAG_NONE() {
+ return FIXUP_FLAG_NONE;
+ },
+ get FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP() {
+ return FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
+ },
+ get FIXUP_FLAGS_MAKE_ALTERNATE_URI() {
+ return FIXUP_FLAGS_MAKE_ALTERNATE_URI;
+ },
+ get FIXUP_FLAG_FORCE_ALTERNATE_URI() {
+ return FIXUP_FLAG_FORCE_ALTERNATE_URI;
+ },
+ get FIXUP_FLAG_PRIVATE_CONTEXT() {
+ return FIXUP_FLAG_PRIVATE_CONTEXT;
+ },
+ get FIXUP_FLAG_FIX_SCHEME_TYPOS() {
+ return FIXUP_FLAG_FIX_SCHEME_TYPOS;
+ },
+
+ getFixupURIInfo(uriString, fixupFlags = FIXUP_FLAG_NONE) {
+ let isPrivateContext = fixupFlags & FIXUP_FLAG_PRIVATE_CONTEXT;
+
+ // Eliminate embedded newlines, which single-line text fields now allow,
+ // and cleanup the empty spaces and tabs that might be on each end.
+ uriString = uriString.trim().replace(lazy.newLinesRegex, "");
+
+ if (!uriString) {
+ throw new Components.Exception(
+ "Should pass a non-null uri",
+ Cr.NS_ERROR_FAILURE
+ );
+ }
+
+ let info = new URIFixupInfo(uriString);
+
+ const { scheme, fixedSchemeUriString, fixupChangedProtocol } =
+ extractScheme(uriString, fixupFlags);
+ uriString = fixedSchemeUriString;
+ info.fixupChangedProtocol = fixupChangedProtocol;
+
+ if (scheme == "view-source") {
+ let { preferredURI, postData } = fixupViewSource(uriString, fixupFlags);
+ info.preferredURI = info.fixedURI = preferredURI;
+ info.postData = postData;
+ return info;
+ }
+
+ if (scheme.length < 2) {
+ // Check if it is a file path. We skip most schemes because the only case
+ // where a file path may look like having a scheme is "X:" on Windows.
+ let fileURI = fileURIFixup(uriString);
+ if (fileURI) {
+ info.preferredURI = info.fixedURI = fileURI;
+ info.fixupChangedProtocol = true;
+ return info;
+ }
+ }
+
+ const isCommonProtocol = COMMON_PROTOCOLS.includes(scheme);
+
+ let canHandleProtocol =
+ scheme &&
+ (isCommonProtocol ||
+ Services.io.getProtocolHandler(scheme) != lazy.defaultProtocolHandler ||
+ this._isKnownExternalProtocol(scheme));
+
+ if (
+ canHandleProtocol ||
+ // If it's an unknown handler and the given URL looks like host:port or
+ // has a user:password we can't pass it to the external protocol handler.
+ // We'll instead try fixing it with http later.
+ (!lazy.possiblyHostPortRegex.test(uriString) &&
+ !lazy.userPasswordRegex.test(uriString))
+ ) {
+ // Just try to create an URL out of it.
+ try {
+ info.fixedURI = Services.io.newURI(uriString);
+ } catch (ex) {
+ if (ex.result != Cr.NS_ERROR_MALFORMED_URI) {
+ throw ex;
+ }
+ }
+ }
+
+ // We're dealing with a theoretically valid URI but we have no idea how to
+ // load it. (e.g. "christmas:humbug")
+ // It's more likely the user wants to search, and so we chuck this over to
+ // their preferred search provider.
+ // TODO (Bug 1588118): Should check FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP
+ // instead of FIXUP_FLAG_FIX_SCHEME_TYPOS.
+ if (
+ info.fixedURI &&
+ lazy.keywordEnabled &&
+ fixupFlags & FIXUP_FLAG_FIX_SCHEME_TYPOS &&
+ scheme &&
+ !canHandleProtocol
+ ) {
+ tryKeywordFixupForURIInfo(uriString, info, isPrivateContext);
+ }
+
+ if (info.fixedURI) {
+ if (!info.preferredURI) {
+ maybeSetAlternateFixedURI(info, fixupFlags);
+ info.preferredURI = info.fixedURI;
+ }
+ fixupConsecutiveDotsHost(info);
+ return info;
+ }
+
+ // Fix up protocol string before calling KeywordURIFixup, because
+ // it cares about the hostname of such URIs.
+ // Prune duff protocol schemes:
+ // ://totallybroken.url.com
+ // //shorthand.url.com
+ let inputHadDuffProtocol =
+ uriString.startsWith("://") || uriString.startsWith("//");
+ if (inputHadDuffProtocol) {
+ uriString = uriString.replace(/^:?\/\//, "");
+ }
+
+ // Avoid fixing up content that looks like tab-separated values.
+ // Assume that 1 tab is accidental, but more than 1 implies this is
+ // supposed to be tab-separated content.
+ if (!isCommonProtocol && lazy.maxOneTabRegex.test(uriString)) {
+ let uriWithProtocol = fixupURIProtocol(uriString);
+ if (uriWithProtocol) {
+ info.fixedURI = uriWithProtocol;
+ info.fixupChangedProtocol = true;
+ maybeSetAlternateFixedURI(info, fixupFlags);
+ info.preferredURI = info.fixedURI;
+ // Check if it's a forced visit. The user can enforce a visit by
+ // appending a slash, but the string must be in a valid uri format.
+ if (uriString.endsWith("/")) {
+ fixupConsecutiveDotsHost(info);
+ return info;
+ }
+ }
+ }
+
+ // Handle "www.<something>" as a URI.
+ const asciiHost = info.fixedURI?.asciiHost;
+ if (
+ asciiHost?.length > 4 &&
+ asciiHost?.startsWith("www.") &&
+ asciiHost?.lastIndexOf(".") == 3
+ ) {
+ return info;
+ }
+
+ // Memoize the public suffix check, since it may be expensive and should
+ // only run once when necessary.
+ let suffixInfo;
+ function checkSuffix(info) {
+ if (!suffixInfo) {
+ suffixInfo = checkAndFixPublicSuffix(info);
+ }
+ return suffixInfo;
+ }
+
+ // See if it is a keyword and whether a keyword must be fixed up.
+ if (
+ lazy.keywordEnabled &&
+ fixupFlags & FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP &&
+ !inputHadDuffProtocol &&
+ !checkSuffix(info).suffix &&
+ keywordURIFixup(uriString, info, isPrivateContext)
+ ) {
+ fixupConsecutiveDotsHost(info);
+ return info;
+ }
+
+ if (
+ info.fixedURI &&
+ (!info.fixupChangedProtocol || !checkSuffix(info).hasUnknownSuffix)
+ ) {
+ fixupConsecutiveDotsHost(info);
+ return info;
+ }
+
+ // If we still haven't been able to construct a valid URI, try to force a
+ // keyword match.
+ if (lazy.keywordEnabled && fixupFlags & FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP) {
+ tryKeywordFixupForURIInfo(info.originalInput, info, isPrivateContext);
+ }
+
+ if (!info.preferredURI) {
+ // We couldn't salvage anything.
+ throw new Components.Exception(
+ "Couldn't build a valid uri",
+ Cr.NS_ERROR_MALFORMED_URI
+ );
+ }
+
+ fixupConsecutiveDotsHost(info);
+ return info;
+ },
+
+ webNavigationFlagsToFixupFlags(href, navigationFlags) {
+ try {
+ Services.io.newURI(href);
+ // Remove LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP for valid uris.
+ navigationFlags &=
+ ~Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ } catch (ex) {}
+
+ let fixupFlags = FIXUP_FLAG_NONE;
+ if (
+ navigationFlags & Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP
+ ) {
+ fixupFlags |= FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
+ }
+ if (navigationFlags & Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS) {
+ fixupFlags |= FIXUP_FLAG_FIX_SCHEME_TYPOS;
+ }
+ return fixupFlags;
+ },
+
+ keywordToURI(keyword, isPrivateContext) {
+ if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) {
+ // There's no search service in the content process, thus all the calls
+ // from it that care about keywords conversion should go through the
+ // parent process.
+ throw new Components.Exception(
+ "Can't invoke URIFixup in the content process",
+ Cr.NS_ERROR_NOT_AVAILABLE
+ );
+ }
+ let info = new URIFixupInfo(keyword);
+
+ // Strip leading "?" and leading/trailing spaces from aKeyword
+ if (keyword.startsWith("?")) {
+ keyword = keyword.substring(1);
+ }
+ keyword = keyword.trim();
+
+ if (!Services.search.hasSuccessfullyInitialized) {
+ return info;
+ }
+
+ // Try falling back to the search service's default search engine
+ // We must use an appropriate search engine depending on the private
+ // context.
+ let engine = isPrivateContext
+ ? Services.search.defaultPrivateEngine
+ : Services.search.defaultEngine;
+
+ // We allow default search plugins to specify alternate parameters that are
+ // specific to keyword searches.
+ let responseType = null;
+ if (engine.supportsResponseType("application/x-moz-keywordsearch")) {
+ responseType = "application/x-moz-keywordsearch";
+ }
+ let submission = engine.getSubmission(keyword, responseType, "keyword");
+ if (
+ !submission ||
+ // For security reasons (avoid redirecting to file, data, or other unsafe
+ // protocols) we only allow fixup to http/https search engines.
+ !submission.uri.scheme.startsWith("http")
+ ) {
+ throw new Components.Exception(
+ "Invalid search submission uri",
+ Cr.NS_ERROR_NOT_AVAILABLE
+ );
+ }
+ let submissionPostDataStream = submission.postData;
+ if (submissionPostDataStream) {
+ info.postData = submissionPostDataStream;
+ }
+
+ info.keywordProviderName = engine.name;
+ info.keywordAsSent = keyword;
+ info.preferredURI = submission.uri;
+ return info;
+ },
+
+ forceHttpFixup(uriString) {
+ if (!uriString) {
+ throw new Components.Exception(
+ "Should pass a non-null uri",
+ Cr.NS_ERROR_FAILURE
+ );
+ }
+
+ let info = new URIFixupInfo(uriString);
+ let { scheme, fixedSchemeUriString, fixupChangedProtocol } = extractScheme(
+ uriString,
+ FIXUP_FLAG_FIX_SCHEME_TYPOS
+ );
+
+ if (scheme != "http" && scheme != "https") {
+ throw new Components.Exception(
+ "Scheme should be either http or https",
+ Cr.NS_ERROR_FAILURE
+ );
+ }
+
+ info.fixupChangedProtocol = fixupChangedProtocol;
+ info.fixedURI = Services.io.newURI(fixedSchemeUriString);
+
+ let host = info.fixedURI.host;
+ if (host != "http" && host != "https" && host != "localhost") {
+ let modifiedHostname = maybeAddPrefixAndSuffix(host);
+ updateHostAndScheme(info, modifiedHostname);
+ info.preferredURI = info.fixedURI;
+ }
+
+ return info;
+ },
+
+ checkHost(uri, listener, originAttributes) {
+ let { displayHost, asciiHost } = uri;
+ if (!displayHost) {
+ throw new Components.Exception(
+ "URI must have displayHost",
+ Cr.NS_ERROR_FAILURE
+ );
+ }
+ if (!asciiHost) {
+ throw new Components.Exception(
+ "URI must have asciiHost",
+ Cr.NS_ERROR_FAILURE
+ );
+ }
+
+ let isIPv4Address = host => {
+ let parts = host.split(".");
+ if (parts.length != 4) {
+ return false;
+ }
+ return parts.every(part => {
+ let n = parseInt(part, 10);
+ return n >= 0 && n <= 255;
+ });
+ };
+
+ // Avoid showing fixup information if we're suggesting an IP. Note that
+ // decimal representations of IPs are normalized to a 'regular'
+ // dot-separated IP address by network code, but that only happens for
+ // numbers that don't overflow. Longer numbers do not get normalized,
+ // but still work to access IP addresses. So for instance,
+ // 1097347366913 (ff7f000001) gets resolved by using the final bytes,
+ // making it the same as 7f000001, which is 127.0.0.1 aka localhost.
+ // While 2130706433 would get normalized by network, 1097347366913
+ // does not, and we have to deal with both cases here:
+ if (isIPv4Address(asciiHost) || /^(?:\d+|0x[a-f0-9]+)$/i.test(asciiHost)) {
+ return;
+ }
+
+ // For dotless hostnames, we want to ensure this ends with a '.' but don't
+ // want the . showing up in the UI if we end up notifying the user, so we
+ // use a separate variable.
+ let lookupName = displayHost;
+ if (lazy.dnsResolveFullyQualifiedNames && !lookupName.includes(".")) {
+ lookupName += ".";
+ }
+
+ Services.obs.notifyObservers(null, "uri-fixup-check-dns");
+ Services.dns.asyncResolve(
+ lookupName,
+ Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
+ 0,
+ null,
+ listener,
+ Services.tm.mainThread,
+ originAttributes
+ );
+ },
+
+ isDomainKnown,
+
+ _isKnownExternalProtocol(scheme) {
+ if (this._trustExternalProtocolService) {
+ return lazy.externalProtocolService.externalProtocolHandlerExists(scheme);
+ }
+
+ try {
+ // nsIExternalProtocolService.getProtocolHandlerInfo() on Android throws
+ // error due to not implemented.
+ return lazy.handlerService.exists(
+ lazy.externalProtocolService.getProtocolHandlerInfo(scheme)
+ );
+ } catch (e) {
+ return false;
+ }
+ },
+
+ classID: Components.ID("{c6cf88b7-452e-47eb-bdc9-86e3561648ef}"),
+ QueryInterface: ChromeUtils.generateQI(["nsIURIFixup"]),
+};
+
+export function URIFixupInfo(originalInput = "") {
+ this._originalInput = originalInput;
+}
+
+URIFixupInfo.prototype = {
+ set consumer(consumer) {
+ this._consumer = consumer || null;
+ },
+ get consumer() {
+ return this._consumer || null;
+ },
+
+ set preferredURI(uri) {
+ this._preferredURI = uri;
+ },
+ get preferredURI() {
+ return this._preferredURI || null;
+ },
+
+ set fixedURI(uri) {
+ this._fixedURI = uri;
+ },
+ get fixedURI() {
+ return this._fixedURI || null;
+ },
+
+ set keywordProviderName(name) {
+ this._keywordProviderName = name;
+ },
+ get keywordProviderName() {
+ return this._keywordProviderName || "";
+ },
+
+ set keywordAsSent(keyword) {
+ this._keywordAsSent = keyword;
+ },
+ get keywordAsSent() {
+ return this._keywordAsSent || "";
+ },
+
+ set fixupChangedProtocol(changed) {
+ this._fixupChangedProtocol = changed;
+ },
+ get fixupChangedProtocol() {
+ return !!this._fixupChangedProtocol;
+ },
+
+ set fixupCreatedAlternateURI(changed) {
+ this._fixupCreatedAlternateURI = changed;
+ },
+ get fixupCreatedAlternateURI() {
+ return !!this._fixupCreatedAlternateURI;
+ },
+
+ set originalInput(input) {
+ this._originalInput = input;
+ },
+ get originalInput() {
+ return this._originalInput || "";
+ },
+
+ set postData(postData) {
+ this._postData = postData;
+ },
+ get postData() {
+ return this._postData || null;
+ },
+
+ classID: Components.ID("{33d75835-722f-42c0-89cc-44f328e56a86}"),
+ QueryInterface: ChromeUtils.generateQI(["nsIURIFixupInfo"]),
+};
+
+// Helpers
+
+/**
+ * Implementation of isDomainKnown, so we don't have to go through the
+ * service.
+ * @param {string} asciiHost
+ * @returns {boolean} whether the domain is known
+ */
+function isDomainKnown(asciiHost) {
+ if (lazy.dnsFirstForSingleWords) {
+ return true;
+ }
+ // Check if this domain is known as an actual
+ // domain (which will prevent a keyword query)
+ // Note that any processing of the host here should stay in sync with
+ // code in the front-end(s) that set the pref.
+ let lastDotIndex = asciiHost.lastIndexOf(".");
+ if (lastDotIndex == asciiHost.length - 1) {
+ asciiHost = asciiHost.substring(0, asciiHost.length - 1);
+ lastDotIndex = asciiHost.lastIndexOf(".");
+ }
+ if (lazy.knownDomains.has(asciiHost.toLowerCase())) {
+ return true;
+ }
+ // If there's no dot or only a leading dot we are done, otherwise we'll check
+ // against the known suffixes.
+ if (lastDotIndex <= 0) {
+ return false;
+ }
+ // Don't use getPublicSuffix here, since the suffix is not in the PSL,
+ // thus it couldn't tell if the suffix is made up of one or multiple
+ // dot-separated parts.
+ let lastPart = asciiHost.substr(lastDotIndex + 1);
+ let suffixes = lazy.knownSuffixes.get(lastPart);
+ if (suffixes) {
+ return Array.from(suffixes).some(s => asciiHost.endsWith(s));
+ }
+ return false;
+}
+
+/**
+ * Checks the suffix of info.fixedURI against the Public Suffix List.
+ * If the suffix is unknown due to a typo this will try to fix it up.
+ * @param {URIFixupInfo} info about the uri to check.
+ * @note this may modify the public suffix of info.fixedURI.
+ * @returns {object} result The lookup result.
+ * @returns {string} result.suffix The public suffix if one can be identified.
+ * @returns {boolean} result.hasUnknownSuffix True when the suffix is not in the
+ * Public Suffix List and it's not in knownSuffixes. False in the other cases.
+ */
+function checkAndFixPublicSuffix(info) {
+ let uri = info.fixedURI;
+ let asciiHost = uri?.asciiHost;
+ if (
+ !asciiHost ||
+ !asciiHost.includes(".") ||
+ asciiHost.endsWith(".") ||
+ isDomainKnown(asciiHost)
+ ) {
+ return { suffix: "", hasUnknownSuffix: false };
+ }
+
+ // Quick bailouts for most common cases, according to Alexa Top 1 million.
+ if (
+ /^\w/.test(asciiHost) &&
+ (asciiHost.endsWith(".com") ||
+ asciiHost.endsWith(".net") ||
+ asciiHost.endsWith(".org") ||
+ asciiHost.endsWith(".ru") ||
+ asciiHost.endsWith(".de"))
+ ) {
+ return {
+ suffix: asciiHost.substring(asciiHost.lastIndexOf(".") + 1),
+ hasUnknownSuffix: false,
+ };
+ }
+ try {
+ let suffix = Services.eTLD.getKnownPublicSuffix(uri);
+ if (suffix) {
+ return { suffix, hasUnknownSuffix: false };
+ }
+ } catch (ex) {
+ return { suffix: "", hasUnknownSuffix: false };
+ }
+ // Suffix is unknown, try to fix most common 3 chars TLDs typos.
+ // .com is the most commonly mistyped tld, so it has more cases.
+ let suffix = Services.eTLD.getPublicSuffix(uri);
+ if (!suffix || lazy.numberRegex.test(suffix)) {
+ return { suffix: "", hasUnknownSuffix: false };
+ }
+ for (let [typo, fixed] of [
+ ["ocm", "com"],
+ ["con", "com"],
+ ["cmo", "com"],
+ ["xom", "com"],
+ ["vom", "com"],
+ ["cpm", "com"],
+ ["com'", "com"],
+ ["ent", "net"],
+ ["ner", "net"],
+ ["nte", "net"],
+ ["met", "net"],
+ ["rog", "org"],
+ ["ogr", "org"],
+ ["prg", "org"],
+ ["orh", "org"],
+ ]) {
+ if (suffix == typo) {
+ let host = uri.host.substring(0, uri.host.length - typo.length) + fixed;
+ let updatePreferredURI = info.preferredURI == info.fixedURI;
+ info.fixedURI = uri.mutate().setHost(host).finalize();
+ if (updatePreferredURI) {
+ info.preferredURI = info.fixedURI;
+ }
+ return { suffix: fixed, hasUnknownSuffix: false };
+ }
+ }
+ return { suffix: "", hasUnknownSuffix: true };
+}
+
+function tryKeywordFixupForURIInfo(uriString, fixupInfo, isPrivateContext) {
+ try {
+ let keywordInfo = Services.uriFixup.keywordToURI(
+ uriString,
+ isPrivateContext
+ );
+ fixupInfo.keywordProviderName = keywordInfo.keywordProviderName;
+ fixupInfo.keywordAsSent = keywordInfo.keywordAsSent;
+ fixupInfo.preferredURI = keywordInfo.preferredURI;
+ return true;
+ } catch (ex) {}
+ return false;
+}
+
+/**
+ * This generates an alternate fixedURI, by adding a prefix and a suffix to
+ * the fixedURI host, if and only if the protocol is http. It should _never_
+ * modify URIs with other protocols.
+ * @param {URIFixupInfo} info an URIInfo object
+ * @param {integer} fixupFlags the fixup flags
+ * @returns {boolean} Whether an alternate uri was generated
+ */
+function maybeSetAlternateFixedURI(info, fixupFlags) {
+ let uri = info.fixedURI;
+ let canUseAlternate =
+ fixupFlags & FIXUP_FLAG_FORCE_ALTERNATE_URI ||
+ (lazy.alternateEnabled && fixupFlags & FIXUP_FLAGS_MAKE_ALTERNATE_URI);
+ if (
+ !canUseAlternate ||
+ // Code only works for http. Not for any other protocol including https!
+ !uri.schemeIs("http") ||
+ // Security - URLs with user / password info should NOT be fixed up
+ uri.userPass ||
+ // Don't fix up hosts with ports
+ uri.port != -1
+ ) {
+ return false;
+ }
+
+ let oldHost = uri.host;
+ // Don't create an alternate uri for localhost, because it would be confusing.
+ // Ditto for 'http' and 'https' as these are frequently the result of typos, e.g.
+ // 'https//foo' (note missing : ).
+ if (oldHost == "localhost" || oldHost == "http" || oldHost == "https") {
+ return false;
+ }
+
+ // Get the prefix and suffix to stick onto the new hostname. By default these
+ // are www. & .com but they could be any other value, e.g. www. & .org
+ let newHost = maybeAddPrefixAndSuffix(oldHost);
+
+ if (newHost == oldHost) {
+ return false;
+ }
+
+ return updateHostAndScheme(info, newHost);
+}
+
+/**
+ * Try to fixup a file URI.
+ * @param {string} uriString The file URI to fix.
+ * @returns {nsIURI} a fixed uri or null.
+ * @note FileURIFixup only returns a URI if it has to add the file: protocol.
+ */
+function fileURIFixup(uriString) {
+ let attemptFixup = false;
+ let path = uriString;
+ if (AppConstants.platform == "win") {
+ // Check for "\"" in the url-string, just a drive (e.g. C:),
+ // or 'A:/...' where the "protocol" is also a single letter.
+ attemptFixup =
+ uriString.includes("\\") ||
+ (uriString[1] == ":" && (uriString.length == 2 || uriString[2] == "/"));
+ if (uriString[1] == ":" && uriString[2] == "/") {
+ path = uriString.replace(/\//g, "\\");
+ }
+ } else {
+ // UNIX: Check if it starts with "/".
+ attemptFixup = uriString.startsWith("/");
+ }
+ if (attemptFixup) {
+ try {
+ // Test if this is a valid path by trying to create a local file
+ // object. The URL of that is returned if successful.
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.initWithPath(path);
+ return Services.io.newURI(
+ lazy.fileProtocolHandler.getURLSpecFromActualFile(file)
+ );
+ } catch (ex) {
+ // Not a file uri.
+ }
+ }
+ return null;
+}
+
+/**
+ * Tries to fixup a string to an nsIURI by adding the default protocol.
+ *
+ * Should fix things like:
+ * no-scheme.com
+ * ftp.no-scheme.com
+ * ftp4.no-scheme.com
+ * no-scheme.com/query?foo=http://www.foo.com
+ * user:pass@no-scheme.com
+ *
+ * @param {string} uriString The string to fixup.
+ * @returns {nsIURI} an nsIURI built adding the default protocol to the string,
+ * or null if fixing was not possible.
+ */
+function fixupURIProtocol(uriString) {
+ let schemePos = uriString.indexOf("://");
+ if (schemePos == -1 || schemePos > uriString.search(/[:\/]/)) {
+ uriString = "http://" + uriString;
+ }
+ try {
+ return Services.io.newURI(uriString);
+ } catch (ex) {
+ // We generated an invalid uri.
+ }
+ return null;
+}
+
+/**
+ * Tries to fixup a string to a search url.
+ * @param {string} uriString the string to fixup.
+ * @param {URIFixupInfo} fixupInfo The fixup info object, modified in-place.
+ * @param {boolean} isPrivateContext Whether this happens in a private context.
+ * @param {nsIInputStream} postData optional POST data for the search
+ * @returns {boolean} Whether the keyword fixup was succesful.
+ */
+function keywordURIFixup(uriString, fixupInfo, isPrivateContext) {
+ // Here is a few examples of strings that should be searched:
+ // "what is mozilla"
+ // "what is mozilla?"
+ // "docshell site:mozilla.org" - has a space in the origin part
+ // "?site:mozilla.org - anything that begins with a question mark
+ // "mozilla'.org" - Things that have a quote before the first dot/colon
+ // "mozilla/test" - unknown host
+ // ".mozilla", "mozilla." - starts or ends with a dot ()
+ // "user@nonQualifiedHost"
+
+ // These other strings should not be searched, because they could be URIs:
+ // "www.blah.com" - Domain with a standard or known suffix
+ // "knowndomain" - known domain
+ // "nonQualifiedHost:8888?something" - has a port
+ // "user:pass@nonQualifiedHost"
+ // "blah.com."
+
+ // We do keyword lookups if the input starts with a question mark.
+ if (uriString.startsWith("?")) {
+ return tryKeywordFixupForURIInfo(
+ fixupInfo.originalInput,
+ fixupInfo,
+ isPrivateContext
+ );
+ }
+
+ // Check for IPs.
+ const userPassword = lazy.userPasswordRegex.exec(uriString);
+ const ipString = userPassword
+ ? uriString.replace(userPassword[2], "")
+ : uriString;
+ if (lazy.IPv4LikeRegex.test(ipString) || lazy.IPv6LikeRegex.test(ipString)) {
+ return false;
+ }
+
+ // Avoid keyword lookup if we can identify a host and it's known, or ends
+ // with a dot and has some path.
+ // Note that if dnsFirstForSingleWords is true isDomainKnown will always
+ // return true, so we can avoid checking dnsFirstForSingleWords after this.
+ let asciiHost = fixupInfo.fixedURI?.asciiHost;
+ if (
+ asciiHost &&
+ (isDomainKnown(asciiHost) ||
+ (asciiHost.endsWith(".") &&
+ asciiHost.indexOf(".") != asciiHost.length - 1))
+ ) {
+ return false;
+ }
+
+ // Avoid keyword lookup if the url seems to have password.
+ if (fixupInfo.fixedURI?.password) {
+ return false;
+ }
+
+ // Even if the host is unknown, avoid keyword lookup if the string has
+ // uri-like characteristics, unless it looks like "user@unknownHost".
+ // Note we already excluded passwords at this point.
+ if (
+ !isURILike(uriString, fixupInfo.fixedURI?.displayHost) ||
+ (fixupInfo.fixedURI?.userPass && fixupInfo.fixedURI?.pathQueryRef === "/")
+ ) {
+ return tryKeywordFixupForURIInfo(
+ fixupInfo.originalInput,
+ fixupInfo,
+ isPrivateContext
+ );
+ }
+
+ return false;
+}
+
+/**
+ * Mimics the logic in Services.io.extractScheme, but avoids crossing XPConnect.
+ * This also tries to fixup the scheme if it was clearly mistyped.
+ * @param {string} uriString the string to examine
+ * @param {integer} fixupFlags The original fixup flags
+ * @returns {object}
+ * scheme: a typo fixed scheme or empty string if one could not be identified
+ * fixedSchemeUriString: uri string with a typo fixed scheme
+ * fixupChangedProtocol: true if the scheme is fixed up
+ */
+function extractScheme(uriString, fixupFlags = FIXUP_FLAG_NONE) {
+ const matches = uriString.match(lazy.possibleProtocolRegex);
+ const hasColon = matches?.[2] === ":";
+ const hasSlash2 = matches?.[3] === "//";
+
+ const isFixupSchemeTypos =
+ lazy.fixupSchemeTypos && fixupFlags & FIXUP_FLAG_FIX_SCHEME_TYPOS;
+
+ if (
+ !matches ||
+ (!hasColon && !hasSlash2) ||
+ (!hasColon && !isFixupSchemeTypos)
+ ) {
+ return {
+ scheme: "",
+ fixedSchemeUriString: uriString,
+ fixupChangedProtocol: false,
+ };
+ }
+
+ let scheme = matches[1].replace("\t", "").toLowerCase();
+ let fixedSchemeUriString = uriString;
+
+ if (isFixupSchemeTypos && hasSlash2) {
+ // Fix up typos for string that user would have intented as protocol.
+ const afterProtocol = uriString.substring(matches[0].length);
+ fixedSchemeUriString = `${scheme}://${afterProtocol}`;
+ }
+
+ let fixupChangedProtocol = false;
+
+ if (isFixupSchemeTypos) {
+ // Fix up common scheme typos.
+ // TODO: Use levenshtein distance here?
+ fixupChangedProtocol = [
+ ["ttp", "http"],
+ ["htp", "http"],
+ ["ttps", "https"],
+ ["tps", "https"],
+ ["ps", "https"],
+ ["htps", "https"],
+ ["ile", "file"],
+ ["le", "file"],
+ ].some(([typo, fixed]) => {
+ if (scheme === typo) {
+ scheme = fixed;
+ fixedSchemeUriString =
+ scheme + fixedSchemeUriString.substring(typo.length);
+ return true;
+ }
+ return false;
+ });
+ }
+
+ return {
+ scheme,
+ fixedSchemeUriString,
+ fixupChangedProtocol,
+ };
+}
+
+/**
+ * View-source is a pseudo scheme. We're interested in fixing up the stuff
+ * after it. The easiest way to do that is to call this method again with
+ * the "view-source:" lopped off and then prepend it again afterwards.
+ * @param {string} uriString The original string to fixup
+ * @param {integer} fixupFlags The original fixup flags
+ * @param {nsIInputStream} postData Optional POST data for the search
+ * @returns {object} {preferredURI, postData} The fixed URI and relative postData
+ * @throws if it's not possible to fixup the url
+ */
+function fixupViewSource(uriString, fixupFlags) {
+ // We disable keyword lookup and alternate URIs so that small typos don't
+ // cause us to look at very different domains.
+ let newFixupFlags =
+ fixupFlags &
+ ~FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP &
+ ~FIXUP_FLAGS_MAKE_ALTERNATE_URI;
+
+ let innerURIString = uriString.substring(12).trim();
+
+ // Prevent recursion.
+ const { scheme: innerScheme } = extractScheme(innerURIString);
+ if (innerScheme == "view-source") {
+ throw new Components.Exception(
+ "Prevent view-source recursion",
+ Cr.NS_ERROR_FAILURE
+ );
+ }
+
+ let info = Services.uriFixup.getFixupURIInfo(innerURIString, newFixupFlags);
+ if (!info.preferredURI) {
+ throw new Components.Exception(
+ "Couldn't build a valid uri",
+ Cr.NS_ERROR_MALFORMED_URI
+ );
+ }
+ return {
+ preferredURI: Services.io.newURI("view-source:" + info.preferredURI.spec),
+ postData: info.postData,
+ };
+}
+
+/**
+ * Fixup the host of fixedURI if it contains consecutive dots.
+ * @param {URIFixupInfo} info an URIInfo object
+ */
+function fixupConsecutiveDotsHost(fixupInfo) {
+ const uri = fixupInfo.fixedURI;
+
+ try {
+ if (!uri?.host.includes("..")) {
+ return;
+ }
+ } catch (e) {
+ return;
+ }
+
+ try {
+ const isPreferredEqualsToFixed = fixupInfo.preferredURI?.equals(uri);
+
+ fixupInfo.fixedURI = uri
+ .mutate()
+ .setHost(uri.host.replace(/\.+/g, "."))
+ .finalize();
+
+ if (isPreferredEqualsToFixed) {
+ fixupInfo.preferredURI = fixupInfo.fixedURI;
+ }
+ } catch (e) {
+ if (e.result !== Cr.NS_ERROR_MALFORMED_URI) {
+ throw e;
+ }
+ }
+}
+
+/**
+ * Return whether or not given string is uri like.
+ * This function returns true like following strings.
+ * - ":8080"
+ * - "localhost:8080" (if given host is "localhost")
+ * - "/foo?bar"
+ * - "/foo#bar"
+ * @param {string} uriString.
+ * @param {string} host.
+ * @param {boolean} true if uri like.
+ */
+function isURILike(uriString, host) {
+ const indexOfSlash = uriString.indexOf("/");
+ if (
+ indexOfSlash >= 0 &&
+ (indexOfSlash < uriString.indexOf("?", indexOfSlash) ||
+ indexOfSlash < uriString.indexOf("#", indexOfSlash))
+ ) {
+ return true;
+ }
+
+ if (uriString.startsWith(host)) {
+ uriString = uriString.substring(host.length);
+ }
+
+ return lazy.portRegex.test(uriString);
+}
+
+/**
+ * Add prefix and suffix to a hostname if both are missing.
+ *
+ * If the host does not start with the prefix, add the prefix to
+ * the hostname.
+ *
+ * By default the prefix and suffix are www. and .com but they could
+ * be any value e.g. www. and .org as they use the preferences
+ * "browser.fixup.alternate.prefix" and "browser.fixup.alternative.suffix"
+ *
+ * If no changes were made, it returns an empty string.
+ *
+ * @param {string} oldHost.
+ * @return {String} Fixed up hostname or an empty string.
+ */
+function maybeAddPrefixAndSuffix(oldHost) {
+ let prefix = Services.prefs.getCharPref(
+ "browser.fixup.alternate.prefix",
+ "www."
+ );
+ let suffix = Services.prefs.getCharPref(
+ "browser.fixup.alternate.suffix",
+ ".com"
+ );
+ let newHost = "";
+ let numDots = (oldHost.match(/\./g) || []).length;
+ if (numDots == 0) {
+ newHost = prefix + oldHost + suffix;
+ } else if (numDots == 1) {
+ if (prefix && oldHost == prefix) {
+ newHost = oldHost + suffix;
+ } else if (suffix && !oldHost.startsWith(prefix)) {
+ newHost = prefix + oldHost;
+ }
+ }
+ return newHost ? newHost : oldHost;
+}
+
+/**
+ * Given an instance of URIFixupInfo, update its fixedURI.
+ *
+ * First, change the protocol to the one stored in
+ * "browser.fixup.alternate.protocol".
+ *
+ * Then, try to update fixedURI's host to newHost.
+ *
+ * @param {URIFixupInfo} info.
+ * @param {string} newHost.
+ * @return {boolean}
+ * True, if info was updated without any errors.
+ * False, if NS_ERROR_MALFORMED_URI error.
+ * @throws If a non-NS_ERROR_MALFORMED_URI error occurs.
+ */
+function updateHostAndScheme(info, newHost) {
+ let oldHost = info.fixedURI.host;
+ let oldScheme = info.fixedURI.scheme;
+ try {
+ info.fixedURI = info.fixedURI
+ .mutate()
+ .setScheme(lazy.alternateProtocol)
+ .setHost(newHost)
+ .finalize();
+ } catch (ex) {
+ if (ex.result != Cr.NS_ERROR_MALFORMED_URI) {
+ throw ex;
+ }
+ return false;
+ }
+ if (oldScheme != info.fixedURI.scheme) {
+ info.fixupChangedProtocol = true;
+ }
+ if (oldHost != info.fixedURI.host) {
+ info.fixupCreatedAlternateURI = true;
+ }
+ return true;
+}
diff --git a/docshell/base/WindowContext.cpp b/docshell/base/WindowContext.cpp
new file mode 100644
index 0000000000..02b4e7d4a8
--- /dev/null
+++ b/docshell/base/WindowContext.cpp
@@ -0,0 +1,662 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/WindowContext.h"
+#include "mozilla/dom/WindowGlobalActorsBinding.h"
+#include "mozilla/dom/WindowGlobalChild.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/dom/SyncedContextInlines.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/UserActivationIPCUtils.h"
+#include "mozilla/PermissionDelegateIPCUtils.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "nsGlobalWindowInner.h"
+#include "nsIScriptError.h"
+#include "nsIXULRuntime.h"
+#include "nsRefPtrHashtable.h"
+#include "nsContentUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+// Explicit specialization of the `Transaction` type. Required by the `extern
+// template class` declaration in the header.
+template class syncedcontext::Transaction<WindowContext>;
+
+static LazyLogModule gWindowContextLog("WindowContext");
+static LazyLogModule gWindowContextSyncLog("WindowContextSync");
+
+extern mozilla::LazyLogModule gUserInteractionPRLog;
+
+#define USER_ACTIVATION_LOG(msg, ...) \
+ MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
+
+using WindowContextByIdMap = nsTHashMap<nsUint64HashKey, WindowContext*>;
+static StaticAutoPtr<WindowContextByIdMap> gWindowContexts;
+
+/* static */
+LogModule* WindowContext::GetLog() { return gWindowContextLog; }
+
+/* static */
+LogModule* WindowContext::GetSyncLog() { return gWindowContextSyncLog; }
+
+/* static */
+already_AddRefed<WindowContext> WindowContext::GetById(
+ uint64_t aInnerWindowId) {
+ if (!gWindowContexts) {
+ return nullptr;
+ }
+ return do_AddRef(gWindowContexts->Get(aInnerWindowId));
+}
+
+BrowsingContextGroup* WindowContext::Group() const {
+ return mBrowsingContext->Group();
+}
+
+WindowGlobalParent* WindowContext::Canonical() {
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
+ return static_cast<WindowGlobalParent*>(this);
+}
+
+bool WindowContext::IsCurrent() const {
+ return mBrowsingContext->mCurrentWindowContext == this;
+}
+
+bool WindowContext::IsInBFCache() {
+ if (mozilla::SessionHistoryInParent()) {
+ return mBrowsingContext->IsInBFCache();
+ }
+ return TopWindowContext()->GetWindowStateSaved();
+}
+
+nsGlobalWindowInner* WindowContext::GetInnerWindow() const {
+ return mWindowGlobalChild ? mWindowGlobalChild->GetWindowGlobal() : nullptr;
+}
+
+Document* WindowContext::GetDocument() const {
+ nsGlobalWindowInner* innerWindow = GetInnerWindow();
+ return innerWindow ? innerWindow->GetDocument() : nullptr;
+}
+
+Document* WindowContext::GetExtantDoc() const {
+ nsGlobalWindowInner* innerWindow = GetInnerWindow();
+ return innerWindow ? innerWindow->GetExtantDoc() : nullptr;
+}
+
+WindowGlobalChild* WindowContext::GetWindowGlobalChild() const {
+ return mWindowGlobalChild;
+}
+
+WindowContext* WindowContext::GetParentWindowContext() {
+ return mBrowsingContext->GetParentWindowContext();
+}
+
+WindowContext* WindowContext::TopWindowContext() {
+ WindowContext* current = this;
+ while (current->GetParentWindowContext()) {
+ current = current->GetParentWindowContext();
+ }
+ return current;
+}
+
+bool WindowContext::IsTop() const { return mBrowsingContext->IsTop(); }
+
+bool WindowContext::SameOriginWithTop() const {
+ return mBrowsingContext->SameOriginWithTop();
+}
+
+nsIGlobalObject* WindowContext::GetParentObject() const {
+ return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
+}
+
+void WindowContext::AppendChildBrowsingContext(
+ BrowsingContext* aBrowsingContext) {
+ MOZ_DIAGNOSTIC_ASSERT(Group() == aBrowsingContext->Group(),
+ "Mismatched groups?");
+ MOZ_DIAGNOSTIC_ASSERT(!mChildren.Contains(aBrowsingContext));
+
+ mChildren.AppendElement(aBrowsingContext);
+ if (!nsContentUtils::ShouldHideObjectOrEmbedImageDocument() ||
+ !aBrowsingContext->IsEmbedderTypeObjectOrEmbed()) {
+ mNonSyntheticChildren.AppendElement(aBrowsingContext);
+ }
+
+ // If we're the current WindowContext in our BrowsingContext, make sure to
+ // clear any cached `children` value.
+ if (IsCurrent()) {
+ BrowsingContext_Binding::ClearCachedChildrenValue(mBrowsingContext);
+ }
+}
+
+void WindowContext::RemoveChildBrowsingContext(
+ BrowsingContext* aBrowsingContext) {
+ MOZ_DIAGNOSTIC_ASSERT(Group() == aBrowsingContext->Group(),
+ "Mismatched groups?");
+
+ mChildren.RemoveElement(aBrowsingContext);
+ mNonSyntheticChildren.RemoveElement(aBrowsingContext);
+
+ // If we're the current WindowContext in our BrowsingContext, make sure to
+ // clear any cached `children` value.
+ if (IsCurrent()) {
+ BrowsingContext_Binding::ClearCachedChildrenValue(mBrowsingContext);
+ }
+}
+
+void WindowContext::UpdateChildSynthetic(BrowsingContext* aBrowsingContext,
+ bool aIsSynthetic) {
+ MOZ_ASSERT(nsContentUtils::ShouldHideObjectOrEmbedImageDocument());
+ if (aIsSynthetic) {
+ mNonSyntheticChildren.RemoveElement(aBrowsingContext);
+ } else {
+ // The same BrowsingContext will be reused for error pages, so it can be in
+ // the list already.
+ if (!mNonSyntheticChildren.Contains(aBrowsingContext)) {
+ mNonSyntheticChildren.AppendElement(aBrowsingContext);
+ }
+ }
+}
+
+void WindowContext::SendCommitTransaction(ContentParent* aParent,
+ const BaseTransaction& aTxn,
+ uint64_t aEpoch) {
+ Unused << aParent->SendCommitWindowContextTransaction(this, aTxn, aEpoch);
+}
+
+void WindowContext::SendCommitTransaction(ContentChild* aChild,
+ const BaseTransaction& aTxn,
+ uint64_t aEpoch) {
+ aChild->SendCommitWindowContextTransaction(this, aTxn, aEpoch);
+}
+
+bool WindowContext::CheckOnlyOwningProcessCanSet(ContentParent* aSource) {
+ if (IsInProcess()) {
+ return true;
+ }
+
+ if (XRE_IsParentProcess() && aSource) {
+ return Canonical()->GetContentParent() == aSource;
+ }
+
+ return false;
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_IsSecure>, const bool& aIsSecure,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_AllowMixedContent>,
+ const bool& aAllowMixedContent,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_HasBeforeUnload>,
+ const bool& aHasBeforeUnload,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_CookieBehavior>,
+ const Maybe<uint32_t>& aValue,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_IsOnContentBlockingAllowList>,
+ const bool& aValue, ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_IsThirdPartyWindow>,
+ const bool& IsThirdPartyWindow,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_IsThirdPartyTrackingResourceWindow>,
+ const bool& aIsThirdPartyTrackingResourceWindow,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_ShouldResistFingerprinting>,
+ const bool& aShouldResistFingerprinting,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_IsSecureContext>,
+ const bool& aIsSecureContext,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_IsOriginalFrameSource>,
+ const bool& aIsOriginalFrameSource,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_DocTreeHadMedia>, const bool& aValue,
+ ContentParent* aSource) {
+ return IsTop();
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_AutoplayPermission>,
+ const uint32_t& aValue, ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_ShortcutsPermission>,
+ const uint32_t& aValue, ContentParent* aSource) {
+ return IsTop() && CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_ActiveMediaSessionContextId>,
+ const Maybe<uint64_t>& aValue,
+ ContentParent* aSource) {
+ return IsTop();
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_PopupPermission>, const uint32_t&,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(
+ FieldIndex<IDX_DelegatedPermissions>,
+ const PermissionDelegateHandler::DelegatedPermissionList& aValue,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(
+ FieldIndex<IDX_DelegatedExactHostMatchPermissions>,
+ const PermissionDelegateHandler::DelegatedPermissionList& aValue,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_IsLocalIP>, const bool& aValue,
+ ContentParent* aSource) {
+ return CheckOnlyOwningProcessCanSet(aSource);
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_AllowJavascript>, bool aValue,
+ ContentParent* aSource) {
+ return (XRE_IsParentProcess() && !aSource) ||
+ CheckOnlyOwningProcessCanSet(aSource);
+}
+
+void WindowContext::DidSet(FieldIndex<IDX_AllowJavascript>, bool aOldValue) {
+ RecomputeCanExecuteScripts();
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_HasActivePeerConnections>, bool,
+ ContentParent*) {
+ return XRE_IsParentProcess() && IsTop();
+}
+
+void WindowContext::RecomputeCanExecuteScripts(bool aApplyChanges) {
+ const bool old = mCanExecuteScripts;
+ if (!AllowJavascript()) {
+ // Scripting has been explicitly disabled on our WindowContext.
+ mCanExecuteScripts = false;
+ } else {
+ // Otherwise, inherit.
+ mCanExecuteScripts = mBrowsingContext->CanExecuteScripts();
+ }
+
+ if (aApplyChanges && old != mCanExecuteScripts) {
+ // Inform our active DOM window.
+ if (nsGlobalWindowInner* window = GetInnerWindow()) {
+ // Only update scriptability if the window is current. Windows will have
+ // scriptability disabled when entering the bfcache and updated when
+ // coming out.
+ if (window->IsCurrentInnerWindow()) {
+ auto& scriptability =
+ xpc::Scriptability::Get(window->GetGlobalJSObject());
+ scriptability.SetWindowAllowsScript(mCanExecuteScripts);
+ }
+ }
+
+ for (const RefPtr<BrowsingContext>& child : Children()) {
+ child->RecomputeCanExecuteScripts();
+ }
+ }
+}
+
+void WindowContext::DidSet(FieldIndex<IDX_SHEntryHasUserInteraction>,
+ bool aOldValue) {
+ MOZ_ASSERT(
+ TopWindowContext() == this,
+ "SHEntryHasUserInteraction can only be set on the top window context");
+ // This field is set when the child notifies us of new user interaction, so we
+ // also set the currently active shentry in the parent as having interaction.
+ if (XRE_IsParentProcess() && mBrowsingContext) {
+ SessionHistoryEntry* activeEntry =
+ mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry();
+ if (activeEntry && GetSHEntryHasUserInteraction()) {
+ activeEntry->SetHasUserInteraction(true);
+ }
+ }
+}
+
+void WindowContext::DidSet(FieldIndex<IDX_UserActivationState>) {
+ MOZ_ASSERT_IF(!IsInProcess(), mUserGestureStart.IsNull());
+ USER_ACTIVATION_LOG("Set user gesture activation %" PRIu8
+ " for %s browsing context 0x%08" PRIx64,
+ static_cast<uint8_t>(GetUserActivationState()),
+ XRE_IsParentProcess() ? "Parent" : "Child", Id());
+ if (IsInProcess()) {
+ USER_ACTIVATION_LOG(
+ "Set user gesture start time for %s browsing context 0x%08" PRIx64,
+ XRE_IsParentProcess() ? "Parent" : "Child", Id());
+ if (GetUserActivationState() == UserActivation::State::FullActivated) {
+ mUserGestureStart = TimeStamp::Now();
+ } else if (GetUserActivationState() == UserActivation::State::None) {
+ mUserGestureStart = TimeStamp();
+ }
+ }
+}
+
+void WindowContext::DidSet(FieldIndex<IDX_HasReportedShadowDOMUsage>,
+ bool aOldValue) {
+ if (!aOldValue && GetHasReportedShadowDOMUsage() && IsInProcess()) {
+ MOZ_ASSERT(TopWindowContext() == this);
+ if (mBrowsingContext) {
+ Document* topLevelDoc = mBrowsingContext->GetDocument();
+ if (topLevelDoc) {
+ nsAutoString uri;
+ Unused << topLevelDoc->GetDocumentURI(uri);
+ if (!uri.IsEmpty()) {
+ nsAutoString msg = u"Shadow DOM used in ["_ns + uri +
+ u"] or in some of its subdocuments."_ns;
+ nsContentUtils::ReportToConsoleNonLocalized(
+ msg, nsIScriptError::infoFlag, "DOM"_ns, topLevelDoc);
+ }
+ }
+ }
+ }
+}
+
+bool WindowContext::CanSet(FieldIndex<IDX_WindowStateSaved>, bool aValue,
+ ContentParent* aSource) {
+ return !mozilla::SessionHistoryInParent() && IsTop() &&
+ CheckOnlyOwningProcessCanSet(aSource);
+}
+
+void WindowContext::CreateFromIPC(IPCInitializer&& aInit) {
+ MOZ_RELEASE_ASSERT(XRE_IsContentProcess(),
+ "Should be a WindowGlobalParent in the parent");
+
+ RefPtr<BrowsingContext> bc = BrowsingContext::Get(aInit.mBrowsingContextId);
+ MOZ_RELEASE_ASSERT(bc);
+
+ if (bc->IsDiscarded()) {
+ // If we have already closed our browsing context, the
+ // WindowGlobalChild actor is bound to be destroyed soon and it's
+ // safe to ignore creating the WindowContext.
+ return;
+ }
+
+ RefPtr<WindowContext> context = new WindowContext(
+ bc, aInit.mInnerWindowId, aInit.mOuterWindowId, std::move(aInit.mFields));
+ context->Init();
+}
+
+void WindowContext::Init() {
+ MOZ_LOG(GetLog(), LogLevel::Debug,
+ ("Registering 0x%" PRIx64 " (bc=0x%" PRIx64 ")", mInnerWindowId,
+ mBrowsingContext->Id()));
+
+ // Register the WindowContext in the `WindowContextByIdMap`.
+ if (!gWindowContexts) {
+ gWindowContexts = new WindowContextByIdMap();
+ ClearOnShutdown(&gWindowContexts);
+ }
+ auto& entry = gWindowContexts->LookupOrInsert(mInnerWindowId);
+ MOZ_RELEASE_ASSERT(!entry, "Duplicate WindowContext for ID!");
+ entry = this;
+
+ // Register this to the browsing context.
+ mBrowsingContext->RegisterWindowContext(this);
+ Group()->Register(this);
+}
+
+void WindowContext::Discard() {
+ MOZ_LOG(GetLog(), LogLevel::Debug,
+ ("Discarding 0x%" PRIx64 " (bc=0x%" PRIx64 ")", mInnerWindowId,
+ mBrowsingContext->Id()));
+ if (mIsDiscarded) {
+ return;
+ }
+
+ mIsDiscarded = true;
+ if (gWindowContexts) {
+ gWindowContexts->Remove(InnerWindowId());
+ }
+ mBrowsingContext->UnregisterWindowContext(this);
+ Group()->Unregister(this);
+}
+
+void WindowContext::AddSecurityState(uint32_t aStateFlags) {
+ MOZ_ASSERT(TopWindowContext() == this);
+ MOZ_ASSERT((aStateFlags &
+ (nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT |
+ nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT |
+ nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT |
+ nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT |
+ nsIWebProgressListener::STATE_HTTPS_ONLY_MODE_UPGRADED |
+ nsIWebProgressListener::STATE_HTTPS_ONLY_MODE_UPGRADE_FAILED)) ==
+ aStateFlags,
+ "Invalid flags specified!");
+
+ if (XRE_IsParentProcess()) {
+ Canonical()->AddSecurityState(aStateFlags);
+ } else {
+ ContentChild* child = ContentChild::GetSingleton();
+ child->SendAddSecurityState(this, aStateFlags);
+ }
+}
+
+void WindowContext::NotifyUserGestureActivation() {
+ Unused << SetUserActivationState(UserActivation::State::FullActivated);
+}
+
+void WindowContext::NotifyResetUserGestureActivation() {
+ Unused << SetUserActivationState(UserActivation::State::None);
+}
+
+bool WindowContext::HasBeenUserGestureActivated() {
+ return GetUserActivationState() != UserActivation::State::None;
+}
+
+const TimeStamp& WindowContext::GetUserGestureStart() const {
+ MOZ_ASSERT(IsInProcess());
+ return mUserGestureStart;
+}
+
+bool WindowContext::HasValidTransientUserGestureActivation() {
+ MOZ_ASSERT(IsInProcess());
+
+ if (GetUserActivationState() != UserActivation::State::FullActivated) {
+ // mUserGestureStart should be null if the document hasn't ever been
+ // activated by user gesture
+ MOZ_ASSERT_IF(GetUserActivationState() == UserActivation::State::None,
+ mUserGestureStart.IsNull());
+ return false;
+ }
+
+ MOZ_ASSERT(!mUserGestureStart.IsNull(),
+ "mUserGestureStart shouldn't be null if the document has ever "
+ "been activated by user gesture");
+ TimeDuration timeout = TimeDuration::FromMilliseconds(
+ StaticPrefs::dom_user_activation_transient_timeout());
+
+ return timeout <= TimeDuration() ||
+ (TimeStamp::Now() - mUserGestureStart) <= timeout;
+}
+
+bool WindowContext::ConsumeTransientUserGestureActivation() {
+ MOZ_ASSERT(IsInProcess());
+ MOZ_ASSERT(IsCurrent());
+
+ if (!HasValidTransientUserGestureActivation()) {
+ return false;
+ }
+
+ BrowsingContext* top = mBrowsingContext->Top();
+ top->PreOrderWalk([&](BrowsingContext* aBrowsingContext) {
+ WindowContext* windowContext = aBrowsingContext->GetCurrentWindowContext();
+ if (windowContext && windowContext->GetUserActivationState() ==
+ UserActivation::State::FullActivated) {
+ Unused << windowContext->SetUserActivationState(
+ UserActivation::State::HasBeenActivated);
+ }
+ });
+
+ return true;
+}
+
+bool WindowContext::CanShowPopup() {
+ uint32_t permit = GetPopupPermission();
+ if (permit == nsIPermissionManager::ALLOW_ACTION) {
+ return true;
+ }
+ if (permit == nsIPermissionManager::DENY_ACTION) {
+ return false;
+ }
+
+ return !StaticPrefs::dom_disable_open_during_load();
+}
+
+void WindowContext::TransientSetHasActivePeerConnections() {
+ if (!IsTop()) {
+ return;
+ }
+
+ mFields.SetWithoutSyncing<IDX_HasActivePeerConnections>(true);
+}
+
+WindowContext::IPCInitializer WindowContext::GetIPCInitializer() {
+ IPCInitializer init;
+ init.mInnerWindowId = mInnerWindowId;
+ init.mOuterWindowId = mOuterWindowId;
+ init.mBrowsingContextId = mBrowsingContext->Id();
+ init.mFields = mFields.RawValues();
+ return init;
+}
+
+WindowContext::WindowContext(BrowsingContext* aBrowsingContext,
+ uint64_t aInnerWindowId, uint64_t aOuterWindowId,
+ FieldValues&& aInit)
+ : mFields(std::move(aInit)),
+ mInnerWindowId(aInnerWindowId),
+ mOuterWindowId(aOuterWindowId),
+ mBrowsingContext(aBrowsingContext) {
+ MOZ_ASSERT(mBrowsingContext);
+ MOZ_ASSERT(mInnerWindowId);
+ MOZ_ASSERT(mOuterWindowId);
+ RecomputeCanExecuteScripts(/* aApplyChanges */ false);
+}
+
+WindowContext::~WindowContext() {
+ if (gWindowContexts) {
+ gWindowContexts->Remove(InnerWindowId());
+ }
+}
+
+JSObject* WindowContext::WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return WindowContext_Binding::Wrap(cx, this, aGivenProto);
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WindowContext)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(WindowContext)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(WindowContext)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(WindowContext)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WindowContext)
+ if (gWindowContexts) {
+ gWindowContexts->Remove(tmp->InnerWindowId());
+ }
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowsingContext)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildren)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mNonSyntheticChildren)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WindowContext)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContext)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildren)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNonSyntheticChildren)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+} // namespace dom
+
+namespace ipc {
+
+void IPDLParamTraits<dom::MaybeDiscarded<dom::WindowContext>>::Write(
+ IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const dom::MaybeDiscarded<dom::WindowContext>& aParam) {
+ uint64_t id = aParam.ContextId();
+ WriteIPDLParam(aWriter, aActor, id);
+}
+
+bool IPDLParamTraits<dom::MaybeDiscarded<dom::WindowContext>>::Read(
+ IPC::MessageReader* aReader, IProtocol* aActor,
+ dom::MaybeDiscarded<dom::WindowContext>* aResult) {
+ uint64_t id = 0;
+ if (!ReadIPDLParam(aReader, aActor, &id)) {
+ return false;
+ }
+
+ if (id == 0) {
+ *aResult = nullptr;
+ } else if (RefPtr<dom::WindowContext> wc = dom::WindowContext::GetById(id)) {
+ *aResult = std::move(wc);
+ } else {
+ aResult->SetDiscarded(id);
+ }
+ return true;
+}
+
+void IPDLParamTraits<dom::WindowContext::IPCInitializer>::Write(
+ IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const dom::WindowContext::IPCInitializer& aInit) {
+ // Write actor ID parameters.
+ WriteIPDLParam(aWriter, aActor, aInit.mInnerWindowId);
+ WriteIPDLParam(aWriter, aActor, aInit.mOuterWindowId);
+ WriteIPDLParam(aWriter, aActor, aInit.mBrowsingContextId);
+ WriteIPDLParam(aWriter, aActor, aInit.mFields);
+}
+
+bool IPDLParamTraits<dom::WindowContext::IPCInitializer>::Read(
+ IPC::MessageReader* aReader, IProtocol* aActor,
+ dom::WindowContext::IPCInitializer* aInit) {
+ // Read actor ID parameters.
+ return ReadIPDLParam(aReader, aActor, &aInit->mInnerWindowId) &&
+ ReadIPDLParam(aReader, aActor, &aInit->mOuterWindowId) &&
+ ReadIPDLParam(aReader, aActor, &aInit->mBrowsingContextId) &&
+ ReadIPDLParam(aReader, aActor, &aInit->mFields);
+}
+
+template struct IPDLParamTraits<dom::WindowContext::BaseTransaction>;
+
+} // namespace ipc
+} // namespace mozilla
diff --git a/docshell/base/WindowContext.h b/docshell/base/WindowContext.h
new file mode 100644
index 0000000000..9396ad9ed1
--- /dev/null
+++ b/docshell/base/WindowContext.h
@@ -0,0 +1,399 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_WindowContext_h
+#define mozilla_dom_WindowContext_h
+
+#include "mozilla/PermissionDelegateHandler.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/Span.h"
+#include "mozilla/dom/MaybeDiscarded.h"
+#include "mozilla/dom/SyncedContext.h"
+#include "mozilla/dom/UserActivation.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsILoadInfo.h"
+#include "nsWrapperCache.h"
+
+class nsIGlobalObject;
+
+class nsGlobalWindowInner;
+
+namespace mozilla {
+class LogModule;
+
+namespace dom {
+
+class WindowGlobalChild;
+class WindowGlobalParent;
+class WindowGlobalInit;
+class BrowsingContext;
+class BrowsingContextGroup;
+
+#define MOZ_EACH_WC_FIELD(FIELD) \
+ /* Whether the SHEntry associated with the current top-level \
+ * window has already seen user interaction. \
+ * As such, this will be reset to false when a new SHEntry is \
+ * created without changing the WC (e.g. when using pushState or \
+ * sub-frame navigation) \
+ * This flag is set for optimization purposes, to avoid \
+ * having to get the top SHEntry and update it on every \
+ * user interaction. \
+ * This is only meaningful on the top-level WC. */ \
+ FIELD(SHEntryHasUserInteraction, bool) \
+ FIELD(CookieBehavior, Maybe<uint32_t>) \
+ FIELD(IsOnContentBlockingAllowList, bool) \
+ /* Whether the given window hierarchy is third party. See \
+ * ThirdPartyUtil::IsThirdPartyWindow for details */ \
+ FIELD(IsThirdPartyWindow, bool) \
+ /* Whether this window's channel has been marked as a third-party \
+ * tracking resource */ \
+ FIELD(IsThirdPartyTrackingResourceWindow, bool) \
+ FIELD(ShouldResistFingerprinting, bool) \
+ FIELD(IsSecureContext, bool) \
+ FIELD(IsOriginalFrameSource, bool) \
+ /* Mixed-Content: If the corresponding documentURI is https, \
+ * then this flag is true. */ \
+ FIELD(IsSecure, bool) \
+ /* Whether the user has overriden the mixed content blocker to allow \
+ * mixed content loads to happen */ \
+ FIELD(AllowMixedContent, bool) \
+ /* Whether this window has registered a "beforeunload" event \
+ * handler */ \
+ FIELD(HasBeforeUnload, bool) \
+ /* Controls whether the WindowContext is currently considered to be \
+ * activated by a gesture */ \
+ FIELD(UserActivationState, UserActivation::State) \
+ FIELD(EmbedderPolicy, nsILoadInfo::CrossOriginEmbedderPolicy) \
+ /* True if this document tree contained at least a HTMLMediaElement. \
+ * This should only be set on top level context. */ \
+ FIELD(DocTreeHadMedia, bool) \
+ FIELD(AutoplayPermission, uint32_t) \
+ FIELD(ShortcutsPermission, uint32_t) \
+ /* Store the Id of the browsing context where active media session \
+ * exists on the top level window context */ \
+ FIELD(ActiveMediaSessionContextId, Maybe<uint64_t>) \
+ /* ALLOW_ACTION if it is allowed to open popups for the sub-tree \
+ * starting and including the current WindowContext */ \
+ FIELD(PopupPermission, uint32_t) \
+ FIELD(DelegatedPermissions, \
+ PermissionDelegateHandler::DelegatedPermissionList) \
+ FIELD(DelegatedExactHostMatchPermissions, \
+ PermissionDelegateHandler::DelegatedPermissionList) \
+ FIELD(HasReportedShadowDOMUsage, bool) \
+ /* Whether the principal of this window is for a local \
+ * IP address */ \
+ FIELD(IsLocalIP, bool) \
+ /* Whether any of the windows in the subtree rooted at this window has \
+ * active peer connections or not (only set on the top window). */ \
+ FIELD(HasActivePeerConnections, bool) \
+ /* Whether we can execute scripts in this WindowContext. Has no effect \
+ * unless scripts are also allowed in the BrowsingContext. */ \
+ FIELD(AllowJavascript, bool) \
+ /* If this field is `true`, it means that this WindowContext's \
+ * WindowState was saved to be stored in the legacy (non-SHIP) BFCache \
+ * implementation. Always false for SHIP */ \
+ FIELD(WindowStateSaved, bool)
+
+class WindowContext : public nsISupports, public nsWrapperCache {
+ MOZ_DECL_SYNCED_CONTEXT(WindowContext, MOZ_EACH_WC_FIELD)
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(WindowContext)
+
+ public:
+ static already_AddRefed<WindowContext> GetById(uint64_t aInnerWindowId);
+ static LogModule* GetLog();
+ static LogModule* GetSyncLog();
+
+ BrowsingContext* GetBrowsingContext() const { return mBrowsingContext; }
+ BrowsingContextGroup* Group() const;
+ uint64_t Id() const { return InnerWindowId(); }
+ uint64_t InnerWindowId() const { return mInnerWindowId; }
+ uint64_t OuterWindowId() const { return mOuterWindowId; }
+ bool IsDiscarded() const { return mIsDiscarded; }
+
+ // Returns `true` if this WindowContext is the current WindowContext in its
+ // BrowsingContext.
+ bool IsCurrent() const;
+
+ // Returns `true` if this WindowContext is currently in the BFCache.
+ bool IsInBFCache();
+
+ bool IsInProcess() const { return mIsInProcess; }
+
+ bool HasBeforeUnload() const { return GetHasBeforeUnload(); }
+
+ bool IsLocalIP() const { return GetIsLocalIP(); }
+
+ bool ShouldResistFingerprinting() const {
+ return GetShouldResistFingerprinting();
+ }
+
+ nsGlobalWindowInner* GetInnerWindow() const;
+ Document* GetDocument() const;
+ Document* GetExtantDoc() const;
+
+ WindowGlobalChild* GetWindowGlobalChild() const;
+
+ // Get the parent WindowContext of this WindowContext, taking the BFCache into
+ // account. This will not cross chrome/content <browser> boundaries.
+ WindowContext* GetParentWindowContext();
+ WindowContext* TopWindowContext();
+
+ bool SameOriginWithTop() const;
+
+ bool IsTop() const;
+
+ Span<RefPtr<BrowsingContext>> Children() { return mChildren; }
+
+ // The filtered version of `Children()`, which contains no browsing contexts
+ // for synthetic documents as created by object loading content.
+ Span<RefPtr<BrowsingContext>> NonSyntheticChildren() {
+ return mNonSyntheticChildren;
+ }
+
+ // Cast this object to it's parent-process canonical form.
+ WindowGlobalParent* Canonical();
+
+ nsIGlobalObject* GetParentObject() const;
+ JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void Discard();
+
+ struct IPCInitializer {
+ uint64_t mInnerWindowId;
+ uint64_t mOuterWindowId;
+ uint64_t mBrowsingContextId;
+
+ FieldValues mFields;
+ };
+ IPCInitializer GetIPCInitializer();
+
+ static void CreateFromIPC(IPCInitializer&& aInit);
+
+ // Add new security state flags.
+ // These should be some of the nsIWebProgressListener 'HTTPS_ONLY_MODE' or
+ // 'MIXED' state flags, and should only be called on the top window context.
+ void AddSecurityState(uint32_t aStateFlags);
+
+ // This function would be called when its corresponding window is activated
+ // by user gesture.
+ void NotifyUserGestureActivation();
+
+ // This function would be called when we want to reset the user gesture
+ // activation flag.
+ void NotifyResetUserGestureActivation();
+
+ // Return true if its corresponding window has been activated by user
+ // gesture.
+ bool HasBeenUserGestureActivated();
+
+ // Return true if its corresponding window has transient user gesture
+ // activation and the transient user gesture activation haven't yet timed
+ // out.
+ bool HasValidTransientUserGestureActivation();
+
+ // See `mUserGestureStart`.
+ const TimeStamp& GetUserGestureStart() const;
+
+ // Return true if the corresponding window has valid transient user gesture
+ // activation and the transient user gesture activation had been consumed
+ // successfully.
+ bool ConsumeTransientUserGestureActivation();
+
+ bool CanShowPopup();
+
+ bool AllowJavascript() const { return GetAllowJavascript(); }
+ bool CanExecuteScripts() const { return mCanExecuteScripts; }
+
+ void TransientSetHasActivePeerConnections();
+
+ protected:
+ WindowContext(BrowsingContext* aBrowsingContext, uint64_t aInnerWindowId,
+ uint64_t aOuterWindowId, FieldValues&& aFields);
+ virtual ~WindowContext();
+
+ virtual void Init();
+
+ private:
+ friend class BrowsingContext;
+ friend class WindowGlobalChild;
+ friend class WindowGlobalActor;
+
+ void AppendChildBrowsingContext(BrowsingContext* aBrowsingContext);
+ void RemoveChildBrowsingContext(BrowsingContext* aBrowsingContext);
+
+ // Update non-synthetic children based on whether `aBrowsingContext`
+ // is synthetic or not. Regardless the synthetic of `aBrowsingContext`, it is
+ // kept in this WindowContext's all children list.
+ void UpdateChildSynthetic(BrowsingContext* aBrowsingContext,
+ bool aIsSynthetic);
+
+ // Send a given `BaseTransaction` object to the correct remote.
+ void SendCommitTransaction(ContentParent* aParent,
+ const BaseTransaction& aTxn, uint64_t aEpoch);
+ void SendCommitTransaction(ContentChild* aChild, const BaseTransaction& aTxn,
+ uint64_t aEpoch);
+
+ bool CheckOnlyOwningProcessCanSet(ContentParent* aSource);
+
+ // Overload `CanSet` to get notifications for a particular field being set.
+ bool CanSet(FieldIndex<IDX_IsSecure>, const bool& aIsSecure,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_AllowMixedContent>, const bool& aAllowMixedContent,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_HasBeforeUnload>, const bool& aHasBeforeUnload,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_CookieBehavior>, const Maybe<uint32_t>& aValue,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_IsOnContentBlockingAllowList>, const bool& aValue,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_EmbedderPolicy>, const bool& aValue,
+ ContentParent* aSource) {
+ return true;
+ }
+
+ bool CanSet(FieldIndex<IDX_IsThirdPartyWindow>,
+ const bool& IsThirdPartyWindow, ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_IsThirdPartyTrackingResourceWindow>,
+ const bool& aIsThirdPartyTrackingResourceWindow,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_ShouldResistFingerprinting>,
+ const bool& aShouldResistFingerprinting, ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_IsSecureContext>, const bool& aIsSecureContext,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_IsOriginalFrameSource>,
+ const bool& aIsOriginalFrameSource, ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_DocTreeHadMedia>, const bool& aValue,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_AutoplayPermission>, const uint32_t& aValue,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_ShortcutsPermission>, const uint32_t& aValue,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_ActiveMediaSessionContextId>,
+ const Maybe<uint64_t>& aValue, ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_PopupPermission>, const uint32_t&,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_SHEntryHasUserInteraction>,
+ const bool& aSHEntryHasUserInteraction, ContentParent* aSource) {
+ return true;
+ }
+ bool CanSet(FieldIndex<IDX_DelegatedPermissions>,
+ const PermissionDelegateHandler::DelegatedPermissionList& aValue,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_DelegatedExactHostMatchPermissions>,
+ const PermissionDelegateHandler::DelegatedPermissionList& aValue,
+ ContentParent* aSource);
+ bool CanSet(FieldIndex<IDX_UserActivationState>,
+ const UserActivation::State& aUserActivationState,
+ ContentParent* aSource) {
+ return true;
+ }
+
+ bool CanSet(FieldIndex<IDX_HasReportedShadowDOMUsage>, const bool& aValue,
+ ContentParent* aSource) {
+ return true;
+ }
+
+ bool CanSet(FieldIndex<IDX_IsLocalIP>, const bool& aValue,
+ ContentParent* aSource);
+
+ bool CanSet(FieldIndex<IDX_AllowJavascript>, bool aValue,
+ ContentParent* aSource);
+ void DidSet(FieldIndex<IDX_AllowJavascript>, bool aOldValue);
+
+ bool CanSet(FieldIndex<IDX_HasActivePeerConnections>, bool, ContentParent*);
+
+ void DidSet(FieldIndex<IDX_HasReportedShadowDOMUsage>, bool aOldValue);
+
+ void DidSet(FieldIndex<IDX_SHEntryHasUserInteraction>, bool aOldValue);
+
+ bool CanSet(FieldIndex<IDX_WindowStateSaved>, bool aValue,
+ ContentParent* aSource);
+
+ // Overload `DidSet` to get notifications for a particular field being set.
+ //
+ // You can also overload the variant that gets the old value if you need it.
+ template <size_t I>
+ void DidSet(FieldIndex<I>) {}
+ template <size_t I, typename T>
+ void DidSet(FieldIndex<I>, T&& aOldValue) {}
+ void DidSet(FieldIndex<IDX_UserActivationState>);
+
+ // Recomputes whether we can execute scripts in this WindowContext based on
+ // the value of AllowJavascript() and whether scripts are allowed in the
+ // BrowsingContext.
+ void RecomputeCanExecuteScripts(bool aApplyChanges = true);
+
+ const uint64_t mInnerWindowId;
+ const uint64_t mOuterWindowId;
+ RefPtr<BrowsingContext> mBrowsingContext;
+ WeakPtr<WindowGlobalChild> mWindowGlobalChild;
+
+ // --- NEVER CHANGE `mChildren` DIRECTLY! ---
+ // Changes to this list need to be synchronized to the list within our
+ // `mBrowsingContext`, and should only be performed through the
+ // `AppendChildBrowsingContext` and `RemoveChildBrowsingContext` methods.
+ nsTArray<RefPtr<BrowsingContext>> mChildren;
+
+ // --- NEVER CHANGE `mNonSyntheticChildren` DIRECTLY! ---
+ // Same reason as for mChildren.
+ // mNonSyntheticChildren contains the same browsing contexts except browsing
+ // contexts created by the synthetic document for object loading contents
+ // loading images. This is used to discern browsing contexts created when
+ // loading images in <object> or <embed> elements, so that they can be hidden
+ // from named targeting, `Window.frames` etc.
+ nsTArray<RefPtr<BrowsingContext>> mNonSyntheticChildren;
+
+ bool mIsDiscarded = false;
+ bool mIsInProcess = false;
+
+ // Determines if we can execute scripts in this WindowContext. True if
+ // AllowJavascript() is true and script execution is allowed in the
+ // BrowsingContext.
+ bool mCanExecuteScripts = true;
+
+ // The start time of user gesture, this is only available if the window
+ // context is in process.
+ TimeStamp mUserGestureStart;
+};
+
+using WindowContextTransaction = WindowContext::BaseTransaction;
+using WindowContextInitializer = WindowContext::IPCInitializer;
+using MaybeDiscardedWindowContext = MaybeDiscarded<WindowContext>;
+
+// Don't specialize the `Transaction` object for every translation unit it's
+// used in. This should help keep code size down.
+extern template class syncedcontext::Transaction<WindowContext>;
+
+} // namespace dom
+
+namespace ipc {
+template <>
+struct IPDLParamTraits<dom::MaybeDiscarded<dom::WindowContext>> {
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const dom::MaybeDiscarded<dom::WindowContext>& aParam);
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ dom::MaybeDiscarded<dom::WindowContext>* aResult);
+};
+
+template <>
+struct IPDLParamTraits<dom::WindowContext::IPCInitializer> {
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const dom::WindowContext::IPCInitializer& aInitializer);
+
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ dom::WindowContext::IPCInitializer* aInitializer);
+};
+} // namespace ipc
+} // namespace mozilla
+
+#endif // !defined(mozilla_dom_WindowContext_h)
diff --git a/docshell/base/crashtests/1257730-1.html b/docshell/base/crashtests/1257730-1.html
new file mode 100644
index 0000000000..028a1adb88
--- /dev/null
+++ b/docshell/base/crashtests/1257730-1.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<!--
+user_pref("browser.send_pings", true);
+-->
+<script>
+
+function boom() {
+ var aLink = document.createElement('a');
+ document.body.appendChild(aLink);
+ aLink.ping = "ping";
+ aLink.href = "href";
+ aLink.click();
+
+ var baseElement = document.createElement('base');
+ baseElement.setAttribute("href", "javascript:void 0");
+ document.head.appendChild(baseElement);
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/docshell/base/crashtests/1331295.html b/docshell/base/crashtests/1331295.html
new file mode 100644
index 0000000000..cdcb29e7fe
--- /dev/null
+++ b/docshell/base/crashtests/1331295.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+function boom() {
+ setTimeout(function(){
+ var o=document.getElementById('b');
+ document.getElementById('a').appendChild(o.parentNode.removeChild(o));
+ },0);
+ var o=document.getElementById('c');
+ var p=document.getElementById('b');
+ p.id=[o.id, o.id=p.id][0];
+ o=document.getElementById('b');
+ o.setAttribute('sandbox', 'disc');
+ window.location.reload(true);
+}
+</script>
+</head>
+<body onload="boom();">
+<header id='a'></header>
+<output id='b'></output>
+<iframe id='c' sandbox='allow-same-origin' src='http://a'></iframe>
+</body>
+</html>
diff --git a/docshell/base/crashtests/1341657.html b/docshell/base/crashtests/1341657.html
new file mode 100644
index 0000000000..d68fa1eb03
--- /dev/null
+++ b/docshell/base/crashtests/1341657.html
@@ -0,0 +1,18 @@
+<html class="reftest-wait">
+ <head>
+ <script>
+ function boom() {
+ o1 = document.createElement("script");
+ o2 = document.implementation.createDocument('', '', null);
+ o3 = document.createElement("iframe");
+ document.documentElement.appendChild(o3);
+ o4 = o3.contentWindow;
+ o5 = document.createTextNode('o2.adoptNode(o3); try { o4.location = "" } catch(e) {}');
+ o1.appendChild(o5);
+ document.documentElement.appendChild(o1);
+ document.documentElement.classList.remove("reftest-wait");
+ }
+ </script>
+ </head>
+ <body onload="boom();"></body>
+</html>
diff --git a/docshell/base/crashtests/1584467.html b/docshell/base/crashtests/1584467.html
new file mode 100644
index 0000000000..5509808bcc
--- /dev/null
+++ b/docshell/base/crashtests/1584467.html
@@ -0,0 +1,12 @@
+<script>
+window.onload = () => {
+ a.addEventListener("DOMSubtreeModified", () => {
+ document.body.appendChild(b)
+ document.body.removeChild(b)
+ window[1]
+ })
+ a.type = ""
+}
+</script>
+<embed id="a">
+<iframe id="b"></iframe>
diff --git a/docshell/base/crashtests/1614211-1.html b/docshell/base/crashtests/1614211-1.html
new file mode 100644
index 0000000000..1d683e0714
--- /dev/null
+++ b/docshell/base/crashtests/1614211-1.html
@@ -0,0 +1,15 @@
+<script>
+window.onload = () => {
+ b.addEventListener('DOMSubtreeModified', () => {
+ var o = document.getElementById('a')
+ var a = o.attributes
+ for (let j = 0; j < a.length; j++) {
+ o.setAttribute(a[j].name, 'i')
+ o.parentNode.appendChild(o)
+ }
+ })
+ b.setAttribute('a', b)
+}
+</script>
+<iframe id='a' sandbox='' allowfullscreen=''></iframe>
+<dfn id='b'>
diff --git a/docshell/base/crashtests/1617315-1.html b/docshell/base/crashtests/1617315-1.html
new file mode 100644
index 0000000000..05d9a704dc
--- /dev/null
+++ b/docshell/base/crashtests/1617315-1.html
@@ -0,0 +1,8 @@
+<script>
+document.addEventListener("DOMContentLoaded", () => {
+ let o = document.getElementById('a')
+ o.setAttribute('id', '')
+ o.setAttribute('sandbox', '')
+})
+</script>
+<iframe id='a' sandbox='s' src='http://%CF'></iframe>
diff --git a/docshell/base/crashtests/1667491.html b/docshell/base/crashtests/1667491.html
new file mode 100644
index 0000000000..ecc77a5e9b
--- /dev/null
+++ b/docshell/base/crashtests/1667491.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+ <meta charset="UTF-8">
+<script>
+ function go() {
+ let win = window.open("1667491_1.html");
+ win.finish = function() {
+ document.documentElement.removeAttribute("class");
+ };
+ }
+</script>
+</head>
+<body onload="go()">
+</body>
+</html>
diff --git a/docshell/base/crashtests/1667491_1.html b/docshell/base/crashtests/1667491_1.html
new file mode 100644
index 0000000000..3df3353f72
--- /dev/null
+++ b/docshell/base/crashtests/1667491_1.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <script>
+ function go() {
+ document.body.appendChild(a)
+ window.frames[0].onbeforeunload = document.createElement("body").onload;
+ window.requestIdleCallback(() => {
+ window.close();
+ finish();
+ });
+ }
+ </script>
+</head>
+<body onload="go()">
+<iframe id="a"></iframe>
+<iframe></iframe>
+</body>
+</html>
+
diff --git a/docshell/base/crashtests/1672873.html b/docshell/base/crashtests/1672873.html
new file mode 100644
index 0000000000..33aa92f0ef
--- /dev/null
+++ b/docshell/base/crashtests/1672873.html
@@ -0,0 +1,6 @@
+<script>
+document.addEventListener('DOMContentLoaded', () => {
+ var x = new Blob([undefined, ''], { })
+ self.history.pushState(x, 'x', 'missing.file')
+})
+</script>
diff --git a/docshell/base/crashtests/1690169-1.html b/docshell/base/crashtests/1690169-1.html
new file mode 100644
index 0000000000..6c9be20be3
--- /dev/null
+++ b/docshell/base/crashtests/1690169-1.html
@@ -0,0 +1,11 @@
+<script>
+var woff = "data:font/woff2;base64,";
+for (let i = 0; i < 20000; i++) {
+ woff += "d09GMgABAAAA";
+}
+window.onload = () => {
+ try { window.open('data:text/html,<spacer>', 'pu9', 'width=911').close() } catch (e) {}
+ document.getElementById('a').setAttribute('src', woff)
+}
+</script>
+<iframe id='a' hidden src='http://a'></iframe>
diff --git a/docshell/base/crashtests/1753136.html b/docshell/base/crashtests/1753136.html
new file mode 100644
index 0000000000..22f679309e
--- /dev/null
+++ b/docshell/base/crashtests/1753136.html
@@ -0,0 +1,2 @@
+<meta charset="UTF-8">
+<iframe sandbox='' src='http://🙅🎂งิ'></iframe>
diff --git a/docshell/base/crashtests/1804803.html b/docshell/base/crashtests/1804803.html
new file mode 100644
index 0000000000..5103c00416
--- /dev/null
+++ b/docshell/base/crashtests/1804803.html
@@ -0,0 +1,13 @@
+<html class="reftest-wait">
+<script>
+function test() {
+ // If the reload in the iframe succeeds we might crash, so wait for it.
+ setTimeout(function() {
+ document.documentElement.className = "";
+ }, 500);
+}
+</script>
+<body onload="test()">
+ <iframe src="1804803.sjs"></iframe>
+</body>
+</html>
diff --git a/docshell/base/crashtests/1804803.sjs b/docshell/base/crashtests/1804803.sjs
new file mode 100644
index 0000000000..0486e32048
--- /dev/null
+++ b/docshell/base/crashtests/1804803.sjs
@@ -0,0 +1,18 @@
+function handleRequest(request, response) {
+ let counter = Number(getState("load"));
+ const reload = counter == 0 ? "self.history.go(0);" : "";
+ setState("load", String(++counter));
+ const document = `
+<script>
+ document.addEventListener('DOMContentLoaded', () => {
+ ${reload}
+ let url = window.location.href;
+ for (let i = 0; i < 50; i++) {
+ self.history.pushState({x: i}, '', url + "#" + i);
+ }
+ });
+</script>
+`;
+
+ response.write(document);
+}
diff --git a/docshell/base/crashtests/369126-1.html b/docshell/base/crashtests/369126-1.html
new file mode 100644
index 0000000000..e9dacec301
--- /dev/null
+++ b/docshell/base/crashtests/369126-1.html
@@ -0,0 +1,16 @@
+<html class="reftest-wait">
+<head>
+<script>
+function boom()
+{
+ document.getElementById("frameset").removeChild(document.getElementById("frame"));
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+
+<frameset id="frameset" onload="setTimeout(boom, 100)">
+ <frame id="frame" src="data:text/html,<body onUnload=&quot;location = 'http://www.mozilla.org/'&quot;>This frame's onunload tries to load another page.">
+</frameset>
+
+</html>
diff --git a/docshell/base/crashtests/40929-1-inner.html b/docshell/base/crashtests/40929-1-inner.html
new file mode 100644
index 0000000000..313046a348
--- /dev/null
+++ b/docshell/base/crashtests/40929-1-inner.html
@@ -0,0 +1,14 @@
+<html><head><title>Infinite Loop</title></head>
+<body onLoad="initNav(); initNav();">
+
+<script language="JavaScript">
+
+function initNav() {
+ ++parent.i;
+ if (parent.i < 10)
+ window.location.href=window.location.href;
+}
+
+</script>
+
+</body></html>
diff --git a/docshell/base/crashtests/40929-1.html b/docshell/base/crashtests/40929-1.html
new file mode 100644
index 0000000000..90685d9f1f
--- /dev/null
+++ b/docshell/base/crashtests/40929-1.html
@@ -0,0 +1,6 @@
+<html>
+<head><title>Infinite Loop</title><script>var i=0;</script></head>
+<body>
+<iframe src="40929-1-inner.html"></iframe>
+</body>
+</html>
diff --git a/docshell/base/crashtests/430124-1.html b/docshell/base/crashtests/430124-1.html
new file mode 100644
index 0000000000..8cdbc1d077
--- /dev/null
+++ b/docshell/base/crashtests/430124-1.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+<body onpagehide="document.getElementById('a').focus();"><div id="a"></div></body>
+</html>
diff --git a/docshell/base/crashtests/430628-1.html b/docshell/base/crashtests/430628-1.html
new file mode 100644
index 0000000000..4a68a5a015
--- /dev/null
+++ b/docshell/base/crashtests/430628-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body onpagehide="document.body.removeChild(document.getElementById('s'));">
+<span id="s" contenteditable="true"></span>
+</body>
+</html>
diff --git a/docshell/base/crashtests/432114-1.html b/docshell/base/crashtests/432114-1.html
new file mode 100644
index 0000000000..8878d6605a
--- /dev/null
+++ b/docshell/base/crashtests/432114-1.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+<title>Bug - Crash [@ PL_DHashTableOperate] with DOMNodeInserted event listener removing window and frameset contenteditable</title>
+</head>
+<body>
+<iframe id="content" src="data:text/html;charset=utf-8,%3Cscript%3E%0Awindow.addEventListener%28%27DOMNodeInserted%27%2C%20function%28%29%20%7Bwindow.frameElement.parentNode.removeChild%28window.frameElement%29%3B%7D%2C%20true%29%3B%0A%3C/script%3E%0A%3Cframeset%20contenteditable%3D%22true%22%3E"></iframe>
+</body>
+</html>
diff --git a/docshell/base/crashtests/432114-2.html b/docshell/base/crashtests/432114-2.html
new file mode 100644
index 0000000000..da77287b61
--- /dev/null
+++ b/docshell/base/crashtests/432114-2.html
@@ -0,0 +1,21 @@
+<html class="reftest-wait">
+<head>
+<title>testcase2 Bug 432114 � Crash [@ PL_DHashTableOperate] with DOMNodeInserted event listener removing window and frameset contenteditable</title>
+</head>
+<body>
+<script>
+ window.addEventListener("DOMNodeRemoved", function() {
+ setTimeout(function() {
+ document.documentElement.removeAttribute("class");
+ }, 0);
+ });
+ var iframe = document.getElementById("content");
+ iframe.onload=function() {
+ dump("iframe onload\n");
+ console.log("iframe onload");
+ };
+</script>
+<iframe id="content" src="file_432114-2.xhtml" style="width:1000px;height: 200px;"></iframe>
+
+</body>
+</html>
diff --git a/docshell/base/crashtests/436900-1-inner.html b/docshell/base/crashtests/436900-1-inner.html
new file mode 100644
index 0000000000..6fe35ccb1a
--- /dev/null
+++ b/docshell/base/crashtests/436900-1-inner.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+
+<meta http-equiv="refresh" content="0">
+
+<script language="javascript">
+
+location.hash += "+++";
+
+function done()
+{
+ parent.document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+<body onload="setTimeout(done, 10)">
+
+</body>
+</html>
diff --git a/docshell/base/crashtests/436900-1.html b/docshell/base/crashtests/436900-1.html
new file mode 100644
index 0000000000..582d1919d1
--- /dev/null
+++ b/docshell/base/crashtests/436900-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+</head>
+<body>
+<iframe src="436900-1-inner.html#foo"></iframe>
+</body>
+</html>
diff --git a/docshell/base/crashtests/436900-2-inner.html b/docshell/base/crashtests/436900-2-inner.html
new file mode 100644
index 0000000000..ea79f75e88
--- /dev/null
+++ b/docshell/base/crashtests/436900-2-inner.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+
+<meta http-equiv="refresh" content="0">
+
+<script language="javascript" id="foo+++">
+
+location.hash += "+++";
+
+function done()
+{
+ parent.document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+<body onload="setTimeout(done, 10)">
+
+</body>
+</html>
diff --git a/docshell/base/crashtests/436900-2.html b/docshell/base/crashtests/436900-2.html
new file mode 100644
index 0000000000..2e1f0c1def
--- /dev/null
+++ b/docshell/base/crashtests/436900-2.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+</head>
+<body>
+<iframe src="436900-2-inner.html#foo"></iframe>
+</body>
+</html>
diff --git a/docshell/base/crashtests/443655.html b/docshell/base/crashtests/443655.html
new file mode 100644
index 0000000000..ce0a8c18b8
--- /dev/null
+++ b/docshell/base/crashtests/443655.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+
+<body onload="document.removeChild(document.documentElement)">
+
+<!-- The order of the two iframes matters! -->
+
+<iframe src='data:text/html,<body onload="s = parent.document.getElementById(&apos;s&apos;).contentWindow;" onunload="s.location = s.location;">'></iframe>
+
+<iframe id="s"></iframe>
+
+</body>
+</html>
diff --git a/docshell/base/crashtests/500328-1.html b/docshell/base/crashtests/500328-1.html
new file mode 100644
index 0000000000..fd97f84ae1
--- /dev/null
+++ b/docshell/base/crashtests/500328-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<body onload="test();">
+<script>
+ function test() {
+ // Test that calling pushState() with a state object which calls
+ // history.back() doesn't crash. We need to make sure that there's at least
+ // one entry in the history before we do anything else.
+ history.pushState(null, "");
+
+ x = {};
+ x.toJSON = { history.back(); return "{a:1}"; };
+ history.pushState(x, "");
+ }
+</script>
+</body>
+</html>
diff --git a/docshell/base/crashtests/514779-1.xhtml b/docshell/base/crashtests/514779-1.xhtml
new file mode 100644
index 0000000000..16ac3d9d66
--- /dev/null
+++ b/docshell/base/crashtests/514779-1.xhtml
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head></head>
+
+<body onunload="document.getElementById('tbody').appendChild(document.createElementNS('http://www.w3.org/1999/xhtml', 'span'))">
+ <iframe/>
+ <tbody contenteditable="true" id="tbody">xy</tbody>
+</body>
+
+</html>
diff --git a/docshell/base/crashtests/614499-1.html b/docshell/base/crashtests/614499-1.html
new file mode 100644
index 0000000000..7053a3f52f
--- /dev/null
+++ b/docshell/base/crashtests/614499-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var f = document.getElementById("f");
+
+ for (var i = 0; i < 50; ++i) {
+ f.contentWindow.history.pushState({}, "");
+ }
+
+ document.body.removeChild(f);
+}
+
+</script>
+</head>
+<body onload="boom();"><iframe id="f" src="data:text/html,1"></iframe></body>
+</html> \ No newline at end of file
diff --git a/docshell/base/crashtests/678872-1.html b/docshell/base/crashtests/678872-1.html
new file mode 100644
index 0000000000..294b3e689b
--- /dev/null
+++ b/docshell/base/crashtests/678872-1.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+var f1, f2;
+
+function b1()
+{
+ f1 = document.getElementById("f1");
+ f2 = document.getElementById("f2");
+ f1.contentWindow.document.write("11");
+ f1.contentWindow.history.back();
+ setTimeout(b2, 0);
+}
+
+function b2()
+{
+ f2.contentWindow.history.forward();
+ f2.contentWindow.location.reload();
+ f1.remove();
+}
+
+</script>
+
+
+</script>
+</head>
+
+<body onload="setTimeout(b1, 0);">
+
+<iframe id="f1" src="data:text/html,1"></iframe>
+<iframe id="f2" src="data:text/html,2"></iframe>
+
+</body>
+</html>
diff --git a/docshell/base/crashtests/914521.html b/docshell/base/crashtests/914521.html
new file mode 100644
index 0000000000..f30d78c10f
--- /dev/null
+++ b/docshell/base/crashtests/914521.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="UTF-8">
+<script>
+
+function f()
+{
+ function spin() {
+ for (var i = 0; i < 8; ++i) {
+ var x = new XMLHttpRequest();
+ x.open('GET', 'data:text/html,' + i, false);
+ x.send();
+ }
+ }
+
+ window.addEventListener("popstate", spin);
+ window.close();
+ window.location = "#c";
+ document.documentElement.removeAttribute("class");
+}
+
+function start()
+{
+ var win = window.open("javascript:'<html><body>dummy</body></html>';", null, "width=300,height=300");
+ win.onload = f;
+}
+
+</script>
+</head>
+<body onload="start();"></body>
+</html>
diff --git a/docshell/base/crashtests/crashtests.list b/docshell/base/crashtests/crashtests.list
new file mode 100644
index 0000000000..f9b214bfa2
--- /dev/null
+++ b/docshell/base/crashtests/crashtests.list
@@ -0,0 +1,25 @@
+load 40929-1.html
+load 369126-1.html
+load 430124-1.html
+load 430628-1.html
+load 432114-1.html
+load 432114-2.html
+load 436900-1.html
+asserts(0-1) load 436900-2.html # bug 566159
+load 443655.html
+load 500328-1.html
+load 514779-1.xhtml
+load 614499-1.html
+load 678872-1.html
+skip-if(Android) pref(dom.disable_open_during_load,false) load 914521.html # Android bug 1584562
+pref(browser.send_pings,true) asserts(0-2) load 1257730-1.html # bug 566159
+load 1331295.html
+load 1341657.html
+load 1584467.html
+load 1614211-1.html
+load 1617315-1.html
+skip-if(Android) pref(dom.disable_open_during_load,false) load 1667491.html
+pref(dom.disable_open_during_load,false) load 1690169-1.html
+load 1672873.html
+load 1753136.html
+HTTP load 1804803.html
diff --git a/docshell/base/crashtests/file_432114-2.xhtml b/docshell/base/crashtests/file_432114-2.xhtml
new file mode 100644
index 0000000000..40bf886b8e
--- /dev/null
+++ b/docshell/base/crashtests/file_432114-2.xhtml
@@ -0,0 +1 @@
+<html xmlns='http://www.w3.org/1999/xhtml'><frameset contenteditable='true'/><script>function doExecCommand(){dump("doExecCommand\n");document.execCommand('formatBlock', false, 'p');}setTimeout(doExecCommand,100); window.addEventListener('DOMNodeRemoved', function() {window.frameElement.parentNode.removeChild(window.frameElement);}, true);</script></html>
diff --git a/docshell/base/moz.build b/docshell/base/moz.build
new file mode 100644
index 0000000000..049a78d440
--- /dev/null
+++ b/docshell/base/moz.build
@@ -0,0 +1,130 @@
+# -*- Mode: python; 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DOM: Navigation")
+
+with Files("crashtests/430628*"):
+ BUG_COMPONENT = ("Core", "DOM: Editor")
+
+with Files("crashtests/432114*"):
+ BUG_COMPONENT = ("Core", "DOM: Editor")
+
+with Files("crashtests/500328*"):
+ BUG_COMPONENT = ("Firefox", "Bookmarks & History")
+
+with Files("IHistory.h"):
+ BUG_COMPONENT = ("Toolkit", "Places")
+
+with Files("*LoadContext.*"):
+ BUG_COMPONENT = ("Core", "Networking")
+
+with Files("nsAboutRedirector.*"):
+ BUG_COMPONENT = ("Core", "General")
+
+with Files("nsIScrollObserver.*"):
+ BUG_COMPONENT = ("Core", "Panning and Zooming")
+
+DIRS += [
+ "timeline",
+]
+
+XPIDL_SOURCES += [
+ "nsIContentViewer.idl",
+ "nsIContentViewerEdit.idl",
+ "nsIDocShell.idl",
+ "nsIDocShellTreeItem.idl",
+ "nsIDocShellTreeOwner.idl",
+ "nsIDocumentLoaderFactory.idl",
+ "nsILoadContext.idl",
+ "nsILoadURIDelegate.idl",
+ "nsIPrivacyTransitionObserver.idl",
+ "nsIReflowObserver.idl",
+ "nsIRefreshURI.idl",
+ "nsITooltipListener.idl",
+ "nsITooltipTextProvider.idl",
+ "nsIURIFixup.idl",
+ "nsIWebNavigation.idl",
+ "nsIWebNavigationInfo.idl",
+ "nsIWebPageDescriptor.idl",
+]
+
+XPIDL_MODULE = "docshell"
+
+EXPORTS += [
+ "nsCTooltipTextProvider.h",
+ "nsDocShell.h",
+ "nsDocShellLoadState.h",
+ "nsDocShellLoadTypes.h",
+ "nsDocShellTreeOwner.h",
+ "nsDSURIContentListener.h",
+ "nsIScrollObserver.h",
+ "nsWebNavigationInfo.h",
+ "SerializedLoadContext.h",
+]
+
+EXPORTS.mozilla += [
+ "BaseHistory.h",
+ "IHistory.h",
+ "LoadContext.h",
+]
+
+EXPORTS.mozilla.dom += [
+ "BrowsingContext.h",
+ "BrowsingContextGroup.h",
+ "BrowsingContextWebProgress.h",
+ "CanonicalBrowsingContext.h",
+ "ChildProcessChannelListener.h",
+ "SyncedContext.h",
+ "SyncedContextInlines.h",
+ "WindowContext.h",
+]
+
+UNIFIED_SOURCES += [
+ "BaseHistory.cpp",
+ "BrowsingContext.cpp",
+ "BrowsingContextGroup.cpp",
+ "BrowsingContextWebProgress.cpp",
+ "CanonicalBrowsingContext.cpp",
+ "ChildProcessChannelListener.cpp",
+ "LoadContext.cpp",
+ "nsAboutRedirector.cpp",
+ "nsDocShell.cpp",
+ "nsDocShellEditorData.cpp",
+ "nsDocShellEnumerator.cpp",
+ "nsDocShellLoadState.cpp",
+ "nsDocShellTelemetryUtils.cpp",
+ "nsDocShellTreeOwner.cpp",
+ "nsDSURIContentListener.cpp",
+ "nsPingListener.cpp",
+ "nsRefreshTimer.cpp",
+ "nsWebNavigationInfo.cpp",
+ "SerializedLoadContext.cpp",
+ "WindowContext.cpp",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+LOCAL_INCLUDES += [
+ "/docshell/shistory",
+ "/dom/base",
+ "/dom/bindings",
+ "/js/xpconnect/src",
+ "/layout/base",
+ "/layout/generic",
+ "/layout/style",
+ "/layout/xul",
+ "/netwerk/base",
+ "/netwerk/protocol/viewsource",
+ "/toolkit/components/browser",
+ "/toolkit/components/find",
+ "/tools/profiler",
+]
+
+EXTRA_JS_MODULES += ["URIFixup.sys.mjs"]
+
+include("/tools/fuzzing/libfuzzer-config.mozbuild")
diff --git a/docshell/base/nsAboutRedirector.cpp b/docshell/base/nsAboutRedirector.cpp
new file mode 100644
index 0000000000..faf334f345
--- /dev/null
+++ b/docshell/base/nsAboutRedirector.cpp
@@ -0,0 +1,299 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsAboutRedirector.h"
+#include "nsNetUtil.h"
+#include "nsAboutProtocolUtils.h"
+#include "nsBaseChannel.h"
+#include "mozilla/ArrayUtils.h"
+#include "nsIProtocolHandler.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/gfx/GPUProcessManager.h"
+
+#define ABOUT_CONFIG_ENABLED_PREF "general.aboutConfig.enable"
+
+NS_IMPL_ISUPPORTS(nsAboutRedirector, nsIAboutModule)
+
+struct RedirEntry {
+ const char* id;
+ const char* url;
+ uint32_t flags;
+};
+
+class CrashChannel final : public nsBaseChannel {
+ public:
+ explicit CrashChannel(nsIURI* aURI) { SetURI(aURI); }
+
+ nsresult OpenContentStream(bool async, nsIInputStream** stream,
+ nsIChannel** channel) override {
+ nsAutoCString spec;
+ mURI->GetSpec(spec);
+
+ if (spec.EqualsASCII("about:crashparent") && XRE_IsParentProcess()) {
+ MOZ_CRASH("Crash via about:crashparent");
+ }
+
+ if (spec.EqualsASCII("about:crashgpu") && XRE_IsParentProcess()) {
+ if (auto* gpu = mozilla::gfx::GPUProcessManager::Get()) {
+ gpu->CrashProcess();
+ }
+ }
+
+ if (spec.EqualsASCII("about:crashcontent") && XRE_IsContentProcess()) {
+ MOZ_CRASH("Crash via about:crashcontent");
+ }
+
+ NS_WARNING("Unhandled about:crash* URI or wrong process");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ protected:
+ virtual ~CrashChannel() = default;
+};
+
+/*
+ Entries which do not have URI_SAFE_FOR_UNTRUSTED_CONTENT will run with chrome
+ privileges. This is potentially dangerous. Please use
+ URI_SAFE_FOR_UNTRUSTED_CONTENT in the third argument to each map item below
+ unless your about: page really needs chrome privileges. Security review is
+ required before adding new map entries without
+ URI_SAFE_FOR_UNTRUSTED_CONTENT.
+
+ URI_SAFE_FOR_UNTRUSTED_CONTENT is not enough to let web pages load that page,
+ for that you need MAKE_LINKABLE.
+
+ NOTE: changes to this redir map need to be accompanied with changes to
+ docshell/build/components.conf
+ */
+static const RedirEntry kRedirMap[] = {
+ {"about", "chrome://global/content/aboutAbout.html", 0},
+ {"addons", "chrome://mozapps/content/extensions/aboutaddons.html",
+ nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::IS_SECURE_CHROME_UI},
+ {"buildconfig", "chrome://global/content/buildconfig.html",
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::IS_SECURE_CHROME_UI},
+ {"checkerboard", "chrome://global/content/aboutCheckerboard.html",
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::ALLOW_SCRIPT},
+#ifndef MOZ_WIDGET_ANDROID
+ {"config", "chrome://global/content/aboutconfig/aboutconfig.html",
+ nsIAboutModule::IS_SECURE_CHROME_UI},
+#else
+ {"config", "chrome://geckoview/content/config.xhtml",
+ nsIAboutModule::IS_SECURE_CHROME_UI},
+#endif
+#ifdef MOZ_CRASHREPORTER
+ {"crashes", "chrome://global/content/crashes.html",
+ nsIAboutModule::IS_SECURE_CHROME_UI},
+#endif
+ {"credits", "https://www.mozilla.org/credits/",
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::URI_MUST_LOAD_IN_CHILD},
+ {"httpsonlyerror", "chrome://global/content/httpsonlyerror/errorpage.html",
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::URI_CAN_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT |
+ nsIAboutModule::HIDE_FROM_ABOUTABOUT},
+ {"license", "chrome://global/content/license.html",
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::IS_SECURE_CHROME_UI},
+ {"logging", "chrome://global/content/aboutLogging.html",
+ nsIAboutModule::ALLOW_SCRIPT},
+ {"logo", "chrome://branding/content/about.png",
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ // Linkable for testing reasons.
+ nsIAboutModule::MAKE_LINKABLE},
+ {"memory", "chrome://global/content/aboutMemory.xhtml",
+ nsIAboutModule::ALLOW_SCRIPT},
+ {"certificate", "chrome://global/content/certviewer/certviewer.html",
+ nsIAboutModule::ALLOW_SCRIPT |
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
+ nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS |
+ nsIAboutModule::IS_SECURE_CHROME_UI},
+ {"mozilla", "chrome://global/content/mozilla.html",
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT},
+ {"neterror", "chrome://global/content/aboutNetError.xhtml",
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::URI_CAN_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT |
+ nsIAboutModule::HIDE_FROM_ABOUTABOUT},
+ {"networking", "chrome://global/content/aboutNetworking.html",
+ nsIAboutModule::ALLOW_SCRIPT},
+ {"performance", "chrome://global/content/aboutPerformance.html",
+ nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::IS_SECURE_CHROME_UI},
+#ifndef ANDROID
+ {"plugins", "chrome://global/content/plugins.html",
+ nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
+ nsIAboutModule::IS_SECURE_CHROME_UI},
+#endif
+ {"processes", "chrome://global/content/aboutProcesses.html",
+ nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::IS_SECURE_CHROME_UI},
+ // about:serviceworkers always wants to load in the parent process because
+ // the only place nsIServiceWorkerManager has any data is in the parent
+ // process.
+ //
+ // There is overlap without about:debugging, but about:debugging is not
+ // available on mobile at this time, and it's useful to be able to know if
+ // a ServiceWorker is registered directly from the mobile browser without
+ // having to connect the device to a desktop machine and all that entails.
+ {"serviceworkers", "chrome://global/content/aboutServiceWorkers.xhtml",
+ nsIAboutModule::ALLOW_SCRIPT},
+#ifndef ANDROID
+ {"profiles", "chrome://global/content/aboutProfiles.xhtml",
+ nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::IS_SECURE_CHROME_UI},
+#endif
+ // about:srcdoc is unresolvable by specification. It is included here
+ // because the security manager would disallow srcdoc iframes otherwise.
+ {"srcdoc", "about:blank",
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::HIDE_FROM_ABOUTABOUT |
+ // Needs to be linkable so content can touch its own srcdoc frames
+ nsIAboutModule::MAKE_LINKABLE | nsIAboutModule::URI_CAN_LOAD_IN_CHILD},
+ {"support", "chrome://global/content/aboutSupport.xhtml",
+ nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::IS_SECURE_CHROME_UI},
+#ifdef XP_WIN
+ {"third-party", "chrome://global/content/aboutThirdParty.html",
+ nsIAboutModule::ALLOW_SCRIPT},
+ {"windows-messages", "chrome://global/content/aboutWindowsMessages.html",
+ nsIAboutModule::ALLOW_SCRIPT},
+#endif
+#ifndef MOZ_GLEAN_ANDROID
+ {"glean", "chrome://global/content/aboutGlean.html",
+# if !defined(NIGHTLY_BUILD) && defined(MOZILLA_OFFICIAL)
+ nsIAboutModule::HIDE_FROM_ABOUTABOUT |
+# endif
+ nsIAboutModule::ALLOW_SCRIPT},
+#endif
+ {"telemetry", "chrome://global/content/aboutTelemetry.xhtml",
+ nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::IS_SECURE_CHROME_UI},
+ {"translations", "chrome://global/content/translations/translations.html",
+ nsIAboutModule::ALLOW_SCRIPT |
+ nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+ nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
+ nsIAboutModule::HIDE_FROM_ABOUTABOUT},
+ {"url-classifier", "chrome://global/content/aboutUrlClassifier.xhtml",
+ nsIAboutModule::ALLOW_SCRIPT},
+ {"webrtc", "chrome://global/content/aboutwebrtc/aboutWebrtc.html",
+ nsIAboutModule::ALLOW_SCRIPT},
+ {"crashparent", "about:blank", nsIAboutModule::HIDE_FROM_ABOUTABOUT},
+ {"crashcontent", "about:blank",
+ nsIAboutModule::HIDE_FROM_ABOUTABOUT |
+ nsIAboutModule::URI_CAN_LOAD_IN_CHILD |
+ nsIAboutModule::URI_MUST_LOAD_IN_CHILD},
+ {"crashgpu", "about:blank", nsIAboutModule::HIDE_FROM_ABOUTABOUT}};
+static const int kRedirTotal = mozilla::ArrayLength(kRedirMap);
+
+NS_IMETHODIMP
+nsAboutRedirector::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ nsIChannel** aResult) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ NS_ENSURE_ARG_POINTER(aLoadInfo);
+ NS_ASSERTION(aResult, "must not be null");
+
+ nsAutoCString path;
+ nsresult rv = NS_GetAboutModuleName(aURI, path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (path.EqualsASCII("crashparent") || path.EqualsASCII("crashcontent") ||
+ path.EqualsASCII("crashgpu")) {
+ bool isExternal;
+ aLoadInfo->GetLoadTriggeredFromExternal(&isExternal);
+ if (isExternal) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIChannel> channel = new CrashChannel(aURI);
+ channel->SetLoadInfo(aLoadInfo);
+ channel.forget(aResult);
+ return NS_OK;
+ }
+
+ if (path.EqualsASCII("config") &&
+ !mozilla::Preferences::GetBool(ABOUT_CONFIG_ENABLED_PREF, true)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ for (int i = 0; i < kRedirTotal; i++) {
+ if (!strcmp(path.get(), kRedirMap[i].id)) {
+ nsCOMPtr<nsIChannel> tempChannel;
+ nsCOMPtr<nsIURI> tempURI;
+ rv = NS_NewURI(getter_AddRefs(tempURI), kRedirMap[i].url);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = NS_NewChannelInternal(getter_AddRefs(tempChannel), tempURI,
+ aLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If tempURI links to an external URI (i.e. something other than
+ // chrome:// or resource://) then set result principal URI on the
+ // load info which forces the channel principal to reflect the displayed
+ // URL rather then being the systemPrincipal.
+ bool isUIResource = false;
+ rv = NS_URIChainHasFlags(tempURI, nsIProtocolHandler::URI_IS_UI_RESOURCE,
+ &isUIResource);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isAboutBlank = NS_IsAboutBlank(tempURI);
+
+ if (!isUIResource && !isAboutBlank) {
+ aLoadInfo->SetResultPrincipalURI(tempURI);
+ }
+
+ tempChannel->SetOriginalURI(aURI);
+
+ tempChannel.forget(aResult);
+ return rv;
+ }
+ }
+
+ NS_ERROR("nsAboutRedirector called for unknown case");
+ return NS_ERROR_ILLEGAL_VALUE;
+}
+
+NS_IMETHODIMP
+nsAboutRedirector::GetURIFlags(nsIURI* aURI, uint32_t* aResult) {
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsAutoCString name;
+ nsresult rv = NS_GetAboutModuleName(aURI, name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (int i = 0; i < kRedirTotal; i++) {
+ if (name.EqualsASCII(kRedirMap[i].id)) {
+ *aResult = kRedirMap[i].flags;
+ return NS_OK;
+ }
+ }
+
+ NS_ERROR("nsAboutRedirector called for unknown case");
+ return NS_ERROR_ILLEGAL_VALUE;
+}
+
+NS_IMETHODIMP
+nsAboutRedirector::GetChromeURI(nsIURI* aURI, nsIURI** chromeURI) {
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsAutoCString name;
+ nsresult rv = NS_GetAboutModuleName(aURI, name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (const auto& redir : kRedirMap) {
+ if (name.EqualsASCII(redir.id)) {
+ return NS_NewURI(chromeURI, redir.url);
+ }
+ }
+
+ NS_ERROR("nsAboutRedirector called for unknown case");
+ return NS_ERROR_ILLEGAL_VALUE;
+}
+
+nsresult nsAboutRedirector::Create(REFNSIID aIID, void** aResult) {
+ RefPtr<nsAboutRedirector> about = new nsAboutRedirector();
+ return about->QueryInterface(aIID, aResult);
+}
diff --git a/docshell/base/nsAboutRedirector.h b/docshell/base/nsAboutRedirector.h
new file mode 100644
index 0000000000..0bf021cc31
--- /dev/null
+++ b/docshell/base/nsAboutRedirector.h
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAboutRedirector_h__
+#define nsAboutRedirector_h__
+
+#include "nsIAboutModule.h"
+
+class nsAboutRedirector : public nsIAboutModule {
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIABOUTMODULE
+
+ nsAboutRedirector() {}
+
+ static nsresult Create(REFNSIID aIID, void** aResult);
+
+ protected:
+ virtual ~nsAboutRedirector() {}
+};
+
+#endif // nsAboutRedirector_h__
diff --git a/docshell/base/nsCTooltipTextProvider.h b/docshell/base/nsCTooltipTextProvider.h
new file mode 100644
index 0000000000..731edf1170
--- /dev/null
+++ b/docshell/base/nsCTooltipTextProvider.h
@@ -0,0 +1,15 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSCTOOLTIPTEXTPROVIDER_H
+#define NSCTOOLTIPTEXTPROVIDER_H
+
+#define NS_TOOLTIPTEXTPROVIDER_CONTRACTID \
+ "@mozilla.org/embedcomp/tooltiptextprovider;1"
+#define NS_DEFAULTTOOLTIPTEXTPROVIDER_CONTRACTID \
+ "@mozilla.org/embedcomp/default-tooltiptextprovider;1"
+
+#endif
diff --git a/docshell/base/nsDSURIContentListener.cpp b/docshell/base/nsDSURIContentListener.cpp
new file mode 100644
index 0000000000..437f729fc0
--- /dev/null
+++ b/docshell/base/nsDSURIContentListener.cpp
@@ -0,0 +1,297 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDocShell.h"
+#include "nsDSURIContentListener.h"
+#include "nsIChannel.h"
+#include "nsServiceManagerUtils.h"
+#include "nsDocShellCID.h"
+#include "nsIWebNavigationInfo.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/Unused.h"
+#include "nsError.h"
+#include "nsContentSecurityManager.h"
+#include "nsDocShellLoadTypes.h"
+#include "nsGlobalWindowOuter.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIMultiPartChannel.h"
+#include "nsWebNavigationInfo.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_ADDREF(MaybeCloseWindowHelper)
+NS_IMPL_RELEASE(MaybeCloseWindowHelper)
+
+NS_INTERFACE_MAP_BEGIN(MaybeCloseWindowHelper)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimerCallback)
+ NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+ NS_INTERFACE_MAP_ENTRY(nsINamed)
+NS_INTERFACE_MAP_END
+
+MaybeCloseWindowHelper::MaybeCloseWindowHelper(BrowsingContext* aContentContext)
+ : mBrowsingContext(aContentContext),
+ mTimer(nullptr),
+ mShouldCloseWindow(false) {}
+
+MaybeCloseWindowHelper::~MaybeCloseWindowHelper() {}
+
+void MaybeCloseWindowHelper::SetShouldCloseWindow(bool aShouldCloseWindow) {
+ mShouldCloseWindow = aShouldCloseWindow;
+}
+
+BrowsingContext* MaybeCloseWindowHelper::MaybeCloseWindow() {
+ if (!mShouldCloseWindow) {
+ return mBrowsingContext;
+ }
+
+ // This method should not be called more than once, but it's better to avoid
+ // closing the current window again.
+ mShouldCloseWindow = false;
+
+ // Reset the window context to the opener window so that the dependent
+ // dialogs have a parent
+ RefPtr<BrowsingContext> newBC = ChooseNewBrowsingContext(mBrowsingContext);
+
+ if (newBC != mBrowsingContext && newBC && !newBC->IsDiscarded()) {
+ mBCToClose = mBrowsingContext;
+ mBrowsingContext = newBC;
+
+ // Now close the old window. Do it on a timer so that we don't run
+ // into issues trying to close the window before it has fully opened.
+ NS_ASSERTION(!mTimer, "mTimer was already initialized once!");
+ NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, 0,
+ nsITimer::TYPE_ONE_SHOT);
+ }
+
+ return mBrowsingContext;
+}
+
+already_AddRefed<BrowsingContext>
+MaybeCloseWindowHelper::ChooseNewBrowsingContext(BrowsingContext* aBC) {
+ RefPtr<BrowsingContext> opener = aBC->GetOpener();
+ if (opener && !opener->IsDiscarded()) {
+ return opener.forget();
+ }
+
+ if (!XRE_IsParentProcess()) {
+ return nullptr;
+ }
+
+ opener = BrowsingContext::Get(aBC->Canonical()->GetCrossGroupOpenerId());
+ if (!opener || opener->IsDiscarded()) {
+ return nullptr;
+ }
+ return opener.forget();
+}
+
+NS_IMETHODIMP
+MaybeCloseWindowHelper::Notify(nsITimer* timer) {
+ NS_ASSERTION(mBCToClose, "No window to close after timer fired");
+
+ mBCToClose->Close(CallerType::System, IgnoreErrors());
+ mBCToClose = nullptr;
+ mTimer = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MaybeCloseWindowHelper::GetName(nsACString& aName) {
+ aName.AssignLiteral("MaybeCloseWindowHelper");
+ return NS_OK;
+}
+
+nsDSURIContentListener::nsDSURIContentListener(nsDocShell* aDocShell)
+ : mDocShell(aDocShell),
+ mExistingJPEGRequest(nullptr),
+ mParentContentListener(nullptr) {}
+
+nsDSURIContentListener::~nsDSURIContentListener() {}
+
+NS_IMPL_ADDREF(nsDSURIContentListener)
+NS_IMPL_RELEASE(nsDSURIContentListener)
+
+NS_INTERFACE_MAP_BEGIN(nsDSURIContentListener)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIURIContentListener)
+ NS_INTERFACE_MAP_ENTRY(nsIURIContentListener)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+NS_IMETHODIMP
+nsDSURIContentListener::DoContent(const nsACString& aContentType,
+ bool aIsContentPreferred,
+ nsIRequest* aRequest,
+ nsIStreamListener** aContentHandler,
+ bool* aAbortProcess) {
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(aContentHandler);
+ NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
+ RefPtr<nsDocShell> docShell = mDocShell;
+
+ *aAbortProcess = false;
+
+ // determine if the channel has just been retargeted to us...
+ nsLoadFlags loadFlags = 0;
+ if (nsCOMPtr<nsIChannel> openedChannel = do_QueryInterface(aRequest)) {
+ openedChannel->GetLoadFlags(&loadFlags);
+ }
+
+ if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) {
+ // XXX: Why does this not stop the content too?
+ docShell->Stop(nsIWebNavigation::STOP_NETWORK);
+ NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
+ docShell->SetLoadType(aIsContentPreferred ? LOAD_LINK : LOAD_NORMAL);
+ }
+
+ // In case of multipart jpeg request (mjpeg) we don't really want to
+ // create new viewer since the one we already have is capable of
+ // rendering multipart jpeg correctly (see bug 625012)
+ nsCOMPtr<nsIChannel> baseChannel;
+ if (nsCOMPtr<nsIMultiPartChannel> mpchan = do_QueryInterface(aRequest)) {
+ mpchan->GetBaseChannel(getter_AddRefs(baseChannel));
+ }
+
+ bool reuseCV = baseChannel && baseChannel == mExistingJPEGRequest &&
+ aContentType.EqualsLiteral("image/jpeg");
+
+ if (mExistingJPEGStreamListener && reuseCV) {
+ RefPtr<nsIStreamListener> copy(mExistingJPEGStreamListener);
+ copy.forget(aContentHandler);
+ rv = NS_OK;
+ } else {
+ rv = docShell->CreateContentViewer(aContentType, aRequest, aContentHandler);
+ if (NS_SUCCEEDED(rv) && reuseCV) {
+ mExistingJPEGStreamListener = *aContentHandler;
+ } else {
+ mExistingJPEGStreamListener = nullptr;
+ }
+ mExistingJPEGRequest = baseChannel;
+ }
+
+ if (rv == NS_ERROR_DOCSHELL_DYING) {
+ aRequest->Cancel(rv);
+ *aAbortProcess = true;
+ return NS_OK;
+ }
+
+ if (NS_FAILED(rv)) {
+ // we don't know how to handle the content
+ nsCOMPtr<nsIStreamListener> forget = dont_AddRef(*aContentHandler);
+ *aContentHandler = nullptr;
+ return rv;
+ }
+
+ if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) {
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow =
+ mDocShell ? mDocShell->GetWindow() : nullptr;
+ NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE);
+ domWindow->Focus(mozilla::dom::CallerType::System);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDSURIContentListener::IsPreferred(const char* aContentType,
+ char** aDesiredContentType,
+ bool* aCanHandle) {
+ NS_ENSURE_ARG_POINTER(aCanHandle);
+ NS_ENSURE_ARG_POINTER(aDesiredContentType);
+
+ // the docshell has no idea if it is the preferred content provider or not.
+ // It needs to ask its parent if it is the preferred content handler or not...
+
+ nsCOMPtr<nsIURIContentListener> parentListener;
+ GetParentContentListener(getter_AddRefs(parentListener));
+ if (parentListener) {
+ return parentListener->IsPreferred(aContentType, aDesiredContentType,
+ aCanHandle);
+ }
+ // we used to return false here if we didn't have a parent properly registered
+ // at the top of the docshell hierarchy to dictate what content types this
+ // docshell should be a preferred handler for. But this really makes it hard
+ // for developers using iframe or browser tags because then they need to make
+ // sure they implement nsIURIContentListener otherwise all link clicks would
+ // get sent to another window because we said we weren't the preferred handler
+ // type. I'm going to change the default now... if we can handle the content,
+ // and someone didn't EXPLICITLY set a nsIURIContentListener at the top of our
+ // docshell chain, then we'll now always attempt to process the content
+ // ourselves...
+ return CanHandleContent(aContentType, true, aDesiredContentType, aCanHandle);
+}
+
+NS_IMETHODIMP
+nsDSURIContentListener::CanHandleContent(const char* aContentType,
+ bool aIsContentPreferred,
+ char** aDesiredContentType,
+ bool* aCanHandleContent) {
+ MOZ_ASSERT(aCanHandleContent, "Null out param?");
+ NS_ENSURE_ARG_POINTER(aDesiredContentType);
+
+ *aCanHandleContent = false;
+ *aDesiredContentType = nullptr;
+
+ if (aContentType) {
+ uint32_t canHandle =
+ nsWebNavigationInfo::IsTypeSupported(nsDependentCString(aContentType));
+ *aCanHandleContent = (canHandle != nsIWebNavigationInfo::UNSUPPORTED);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDSURIContentListener::GetLoadCookie(nsISupports** aLoadCookie) {
+ NS_IF_ADDREF(*aLoadCookie = nsDocShell::GetAsSupports(mDocShell));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDSURIContentListener::SetLoadCookie(nsISupports* aLoadCookie) {
+#ifdef DEBUG
+ RefPtr<nsDocLoader> cookieAsDocLoader =
+ nsDocLoader::GetAsDocLoader(aLoadCookie);
+ NS_ASSERTION(cookieAsDocLoader && cookieAsDocLoader == mDocShell,
+ "Invalid load cookie being set!");
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDSURIContentListener::GetParentContentListener(
+ nsIURIContentListener** aParentListener) {
+ if (mWeakParentContentListener) {
+ nsCOMPtr<nsIURIContentListener> tempListener =
+ do_QueryReferent(mWeakParentContentListener);
+ *aParentListener = tempListener;
+ NS_IF_ADDREF(*aParentListener);
+ } else {
+ *aParentListener = mParentContentListener;
+ NS_IF_ADDREF(*aParentListener);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDSURIContentListener::SetParentContentListener(
+ nsIURIContentListener* aParentListener) {
+ if (aParentListener) {
+ // Store the parent listener as a weak ref. Parents not supporting
+ // nsISupportsWeakReference assert but may still be used.
+ mParentContentListener = nullptr;
+ mWeakParentContentListener = do_GetWeakReference(aParentListener);
+ if (!mWeakParentContentListener) {
+ mParentContentListener = aParentListener;
+ }
+ } else {
+ mWeakParentContentListener = nullptr;
+ mParentContentListener = nullptr;
+ }
+ return NS_OK;
+}
diff --git a/docshell/base/nsDSURIContentListener.h b/docshell/base/nsDSURIContentListener.h
new file mode 100644
index 0000000000..61ed36456f
--- /dev/null
+++ b/docshell/base/nsDSURIContentListener.h
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDSURIContentListener_h__
+#define nsDSURIContentListener_h__
+
+#include "nsCOMPtr.h"
+#include "nsIURIContentListener.h"
+#include "nsWeakReference.h"
+#include "nsITimer.h"
+
+class nsDocShell;
+class nsIInterfaceRequestor;
+class nsIWebNavigationInfo;
+class nsPIDOMWindowOuter;
+
+// Helper Class to eventually close an already opened window
+class MaybeCloseWindowHelper final : public nsITimerCallback, public nsINamed {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ explicit MaybeCloseWindowHelper(
+ mozilla::dom::BrowsingContext* aContentContext);
+
+ /**
+ * Closes the provided window async (if mShouldCloseWindow is true) and
+ * returns a valid browsingContext to be used instead as parent for dialogs or
+ * similar things.
+ * In case mShouldCloseWindow is true, the returned BrowsingContext will be
+ * the window's opener (or original cross-group opener in the case of a
+ * `noopener` popup).
+ */
+ mozilla::dom::BrowsingContext* MaybeCloseWindow();
+
+ void SetShouldCloseWindow(bool aShouldCloseWindow);
+
+ protected:
+ ~MaybeCloseWindowHelper();
+
+ private:
+ already_AddRefed<mozilla::dom::BrowsingContext> ChooseNewBrowsingContext(
+ mozilla::dom::BrowsingContext* aBC);
+
+ /**
+ * The dom window associated to handle content.
+ */
+ RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext;
+
+ /**
+ * Used to close the window on a timer, to avoid any exceptions that are
+ * thrown if we try to close the window before it's fully loaded.
+ */
+ RefPtr<mozilla::dom::BrowsingContext> mBCToClose;
+ nsCOMPtr<nsITimer> mTimer;
+
+ /**
+ * This is set based on whether the channel indicates that a new window
+ * was opened, e.g. for a download, or was blocked. If so, then we
+ * close it.
+ */
+ bool mShouldCloseWindow;
+};
+
+class nsDSURIContentListener final : public nsIURIContentListener,
+ public nsSupportsWeakReference {
+ friend class nsDocShell;
+
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIURICONTENTLISTENER
+
+ protected:
+ explicit nsDSURIContentListener(nsDocShell* aDocShell);
+ virtual ~nsDSURIContentListener();
+
+ void DropDocShellReference() {
+ mDocShell = nullptr;
+ mExistingJPEGRequest = nullptr;
+ mExistingJPEGStreamListener = nullptr;
+ }
+
+ protected:
+ nsDocShell* mDocShell;
+ // Hack to handle multipart images without creating a new viewer
+ nsCOMPtr<nsIStreamListener> mExistingJPEGStreamListener;
+ nsCOMPtr<nsIChannel> mExistingJPEGRequest;
+
+ // Store the parent listener in either of these depending on
+ // if supports weak references or not. Proper weak refs are
+ // preferred and encouraged!
+ nsWeakPtr mWeakParentContentListener;
+ nsIURIContentListener* mParentContentListener;
+};
+
+#endif /* nsDSURIContentListener_h__ */
diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp
new file mode 100644
index 0000000000..57ee34cad0
--- /dev/null
+++ b/docshell/base/nsDocShell.cpp
@@ -0,0 +1,13877 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDocShell.h"
+
+#include <algorithm>
+
+#ifdef XP_WIN
+# include <process.h>
+# define getpid _getpid
+#else
+# include <unistd.h> // for getpid()
+#endif
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/Casting.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/Components.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/HTMLEditor.h"
+#include "mozilla/InputTaskManager.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/Logging.h"
+#include "mozilla/MediaFeatureChange.h"
+#include "mozilla/ObservedDocShell.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/ScrollTypes.h"
+#include "mozilla/SimpleEnumerator.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_docshell.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_extensions.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "mozilla/StaticPrefs_ui.h"
+#include "mozilla/StaticPrefs_fission.h"
+#include "mozilla/StartupTimeline.h"
+#include "mozilla/StorageAccess.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "mozilla/Telemetry.h"
+
+#include "mozilla/Unused.h"
+#include "mozilla/WidgetUtils.h"
+
+#include "mozilla/dom/AutoEntryScript.h"
+#include "mozilla/dom/ChildProcessChannelListener.h"
+#include "mozilla/dom/ClientChannelHelper.h"
+#include "mozilla/dom/ClientHandle.h"
+#include "mozilla/dom/ClientInfo.h"
+#include "mozilla/dom/ClientManager.h"
+#include "mozilla/dom/ClientSource.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentFrameMessageManager.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLAnchorElement.h"
+#include "mozilla/dom/HTMLIFrameElement.h"
+#include "mozilla/dom/PerformanceNavigation.h"
+#include "mozilla/dom/PermissionMessageUtils.h"
+#include "mozilla/dom/PopupBlocker.h"
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+#include "mozilla/dom/ScreenOrientation.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/ServiceWorkerInterceptController.h"
+#include "mozilla/dom/ServiceWorkerUtils.h"
+#include "mozilla/dom/SessionHistoryEntry.h"
+#include "mozilla/dom/SessionStorageManager.h"
+#include "mozilla/dom/SessionStoreChangeListener.h"
+#include "mozilla/dom/SessionStoreChild.h"
+#include "mozilla/dom/SessionStoreUtils.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/dom/UserActivation.h"
+#include "mozilla/dom/ChildSHistory.h"
+#include "mozilla/dom/nsCSPContext.h"
+#include "mozilla/dom/nsHTTPSOnlyUtils.h"
+#include "mozilla/dom/LoadURIOptionsBinding.h"
+#include "mozilla/dom/JSWindowActorChild.h"
+#include "mozilla/dom/DocumentBinding.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/net/DocumentChannel.h"
+#include "mozilla/net/DocumentChannelChild.h"
+#include "mozilla/net/ParentChannelWrapper.h"
+#include "mozilla/net/UrlClassifierFeatureFactory.h"
+#include "ReferrerInfo.h"
+
+#include "nsIAuthPrompt.h"
+#include "nsIAuthPrompt2.h"
+#include "nsICachingChannel.h"
+#include "nsICaptivePortalService.h"
+#include "nsIChannel.h"
+#include "nsIChannelEventSink.h"
+#include "nsIClassOfService.h"
+#include "nsIConsoleReportCollector.h"
+#include "nsIContent.h"
+#include "nsIContentInlines.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIContentViewer.h"
+#include "nsIController.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDocShellTreeOwner.h"
+#include "mozilla/dom/Document.h"
+#include "nsHTMLDocument.h"
+#include "nsIDocumentLoaderFactory.h"
+#include "nsIDOMWindow.h"
+#include "nsIEditingSession.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsIExternalProtocolService.h"
+#include "nsIFormPOSTActionChannel.h"
+#include "nsIFrame.h"
+#include "nsIGlobalObject.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIIDNService.h"
+#include "nsIInputStreamChannel.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsILayoutHistoryState.h"
+#include "nsILoadInfo.h"
+#include "nsILoadURIDelegate.h"
+#include "nsIMultiPartChannel.h"
+#include "nsINestedURI.h"
+#include "nsINetworkPredictor.h"
+#include "nsINode.h"
+#include "nsINSSErrorsService.h"
+#include "nsIObserverService.h"
+#include "nsIOService.h"
+#include "nsIPrincipal.h"
+#include "nsIPrivacyTransitionObserver.h"
+#include "nsIPrompt.h"
+#include "nsIPromptCollection.h"
+#include "nsIPromptFactory.h"
+#include "nsIPublicKeyPinningService.h"
+#include "nsIReflowObserver.h"
+#include "nsIScriptChannel.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIScrollableFrame.h"
+#include "nsIScrollObserver.h"
+#include "nsISupportsPrimitives.h"
+#include "nsISecureBrowserUI.h"
+#include "nsISeekableStream.h"
+#include "nsISelectionDisplay.h"
+#include "nsISHEntry.h"
+#include "nsISiteSecurityService.h"
+#include "nsISocketProvider.h"
+#include "nsIStringBundle.h"
+#include "nsIStructuredCloneContainer.h"
+#include "nsIBrowserChild.h"
+#include "nsITextToSubURI.h"
+#include "nsITimedChannel.h"
+#include "nsITimer.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsIUploadChannel.h"
+#include "nsIURIFixup.h"
+#include "nsIURIMutator.h"
+#include "nsIURILoader.h"
+#include "nsIViewSourceChannel.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsIWebBrowserChromeFocus.h"
+#include "nsIWebBrowserFind.h"
+#include "nsIWebProgress.h"
+#include "nsIWidget.h"
+#include "nsIWindowWatcher.h"
+#include "nsIWritablePropertyBag2.h"
+#include "nsIX509Cert.h"
+#include "nsIXULRuntime.h"
+
+#include "nsCommandManager.h"
+#include "nsPIDOMWindow.h"
+#include "nsPIWindowRoot.h"
+
+#include "IHistory.h"
+#include "IUrlClassifierUITelemetry.h"
+
+#include "nsArray.h"
+#include "nsArrayUtils.h"
+#include "nsCExternalHandlerService.h"
+#include "nsContentDLF.h"
+#include "nsContentPolicyUtils.h" // NS_CheckContentLoadPolicy(...)
+#include "nsContentSecurityManager.h"
+#include "nsContentSecurityUtils.h"
+#include "nsContentUtils.h"
+#include "nsCURILoader.h"
+#include "nsDocShellCID.h"
+#include "nsDocShellEditorData.h"
+#include "nsDocShellEnumerator.h"
+#include "nsDocShellLoadState.h"
+#include "nsDocShellLoadTypes.h"
+#include "nsDOMCID.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsDSURIContentListener.h"
+#include "nsEditingSession.h"
+#include "nsError.h"
+#include "nsEscape.h"
+#include "nsFocusManager.h"
+#include "nsGlobalWindow.h"
+#include "nsJSEnvironment.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsObjectLoadingContent.h"
+#include "nsPingListener.h"
+#include "nsPoint.h"
+#include "nsQueryObject.h"
+#include "nsQueryActor.h"
+#include "nsRect.h"
+#include "nsRefreshTimer.h"
+#include "nsSandboxFlags.h"
+#include "nsSHEntry.h"
+#include "nsSHistory.h"
+#include "nsSHEntry.h"
+#include "nsStructuredCloneContainer.h"
+#include "nsSubDocumentFrame.h"
+#include "nsURILoader.h"
+#include "nsURLHelper.h"
+#include "nsView.h"
+#include "nsViewManager.h"
+#include "nsViewSourceHandler.h"
+#include "nsWebBrowserFind.h"
+#include "nsWhitespaceTokenizer.h"
+#include "nsWidgetsCID.h"
+#include "nsXULAppAPI.h"
+
+#include "ThirdPartyUtil.h"
+#include "GeckoProfiler.h"
+#include "mozilla/NullPrincipal.h"
+#include "Navigator.h"
+#include "prenv.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "sslerr.h"
+#include "mozpkix/pkix.h"
+#include "NSSErrorsService.h"
+
+#include "timeline/JavascriptTimelineMarker.h"
+#include "nsDocShellTelemetryUtils.h"
+
+#ifdef MOZ_PLACES
+# include "nsIFaviconService.h"
+# include "mozIPlacesPendingOperation.h"
+#endif
+
+#if NS_PRINT_PREVIEW
+# include "nsIDocumentViewerPrint.h"
+# include "nsIWebBrowserPrint.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::net;
+
+using mozilla::ipc::Endpoint;
+
+// Threshold value in ms for META refresh based redirects
+#define REFRESH_REDIRECT_TIMER 15000
+
+static mozilla::LazyLogModule gCharsetMenuLog("CharsetMenu");
+
+#define LOGCHARSETMENU(args) \
+ MOZ_LOG(gCharsetMenuLog, mozilla::LogLevel::Debug, args)
+
+#ifdef DEBUG
+unsigned long nsDocShell::gNumberOfDocShells = 0;
+static uint64_t gDocshellIDCounter = 0;
+
+static mozilla::LazyLogModule gDocShellLog("nsDocShell");
+static mozilla::LazyLogModule gDocShellAndDOMWindowLeakLogging(
+ "DocShellAndDOMWindowLeak");
+#endif
+static mozilla::LazyLogModule gDocShellLeakLog("nsDocShellLeak");
+extern mozilla::LazyLogModule gPageCacheLog;
+mozilla::LazyLogModule gSHLog("SessionHistory");
+extern mozilla::LazyLogModule gSHIPBFCacheLog;
+
+const char kAppstringsBundleURL[] =
+ "chrome://global/locale/appstrings.properties";
+
+static bool IsTopLevelDoc(BrowsingContext* aBrowsingContext,
+ nsILoadInfo* aLoadInfo) {
+ MOZ_ASSERT(aBrowsingContext);
+ MOZ_ASSERT(aLoadInfo);
+
+ if (aLoadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_DOCUMENT) {
+ return false;
+ }
+
+ return aBrowsingContext->IsTopContent();
+}
+
+// True if loading for top level document loading in active tab.
+static bool IsUrgentStart(BrowsingContext* aBrowsingContext,
+ nsILoadInfo* aLoadInfo, uint32_t aLoadType) {
+ MOZ_ASSERT(aBrowsingContext);
+ MOZ_ASSERT(aLoadInfo);
+
+ if (!IsTopLevelDoc(aBrowsingContext, aLoadInfo)) {
+ return false;
+ }
+
+ if (aLoadType &
+ (nsIDocShell::LOAD_CMD_NORMAL | nsIDocShell::LOAD_CMD_HISTORY)) {
+ return true;
+ }
+
+ return aBrowsingContext->IsActive();
+}
+
+nsDocShell::nsDocShell(BrowsingContext* aBrowsingContext,
+ uint64_t aContentWindowID)
+ : nsDocLoader(true),
+ mContentWindowID(aContentWindowID),
+ mBrowsingContext(aBrowsingContext),
+ mParentCharset(nullptr),
+ mTreeOwner(nullptr),
+ mScrollbarPref(ScrollbarPreference::Auto),
+ mCharsetReloadState(eCharsetReloadInit),
+ mParentCharsetSource(0),
+ mFrameMargins(-1, -1),
+ mItemType(aBrowsingContext->IsContent() ? typeContent : typeChrome),
+ mPreviousEntryIndex(-1),
+ mLoadedEntryIndex(-1),
+ mBusyFlags(BUSY_FLAGS_NONE),
+ mAppType(nsIDocShell::APP_TYPE_UNKNOWN),
+ mLoadType(0),
+ mFailedLoadType(0),
+ mJSRunToCompletionDepth(0),
+ mMetaViewportOverride(nsIDocShell::META_VIEWPORT_OVERRIDE_NONE),
+ mChannelToDisconnectOnPageHide(0),
+ mCreatingDocument(false),
+#ifdef DEBUG
+ mInEnsureScriptEnv(false),
+#endif
+ mInitialized(false),
+ mAllowSubframes(true),
+ mAllowMetaRedirects(true),
+ mAllowImages(true),
+ mAllowMedia(true),
+ mAllowDNSPrefetch(true),
+ mAllowWindowControl(true),
+ mCSSErrorReportingEnabled(false),
+ mAllowAuth(mItemType == typeContent),
+ mAllowKeywordFixup(false),
+ mDisableMetaRefreshWhenInactive(false),
+ mWindowDraggingAllowed(false),
+ mInFrameSwap(false),
+ mFiredUnloadEvent(false),
+ mEODForCurrentDocument(false),
+ mURIResultedInDocument(false),
+ mIsBeingDestroyed(false),
+ mIsExecutingOnLoadHandler(false),
+ mSavingOldViewer(false),
+ mInvisible(false),
+ mHasLoadedNonBlankURI(false),
+ mBlankTiming(false),
+ mTitleValidForCurrentURI(false),
+ mWillChangeProcess(false),
+ mIsNavigating(false),
+ mForcedAutodetection(false),
+ mCheckingSessionHistory(false),
+ mNeedToReportActiveAfterLoadingBecomesActive(false) {
+ // If no outer window ID was provided, generate a new one.
+ if (aContentWindowID == 0) {
+ mContentWindowID = nsContentUtils::GenerateWindowId();
+ }
+
+ MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, ("DOCSHELL %p created\n", this));
+
+#ifdef DEBUG
+ mDocShellID = gDocshellIDCounter++;
+ // We're counting the number of |nsDocShells| to help find leaks
+ ++gNumberOfDocShells;
+ MOZ_LOG(gDocShellAndDOMWindowLeakLogging, LogLevel::Info,
+ ("++DOCSHELL %p == %ld [pid = %d] [id = %" PRIu64 "]\n", (void*)this,
+ gNumberOfDocShells, getpid(), mDocShellID));
+#endif
+}
+
+nsDocShell::~nsDocShell() {
+ MOZ_ASSERT(!mObserved);
+
+ // Avoid notifying observers while we're in the dtor.
+ mIsBeingDestroyed = true;
+
+ Destroy();
+
+ if (mContentViewer) {
+ mContentViewer->Close(nullptr);
+ mContentViewer->Destroy();
+ mContentViewer = nullptr;
+ }
+
+ MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, ("DOCSHELL %p destroyed\n", this));
+
+#ifdef DEBUG
+ if (MOZ_LOG_TEST(gDocShellAndDOMWindowLeakLogging, LogLevel::Info)) {
+ nsAutoCString url;
+ if (mLastOpenedURI) {
+ url = mLastOpenedURI->GetSpecOrDefault();
+
+ // Data URLs can be very long, so truncate to avoid flooding the log.
+ const uint32_t maxURLLength = 1000;
+ if (url.Length() > maxURLLength) {
+ url.Truncate(maxURLLength);
+ }
+ }
+
+ // We're counting the number of |nsDocShells| to help find leaks
+ --gNumberOfDocShells;
+ MOZ_LOG(
+ gDocShellAndDOMWindowLeakLogging, LogLevel::Info,
+ ("--DOCSHELL %p == %ld [pid = %d] [id = %" PRIu64 "] [url = %s]\n",
+ (void*)this, gNumberOfDocShells, getpid(), mDocShellID, url.get()));
+ }
+#endif
+}
+
+bool nsDocShell::Initialize() {
+ if (mInitialized) {
+ // We've already been initialized.
+ return true;
+ }
+
+ NS_ASSERTION(mItemType == typeContent || mItemType == typeChrome,
+ "Unexpected item type in docshell");
+
+ NS_ENSURE_TRUE(Preferences::GetRootBranch(), false);
+ mInitialized = true;
+
+ mDisableMetaRefreshWhenInactive =
+ Preferences::GetBool("browser.meta_refresh_when_inactive.disabled",
+ mDisableMetaRefreshWhenInactive);
+
+ if (nsCOMPtr<nsIObserverService> serv = services::GetObserverService()) {
+ const char* msg = mItemType == typeContent ? NS_WEBNAVIGATION_CREATE
+ : NS_CHROME_WEBNAVIGATION_CREATE;
+ serv->NotifyWhenScriptSafe(GetAsSupports(this), msg, nullptr);
+ }
+
+ return true;
+}
+
+/* static */
+already_AddRefed<nsDocShell> nsDocShell::Create(
+ BrowsingContext* aBrowsingContext, uint64_t aContentWindowID) {
+ MOZ_ASSERT(aBrowsingContext, "DocShell without a BrowsingContext!");
+
+ nsresult rv;
+ RefPtr<nsDocShell> ds = new nsDocShell(aBrowsingContext, aContentWindowID);
+
+ // Initialize the underlying nsDocLoader.
+ rv = ds->nsDocLoader::InitWithBrowsingContext(aBrowsingContext);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ // Create our ContentListener
+ ds->mContentListener = new nsDSURIContentListener(ds);
+
+ // We enable if we're in the parent process in order to support non-e10s
+ // configurations.
+ // Note: This check is duplicated in SharedWorkerInterfaceRequestor's
+ // constructor.
+ if (XRE_IsParentProcess()) {
+ ds->mInterceptController = new ServiceWorkerInterceptController();
+ }
+
+ // We want to hold a strong ref to the loadgroup, so it better hold a weak
+ // ref to us... use an InterfaceRequestorProxy to do this.
+ nsCOMPtr<nsIInterfaceRequestor> proxy = new InterfaceRequestorProxy(ds);
+ ds->mLoadGroup->SetNotificationCallbacks(proxy);
+
+ // XXX(nika): We have our BrowsingContext, so we might be able to skip this.
+ // It could be nice to directly set up our DocLoader tree?
+ rv = nsDocLoader::AddDocLoaderAsChildOfRoot(ds);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ // Add |ds| as a progress listener to itself. A little weird, but simpler
+ // than reproducing all the listener-notification logic in overrides of the
+ // various methods via which nsDocLoader can be notified. Note that this
+ // holds an nsWeakPtr to |ds|, so it's ok.
+ rv = ds->AddProgressListener(ds, nsIWebProgress::NOTIFY_STATE_DOCUMENT |
+ nsIWebProgress::NOTIFY_STATE_NETWORK |
+ nsIWebProgress::NOTIFY_LOCATION);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ // If our BrowsingContext has private browsing enabled, update the number of
+ // private browsing docshells.
+ if (aBrowsingContext->UsePrivateBrowsing()) {
+ ds->NotifyPrivateBrowsingChanged();
+ }
+
+ // If our parent window is present in this process, set up our parent now.
+ RefPtr<WindowContext> parentWC = aBrowsingContext->GetParentWindowContext();
+ if (parentWC && parentWC->IsInProcess()) {
+ // If we don't have a parent element anymore, we can't finish this load!
+ // How'd we get here?
+ RefPtr<Element> parentElement = aBrowsingContext->GetEmbedderElement();
+ if (!parentElement) {
+ MOZ_ASSERT_UNREACHABLE("nsDocShell::Create() - !parentElement");
+ return nullptr;
+ }
+
+ // We have an in-process parent window, but don't have a parent nsDocShell?
+ // How'd we get here!
+ nsCOMPtr<nsIDocShell> parentShell =
+ parentElement->OwnerDoc()->GetDocShell();
+ if (!parentShell) {
+ MOZ_ASSERT_UNREACHABLE("nsDocShell::Create() - !parentShell");
+ return nullptr;
+ }
+ parentShell->AddChild(ds);
+ }
+
+ // Make |ds| the primary DocShell for the given context.
+ aBrowsingContext->SetDocShell(ds);
+
+ // Set |ds| default load flags on load group.
+ ds->SetLoadGroupDefaultLoadFlags(aBrowsingContext->GetDefaultLoadFlags());
+
+ if (XRE_IsParentProcess()) {
+ aBrowsingContext->Canonical()->MaybeAddAsProgressListener(ds);
+ }
+
+ return ds.forget();
+}
+
+void nsDocShell::DestroyChildren() {
+ for (auto* child : mChildList.ForwardRange()) {
+ nsCOMPtr<nsIDocShellTreeItem> shell = do_QueryObject(child);
+ NS_ASSERTION(shell, "docshell has null child");
+
+ if (shell) {
+ shell->SetTreeOwner(nullptr);
+ }
+ }
+
+ nsDocLoader::DestroyChildren();
+}
+
+NS_IMPL_CYCLE_COLLECTION_WEAK_PTR_INHERITED(nsDocShell, nsDocLoader,
+ mScriptGlobal, mInitialClientSource,
+ mBrowsingContext,
+ mChromeEventHandler)
+
+NS_IMPL_ADDREF_INHERITED(nsDocShell, nsDocLoader)
+NS_IMPL_RELEASE_INHERITED(nsDocShell, nsDocLoader)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDocShell)
+ NS_INTERFACE_MAP_ENTRY(nsIDocShell)
+ NS_INTERFACE_MAP_ENTRY(nsIDocShellTreeItem)
+ NS_INTERFACE_MAP_ENTRY(nsIWebNavigation)
+ NS_INTERFACE_MAP_ENTRY(nsIBaseWindow)
+ NS_INTERFACE_MAP_ENTRY(nsIRefreshURI)
+ NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsIWebPageDescriptor)
+ NS_INTERFACE_MAP_ENTRY(nsIAuthPromptProvider)
+ NS_INTERFACE_MAP_ENTRY(nsILoadContext)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsINetworkInterceptController,
+ mInterceptController)
+NS_INTERFACE_MAP_END_INHERITING(nsDocLoader)
+
+NS_IMETHODIMP
+nsDocShell::GetInterface(const nsIID& aIID, void** aSink) {
+ MOZ_ASSERT(aSink, "null out param");
+
+ *aSink = nullptr;
+
+ if (aIID.Equals(NS_GET_IID(nsICommandManager))) {
+ NS_ENSURE_SUCCESS(EnsureCommandHandler(), NS_ERROR_FAILURE);
+ *aSink = static_cast<nsICommandManager*>(mCommandManager.get());
+ } else if (aIID.Equals(NS_GET_IID(nsIURIContentListener))) {
+ *aSink = mContentListener;
+ } else if ((aIID.Equals(NS_GET_IID(nsIScriptGlobalObject)) ||
+ aIID.Equals(NS_GET_IID(nsIGlobalObject)) ||
+ aIID.Equals(NS_GET_IID(nsPIDOMWindowOuter)) ||
+ aIID.Equals(NS_GET_IID(mozIDOMWindowProxy)) ||
+ aIID.Equals(NS_GET_IID(nsIDOMWindow))) &&
+ NS_SUCCEEDED(EnsureScriptEnvironment())) {
+ return mScriptGlobal->QueryInterface(aIID, aSink);
+ } else if (aIID.Equals(NS_GET_IID(Document)) &&
+ NS_SUCCEEDED(EnsureContentViewer())) {
+ RefPtr<Document> doc = mContentViewer->GetDocument();
+ doc.forget(aSink);
+ return *aSink ? NS_OK : NS_NOINTERFACE;
+ } else if (aIID.Equals(NS_GET_IID(nsIPrompt)) &&
+ NS_SUCCEEDED(EnsureScriptEnvironment())) {
+ nsresult rv;
+ nsCOMPtr<nsIWindowWatcher> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the an auth prompter for our window so that the parenting
+ // of the dialogs works as it should when using tabs.
+ nsIPrompt* prompt;
+ rv = wwatch->GetNewPrompter(mScriptGlobal, &prompt);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aSink = prompt;
+ return NS_OK;
+ } else if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
+ aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
+ return NS_SUCCEEDED(GetAuthPrompt(PROMPT_NORMAL, aIID, aSink))
+ ? NS_OK
+ : NS_NOINTERFACE;
+ } else if (aIID.Equals(NS_GET_IID(nsISHistory))) {
+ // This is deprecated, you should instead directly get
+ // ChildSHistory from the browsing context.
+ MOZ_DIAGNOSTIC_ASSERT(
+ false, "Do not try to get a nsISHistory interface from nsIDocShell");
+ return NS_NOINTERFACE;
+ } else if (aIID.Equals(NS_GET_IID(nsIWebBrowserFind))) {
+ nsresult rv = EnsureFind();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ *aSink = mFind;
+ NS_ADDREF((nsISupports*)*aSink);
+ return NS_OK;
+ } else if (aIID.Equals(NS_GET_IID(nsISelectionDisplay))) {
+ if (PresShell* presShell = GetPresShell()) {
+ return presShell->QueryInterface(aIID, aSink);
+ }
+ } else if (aIID.Equals(NS_GET_IID(nsIDocShellTreeOwner))) {
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+ nsresult rv = GetTreeOwner(getter_AddRefs(treeOwner));
+ if (NS_SUCCEEDED(rv) && treeOwner) {
+ return treeOwner->QueryInterface(aIID, aSink);
+ }
+ } else if (aIID.Equals(NS_GET_IID(nsIBrowserChild))) {
+ *aSink = GetBrowserChild().take();
+ return *aSink ? NS_OK : NS_ERROR_FAILURE;
+ } else {
+ return nsDocLoader::GetInterface(aIID, aSink);
+ }
+
+ NS_IF_ADDREF(((nsISupports*)*aSink));
+ return *aSink ? NS_OK : NS_NOINTERFACE;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetCancelContentJSEpoch(int32_t aEpoch) {
+ // Note: this gets called fairly early (before a pageload actually starts).
+ // We could probably defer this even longer.
+ nsCOMPtr<nsIBrowserChild> browserChild = GetBrowserChild();
+ static_cast<BrowserChild*>(browserChild.get())
+ ->SetCancelContentJSEpoch(aEpoch);
+ return NS_OK;
+}
+
+nsresult nsDocShell::CheckDisallowedJavascriptLoad(
+ nsDocShellLoadState* aLoadState) {
+ if (!net::SchemeIsJavascript(aLoadState->URI())) {
+ return NS_OK;
+ }
+
+ if (nsCOMPtr<nsIPrincipal> targetPrincipal =
+ GetInheritedPrincipal(/* aConsiderCurrentDocument */ true)) {
+ if (!aLoadState->TriggeringPrincipal()->Subsumes(targetPrincipal)) {
+ return NS_ERROR_DOM_BAD_CROSS_ORIGIN_URI;
+ }
+ return NS_OK;
+ }
+ return NS_ERROR_DOM_BAD_CROSS_ORIGIN_URI;
+}
+
+NS_IMETHODIMP
+nsDocShell::LoadURI(nsDocShellLoadState* aLoadState, bool aSetNavigating) {
+ return LoadURI(aLoadState, aSetNavigating, false);
+}
+
+nsresult nsDocShell::LoadURI(nsDocShellLoadState* aLoadState,
+ bool aSetNavigating,
+ bool aContinueHandlingSubframeHistory) {
+ MOZ_ASSERT(aLoadState, "Must have a valid load state!");
+ // NOTE: This comparison between what appears to be internal/external load
+ // flags is intentional, as it's ensuring that the caller isn't using any of
+ // the flags reserved for implementations by the `nsIWebNavigation` interface.
+ // In the future, this check may be dropped.
+ MOZ_ASSERT(
+ (aLoadState->LoadFlags() & INTERNAL_LOAD_FLAGS_LOADURI_SETUP_FLAGS) == 0,
+ "Should not have these flags set");
+ MOZ_ASSERT(aLoadState->TargetBrowsingContext().IsNull(),
+ "Targeting doesn't occur until InternalLoad");
+
+ if (!aLoadState->TriggeringPrincipal()) {
+ MOZ_ASSERT(false, "LoadURI must have a triggering principal");
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_TRY(CheckDisallowedJavascriptLoad(aLoadState));
+
+ bool oldIsNavigating = mIsNavigating;
+ auto cleanupIsNavigating =
+ MakeScopeExit([&]() { mIsNavigating = oldIsNavigating; });
+ if (aSetNavigating) {
+ mIsNavigating = true;
+ }
+
+ PopupBlocker::PopupControlState popupState = PopupBlocker::openOverridden;
+ if (aLoadState->HasLoadFlags(LOAD_FLAGS_ALLOW_POPUPS)) {
+ popupState = PopupBlocker::openAllowed;
+ // If we allow popups as part of the navigation, ensure we fake a user
+ // interaction, so that popups can, in fact, be allowed to open.
+ if (WindowContext* wc = mBrowsingContext->GetCurrentWindowContext()) {
+ wc->NotifyUserGestureActivation();
+ }
+ }
+
+ AutoPopupStatePusher statePusher(popupState);
+
+ if (aLoadState->GetCancelContentJSEpoch().isSome()) {
+ SetCancelContentJSEpoch(*aLoadState->GetCancelContentJSEpoch());
+ }
+
+ // Note: we allow loads to get through here even if mFiredUnloadEvent is
+ // true; that case will get handled in LoadInternal or LoadHistoryEntry,
+ // so we pass false as the second parameter to IsNavigationAllowed.
+ // However, we don't allow the page to change location *in the middle of*
+ // firing beforeunload, so we do need to check if *beforeunload* is currently
+ // firing, so we call IsNavigationAllowed rather than just IsPrintingOrPP.
+ if (!IsNavigationAllowed(true, false)) {
+ return NS_OK; // JS may not handle returning of an error code
+ }
+
+ nsLoadFlags defaultLoadFlags = mBrowsingContext->GetDefaultLoadFlags();
+ if (aLoadState->HasLoadFlags(LOAD_FLAGS_FORCE_TRR)) {
+ defaultLoadFlags |= nsIRequest::LOAD_TRR_ONLY_MODE;
+ } else if (aLoadState->HasLoadFlags(LOAD_FLAGS_DISABLE_TRR)) {
+ defaultLoadFlags |= nsIRequest::LOAD_TRR_DISABLED_MODE;
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(mBrowsingContext->SetDefaultLoadFlags(defaultLoadFlags));
+
+ if (!StartupTimeline::HasRecord(StartupTimeline::FIRST_LOAD_URI) &&
+ mItemType == typeContent && !NS_IsAboutBlank(aLoadState->URI())) {
+ StartupTimeline::RecordOnce(StartupTimeline::FIRST_LOAD_URI);
+ }
+
+ // LoadType used to be set to a default value here, if no LoadInfo/LoadState
+ // object was passed in. That functionality has been removed as of bug
+ // 1492648. LoadType should now be set up by the caller at the time they
+ // create their nsDocShellLoadState object to pass into LoadURI.
+
+ MOZ_LOG(
+ gDocShellLeakLog, LogLevel::Debug,
+ ("nsDocShell[%p]: loading %s with flags 0x%08x", this,
+ aLoadState->URI()->GetSpecOrDefault().get(), aLoadState->LoadFlags()));
+
+ if ((!aLoadState->LoadIsFromSessionHistory() &&
+ !LOAD_TYPE_HAS_FLAGS(aLoadState->LoadType(),
+ LOAD_FLAGS_REPLACE_HISTORY)) ||
+ aContinueHandlingSubframeHistory) {
+ // This is possibly a subframe, so handle it accordingly.
+ //
+ // If history exists, it will be loaded into the aLoadState object, and the
+ // LoadType will be changed.
+ if (MaybeHandleSubframeHistory(aLoadState,
+ aContinueHandlingSubframeHistory)) {
+ // MaybeHandleSubframeHistory returns true if we need to continue loading
+ // asynchronously.
+ return NS_OK;
+ }
+ }
+
+ if (aLoadState->LoadIsFromSessionHistory()) {
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell[%p]: loading from session history", this));
+
+ if (!mozilla::SessionHistoryInParent()) {
+ nsCOMPtr<nsISHEntry> entry = aLoadState->SHEntry();
+ return LoadHistoryEntry(entry, aLoadState->LoadType(),
+ aLoadState->HasValidUserGestureActivation());
+ }
+
+ // FIXME Null check aLoadState->GetLoadingSessionHistoryInfo()?
+ return LoadHistoryEntry(*aLoadState->GetLoadingSessionHistoryInfo(),
+ aLoadState->LoadType(),
+ aLoadState->HasValidUserGestureActivation());
+ }
+
+ // On history navigation via Back/Forward buttons, don't execute
+ // automatic JavaScript redirection such as |location.href = ...| or
+ // |window.open()|
+ //
+ // LOAD_NORMAL: window.open(...) etc.
+ // LOAD_STOP_CONTENT: location.href = ..., location.assign(...)
+ if ((aLoadState->LoadType() == LOAD_NORMAL ||
+ aLoadState->LoadType() == LOAD_STOP_CONTENT) &&
+ ShouldBlockLoadingForBackButton()) {
+ return NS_OK;
+ }
+
+ BrowsingContext::Type bcType = mBrowsingContext->GetType();
+
+ // Set up the inheriting principal in LoadState.
+ nsresult rv = aLoadState->SetupInheritingPrincipal(
+ bcType, mBrowsingContext->OriginAttributesRef());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aLoadState->SetupTriggeringPrincipal(
+ mBrowsingContext->OriginAttributesRef());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aLoadState->CalculateLoadURIFlags();
+
+ MOZ_ASSERT(aLoadState->TypeHint().IsVoid(),
+ "Typehint should be null when calling InternalLoad from LoadURI");
+ MOZ_ASSERT(aLoadState->FileName().IsVoid(),
+ "FileName should be null when calling InternalLoad from LoadURI");
+ MOZ_ASSERT(!aLoadState->LoadIsFromSessionHistory(),
+ "Shouldn't be loading from an entry when calling InternalLoad "
+ "from LoadURI");
+
+ // If we have a system triggering principal, we can assume that this load was
+ // triggered by some UI in the browser chrome, such as the URL bar or
+ // bookmark bar. This should count as a user interaction for the current sh
+ // entry, so that the user may navigate back to the current entry, from the
+ // entry that is going to be added as part of this load.
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal =
+ aLoadState->TriggeringPrincipal();
+ if (triggeringPrincipal && triggeringPrincipal->IsSystemPrincipal()) {
+ if (mozilla::SessionHistoryInParent()) {
+ WindowContext* topWc = mBrowsingContext->GetTopWindowContext();
+ if (topWc && !topWc->IsDiscarded()) {
+ MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(true));
+ }
+ } else {
+ bool oshe = false;
+ nsCOMPtr<nsISHEntry> currentSHEntry;
+ GetCurrentSHEntry(getter_AddRefs(currentSHEntry), &oshe);
+ if (currentSHEntry) {
+ currentSHEntry->SetHasUserInteraction(true);
+ }
+ }
+ }
+
+ rv = InternalLoad(aLoadState);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aLoadState->GetOriginalURIString().isSome()) {
+ // Save URI string in case it's needed later when
+ // sending to search engine service in EndPageLoad()
+ mOriginalUriString = *aLoadState->GetOriginalURIString();
+ }
+
+ return NS_OK;
+}
+
+bool nsDocShell::IsLoadingFromSessionHistory() {
+ return mActiveEntryIsLoadingFromSessionHistory;
+}
+
+// StopDetector is modeled similarly to OnloadBlocker; it is a rather
+// dummy nsIRequest implementation which can be added to an nsILoadGroup to
+// detect Cancel calls.
+class StopDetector final : public nsIRequest {
+ public:
+ StopDetector() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUEST
+
+ bool Canceled() { return mCanceled; }
+
+ private:
+ ~StopDetector() = default;
+
+ bool mCanceled = false;
+};
+
+NS_IMPL_ISUPPORTS(StopDetector, nsIRequest)
+
+NS_IMETHODIMP
+StopDetector::GetName(nsACString& aResult) {
+ aResult.AssignLiteral("about:stop-detector");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StopDetector::IsPending(bool* aRetVal) {
+ *aRetVal = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StopDetector::GetStatus(nsresult* aStatus) {
+ *aStatus = NS_OK;
+ return NS_OK;
+}
+
+NS_IMETHODIMP StopDetector::SetCanceledReason(const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP StopDetector::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP StopDetector::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+StopDetector::Cancel(nsresult aStatus) {
+ mCanceled = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StopDetector::Suspend(void) { return NS_OK; }
+NS_IMETHODIMP
+StopDetector::Resume(void) { return NS_OK; }
+
+NS_IMETHODIMP
+StopDetector::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ *aLoadGroup = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StopDetector::SetLoadGroup(nsILoadGroup* aLoadGroup) { return NS_OK; }
+
+NS_IMETHODIMP
+StopDetector::GetLoadFlags(nsLoadFlags* aLoadFlags) {
+ *aLoadFlags = nsIRequest::LOAD_NORMAL;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StopDetector::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ return GetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+StopDetector::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return SetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+StopDetector::SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; }
+
+bool nsDocShell::MaybeHandleSubframeHistory(
+ nsDocShellLoadState* aLoadState, bool aContinueHandlingSubframeHistory) {
+ // First, verify if this is a subframe.
+ // Note, it is ok to rely on docshell here and not browsing context since when
+ // an iframe is created, it has first in-process docshell.
+ nsCOMPtr<nsIDocShellTreeItem> parentAsItem;
+ GetInProcessSameTypeParent(getter_AddRefs(parentAsItem));
+ nsCOMPtr<nsIDocShell> parentDS(do_QueryInterface(parentAsItem));
+
+ if (!parentDS || parentDS == static_cast<nsIDocShell*>(this)) {
+ if (mBrowsingContext && mBrowsingContext->IsTop()) {
+ // This is the root docshell. If we got here while
+ // executing an onLoad Handler,this load will not go
+ // into session history.
+ // XXX Why is this code in a method which deals with iframes!
+ bool inOnLoadHandler = false;
+ GetIsExecutingOnLoadHandler(&inOnLoadHandler);
+ if (inOnLoadHandler) {
+ aLoadState->SetLoadType(LOAD_NORMAL_REPLACE);
+ }
+ }
+ return false;
+ }
+
+ /* OK. It is a subframe. Checkout the parent's loadtype. If the parent was
+ * loaded through a history mechanism, then get the SH entry for the child
+ * from the parent. This is done to restore frameset navigation while going
+ * back/forward. If the parent was loaded through any other loadType, set the
+ * child's loadType too accordingly, so that session history does not get
+ * confused.
+ */
+
+ // Get the parent's load type
+ uint32_t parentLoadType;
+ parentDS->GetLoadType(&parentLoadType);
+
+ if (!aContinueHandlingSubframeHistory) {
+ if (mozilla::SessionHistoryInParent()) {
+ if (nsDocShell::Cast(parentDS.get())->IsLoadingFromSessionHistory() &&
+ !GetCreatedDynamically()) {
+ if (XRE_IsContentProcess()) {
+ dom::ContentChild* contentChild = dom::ContentChild::GetSingleton();
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ GetLoadGroup(getter_AddRefs(loadGroup));
+ if (contentChild && loadGroup && !mCheckingSessionHistory) {
+ RefPtr<Document> parentDoc = parentDS->GetDocument();
+ parentDoc->BlockOnload();
+ RefPtr<BrowsingContext> browsingContext = mBrowsingContext;
+ Maybe<uint64_t> currentLoadIdentifier =
+ mBrowsingContext->GetCurrentLoadIdentifier();
+ RefPtr<nsDocShellLoadState> loadState = aLoadState;
+ bool isNavigating = mIsNavigating;
+ RefPtr<StopDetector> stopDetector = new StopDetector();
+ loadGroup->AddRequest(stopDetector, nullptr);
+ // Need to set mCheckingSessionHistory so that
+ // GetIsAttemptingToNavigate() returns true.
+ mCheckingSessionHistory = true;
+
+ auto resolve =
+ [currentLoadIdentifier, browsingContext, parentDoc, loadState,
+ isNavigating, loadGroup, stopDetector](
+ mozilla::Maybe<LoadingSessionHistoryInfo>&& aResult) {
+ RefPtr<nsDocShell> docShell =
+ static_cast<nsDocShell*>(browsingContext->GetDocShell());
+ auto unblockParent = MakeScopeExit(
+ [loadGroup, stopDetector, parentDoc, docShell]() {
+ if (docShell) {
+ docShell->mCheckingSessionHistory = false;
+ }
+ loadGroup->RemoveRequest(stopDetector, nullptr, NS_OK);
+ parentDoc->UnblockOnload(false);
+ });
+
+ if (!docShell || !docShell->mCheckingSessionHistory) {
+ return;
+ }
+
+ if (stopDetector->Canceled()) {
+ return;
+ }
+ if (currentLoadIdentifier ==
+ browsingContext->GetCurrentLoadIdentifier() &&
+ aResult.isSome()) {
+ loadState->SetLoadingSessionHistoryInfo(aResult.value());
+ // This is an initial subframe load from the session
+ // history, index doesn't need to be updated.
+ loadState->SetLoadIsFromSessionHistory(0, false);
+ }
+
+ // We got the results back from the parent process, call
+ // LoadURI again with the possibly updated data.
+ docShell->LoadURI(loadState, isNavigating, true);
+ };
+ auto reject = [loadGroup, stopDetector, browsingContext,
+ parentDoc](mozilla::ipc::ResponseRejectReason) {
+ RefPtr<nsDocShell> docShell =
+ static_cast<nsDocShell*>(browsingContext->GetDocShell());
+ if (docShell) {
+ docShell->mCheckingSessionHistory = false;
+ }
+ // In practise reject shouldn't be called ever.
+ loadGroup->RemoveRequest(stopDetector, nullptr, NS_OK);
+ parentDoc->UnblockOnload(false);
+ };
+ contentChild->SendGetLoadingSessionHistoryInfoFromParent(
+ mBrowsingContext, std::move(resolve), std::move(reject));
+ return true;
+ }
+ } else {
+ Maybe<LoadingSessionHistoryInfo> info;
+ mBrowsingContext->Canonical()->GetLoadingSessionHistoryInfoFromParent(
+ info);
+ if (info.isSome()) {
+ aLoadState->SetLoadingSessionHistoryInfo(info.value());
+ // This is an initial subframe load from the session
+ // history, index doesn't need to be updated.
+ aLoadState->SetLoadIsFromSessionHistory(0, false);
+ }
+ }
+ }
+ } else {
+ // Get the ShEntry for the child from the parent
+ nsCOMPtr<nsISHEntry> currentSH;
+ bool oshe = false;
+ parentDS->GetCurrentSHEntry(getter_AddRefs(currentSH), &oshe);
+ bool dynamicallyAddedChild = GetCreatedDynamically();
+
+ if (!dynamicallyAddedChild && !oshe && currentSH) {
+ // Only use the old SHEntry, if we're sure enough that
+ // it wasn't originally for some other frame.
+ nsCOMPtr<nsISHEntry> shEntry;
+ currentSH->GetChildSHEntryIfHasNoDynamicallyAddedChild(
+ mBrowsingContext->ChildOffset(), getter_AddRefs(shEntry));
+ if (shEntry) {
+ aLoadState->SetSHEntry(shEntry);
+ }
+ }
+ }
+ }
+
+ // Make some decisions on the child frame's loadType based on the
+ // parent's loadType, if the subframe hasn't loaded anything into it.
+ //
+ // In some cases privileged scripts may try to get the DOMWindow
+ // reference of this docshell before the loading starts, causing the
+ // initial about:blank content viewer being created and mCurrentURI being
+ // set. To handle this case we check if mCurrentURI is about:blank and
+ // currentSHEntry is null.
+ bool oshe = false;
+ nsCOMPtr<nsISHEntry> currentChildEntry;
+ GetCurrentSHEntry(getter_AddRefs(currentChildEntry), &oshe);
+
+ if (mCurrentURI && (!NS_IsAboutBlank(mCurrentURI) || currentChildEntry ||
+ mLoadingEntry || mActiveEntry)) {
+ // This is a pre-existing subframe. If
+ // 1. The load of this frame was not originally initiated by session
+ // history directly (i.e. (!shEntry) condition succeeded, but it can
+ // still be a history load on parent which causes this frame being
+ // loaded), which we checked with the above assert, and
+ // 2. mCurrentURI is not null, nor the initial about:blank,
+ // it is possible that a parent's onLoadHandler or even self's
+ // onLoadHandler is loading a new page in this child. Check parent's and
+ // self's busy flag and if it is set, we don't want this onLoadHandler
+ // load to get in to session history.
+ BusyFlags parentBusy = parentDS->GetBusyFlags();
+ BusyFlags selfBusy = GetBusyFlags();
+
+ if (parentBusy & BUSY_FLAGS_BUSY || selfBusy & BUSY_FLAGS_BUSY) {
+ aLoadState->SetLoadType(LOAD_NORMAL_REPLACE);
+ aLoadState->ClearLoadIsFromSessionHistory();
+ }
+ return false;
+ }
+
+ // This is a newly created frame. Check for exception cases first.
+ // By default the subframe will inherit the parent's loadType.
+ if (aLoadState->LoadIsFromSessionHistory() &&
+ (parentLoadType == LOAD_NORMAL || parentLoadType == LOAD_LINK)) {
+ // The parent was loaded normally. In this case, this *brand new*
+ // child really shouldn't have a SHEntry. If it does, it could be
+ // because the parent is replacing an existing frame with a new frame,
+ // in the onLoadHandler. We don't want this url to get into session
+ // history. Clear off shEntry, and set load type to
+ // LOAD_BYPASS_HISTORY.
+ bool inOnLoadHandler = false;
+ parentDS->GetIsExecutingOnLoadHandler(&inOnLoadHandler);
+ if (inOnLoadHandler) {
+ aLoadState->SetLoadType(LOAD_NORMAL_REPLACE);
+ aLoadState->ClearLoadIsFromSessionHistory();
+ }
+ } else if (parentLoadType == LOAD_REFRESH) {
+ // Clear shEntry. For refresh loads, we have to load
+ // what comes through the pipe, not what's in history.
+ aLoadState->ClearLoadIsFromSessionHistory();
+ } else if ((parentLoadType == LOAD_BYPASS_HISTORY) ||
+ (aLoadState->LoadIsFromSessionHistory() &&
+ ((parentLoadType & LOAD_CMD_HISTORY) ||
+ (parentLoadType == LOAD_RELOAD_NORMAL) ||
+ (parentLoadType == LOAD_RELOAD_CHARSET_CHANGE) ||
+ (parentLoadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE) ||
+ (parentLoadType ==
+ LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE)))) {
+ // If the parent url, bypassed history or was loaded from
+ // history, pass on the parent's loadType to the new child
+ // frame too, so that the child frame will also
+ // avoid getting into history.
+ aLoadState->SetLoadType(parentLoadType);
+ } else if (parentLoadType == LOAD_ERROR_PAGE) {
+ // If the parent document is an error page, we don't
+ // want to update global/session history. However,
+ // this child frame is not an error page.
+ aLoadState->SetLoadType(LOAD_BYPASS_HISTORY);
+ } else if ((parentLoadType == LOAD_RELOAD_BYPASS_CACHE) ||
+ (parentLoadType == LOAD_RELOAD_BYPASS_PROXY) ||
+ (parentLoadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE)) {
+ // the new frame should inherit the parent's load type so that it also
+ // bypasses the cache and/or proxy
+ aLoadState->SetLoadType(parentLoadType);
+ }
+
+ return false;
+}
+
+/*
+ * Reset state to a new content model within the current document and the
+ * document viewer. Called by the document before initiating an out of band
+ * document.write().
+ */
+NS_IMETHODIMP
+nsDocShell::PrepareForNewContentModel() {
+ // Clear out our form control state, because the state of controls
+ // in the pre-open() document should not affect the state of
+ // controls that are now going to be written.
+ SetLayoutHistoryState(nullptr);
+ mEODForCurrentDocument = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::FirePageHideNotification(bool aIsUnload) {
+ FirePageHideNotificationInternal(aIsUnload, false);
+ return NS_OK;
+}
+
+void nsDocShell::FirePageHideNotificationInternal(
+ bool aIsUnload, bool aSkipCheckingDynEntries) {
+ if (mContentViewer && !mFiredUnloadEvent) {
+ // Keep an explicit reference since calling PageHide could release
+ // mContentViewer
+ nsCOMPtr<nsIContentViewer> contentViewer(mContentViewer);
+ mFiredUnloadEvent = true;
+
+ if (mTiming) {
+ mTiming->NotifyUnloadEventStart();
+ }
+
+ contentViewer->PageHide(aIsUnload);
+
+ if (mTiming) {
+ mTiming->NotifyUnloadEventEnd();
+ }
+
+ AutoTArray<nsCOMPtr<nsIDocShell>, 8> kids;
+ uint32_t n = mChildList.Length();
+ kids.SetCapacity(n);
+ for (uint32_t i = 0; i < n; i++) {
+ kids.AppendElement(do_QueryInterface(ChildAt(i)));
+ }
+
+ n = kids.Length();
+ for (uint32_t i = 0; i < n; ++i) {
+ RefPtr<nsDocShell> child = static_cast<nsDocShell*>(kids[i].get());
+ if (child) {
+ // Skip checking dynamic subframe entries in our children.
+ child->FirePageHideNotificationInternal(aIsUnload, true);
+ }
+ }
+
+ // If the document is unloading, remove all dynamic subframe entries.
+ if (aIsUnload && !aSkipCheckingDynEntries) {
+ RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
+ if (rootSH) {
+ MOZ_LOG(
+ gSHLog, LogLevel::Debug,
+ ("nsDocShell %p unloading, remove dynamic subframe entries", this));
+ if (mozilla::SessionHistoryInParent()) {
+ if (mActiveEntry) {
+ mBrowsingContext->RemoveDynEntriesFromActiveSessionHistoryEntry();
+ }
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell %p unloading, no active entries", this));
+ } else if (mOSHE) {
+ int32_t index = rootSH->Index();
+ rootSH->LegacySHistory()->RemoveDynEntries(index, mOSHE);
+ }
+ }
+ }
+
+ // Now make sure our editor, if any, is detached before we go
+ // any farther.
+ DetachEditorFromWindow();
+ }
+}
+
+void nsDocShell::ThawFreezeNonRecursive(bool aThaw) {
+ MOZ_ASSERT(mozilla::BFCacheInParent());
+
+ if (!mScriptGlobal) {
+ return;
+ }
+
+ RefPtr<nsGlobalWindowInner> inner =
+ mScriptGlobal->GetCurrentInnerWindowInternal();
+ if (inner) {
+ if (aThaw) {
+ inner->Thaw(false);
+ } else {
+ inner->Freeze(false);
+ }
+ }
+}
+
+void nsDocShell::FirePageHideShowNonRecursive(bool aShow) {
+ MOZ_ASSERT(mozilla::BFCacheInParent());
+
+ if (!mContentViewer) {
+ return;
+ }
+
+ // Emulate what non-SHIP BFCache does too. In pageshow case
+ // add and remove a request and before that call SetCurrentURI to get
+ // the location change notification.
+ // For pagehide, set mFiredUnloadEvent to true, so that unload doesn't fire.
+ nsCOMPtr<nsIContentViewer> contentViewer(mContentViewer);
+ if (aShow) {
+ contentViewer->SetIsHidden(false);
+ mRefreshURIList = std::move(mBFCachedRefreshURIList);
+ RefreshURIFromQueue();
+ mFiredUnloadEvent = false;
+ RefPtr<Document> doc = contentViewer->GetDocument();
+ if (doc) {
+ doc->NotifyActivityChanged();
+ RefPtr<nsGlobalWindowInner> inner =
+ mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindowInternal()
+ : nullptr;
+ if (mBrowsingContext->IsTop()) {
+ doc->NotifyPossibleTitleChange(false);
+ doc->SetLoadingOrRestoredFromBFCacheTimeStampToNow();
+ if (inner) {
+ // Now that we have found the inner window of the page restored
+ // from the history, we have to make sure that
+ // performance.navigation.type is 2.
+ // Traditionally this type change has been done to the top level page
+ // only.
+ Performance* performance = inner->GetPerformance();
+ if (performance) {
+ performance->GetDOMTiming()->NotifyRestoreStart();
+ }
+ }
+ }
+
+ nsCOMPtr<nsIChannel> channel = doc->GetChannel();
+ if (channel) {
+ SetLoadType(LOAD_HISTORY);
+ mEODForCurrentDocument = false;
+ mIsRestoringDocument = true;
+ mLoadGroup->AddRequest(channel, nullptr);
+ SetCurrentURI(doc->GetDocumentURI(), channel,
+ /* aFireOnLocationChange */ true,
+ /* aIsInitialAboutBlank */ false,
+ /* aLocationFlags */ 0);
+ mLoadGroup->RemoveRequest(channel, nullptr, NS_OK);
+ mIsRestoringDocument = false;
+ }
+ RefPtr<PresShell> presShell = GetPresShell();
+ if (presShell) {
+ presShell->Thaw(false);
+ }
+
+ if (inner) {
+ inner->FireDelayedDOMEvents(false);
+ }
+ }
+ } else if (!mFiredUnloadEvent) {
+ // XXXBFCache check again that the page can enter bfcache.
+ // XXXBFCache should mTiming->NotifyUnloadEventStart()/End() be called here?
+
+ if (mRefreshURIList) {
+ RefreshURIToQueue();
+ mBFCachedRefreshURIList = std::move(mRefreshURIList);
+ } else {
+ // If Stop was called, the list was moved to mSavedRefreshURIList after
+ // calling SuspendRefreshURIs, which calls RefreshURIToQueue.
+ mBFCachedRefreshURIList = std::move(mSavedRefreshURIList);
+ }
+
+ mFiredUnloadEvent = true;
+ contentViewer->PageHide(false);
+
+ RefPtr<PresShell> presShell = GetPresShell();
+ if (presShell) {
+ presShell->Freeze(false);
+ }
+ }
+}
+
+nsresult nsDocShell::Dispatch(TaskCategory aCategory,
+ already_AddRefed<nsIRunnable>&& aRunnable) {
+ nsCOMPtr<nsIRunnable> runnable(aRunnable);
+ nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
+ if (NS_WARN_IF(!win)) {
+ // Window should only be unavailable after destroyed.
+ MOZ_ASSERT(mIsBeingDestroyed);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (win->GetDocGroup()) {
+ return win->GetDocGroup()->Dispatch(aCategory, runnable.forget());
+ }
+
+ return SchedulerGroup::Dispatch(aCategory, runnable.forget());
+}
+
+NS_IMETHODIMP
+nsDocShell::DispatchLocationChangeEvent() {
+ return Dispatch(
+ TaskCategory::Other,
+ NewRunnableMethod("nsDocShell::FireDummyOnLocationChange", this,
+ &nsDocShell::FireDummyOnLocationChange));
+}
+
+NS_IMETHODIMP
+nsDocShell::StartDelayedAutoplayMediaComponents() {
+ RefPtr<nsPIDOMWindowOuter> outerWindow = GetWindow();
+ if (outerWindow) {
+ outerWindow->ActivateMediaComponents();
+ }
+ return NS_OK;
+}
+
+bool nsDocShell::MaybeInitTiming() {
+ if (mTiming && !mBlankTiming) {
+ return false;
+ }
+
+ bool canBeReset = false;
+
+ if (mScriptGlobal && mBlankTiming) {
+ nsPIDOMWindowInner* innerWin = mScriptGlobal->GetCurrentInnerWindow();
+ if (innerWin && innerWin->GetPerformance()) {
+ mTiming = innerWin->GetPerformance()->GetDOMTiming();
+ mBlankTiming = false;
+ }
+ }
+
+ if (!mTiming) {
+ mTiming = new nsDOMNavigationTiming(this);
+ canBeReset = true;
+ }
+
+ mTiming->NotifyNavigationStart(
+ mBrowsingContext->IsActive()
+ ? nsDOMNavigationTiming::DocShellState::eActive
+ : nsDOMNavigationTiming::DocShellState::eInactive);
+
+ return canBeReset;
+}
+
+void nsDocShell::MaybeResetInitTiming(bool aReset) {
+ if (aReset) {
+ mTiming = nullptr;
+ }
+}
+
+nsDOMNavigationTiming* nsDocShell::GetNavigationTiming() const {
+ return mTiming;
+}
+
+nsPresContext* nsDocShell::GetEldestPresContext() {
+ nsIContentViewer* viewer = mContentViewer;
+ while (viewer) {
+ nsIContentViewer* prevViewer = viewer->GetPreviousViewer();
+ if (!prevViewer) {
+ return viewer->GetPresContext();
+ }
+ viewer = prevViewer;
+ }
+
+ return nullptr;
+}
+
+nsPresContext* nsDocShell::GetPresContext() {
+ if (!mContentViewer) {
+ return nullptr;
+ }
+
+ return mContentViewer->GetPresContext();
+}
+
+PresShell* nsDocShell::GetPresShell() {
+ nsPresContext* presContext = GetPresContext();
+ return presContext ? presContext->GetPresShell() : nullptr;
+}
+
+PresShell* nsDocShell::GetEldestPresShell() {
+ nsPresContext* presContext = GetEldestPresContext();
+
+ if (presContext) {
+ return presContext->GetPresShell();
+ }
+
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetContentViewer(nsIContentViewer** aContentViewer) {
+ NS_ENSURE_ARG_POINTER(aContentViewer);
+
+ *aContentViewer = mContentViewer;
+ NS_IF_ADDREF(*aContentViewer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetOuterWindowID(uint64_t* aWindowID) {
+ *aWindowID = mContentWindowID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetChromeEventHandler(EventTarget* aChromeEventHandler) {
+ mChromeEventHandler = aChromeEventHandler;
+
+ if (mScriptGlobal) {
+ mScriptGlobal->SetChromeEventHandler(mChromeEventHandler);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetChromeEventHandler(EventTarget** aChromeEventHandler) {
+ NS_ENSURE_ARG_POINTER(aChromeEventHandler);
+ RefPtr<EventTarget> handler = mChromeEventHandler;
+ handler.forget(aChromeEventHandler);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetCurrentURIForSessionStore(nsIURI* aURI) {
+ // Note that securityUI will set STATE_IS_INSECURE, even if
+ // the scheme of |aURI| is "https".
+ SetCurrentURI(aURI, nullptr,
+ /* aFireOnLocationChange */
+ true,
+ /* aIsInitialAboutBlank */
+ false,
+ /* aLocationFlags */
+ nsIWebProgressListener::LOCATION_CHANGE_SESSION_STORE);
+ return NS_OK;
+}
+
+bool nsDocShell::SetCurrentURI(nsIURI* aURI, nsIRequest* aRequest,
+ bool aFireOnLocationChange,
+ bool aIsInitialAboutBlank,
+ uint32_t aLocationFlags) {
+ MOZ_ASSERT(!mIsBeingDestroyed);
+
+ MOZ_LOG(gDocShellLeakLog, LogLevel::Debug,
+ ("DOCSHELL %p SetCurrentURI %s\n", this,
+ aURI ? aURI->GetSpecOrDefault().get() : ""));
+
+ // We don't want to send a location change when we're displaying an error
+ // page, and we don't want to change our idea of "current URI" either
+ if (mLoadType == LOAD_ERROR_PAGE) {
+ return false;
+ }
+
+ bool uriIsEqual = false;
+ if (!mCurrentURI || !aURI ||
+ NS_FAILED(mCurrentURI->Equals(aURI, &uriIsEqual)) || !uriIsEqual) {
+ mTitleValidForCurrentURI = false;
+ }
+
+ mCurrentURI = aURI;
+
+#ifdef DEBUG
+ mLastOpenedURI = aURI;
+#endif
+
+ if (!NS_IsAboutBlank(mCurrentURI)) {
+ mHasLoadedNonBlankURI = true;
+ }
+
+ // Don't fire onLocationChange when creating a subframe's initial about:blank
+ // document, as this can happen when it's not safe for us to run script.
+ if (aIsInitialAboutBlank && !mHasLoadedNonBlankURI &&
+ !mBrowsingContext->IsTop()) {
+ MOZ_ASSERT(!aRequest && aLocationFlags == 0);
+ return false;
+ }
+
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+
+ if (aFireOnLocationChange) {
+ FireOnLocationChange(this, aRequest, aURI, aLocationFlags);
+ }
+ return !aFireOnLocationChange;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetCharset(nsACString& aCharset) {
+ aCharset.Truncate();
+
+ PresShell* presShell = GetPresShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+ Document* doc = presShell->GetDocument();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+ doc->GetDocumentCharacterSet()->Name(aCharset);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::ForceEncodingDetection() {
+ nsCOMPtr<nsIContentViewer> viewer;
+ GetContentViewer(getter_AddRefs(viewer));
+ if (!viewer) {
+ return NS_OK;
+ }
+
+ Document* doc = viewer->GetDocument();
+ if (!doc || doc->WillIgnoreCharsetOverride()) {
+ return NS_OK;
+ }
+
+ mForcedAutodetection = true;
+
+ nsIURI* url = doc->GetOriginalURI();
+ bool isFileURL = url && SchemeIsFile(url);
+
+ int32_t charsetSource = doc->GetDocumentCharacterSetSource();
+ auto encoding = doc->GetDocumentCharacterSet();
+ // AsHTMLDocument is valid, because we called
+ // WillIgnoreCharsetOverride() above.
+ if (doc->AsHTMLDocument()->IsPlainText()) {
+ switch (charsetSource) {
+ case kCharsetFromInitialAutoDetectionASCII:
+ // Deliberately no final version
+ LOGCHARSETMENU(("TEXT:UnlabeledAscii"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT::UnlabeledAscii);
+ break;
+ case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Generic:
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Generic:
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8GenericInitialWasASCII:
+ case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Content:
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Content:
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8ContentInitialWasASCII:
+ LOGCHARSETMENU(("TEXT:UnlabeledNonUtf8"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT::
+ UnlabeledNonUtf8);
+ break;
+ case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD:
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD:
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLDInitialWasASCII:
+ LOGCHARSETMENU(("TEXT:UnlabeledNonUtf8TLD"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT::
+ UnlabeledNonUtf8TLD);
+ break;
+ case kCharsetFromInitialAutoDetectionWouldHaveBeenUTF8:
+ case kCharsetFromFinalAutoDetectionWouldHaveBeenUTF8InitialWasASCII:
+ LOGCHARSETMENU(("TEXT:UnlabeledUtf8"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT::UnlabeledUtf8);
+ break;
+ case kCharsetFromChannel:
+ if (encoding == UTF_8_ENCODING) {
+ LOGCHARSETMENU(("TEXT:ChannelUtf8"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT::ChannelUtf8);
+ } else {
+ LOGCHARSETMENU(("TEXT:ChannelNonUtf8"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT::
+ ChannelNonUtf8);
+ }
+ break;
+ default:
+ LOGCHARSETMENU(("TEXT:Bug"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT::Bug);
+ break;
+ }
+ } else {
+ switch (charsetSource) {
+ case kCharsetFromInitialAutoDetectionASCII:
+ // Deliberately no final version
+ LOGCHARSETMENU(("HTML:UnlabeledAscii"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::UnlabeledAscii);
+ break;
+ case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Generic:
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Generic:
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8GenericInitialWasASCII:
+ case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Content:
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Content:
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8ContentInitialWasASCII:
+ LOGCHARSETMENU(("HTML:UnlabeledNonUtf8"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::
+ UnlabeledNonUtf8);
+ break;
+ case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD:
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD:
+ case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLDInitialWasASCII:
+ LOGCHARSETMENU(("HTML:UnlabeledNonUtf8TLD"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::
+ UnlabeledNonUtf8TLD);
+ break;
+ case kCharsetFromInitialAutoDetectionWouldHaveBeenUTF8:
+ case kCharsetFromFinalAutoDetectionWouldHaveBeenUTF8InitialWasASCII:
+ LOGCHARSETMENU(("HTML:UnlabeledUtf8"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::UnlabeledUtf8);
+ break;
+ case kCharsetFromChannel:
+ if (encoding == UTF_8_ENCODING) {
+ LOGCHARSETMENU(("HTML:ChannelUtf8"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::ChannelUtf8);
+ } else {
+ LOGCHARSETMENU(("HTML:ChannelNonUtf8"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::
+ ChannelNonUtf8);
+ }
+ break;
+ case kCharsetFromXmlDeclaration:
+ case kCharsetFromMetaTag:
+ if (isFileURL) {
+ LOGCHARSETMENU(("HTML:LocalLabeled"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::LocalLabeled);
+ } else if (encoding == UTF_8_ENCODING) {
+ LOGCHARSETMENU(("HTML:MetaUtf8"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::InternalUtf8);
+ } else {
+ LOGCHARSETMENU(("HTML:MetaNonUtf8"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::
+ InternalNonUtf8);
+ }
+ break;
+ default:
+ LOGCHARSETMENU(("HTML:Bug"));
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::Bug);
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+void nsDocShell::SetParentCharset(const Encoding*& aCharset,
+ int32_t aCharsetSource,
+ nsIPrincipal* aPrincipal) {
+ mParentCharset = aCharset;
+ mParentCharsetSource = aCharsetSource;
+ mParentCharsetPrincipal = aPrincipal;
+}
+
+void nsDocShell::GetParentCharset(const Encoding*& aCharset,
+ int32_t* aCharsetSource,
+ nsIPrincipal** aPrincipal) {
+ aCharset = mParentCharset;
+ *aCharsetSource = mParentCharsetSource;
+ NS_IF_ADDREF(*aPrincipal = mParentCharsetPrincipal);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetHasTrackingContentBlocked(Promise** aPromise) {
+ MOZ_ASSERT(aPromise);
+
+ ErrorResult rv;
+ RefPtr<Document> doc(GetDocument());
+ RefPtr<Promise> retPromise = Promise::Create(doc->GetOwnerGlobal(), rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return rv.StealNSResult();
+ }
+
+ // Retrieve the document's content blocking events from the parent process.
+ RefPtr<Document::GetContentBlockingEventsPromise> promise =
+ doc->GetContentBlockingEvents();
+ if (promise) {
+ promise->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [retPromise](const Document::GetContentBlockingEventsPromise::
+ ResolveOrRejectValue& aValue) {
+ if (aValue.IsResolve()) {
+ bool has = aValue.ResolveValue() &
+ nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT;
+ retPromise->MaybeResolve(has);
+ } else {
+ retPromise->MaybeResolve(false);
+ }
+ });
+ } else {
+ retPromise->MaybeResolve(false);
+ }
+
+ retPromise.forget(aPromise);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetAllowPlugins(bool* aAllowPlugins) {
+ NS_ENSURE_ARG_POINTER(aAllowPlugins);
+
+ *aAllowPlugins = mBrowsingContext->GetAllowPlugins();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetAllowPlugins(bool aAllowPlugins) {
+ // XXX should enable or disable a plugin host
+ return mBrowsingContext->SetAllowPlugins(aAllowPlugins);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetCssErrorReportingEnabled(bool* aEnabled) {
+ MOZ_ASSERT(aEnabled);
+ *aEnabled = mCSSErrorReportingEnabled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetCssErrorReportingEnabled(bool aEnabled) {
+ mCSSErrorReportingEnabled = aEnabled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetUsePrivateBrowsing(bool* aUsePrivateBrowsing) {
+ NS_ENSURE_ARG_POINTER(aUsePrivateBrowsing);
+ return mBrowsingContext->GetUsePrivateBrowsing(aUsePrivateBrowsing);
+}
+
+void nsDocShell::NotifyPrivateBrowsingChanged() {
+ MOZ_ASSERT(!mIsBeingDestroyed);
+
+ nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mPrivacyObservers);
+ while (iter.HasMore()) {
+ nsWeakPtr ref = iter.GetNext();
+ nsCOMPtr<nsIPrivacyTransitionObserver> obs = do_QueryReferent(ref);
+ if (!obs) {
+ iter.Remove();
+ } else {
+ obs->PrivateModeChanged(UsePrivateBrowsing());
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsDocShell::SetUsePrivateBrowsing(bool aUsePrivateBrowsing) {
+ return mBrowsingContext->SetUsePrivateBrowsing(aUsePrivateBrowsing);
+}
+
+NS_IMETHODIMP
+nsDocShell::SetPrivateBrowsing(bool aUsePrivateBrowsing) {
+ return mBrowsingContext->SetPrivateBrowsing(aUsePrivateBrowsing);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetHasLoadedNonBlankURI(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = mHasLoadedNonBlankURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetUseRemoteTabs(bool* aUseRemoteTabs) {
+ NS_ENSURE_ARG_POINTER(aUseRemoteTabs);
+ return mBrowsingContext->GetUseRemoteTabs(aUseRemoteTabs);
+}
+
+NS_IMETHODIMP
+nsDocShell::SetRemoteTabs(bool aUseRemoteTabs) {
+ return mBrowsingContext->SetRemoteTabs(aUseRemoteTabs);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetUseRemoteSubframes(bool* aUseRemoteSubframes) {
+ NS_ENSURE_ARG_POINTER(aUseRemoteSubframes);
+ return mBrowsingContext->GetUseRemoteSubframes(aUseRemoteSubframes);
+}
+
+NS_IMETHODIMP
+nsDocShell::SetRemoteSubframes(bool aUseRemoteSubframes) {
+ return mBrowsingContext->SetRemoteSubframes(aUseRemoteSubframes);
+}
+
+NS_IMETHODIMP
+nsDocShell::AddWeakPrivacyTransitionObserver(
+ nsIPrivacyTransitionObserver* aObserver) {
+ nsWeakPtr weakObs = do_GetWeakReference(aObserver);
+ if (!weakObs) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ mPrivacyObservers.AppendElement(weakObs);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::AddWeakReflowObserver(nsIReflowObserver* aObserver) {
+ nsWeakPtr weakObs = do_GetWeakReference(aObserver);
+ if (!weakObs) {
+ return NS_ERROR_FAILURE;
+ }
+ mReflowObservers.AppendElement(weakObs);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::RemoveWeakReflowObserver(nsIReflowObserver* aObserver) {
+ nsWeakPtr obs = do_GetWeakReference(aObserver);
+ return mReflowObservers.RemoveElement(obs) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsDocShell::NotifyReflowObservers(bool aInterruptible,
+ DOMHighResTimeStamp aStart,
+ DOMHighResTimeStamp aEnd) {
+ nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mReflowObservers);
+ while (iter.HasMore()) {
+ nsWeakPtr ref = iter.GetNext();
+ nsCOMPtr<nsIReflowObserver> obs = do_QueryReferent(ref);
+ if (!obs) {
+ iter.Remove();
+ } else if (aInterruptible) {
+ obs->ReflowInterruptible(aStart, aEnd);
+ } else {
+ obs->Reflow(aStart, aEnd);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetAllowMetaRedirects(bool* aReturn) {
+ NS_ENSURE_ARG_POINTER(aReturn);
+
+ *aReturn = mAllowMetaRedirects;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetAllowMetaRedirects(bool aValue) {
+ mAllowMetaRedirects = aValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetAllowSubframes(bool* aAllowSubframes) {
+ NS_ENSURE_ARG_POINTER(aAllowSubframes);
+
+ *aAllowSubframes = mAllowSubframes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetAllowSubframes(bool aAllowSubframes) {
+ mAllowSubframes = aAllowSubframes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetAllowImages(bool* aAllowImages) {
+ NS_ENSURE_ARG_POINTER(aAllowImages);
+
+ *aAllowImages = mAllowImages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetAllowImages(bool aAllowImages) {
+ mAllowImages = aAllowImages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetAllowMedia(bool* aAllowMedia) {
+ *aAllowMedia = mAllowMedia;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetAllowMedia(bool aAllowMedia) {
+ mAllowMedia = aAllowMedia;
+
+ // Mute or unmute audio contexts attached to the inner window.
+ if (mScriptGlobal) {
+ if (nsPIDOMWindowInner* innerWin = mScriptGlobal->GetCurrentInnerWindow()) {
+ if (aAllowMedia) {
+ innerWin->UnmuteAudioContexts();
+ } else {
+ innerWin->MuteAudioContexts();
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetAllowDNSPrefetch(bool* aAllowDNSPrefetch) {
+ *aAllowDNSPrefetch = mAllowDNSPrefetch;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetAllowDNSPrefetch(bool aAllowDNSPrefetch) {
+ mAllowDNSPrefetch = aAllowDNSPrefetch;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetAllowWindowControl(bool* aAllowWindowControl) {
+ *aAllowWindowControl = mAllowWindowControl;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetAllowWindowControl(bool aAllowWindowControl) {
+ mAllowWindowControl = aAllowWindowControl;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetAllowContentRetargeting(bool* aAllowContentRetargeting) {
+ *aAllowContentRetargeting = mBrowsingContext->GetAllowContentRetargeting();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetAllowContentRetargeting(bool aAllowContentRetargeting) {
+ BrowsingContext::Transaction txn;
+ txn.SetAllowContentRetargeting(aAllowContentRetargeting);
+ txn.SetAllowContentRetargetingOnChildren(aAllowContentRetargeting);
+ return txn.Commit(mBrowsingContext);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetAllowContentRetargetingOnChildren(
+ bool* aAllowContentRetargetingOnChildren) {
+ *aAllowContentRetargetingOnChildren =
+ mBrowsingContext->GetAllowContentRetargetingOnChildren();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetAllowContentRetargetingOnChildren(
+ bool aAllowContentRetargetingOnChildren) {
+ return mBrowsingContext->SetAllowContentRetargetingOnChildren(
+ aAllowContentRetargetingOnChildren);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetMayEnableCharacterEncodingMenu(
+ bool* aMayEnableCharacterEncodingMenu) {
+ *aMayEnableCharacterEncodingMenu = false;
+ if (!mContentViewer) {
+ return NS_OK;
+ }
+ Document* doc = mContentViewer->GetDocument();
+ if (!doc) {
+ return NS_OK;
+ }
+ if (doc->WillIgnoreCharsetOverride()) {
+ return NS_OK;
+ }
+
+ *aMayEnableCharacterEncodingMenu = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetAllDocShellsInSubtree(int32_t aItemType,
+ DocShellEnumeratorDirection aDirection,
+ nsTArray<RefPtr<nsIDocShell>>& aResult) {
+ aResult.Clear();
+
+ nsDocShellEnumerator docShellEnum(
+ (aDirection == ENUMERATE_FORWARDS)
+ ? nsDocShellEnumerator::EnumerationDirection::Forwards
+ : nsDocShellEnumerator::EnumerationDirection::Backwards,
+ aItemType, *this);
+
+ nsresult rv = docShellEnum.BuildDocShellArray(aResult);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetAppType(AppType* aAppType) {
+ *aAppType = mAppType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetAppType(AppType aAppType) {
+ mAppType = aAppType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetAllowAuth(bool* aAllowAuth) {
+ *aAllowAuth = mAllowAuth;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetAllowAuth(bool aAllowAuth) {
+ mAllowAuth = aAllowAuth;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetZoom(float* aZoom) {
+ NS_ENSURE_ARG_POINTER(aZoom);
+ *aZoom = 1.0f;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetZoom(float aZoom) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+nsDocShell::GetBusyFlags(BusyFlags* aBusyFlags) {
+ NS_ENSURE_ARG_POINTER(aBusyFlags);
+
+ *aBusyFlags = mBusyFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::TabToTreeOwner(bool aForward, bool aForDocumentNavigation,
+ bool* aTookFocus) {
+ NS_ENSURE_ARG_POINTER(aTookFocus);
+
+ nsCOMPtr<nsIWebBrowserChromeFocus> chromeFocus = do_GetInterface(mTreeOwner);
+ if (chromeFocus) {
+ if (aForward) {
+ *aTookFocus =
+ NS_SUCCEEDED(chromeFocus->FocusNextElement(aForDocumentNavigation));
+ } else {
+ *aTookFocus =
+ NS_SUCCEEDED(chromeFocus->FocusPrevElement(aForDocumentNavigation));
+ }
+ } else {
+ *aTookFocus = false;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetLoadURIDelegate(nsILoadURIDelegate** aLoadURIDelegate) {
+ nsCOMPtr<nsILoadURIDelegate> delegate = GetLoadURIDelegate();
+ delegate.forget(aLoadURIDelegate);
+ return NS_OK;
+}
+
+already_AddRefed<nsILoadURIDelegate> nsDocShell::GetLoadURIDelegate() {
+ if (nsCOMPtr<nsILoadURIDelegate> result =
+ do_QueryActor("LoadURIDelegate", GetDocument())) {
+ return result.forget();
+ }
+
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetUseErrorPages(bool* aUseErrorPages) {
+ *aUseErrorPages = mBrowsingContext->GetUseErrorPages();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetUseErrorPages(bool aUseErrorPages) {
+ return mBrowsingContext->SetUseErrorPages(aUseErrorPages);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetPreviousEntryIndex(int32_t* aPreviousEntryIndex) {
+ *aPreviousEntryIndex = mPreviousEntryIndex;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetLoadedEntryIndex(int32_t* aLoadedEntryIndex) {
+ *aLoadedEntryIndex = mLoadedEntryIndex;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::HistoryPurged(int32_t aNumEntries) {
+ // These indices are used for fastback cache eviction, to determine
+ // which session history entries are candidates for content viewer
+ // eviction. We need to adjust by the number of entries that we
+ // just purged from history, so that we look at the right session history
+ // entries during eviction.
+ mPreviousEntryIndex = std::max(-1, mPreviousEntryIndex - aNumEntries);
+ mLoadedEntryIndex = std::max(0, mLoadedEntryIndex - aNumEntries);
+
+ for (auto* child : mChildList.ForwardRange()) {
+ nsCOMPtr<nsIDocShell> shell = do_QueryObject(child);
+ if (shell) {
+ shell->HistoryPurged(aNumEntries);
+ }
+ }
+
+ return NS_OK;
+}
+
+void nsDocShell::TriggerParentCheckDocShellIsEmpty() {
+ if (RefPtr<nsDocShell> parent = GetInProcessParentDocshell()) {
+ parent->DocLoaderIsEmpty(true);
+ }
+ if (GetBrowsingContext()->IsContentSubframe() &&
+ !GetBrowsingContext()->GetParent()->IsInProcess()) {
+ if (BrowserChild* browserChild = BrowserChild::GetFrom(this)) {
+ mozilla::Unused << browserChild->SendMaybeFireEmbedderLoadEvents(
+ EmbedderElementEventType::NoEvent);
+ }
+ }
+}
+
+nsresult nsDocShell::HistoryEntryRemoved(int32_t aIndex) {
+ // These indices are used for fastback cache eviction, to determine
+ // which session history entries are candidates for content viewer
+ // eviction. We need to adjust by the number of entries that we
+ // just purged from history, so that we look at the right session history
+ // entries during eviction.
+ if (aIndex == mPreviousEntryIndex) {
+ mPreviousEntryIndex = -1;
+ } else if (aIndex < mPreviousEntryIndex) {
+ --mPreviousEntryIndex;
+ }
+ if (mLoadedEntryIndex == aIndex) {
+ mLoadedEntryIndex = 0;
+ } else if (aIndex < mLoadedEntryIndex) {
+ --mLoadedEntryIndex;
+ }
+
+ for (auto* child : mChildList.ForwardRange()) {
+ nsCOMPtr<nsIDocShell> shell = do_QueryObject(child);
+ if (shell) {
+ static_cast<nsDocShell*>(shell.get())->HistoryEntryRemoved(aIndex);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetRecordProfileTimelineMarkers(bool aValue) {
+ bool currentValue = nsIDocShell::GetRecordProfileTimelineMarkers();
+ if (currentValue == aValue) {
+ return NS_OK;
+ }
+
+ if (aValue) {
+ MOZ_ASSERT(!TimelineConsumers::HasConsumer(this));
+ TimelineConsumers::AddConsumer(this);
+ MOZ_ASSERT(TimelineConsumers::HasConsumer(this));
+ UseEntryScriptProfiling();
+ } else {
+ MOZ_ASSERT(TimelineConsumers::HasConsumer(this));
+ TimelineConsumers::RemoveConsumer(this);
+ MOZ_ASSERT(!TimelineConsumers::HasConsumer(this));
+ UnuseEntryScriptProfiling();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetRecordProfileTimelineMarkers(bool* aValue) {
+ *aValue = !!mObserved;
+ return NS_OK;
+}
+
+nsresult nsDocShell::PopProfileTimelineMarkers(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aOut) {
+ nsTArray<dom::ProfileTimelineMarker> store;
+ SequenceRooter<dom::ProfileTimelineMarker> rooter(aCx, &store);
+
+ TimelineConsumers::PopMarkers(this, aCx, store);
+
+ if (!ToJSValue(aCx, store, aOut)) {
+ JS_ClearPendingException(aCx);
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsDocShell::Now(DOMHighResTimeStamp* aWhen) {
+ *aWhen = (TimeStamp::Now() - TimeStamp::ProcessCreation()).ToMilliseconds();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetWindowDraggingAllowed(bool aValue) {
+ RefPtr<nsDocShell> parent = GetInProcessParentDocshell();
+ if (!aValue && mItemType == typeChrome && !parent) {
+ // Window dragging is always allowed for top level
+ // chrome docshells.
+ return NS_ERROR_FAILURE;
+ }
+ mWindowDraggingAllowed = aValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetWindowDraggingAllowed(bool* aValue) {
+ // window dragging regions in CSS (-moz-window-drag:drag)
+ // can be slow. Default behavior is to only allow it for
+ // chrome top level windows.
+ RefPtr<nsDocShell> parent = GetInProcessParentDocshell();
+ if (mItemType == typeChrome && !parent) {
+ // Top level chrome window
+ *aValue = true;
+ } else {
+ *aValue = mWindowDraggingAllowed;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetCurrentDocumentChannel(nsIChannel** aResult) {
+ NS_IF_ADDREF(*aResult = GetCurrentDocChannel());
+ return NS_OK;
+}
+
+nsIChannel* nsDocShell::GetCurrentDocChannel() {
+ if (mContentViewer) {
+ Document* doc = mContentViewer->GetDocument();
+ if (doc) {
+ return doc->GetChannel();
+ }
+ }
+ return nullptr;
+}
+
+NS_IMETHODIMP
+nsDocShell::AddWeakScrollObserver(nsIScrollObserver* aObserver) {
+ nsWeakPtr weakObs = do_GetWeakReference(aObserver);
+ if (!weakObs) {
+ return NS_ERROR_FAILURE;
+ }
+ mScrollObservers.AppendElement(weakObs);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::RemoveWeakScrollObserver(nsIScrollObserver* aObserver) {
+ nsWeakPtr obs = do_GetWeakReference(aObserver);
+ return mScrollObservers.RemoveElement(obs) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+void nsDocShell::NotifyAsyncPanZoomStarted() {
+ nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mScrollObservers);
+ while (iter.HasMore()) {
+ nsWeakPtr ref = iter.GetNext();
+ nsCOMPtr<nsIScrollObserver> obs = do_QueryReferent(ref);
+ if (obs) {
+ obs->AsyncPanZoomStarted();
+ } else {
+ iter.Remove();
+ }
+ }
+}
+
+void nsDocShell::NotifyAsyncPanZoomStopped() {
+ nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mScrollObservers);
+ while (iter.HasMore()) {
+ nsWeakPtr ref = iter.GetNext();
+ nsCOMPtr<nsIScrollObserver> obs = do_QueryReferent(ref);
+ if (obs) {
+ obs->AsyncPanZoomStopped();
+ } else {
+ iter.Remove();
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsDocShell::NotifyScrollObservers() {
+ nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mScrollObservers);
+ while (iter.HasMore()) {
+ nsWeakPtr ref = iter.GetNext();
+ nsCOMPtr<nsIScrollObserver> obs = do_QueryReferent(ref);
+ if (obs) {
+ obs->ScrollPositionChanged();
+ } else {
+ iter.Remove();
+ }
+ }
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsDocShell::nsIDocShellTreeItem
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShell::GetName(nsAString& aName) {
+ aName = mBrowsingContext->Name();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetName(const nsAString& aName) {
+ return mBrowsingContext->SetName(aName);
+}
+
+NS_IMETHODIMP
+nsDocShell::NameEquals(const nsAString& aName, bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mBrowsingContext->NameEquals(aName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetCustomUserAgent(nsAString& aCustomUserAgent) {
+ mBrowsingContext->GetCustomUserAgent(aCustomUserAgent);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetCustomUserAgent(const nsAString& aCustomUserAgent) {
+ if (mWillChangeProcess) {
+ NS_WARNING("SetCustomUserAgent: Process is changing. Ignoring set");
+ return NS_ERROR_FAILURE;
+ }
+
+ return mBrowsingContext->SetCustomUserAgent(aCustomUserAgent);
+}
+
+NS_IMETHODIMP
+nsDocShell::ClearCachedPlatform() {
+ RefPtr<nsGlobalWindowInner> win =
+ mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr;
+ if (win) {
+ Navigator* navigator = win->Navigator();
+ if (navigator) {
+ navigator->ClearPlatformCache();
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::ClearCachedUserAgent() {
+ RefPtr<nsGlobalWindowInner> win =
+ mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr;
+ if (win) {
+ Navigator* navigator = win->Navigator();
+ if (navigator) {
+ navigator->ClearUserAgentCache();
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetMetaViewportOverride(
+ MetaViewportOverride* aMetaViewportOverride) {
+ NS_ENSURE_ARG_POINTER(aMetaViewportOverride);
+
+ *aMetaViewportOverride = mMetaViewportOverride;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetMetaViewportOverride(
+ MetaViewportOverride aMetaViewportOverride) {
+ // We don't have a way to verify this coming from Javascript, so this check is
+ // still needed.
+ if (!(aMetaViewportOverride == META_VIEWPORT_OVERRIDE_NONE ||
+ aMetaViewportOverride == META_VIEWPORT_OVERRIDE_ENABLED ||
+ aMetaViewportOverride == META_VIEWPORT_OVERRIDE_DISABLED)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mMetaViewportOverride = aMetaViewportOverride;
+
+ // Inform our presShell that it needs to re-check its need for a viewport
+ // override.
+ if (RefPtr<PresShell> presShell = GetPresShell()) {
+ presShell->MaybeRecreateMobileViewportManager(true);
+ }
+
+ return NS_OK;
+}
+
+/* virtual */
+int32_t nsDocShell::ItemType() { return mItemType; }
+
+NS_IMETHODIMP
+nsDocShell::GetItemType(int32_t* aItemType) {
+ NS_ENSURE_ARG_POINTER(aItemType);
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ (mBrowsingContext->IsContent() ? typeContent : typeChrome) == mItemType);
+ *aItemType = mItemType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetInProcessParent(nsIDocShellTreeItem** aParent) {
+ if (!mParent) {
+ *aParent = nullptr;
+ } else {
+ CallQueryInterface(mParent, aParent);
+ }
+ // Note that in the case when the parent is not an nsIDocShellTreeItem we
+ // don't want to throw; we just want to return null.
+ return NS_OK;
+}
+
+// With Fission, related nsDocShell objects may exist in a different process. In
+// that case, this method will return `nullptr`, despite a parent nsDocShell
+// object existing.
+//
+// Prefer using `BrowsingContext::Parent()`, which will succeed even if the
+// parent entry is not in the current process, and handle the case where the
+// parent nsDocShell is inaccessible.
+already_AddRefed<nsDocShell> nsDocShell::GetInProcessParentDocshell() {
+ nsCOMPtr<nsIDocShell> docshell = do_QueryInterface(GetAsSupports(mParent));
+ return docshell.forget().downcast<nsDocShell>();
+}
+
+void nsDocShell::MaybeCreateInitialClientSource(nsIPrincipal* aPrincipal) {
+ MOZ_ASSERT(!mIsBeingDestroyed);
+
+ // If there is an existing document then there is no need to create
+ // a client for a future initial about:blank document.
+ if (mScriptGlobal && mScriptGlobal->GetCurrentInnerWindowInternal() &&
+ mScriptGlobal->GetCurrentInnerWindowInternal()->GetExtantDoc()) {
+ MOZ_DIAGNOSTIC_ASSERT(mScriptGlobal->GetCurrentInnerWindowInternal()
+ ->GetClientInfo()
+ .isSome());
+ MOZ_DIAGNOSTIC_ASSERT(!mInitialClientSource);
+ return;
+ }
+
+ // Don't recreate the initial client source. We call this multiple times
+ // when DoChannelLoad() is called before CreateAboutBlankContentViewer.
+ if (mInitialClientSource) {
+ return;
+ }
+
+ // Don't pre-allocate the client when we are sandboxed. The inherited
+ // principal does not take sandboxing into account.
+ // TODO: Refactor sandboxing principal code out so we can use it here.
+ if (!aPrincipal && mBrowsingContext->GetSandboxFlags()) {
+ return;
+ }
+
+ // We cannot get inherited foreign partitioned principal here. Instead, we
+ // directly check which principal we want to inherit for the service worker.
+ nsIPrincipal* principal =
+ aPrincipal
+ ? aPrincipal
+ : GetInheritedPrincipal(
+ false, StoragePrincipalHelper::
+ ShouldUsePartitionPrincipalForServiceWorker(this));
+
+ // Sometimes there is no principal available when we are called from
+ // CreateAboutBlankContentViewer. For example, sometimes the principal
+ // is only extracted from the load context after the document is created
+ // in Document::ResetToURI(). Ideally we would do something similar
+ // here, but for now lets just avoid the issue by not preallocating the
+ // client.
+ if (!principal) {
+ return;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
+ if (!win) {
+ return;
+ }
+
+ mInitialClientSource = ClientManager::CreateSource(
+ ClientType::Window, win->EventTargetFor(TaskCategory::Other), principal);
+ MOZ_DIAGNOSTIC_ASSERT(mInitialClientSource);
+
+ // Mark the initial client as execution ready, but owned by the docshell.
+ // If the client is actually used this will cause ClientSource to force
+ // the creation of the initial about:blank by calling
+ // nsDocShell::GetDocument().
+ mInitialClientSource->DocShellExecutionReady(this);
+
+ // Next, check to see if the parent is controlled.
+ nsCOMPtr<nsIDocShell> parent = GetInProcessParentDocshell();
+ nsPIDOMWindowOuter* parentOuter = parent ? parent->GetWindow() : nullptr;
+ nsPIDOMWindowInner* parentInner =
+ parentOuter ? parentOuter->GetCurrentInnerWindow() : nullptr;
+ if (!parentInner) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), "about:blank"_ns));
+
+ // We're done if there is no parent controller or if this docshell
+ // is not permitted to control for some reason.
+ Maybe<ServiceWorkerDescriptor> controller(parentInner->GetController());
+ if (controller.isNothing() ||
+ !ServiceWorkerAllowedToControlWindow(principal, uri)) {
+ return;
+ }
+
+ mInitialClientSource->InheritController(controller.ref());
+}
+
+Maybe<ClientInfo> nsDocShell::GetInitialClientInfo() const {
+ if (mInitialClientSource) {
+ Maybe<ClientInfo> result;
+ result.emplace(mInitialClientSource->Info());
+ return result;
+ }
+
+ nsGlobalWindowInner* innerWindow =
+ mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr;
+ Document* doc = innerWindow ? innerWindow->GetExtantDoc() : nullptr;
+
+ if (!doc || !doc->IsInitialDocument()) {
+ return Maybe<ClientInfo>();
+ }
+
+ return innerWindow->GetClientInfo();
+}
+
+nsresult nsDocShell::SetDocLoaderParent(nsDocLoader* aParent) {
+ bool wasFrame = IsSubframe();
+
+ nsresult rv = nsDocLoader::SetDocLoaderParent(aParent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupportsPriority> priorityGroup = do_QueryInterface(mLoadGroup);
+ if (wasFrame != IsSubframe() && priorityGroup) {
+ priorityGroup->AdjustPriority(wasFrame ? -1 : 1);
+ }
+
+ // Curse ambiguous nsISupports inheritance!
+ nsISupports* parent = GetAsSupports(aParent);
+
+ // If parent is another docshell, we inherit all their flags for
+ // allowing plugins, scripting etc.
+ bool value;
+ nsCOMPtr<nsIDocShell> parentAsDocShell(do_QueryInterface(parent));
+
+ if (parentAsDocShell) {
+ if (mAllowMetaRedirects &&
+ NS_SUCCEEDED(parentAsDocShell->GetAllowMetaRedirects(&value))) {
+ SetAllowMetaRedirects(value);
+ }
+ if (mAllowSubframes &&
+ NS_SUCCEEDED(parentAsDocShell->GetAllowSubframes(&value))) {
+ SetAllowSubframes(value);
+ }
+ if (mAllowImages &&
+ NS_SUCCEEDED(parentAsDocShell->GetAllowImages(&value))) {
+ SetAllowImages(value);
+ }
+ SetAllowMedia(parentAsDocShell->GetAllowMedia() && mAllowMedia);
+ if (mAllowWindowControl &&
+ NS_SUCCEEDED(parentAsDocShell->GetAllowWindowControl(&value))) {
+ SetAllowWindowControl(value);
+ }
+ if (NS_FAILED(parentAsDocShell->GetAllowDNSPrefetch(&value))) {
+ value = false;
+ }
+ SetAllowDNSPrefetch(mAllowDNSPrefetch && value);
+
+ // We don't need to inherit metaViewportOverride, because the viewport
+ // is only relevant for the outermost nsDocShell, not for any iframes
+ // like this that might be embedded within it.
+ }
+
+ nsCOMPtr<nsIURIContentListener> parentURIListener(do_GetInterface(parent));
+ if (parentURIListener) {
+ mContentListener->SetParentContentListener(parentURIListener);
+ }
+
+ return NS_OK;
+}
+
+void nsDocShell::MaybeRestoreWindowName() {
+ if (!StaticPrefs::privacy_window_name_update_enabled()) {
+ return;
+ }
+
+ // We only restore window.name for the top-level content.
+ if (!mBrowsingContext->IsTopContent()) {
+ return;
+ }
+
+ nsAutoString name;
+
+ // Following implements https://html.spec.whatwg.org/#history-traversal:
+ // Step 4.4. Check if the loading entry has a name.
+
+ if (mLSHE) {
+ mLSHE->GetName(name);
+ }
+
+ if (mLoadingEntry) {
+ name = mLoadingEntry->mInfo.GetName();
+ }
+
+ if (name.IsEmpty()) {
+ return;
+ }
+
+ // Step 4.4.1. Set the name to the browsing context.
+ Unused << mBrowsingContext->SetName(name);
+
+ // Step 4.4.2. Clear the name of all entries that are contiguous and
+ // same-origin with the loading entry.
+ if (mLSHE) {
+ nsSHistory::WalkContiguousEntries(
+ mLSHE, [](nsISHEntry* aEntry) { aEntry->SetName(EmptyString()); });
+ }
+
+ if (mLoadingEntry) {
+ // Clear the name of the session entry in the child side. For parent side,
+ // the clearing will be done when we commit the history to the parent.
+ mLoadingEntry->mInfo.SetName(EmptyString());
+ }
+}
+
+void nsDocShell::StoreWindowNameToSHEntries() {
+ MOZ_ASSERT(mBrowsingContext->IsTopContent());
+
+ nsAutoString name;
+ mBrowsingContext->GetName(name);
+
+ if (mOSHE) {
+ nsSHistory::WalkContiguousEntries(
+ mOSHE, [&](nsISHEntry* aEntry) { aEntry->SetName(name); });
+ }
+
+ if (mozilla::SessionHistoryInParent()) {
+ if (XRE_IsParentProcess()) {
+ SessionHistoryEntry* entry =
+ mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry();
+ if (entry) {
+ nsSHistory::WalkContiguousEntries(
+ entry, [&](nsISHEntry* aEntry) { aEntry->SetName(name); });
+ }
+ } else {
+ // Ask parent process to store the name in entries.
+ mozilla::Unused
+ << ContentChild::GetSingleton()
+ ->SendSessionHistoryEntryStoreWindowNameInContiguousEntries(
+ mBrowsingContext, name);
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsDocShell::GetInProcessSameTypeParent(nsIDocShellTreeItem** aParent) {
+ if (BrowsingContext* parentBC = mBrowsingContext->GetParent()) {
+ *aParent = do_AddRef(parentBC->GetDocShell()).take();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetSameTypeInProcessParentIgnoreBrowserBoundaries(
+ nsIDocShell** aParent) {
+ NS_ENSURE_ARG_POINTER(aParent);
+ *aParent = nullptr;
+
+ nsCOMPtr<nsIDocShellTreeItem> parent =
+ do_QueryInterface(GetAsSupports(mParent));
+ if (!parent) {
+ return NS_OK;
+ }
+
+ if (parent->ItemType() == mItemType) {
+ nsCOMPtr<nsIDocShell> parentDS = do_QueryInterface(parent);
+ parentDS.forget(aParent);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetInProcessRootTreeItem(nsIDocShellTreeItem** aRootTreeItem) {
+ NS_ENSURE_ARG_POINTER(aRootTreeItem);
+
+ RefPtr<nsDocShell> root = this;
+ RefPtr<nsDocShell> parent = root->GetInProcessParentDocshell();
+ while (parent) {
+ root = parent;
+ parent = root->GetInProcessParentDocshell();
+ }
+
+ root.forget(aRootTreeItem);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetInProcessSameTypeRootTreeItem(
+ nsIDocShellTreeItem** aRootTreeItem) {
+ NS_ENSURE_ARG_POINTER(aRootTreeItem);
+ *aRootTreeItem = static_cast<nsIDocShellTreeItem*>(this);
+
+ nsCOMPtr<nsIDocShellTreeItem> parent;
+ NS_ENSURE_SUCCESS(GetInProcessSameTypeParent(getter_AddRefs(parent)),
+ NS_ERROR_FAILURE);
+ while (parent) {
+ *aRootTreeItem = parent;
+ NS_ENSURE_SUCCESS(
+ (*aRootTreeItem)->GetInProcessSameTypeParent(getter_AddRefs(parent)),
+ NS_ERROR_FAILURE);
+ }
+ NS_ADDREF(*aRootTreeItem);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetTreeOwner(nsIDocShellTreeOwner** aTreeOwner) {
+ NS_ENSURE_ARG_POINTER(aTreeOwner);
+
+ *aTreeOwner = mTreeOwner;
+ NS_IF_ADDREF(*aTreeOwner);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetTreeOwner(nsIDocShellTreeOwner* aTreeOwner) {
+ if (mIsBeingDestroyed && aTreeOwner) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Don't automatically set the progress based on the tree owner for frames
+ if (!IsSubframe()) {
+ nsCOMPtr<nsIWebProgress> webProgress =
+ do_QueryInterface(GetAsSupports(this));
+
+ if (webProgress) {
+ nsCOMPtr<nsIWebProgressListener> oldListener =
+ do_QueryInterface(mTreeOwner);
+ nsCOMPtr<nsIWebProgressListener> newListener =
+ do_QueryInterface(aTreeOwner);
+
+ if (oldListener) {
+ webProgress->RemoveProgressListener(oldListener);
+ }
+
+ if (newListener) {
+ webProgress->AddProgressListener(newListener,
+ nsIWebProgress::NOTIFY_ALL);
+ }
+ }
+ }
+
+ mTreeOwner = aTreeOwner; // Weak reference per API
+
+ for (auto* childDocLoader : mChildList.ForwardRange()) {
+ nsCOMPtr<nsIDocShellTreeItem> child = do_QueryObject(childDocLoader);
+ NS_ENSURE_TRUE(child, NS_ERROR_FAILURE);
+
+ if (child->ItemType() == mItemType) {
+ child->SetTreeOwner(aTreeOwner);
+ }
+ }
+
+ // If we're in the content process and have had a TreeOwner set on us, extract
+ // our BrowserChild actor. If we've already had our BrowserChild set, assert
+ // that it hasn't changed.
+ if (mTreeOwner && XRE_IsContentProcess()) {
+ nsCOMPtr<nsIBrowserChild> newBrowserChild = do_GetInterface(mTreeOwner);
+ MOZ_ASSERT(newBrowserChild,
+ "No BrowserChild actor for tree owner in Content!");
+
+ if (mBrowserChild) {
+ nsCOMPtr<nsIBrowserChild> oldBrowserChild =
+ do_QueryReferent(mBrowserChild);
+ MOZ_RELEASE_ASSERT(
+ oldBrowserChild == newBrowserChild,
+ "Cannot change BrowserChild during nsDocShell lifetime!");
+ } else {
+ mBrowserChild = do_GetWeakReference(newBrowserChild);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetHistoryID(nsID& aID) {
+ aID = mBrowsingContext->GetHistoryID();
+ return NS_OK;
+}
+
+const nsID& nsDocShell::HistoryID() { return mBrowsingContext->GetHistoryID(); }
+
+NS_IMETHODIMP
+nsDocShell::GetIsInUnload(bool* aIsInUnload) {
+ *aIsInUnload = mFiredUnloadEvent;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetInProcessChildCount(int32_t* aChildCount) {
+ NS_ENSURE_ARG_POINTER(aChildCount);
+ *aChildCount = mChildList.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::AddChild(nsIDocShellTreeItem* aChild) {
+ NS_ENSURE_ARG_POINTER(aChild);
+
+ RefPtr<nsDocLoader> childAsDocLoader = GetAsDocLoader(aChild);
+ NS_ENSURE_TRUE(childAsDocLoader, NS_ERROR_UNEXPECTED);
+
+ // Make sure we're not creating a loop in the docshell tree
+ nsDocLoader* ancestor = this;
+ do {
+ if (childAsDocLoader == ancestor) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ ancestor = ancestor->GetParent();
+ } while (ancestor);
+
+ // Make sure to remove the child from its current parent.
+ nsDocLoader* childsParent = childAsDocLoader->GetParent();
+ if (childsParent) {
+ nsresult rv = childsParent->RemoveChildLoader(childAsDocLoader);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Make sure to clear the treeowner in case this child is a different type
+ // from us.
+ aChild->SetTreeOwner(nullptr);
+
+ nsresult res = AddChildLoader(childAsDocLoader);
+ NS_ENSURE_SUCCESS(res, res);
+ NS_ASSERTION(!mChildList.IsEmpty(),
+ "child list must not be empty after a successful add");
+
+ /* Set the child's global history if the parent has one */
+ if (mBrowsingContext->GetUseGlobalHistory()) {
+ // childDocShell->SetUseGlobalHistory(true);
+ // this should be set through BC inherit
+ MOZ_ASSERT(aChild->GetBrowsingContext()->GetUseGlobalHistory());
+ }
+
+ if (aChild->ItemType() != mItemType) {
+ return NS_OK;
+ }
+
+ aChild->SetTreeOwner(mTreeOwner);
+
+ nsCOMPtr<nsIDocShell> childAsDocShell(do_QueryInterface(aChild));
+ if (!childAsDocShell) {
+ return NS_OK;
+ }
+
+ // charset, style-disabling, and zoom will be inherited in SetupNewViewer()
+
+ // Now take this document's charset and set the child's parentCharset field
+ // to it. We'll later use that field, in the loading process, for the
+ // charset choosing algorithm.
+ // If we fail, at any point, we just return NS_OK.
+ // This code has some performance impact. But this will be reduced when
+ // the current charset will finally be stored as an Atom, avoiding the
+ // alias resolution extra look-up.
+
+ // we are NOT going to propagate the charset is this Chrome's docshell
+ if (mItemType == nsIDocShellTreeItem::typeChrome) {
+ return NS_OK;
+ }
+
+ // get the parent's current charset
+ if (!mContentViewer) {
+ return NS_OK;
+ }
+ Document* doc = mContentViewer->GetDocument();
+ if (!doc) {
+ return NS_OK;
+ }
+
+ const Encoding* parentCS = doc->GetDocumentCharacterSet();
+ int32_t charsetSource = doc->GetDocumentCharacterSetSource();
+ // set the child's parentCharset
+ childAsDocShell->SetParentCharset(parentCS, charsetSource,
+ doc->NodePrincipal());
+
+ // printf("### 1 >>> Adding child. Parent CS = %s. ItemType = %d.\n",
+ // NS_LossyConvertUTF16toASCII(parentCS).get(), mItemType);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::RemoveChild(nsIDocShellTreeItem* aChild) {
+ NS_ENSURE_ARG_POINTER(aChild);
+
+ RefPtr<nsDocLoader> childAsDocLoader = GetAsDocLoader(aChild);
+ NS_ENSURE_TRUE(childAsDocLoader, NS_ERROR_UNEXPECTED);
+
+ nsresult rv = RemoveChildLoader(childAsDocLoader);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aChild->SetTreeOwner(nullptr);
+
+ return nsDocLoader::AddDocLoaderAsChildOfRoot(childAsDocLoader);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetInProcessChildAt(int32_t aIndex, nsIDocShellTreeItem** aChild) {
+ NS_ENSURE_ARG_POINTER(aChild);
+
+ RefPtr<nsDocShell> child = GetInProcessChildAt(aIndex);
+ NS_ENSURE_TRUE(child, NS_ERROR_UNEXPECTED);
+
+ child.forget(aChild);
+
+ return NS_OK;
+}
+
+nsDocShell* nsDocShell::GetInProcessChildAt(int32_t aIndex) {
+#ifdef DEBUG
+ if (aIndex < 0) {
+ NS_WARNING("Negative index passed to GetChildAt");
+ } else if (static_cast<uint32_t>(aIndex) >= mChildList.Length()) {
+ NS_WARNING("Too large an index passed to GetChildAt");
+ }
+#endif
+
+ nsIDocumentLoader* child = ChildAt(aIndex);
+
+ // child may be nullptr here.
+ return static_cast<nsDocShell*>(child);
+}
+
+nsresult nsDocShell::AddChildSHEntry(nsISHEntry* aCloneRef,
+ nsISHEntry* aNewEntry,
+ int32_t aChildOffset, uint32_t aLoadType,
+ bool aCloneChildren) {
+ MOZ_ASSERT(!mozilla::SessionHistoryInParent());
+ nsresult rv = NS_OK;
+
+ if (mLSHE && aLoadType != LOAD_PUSHSTATE) {
+ /* You get here if you are currently building a
+ * hierarchy ie.,you just visited a frameset page
+ */
+ if (NS_FAILED(mLSHE->ReplaceChild(aNewEntry))) {
+ rv = mLSHE->AddChild(aNewEntry, aChildOffset);
+ }
+ } else if (!aCloneRef) {
+ /* This is an initial load in some subframe. Just append it if we can */
+ if (mOSHE) {
+ rv = mOSHE->AddChild(aNewEntry, aChildOffset, UseRemoteSubframes());
+ }
+ } else {
+ RefPtr<ChildSHistory> shistory = GetRootSessionHistory();
+ if (shistory) {
+ rv = shistory->LegacySHistory()->AddChildSHEntryHelper(
+ aCloneRef, aNewEntry, mBrowsingContext->Top(), aCloneChildren);
+ }
+ }
+ return rv;
+}
+
+nsresult nsDocShell::AddChildSHEntryToParent(nsISHEntry* aNewEntry,
+ int32_t aChildOffset,
+ bool aCloneChildren) {
+ MOZ_ASSERT(!mozilla::SessionHistoryInParent());
+ /* You will get here when you are in a subframe and
+ * a new url has been loaded on you.
+ * The mOSHE in this subframe will be the previous url's
+ * mOSHE. This mOSHE will be used as the identification
+ * for this subframe in the CloneAndReplace function.
+ */
+
+ // In this case, we will end up calling AddEntry, which increases the
+ // current index by 1
+ RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
+ if (rootSH) {
+ mPreviousEntryIndex = rootSH->Index();
+ }
+
+ nsresult rv;
+ // XXX(farre): this is not Fission safe, expect errors. This never
+ // get's executed once session history in the parent is enabled.
+ nsCOMPtr<nsIDocShell> parent = do_QueryInterface(GetAsSupports(mParent), &rv);
+ NS_WARNING_ASSERTION(
+ parent || !UseRemoteSubframes(),
+ "Failed to add child session history entry! This will be resolved once "
+ "session history in the parent is enabled.");
+ if (parent) {
+ rv = nsDocShell::Cast(parent)->AddChildSHEntry(
+ mOSHE, aNewEntry, aChildOffset, mLoadType, aCloneChildren);
+ }
+
+ if (rootSH) {
+ mLoadedEntryIndex = rootSH->Index();
+
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) {
+ MOZ_LOG(gPageCacheLog, LogLevel::Verbose,
+ ("Previous index: %d, Loaded index: %d", mPreviousEntryIndex,
+ mLoadedEntryIndex));
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetCurrentSHEntry(nsISHEntry** aEntry, bool* aOSHE) {
+ *aOSHE = false;
+ *aEntry = nullptr;
+ if (mLSHE) {
+ NS_ADDREF(*aEntry = mLSHE);
+ } else if (mOSHE) {
+ NS_ADDREF(*aEntry = mOSHE);
+ *aOSHE = true;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDocShell::SynchronizeLayoutHistoryState() {
+ if (mActiveEntry && mActiveEntry->GetLayoutHistoryState() &&
+ mBrowsingContext) {
+ if (XRE_IsContentProcess()) {
+ dom::ContentChild* contentChild = dom::ContentChild::GetSingleton();
+ if (contentChild) {
+ contentChild->SendSynchronizeLayoutHistoryState(
+ mBrowsingContext, mActiveEntry->GetLayoutHistoryState());
+ }
+ } else {
+ SessionHistoryEntry* entry =
+ mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry();
+ if (entry) {
+ entry->SetLayoutHistoryState(mActiveEntry->GetLayoutHistoryState());
+ }
+ }
+ if (mLoadingEntry &&
+ mLoadingEntry->mInfo.SharedId() == mActiveEntry->SharedId()) {
+ mLoadingEntry->mInfo.SetLayoutHistoryState(
+ mActiveEntry->GetLayoutHistoryState());
+ }
+ }
+
+ return NS_OK;
+}
+
+void nsDocShell::SetLoadGroupDefaultLoadFlags(nsLoadFlags aLoadFlags) {
+ if (mLoadGroup) {
+ mLoadGroup->SetDefaultLoadFlags(aLoadFlags);
+ } else {
+ NS_WARNING(
+ "nsDocShell::SetLoadGroupDefaultLoadFlags has no loadGroup to "
+ "propagate the mode to");
+ }
+}
+
+nsIScriptGlobalObject* nsDocShell::GetScriptGlobalObject() {
+ NS_ENSURE_SUCCESS(EnsureScriptEnvironment(), nullptr);
+ return mScriptGlobal;
+}
+
+Document* nsDocShell::GetDocument() {
+ NS_ENSURE_SUCCESS(EnsureContentViewer(), nullptr);
+ return mContentViewer->GetDocument();
+}
+
+Document* nsDocShell::GetExtantDocument() {
+ return mContentViewer ? mContentViewer->GetDocument() : nullptr;
+}
+
+nsPIDOMWindowOuter* nsDocShell::GetWindow() {
+ if (NS_FAILED(EnsureScriptEnvironment())) {
+ return nullptr;
+ }
+ return mScriptGlobal;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetDomWindow(mozIDOMWindowProxy** aWindow) {
+ NS_ENSURE_ARG_POINTER(aWindow);
+
+ nsresult rv = EnsureScriptEnvironment();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<nsGlobalWindowOuter> window = mScriptGlobal;
+ window.forget(aWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetMessageManager(ContentFrameMessageManager** aMessageManager) {
+ RefPtr<ContentFrameMessageManager> mm;
+ if (RefPtr<BrowserChild> browserChild = BrowserChild::GetFrom(this)) {
+ mm = browserChild->GetMessageManager();
+ } else if (nsPIDOMWindowOuter* win = GetWindow()) {
+ mm = win->GetMessageManager();
+ }
+ mm.forget(aMessageManager);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetIsNavigating(bool* aOut) {
+ *aOut = mIsNavigating;
+ return NS_OK;
+}
+
+void nsDocShell::ClearFrameHistory(nsISHEntry* aEntry) {
+ MOZ_ASSERT(!mozilla::SessionHistoryInParent());
+ RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
+ if (!rootSH || !aEntry) {
+ return;
+ }
+
+ rootSH->LegacySHistory()->RemoveFrameEntries(aEntry);
+}
+
+//-------------------------------------
+//-- Helper Method for Print discovery
+//-------------------------------------
+bool nsDocShell::NavigationBlockedByPrinting(bool aDisplayErrorDialog) {
+ if (!mBrowsingContext->Top()->GetIsPrinting()) {
+ return false;
+ }
+ if (aDisplayErrorDialog) {
+ DisplayLoadError(NS_ERROR_DOCUMENT_IS_PRINTMODE, nullptr, nullptr, nullptr);
+ }
+ return true;
+}
+
+bool nsDocShell::IsNavigationAllowed(bool aDisplayPrintErrorDialog,
+ bool aCheckIfUnloadFired) {
+ bool isAllowed = !NavigationBlockedByPrinting(aDisplayPrintErrorDialog) &&
+ (!aCheckIfUnloadFired || !mFiredUnloadEvent);
+ if (!isAllowed) {
+ return false;
+ }
+ if (!mContentViewer) {
+ return true;
+ }
+ bool firingBeforeUnload;
+ mContentViewer->GetBeforeUnloadFiring(&firingBeforeUnload);
+ return !firingBeforeUnload;
+}
+
+//*****************************************************************************
+// nsDocShell::nsIWebNavigation
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShell::GetCanGoBack(bool* aCanGoBack) {
+ *aCanGoBack = false;
+ if (!IsNavigationAllowed(false)) {
+ return NS_OK; // JS may not handle returning of an error code
+ }
+ RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
+ if (rootSH) {
+ *aCanGoBack = rootSH->CanGo(-1);
+ MOZ_LOG(gSHLog, LogLevel::Verbose,
+ ("nsDocShell %p CanGoBack()->%d", this, *aCanGoBack));
+
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetCanGoForward(bool* aCanGoForward) {
+ *aCanGoForward = false;
+ if (!IsNavigationAllowed(false)) {
+ return NS_OK; // JS may not handle returning of an error code
+ }
+ RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
+ if (rootSH) {
+ *aCanGoForward = rootSH->CanGo(1);
+ MOZ_LOG(gSHLog, LogLevel::Verbose,
+ ("nsDocShell %p CanGoForward()->%d", this, *aCanGoForward));
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsDocShell::GoBack(bool aRequireUserInteraction, bool aUserActivation) {
+ if (!IsNavigationAllowed()) {
+ return NS_OK; // JS may not handle returning of an error code
+ }
+
+ auto cleanupIsNavigating = MakeScopeExit([&]() { mIsNavigating = false; });
+ mIsNavigating = true;
+
+ RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
+ NS_ENSURE_TRUE(rootSH, NS_ERROR_FAILURE);
+ ErrorResult rv;
+ rootSH->Go(-1, aRequireUserInteraction, aUserActivation, rv);
+ return rv.StealNSResult();
+}
+
+NS_IMETHODIMP
+nsDocShell::GoForward(bool aRequireUserInteraction, bool aUserActivation) {
+ if (!IsNavigationAllowed()) {
+ return NS_OK; // JS may not handle returning of an error code
+ }
+
+ auto cleanupIsNavigating = MakeScopeExit([&]() { mIsNavigating = false; });
+ mIsNavigating = true;
+
+ RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
+ NS_ENSURE_TRUE(rootSH, NS_ERROR_FAILURE);
+ ErrorResult rv;
+ rootSH->Go(1, aRequireUserInteraction, aUserActivation, rv);
+ return rv.StealNSResult();
+}
+
+// XXX(nika): We may want to stop exposing this API in the child process? Going
+// to a specific index from multiple different processes could definitely race.
+NS_IMETHODIMP
+nsDocShell::GotoIndex(int32_t aIndex, bool aUserActivation) {
+ if (!IsNavigationAllowed()) {
+ return NS_OK; // JS may not handle returning of an error code
+ }
+
+ auto cleanupIsNavigating = MakeScopeExit([&]() { mIsNavigating = false; });
+ mIsNavigating = true;
+
+ RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
+ NS_ENSURE_TRUE(rootSH, NS_ERROR_FAILURE);
+
+ ErrorResult rv;
+ rootSH->GotoIndex(aIndex, aIndex - rootSH->Index(), false, aUserActivation,
+ rv);
+ return rv.StealNSResult();
+}
+
+nsresult nsDocShell::LoadURI(nsIURI* aURI,
+ const LoadURIOptions& aLoadURIOptions) {
+ if (!IsNavigationAllowed()) {
+ return NS_OK; // JS may not handle returning of an error code
+ }
+ RefPtr<nsDocShellLoadState> loadState;
+ nsresult rv = nsDocShellLoadState::CreateFromLoadURIOptions(
+ mBrowsingContext, aURI, aLoadURIOptions, getter_AddRefs(loadState));
+ MOZ_ASSERT(rv != NS_ERROR_MALFORMED_URI);
+ if (NS_FAILED(rv) || !loadState) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return LoadURI(loadState, true);
+}
+
+NS_IMETHODIMP
+nsDocShell::LoadURIFromScript(nsIURI* aURI,
+ JS::Handle<JS::Value> aLoadURIOptions,
+ JSContext* aCx) {
+ // generate dictionary for aLoadURIOptions and forward call
+ LoadURIOptions loadURIOptions;
+ if (!loadURIOptions.Init(aCx, aLoadURIOptions)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return LoadURI(aURI, loadURIOptions);
+}
+
+nsresult nsDocShell::FixupAndLoadURIString(
+ const nsAString& aURIString, const LoadURIOptions& aLoadURIOptions) {
+ if (!IsNavigationAllowed()) {
+ return NS_OK; // JS may not handle returning of an error code
+ }
+
+ RefPtr<nsDocShellLoadState> loadState;
+ nsresult rv = nsDocShellLoadState::CreateFromLoadURIOptions(
+ mBrowsingContext, aURIString, aLoadURIOptions, getter_AddRefs(loadState));
+
+ uint32_t loadFlags = aLoadURIOptions.mLoadFlags;
+ if (NS_ERROR_MALFORMED_URI == rv) {
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("Creating an active entry on nsDocShell %p to %s (because "
+ "we're showing an error page)",
+ this, NS_ConvertUTF16toUTF8(aURIString).get()));
+
+ // We need to store a session history entry. We don't have a valid URI, so
+ // we use about:blank instead.
+ nsCOMPtr<nsIURI> uri;
+ MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), "about:blank"_ns));
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal;
+ if (aLoadURIOptions.mTriggeringPrincipal) {
+ triggeringPrincipal = aLoadURIOptions.mTriggeringPrincipal;
+ } else {
+ triggeringPrincipal = nsContentUtils::GetSystemPrincipal();
+ }
+ if (mozilla::SessionHistoryInParent()) {
+ mActiveEntry = MakeUnique<SessionHistoryInfo>(
+ uri, triggeringPrincipal, nullptr, nullptr, nullptr,
+ nsLiteralCString("text/html"));
+ mBrowsingContext->SetActiveSessionHistoryEntry(
+ Nothing(), mActiveEntry.get(), MAKE_LOAD_TYPE(LOAD_NORMAL, loadFlags),
+ /* aUpdatedCacheKey = */ 0);
+ }
+ if (DisplayLoadError(rv, nullptr, PromiseFlatString(aURIString).get(),
+ nullptr) &&
+ (loadFlags & LOAD_FLAGS_ERROR_LOAD_CHANGES_RV) != 0) {
+ return NS_ERROR_LOAD_SHOWED_ERRORPAGE;
+ }
+ }
+
+ if (NS_FAILED(rv) || !loadState) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return LoadURI(loadState, true);
+}
+
+NS_IMETHODIMP
+nsDocShell::FixupAndLoadURIStringFromScript(
+ const nsAString& aURIString, JS::Handle<JS::Value> aLoadURIOptions,
+ JSContext* aCx) {
+ // generate dictionary for aLoadURIOptions and forward call
+ LoadURIOptions loadURIOptions;
+ if (!loadURIOptions.Init(aCx, aLoadURIOptions)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return FixupAndLoadURIString(aURIString, loadURIOptions);
+}
+
+void nsDocShell::UnblockEmbedderLoadEventForFailure(bool aFireFrameErrorEvent) {
+ // If we're not in a content frame, or are at a BrowsingContext tree boundary,
+ // such as the content-chrome boundary, don't fire the error event.
+ if (mBrowsingContext->IsTopContent() || mBrowsingContext->IsChrome()) {
+ return;
+ }
+
+ // If embedder is same-process, then unblocking the load event is already
+ // handled by nsDocLoader. Fire the error event on our embedder element if
+ // requested.
+ //
+ // XXX: Bug 1440212 is looking into potentially changing this behaviour to act
+ // more like the remote case when in-process.
+ RefPtr<Element> element = mBrowsingContext->GetEmbedderElement();
+ if (element) {
+ if (aFireFrameErrorEvent) {
+ if (RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(element)) {
+ if (RefPtr<nsFrameLoader> fl = flo->GetFrameLoader()) {
+ fl->FireErrorEvent();
+ }
+ }
+ }
+ return;
+ }
+
+ // If we have a cross-process parent document, we must notify it that we no
+ // longer block its load event. This is necessary for OOP sub-documents
+ // because error documents do not result in a call to
+ // SendMaybeFireEmbedderLoadEvents via any of the normal call paths.
+ // (Obviously, we must do this before any of the returns below.)
+ RefPtr<BrowserChild> browserChild = BrowserChild::GetFrom(this);
+ if (browserChild) {
+ mozilla::Unused << browserChild->SendMaybeFireEmbedderLoadEvents(
+ aFireFrameErrorEvent ? EmbedderElementEventType::ErrorEvent
+ : EmbedderElementEventType::NoEvent);
+ }
+}
+
+NS_IMETHODIMP
+nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI,
+ const char16_t* aURL, nsIChannel* aFailedChannel,
+ bool* aDisplayedErrorPage) {
+ MOZ_LOG(gDocShellLeakLog, LogLevel::Debug,
+ ("DOCSHELL %p DisplayLoadError %s\n", this,
+ aURI ? aURI->GetSpecOrDefault().get() : ""));
+
+ *aDisplayedErrorPage = false;
+ // Get prompt and string bundle services
+ nsCOMPtr<nsIPrompt> prompter;
+ nsCOMPtr<nsIStringBundle> stringBundle;
+ GetPromptAndStringBundle(getter_AddRefs(prompter),
+ getter_AddRefs(stringBundle));
+
+ NS_ENSURE_TRUE(stringBundle, NS_ERROR_FAILURE);
+ NS_ENSURE_TRUE(prompter, NS_ERROR_FAILURE);
+
+ const char* error = nullptr;
+ // The key used to select the appropriate error message from the properties
+ // file.
+ const char* errorDescriptionID = nullptr;
+ AutoTArray<nsString, 3> formatStrs;
+ bool addHostPort = false;
+ bool isBadStsCertError = false;
+ nsresult rv = NS_OK;
+ nsAutoString messageStr;
+ nsAutoCString cssClass;
+ nsAutoCString errorPage;
+
+ errorPage.AssignLiteral("neterror");
+
+ // Turn the error code into a human readable error message.
+ if (NS_ERROR_UNKNOWN_PROTOCOL == aError) {
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ // Extract the schemes into a comma delimited list.
+ nsAutoCString scheme;
+ aURI->GetScheme(scheme);
+ CopyASCIItoUTF16(scheme, *formatStrs.AppendElement());
+ nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(aURI);
+ while (nestedURI) {
+ nsCOMPtr<nsIURI> tempURI;
+ nsresult rv2;
+ rv2 = nestedURI->GetInnerURI(getter_AddRefs(tempURI));
+ if (NS_SUCCEEDED(rv2) && tempURI) {
+ tempURI->GetScheme(scheme);
+ formatStrs[0].AppendLiteral(", ");
+ AppendASCIItoUTF16(scheme, formatStrs[0]);
+ }
+ nestedURI = do_QueryInterface(tempURI);
+ }
+ error = "unknownProtocolFound";
+ } else if (NS_ERROR_FILE_NOT_FOUND == aError) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ error = "fileNotFound";
+ } else if (NS_ERROR_FILE_ACCESS_DENIED == aError) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ error = "fileAccessDenied";
+ } else if (NS_ERROR_UNKNOWN_HOST == aError) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ // Get the host
+ nsAutoCString host;
+ nsCOMPtr<nsIURI> innermostURI = NS_GetInnermostURI(aURI);
+ innermostURI->GetHost(host);
+ CopyUTF8toUTF16(host, *formatStrs.AppendElement());
+ errorDescriptionID = "dnsNotFound2";
+ error = "dnsNotFound";
+ } else if (NS_ERROR_CONNECTION_REFUSED == aError ||
+ NS_ERROR_PROXY_BAD_GATEWAY == aError) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ addHostPort = true;
+ error = "connectionFailure";
+ } else if (NS_ERROR_NET_INTERRUPT == aError) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ addHostPort = true;
+ error = "netInterrupt";
+ } else if (NS_ERROR_NET_TIMEOUT == aError ||
+ NS_ERROR_PROXY_GATEWAY_TIMEOUT == aError ||
+ NS_ERROR_NET_TIMEOUT_EXTERNAL == aError) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ // Get the host
+ nsAutoCString host;
+ aURI->GetHost(host);
+ CopyUTF8toUTF16(host, *formatStrs.AppendElement());
+ error = "netTimeout";
+ } else if (NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION == aError ||
+ NS_ERROR_CSP_FORM_ACTION_VIOLATION == aError ||
+ NS_ERROR_CSP_NAVIGATE_TO_VIOLATION == aError) {
+ // CSP error
+ cssClass.AssignLiteral("neterror");
+ error = "cspBlocked";
+ } else if (NS_ERROR_XFO_VIOLATION == aError) {
+ // XFO error
+ cssClass.AssignLiteral("neterror");
+ error = "xfoBlocked";
+ } else if (NS_ERROR_GET_MODULE(aError) == NS_ERROR_MODULE_SECURITY) {
+ nsCOMPtr<nsINSSErrorsService> nsserr =
+ do_GetService(NS_NSS_ERRORS_SERVICE_CONTRACTID);
+
+ uint32_t errorClass;
+ if (!nsserr || NS_FAILED(nsserr->GetErrorClass(aError, &errorClass))) {
+ errorClass = nsINSSErrorsService::ERROR_CLASS_SSL_PROTOCOL;
+ }
+
+ nsCOMPtr<nsITransportSecurityInfo> tsi;
+ if (aFailedChannel) {
+ aFailedChannel->GetSecurityInfo(getter_AddRefs(tsi));
+ }
+ if (tsi) {
+ uint32_t securityState;
+ tsi->GetSecurityState(&securityState);
+ if (securityState & nsIWebProgressListener::STATE_USES_SSL_3) {
+ error = "sslv3Used";
+ addHostPort = true;
+ } else if (securityState &
+ nsIWebProgressListener::STATE_USES_WEAK_CRYPTO) {
+ error = "weakCryptoUsed";
+ addHostPort = true;
+ }
+ } else {
+ // No channel, let's obtain the generic error message
+ if (nsserr) {
+ nsserr->GetErrorMessage(aError, messageStr);
+ }
+ }
+ // We don't have a message string here anymore but DisplayLoadError
+ // requires a non-empty messageStr.
+ messageStr.Truncate();
+ messageStr.AssignLiteral(u" ");
+ if (errorClass == nsINSSErrorsService::ERROR_CLASS_BAD_CERT) {
+ error = "nssBadCert";
+
+ // If this is an HTTP Strict Transport Security host or a pinned host
+ // and the certificate is bad, don't allow overrides (RFC 6797 section
+ // 12.1).
+ bool isStsHost = false;
+ bool isPinnedHost = false;
+ OriginAttributes attrsForHSTS;
+ if (aFailedChannel) {
+ StoragePrincipalHelper::GetOriginAttributesForHSTS(aFailedChannel,
+ attrsForHSTS);
+ } else {
+ attrsForHSTS = GetOriginAttributes();
+ }
+
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsISiteSecurityService> sss =
+ do_GetService(NS_SSSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = sss->IsSecureURI(aURI, attrsForHSTS, &isStsHost);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ mozilla::dom::ContentChild* cc =
+ mozilla::dom::ContentChild::GetSingleton();
+ cc->SendIsSecureURI(aURI, attrsForHSTS, &isStsHost);
+ }
+ nsCOMPtr<nsIPublicKeyPinningService> pkps =
+ do_GetService(NS_PKPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = pkps->HostHasPins(aURI, &isPinnedHost);
+
+ if (Preferences::GetBool("browser.xul.error_pages.expert_bad_cert",
+ false)) {
+ cssClass.AssignLiteral("expertBadCert");
+ }
+
+ // HSTS/pinning takes precedence over the expert bad cert pref. We
+ // never want to show the "Add Exception" button for these sites.
+ // In the future we should differentiate between an HSTS host and a
+ // pinned host and display a more informative message to the user.
+ if (isStsHost || isPinnedHost) {
+ isBadStsCertError = true;
+ cssClass.AssignLiteral("badStsCert");
+ }
+
+ // See if an alternate cert error page is registered
+ nsAutoCString alternateErrorPage;
+ nsresult rv = Preferences::GetCString(
+ "security.alternate_certificate_error_page", alternateErrorPage);
+ if (NS_SUCCEEDED(rv)) {
+ errorPage.Assign(alternateErrorPage);
+ }
+ } else {
+ error = "nssFailure2";
+ }
+ } else if (NS_ERROR_PHISHING_URI == aError ||
+ NS_ERROR_MALWARE_URI == aError ||
+ NS_ERROR_UNWANTED_URI == aError ||
+ NS_ERROR_HARMFUL_URI == aError) {
+ nsAutoCString host;
+ aURI->GetHost(host);
+ CopyUTF8toUTF16(host, *formatStrs.AppendElement());
+
+ // Malware and phishing detectors may want to use an alternate error
+ // page, but if the pref's not set, we'll fall back on the standard page
+ nsAutoCString alternateErrorPage;
+ nsresult rv = Preferences::GetCString("urlclassifier.alternate_error_page",
+ alternateErrorPage);
+ if (NS_SUCCEEDED(rv)) {
+ errorPage.Assign(alternateErrorPage);
+ }
+
+ if (NS_ERROR_PHISHING_URI == aError) {
+ error = "deceptiveBlocked";
+ } else if (NS_ERROR_MALWARE_URI == aError) {
+ error = "malwareBlocked";
+ } else if (NS_ERROR_UNWANTED_URI == aError) {
+ error = "unwantedBlocked";
+ } else if (NS_ERROR_HARMFUL_URI == aError) {
+ error = "harmfulBlocked";
+ }
+
+ cssClass.AssignLiteral("blacklist");
+ } else if (NS_ERROR_CONTENT_CRASHED == aError) {
+ errorPage.AssignLiteral("tabcrashed");
+ error = "tabcrashed";
+
+ RefPtr<EventTarget> handler = mChromeEventHandler;
+ if (handler) {
+ nsCOMPtr<Element> element = do_QueryInterface(handler);
+ element->GetAttribute(u"crashedPageTitle"_ns, messageStr);
+ }
+
+ // DisplayLoadError requires a non-empty messageStr to proceed and call
+ // LoadErrorPage. If the page doesn't have a title, we will use a blank
+ // space which will be trimmed and thus treated as empty by the front-end.
+ if (messageStr.IsEmpty()) {
+ messageStr.AssignLiteral(u" ");
+ }
+ } else if (NS_ERROR_FRAME_CRASHED == aError) {
+ errorPage.AssignLiteral("framecrashed");
+ error = "framecrashed";
+ messageStr.AssignLiteral(u" ");
+ } else if (NS_ERROR_BUILDID_MISMATCH == aError) {
+ errorPage.AssignLiteral("restartrequired");
+ error = "restartrequired";
+
+ // DisplayLoadError requires a non-empty messageStr to proceed and call
+ // LoadErrorPage. If the page doesn't have a title, we will use a blank
+ // space which will be trimmed and thus treated as empty by the front-end.
+ if (messageStr.IsEmpty()) {
+ messageStr.AssignLiteral(u" ");
+ }
+ } else {
+ // Errors requiring simple formatting
+ switch (aError) {
+ case NS_ERROR_MALFORMED_URI:
+ // URI is malformed
+ error = "malformedURI";
+ errorDescriptionID = "malformedURI2";
+ break;
+ case NS_ERROR_REDIRECT_LOOP:
+ // Doc failed to load because the server generated too many redirects
+ error = "redirectLoop";
+ break;
+ case NS_ERROR_UNKNOWN_SOCKET_TYPE:
+ // Doc failed to load because PSM is not installed
+ error = "unknownSocketType";
+ break;
+ case NS_ERROR_NET_RESET:
+ // Doc failed to load because the server kept reseting the connection
+ // before we could read any data from it
+ error = "netReset";
+ break;
+ case NS_ERROR_DOCUMENT_NOT_CACHED:
+ // Doc failed to load because the cache does not contain a copy of
+ // the document.
+ error = "notCached";
+ break;
+ case NS_ERROR_OFFLINE:
+ // Doc failed to load because we are offline.
+ error = "netOffline";
+ break;
+ case NS_ERROR_DOCUMENT_IS_PRINTMODE:
+ // Doc navigation attempted while Printing or Print Preview
+ error = "isprinting";
+ break;
+ case NS_ERROR_PORT_ACCESS_NOT_ALLOWED:
+ // Port blocked for security reasons
+ addHostPort = true;
+ error = "deniedPortAccess";
+ break;
+ case NS_ERROR_UNKNOWN_PROXY_HOST:
+ // Proxy hostname could not be resolved.
+ error = "proxyResolveFailure";
+ break;
+ case NS_ERROR_PROXY_CONNECTION_REFUSED:
+ case NS_ERROR_PROXY_FORBIDDEN:
+ case NS_ERROR_PROXY_NOT_IMPLEMENTED:
+ case NS_ERROR_PROXY_AUTHENTICATION_FAILED:
+ case NS_ERROR_PROXY_TOO_MANY_REQUESTS:
+ // Proxy connection was refused.
+ error = "proxyConnectFailure";
+ break;
+ case NS_ERROR_INVALID_CONTENT_ENCODING:
+ // Bad Content Encoding.
+ error = "contentEncodingError";
+ break;
+ case NS_ERROR_UNSAFE_CONTENT_TYPE:
+ // Channel refused to load from an unrecognized content type.
+ error = "unsafeContentType";
+ break;
+ case NS_ERROR_CORRUPTED_CONTENT:
+ // Broken Content Detected. e.g. Content-MD5 check failure.
+ error = "corruptedContentErrorv2";
+ break;
+ case NS_ERROR_INTERCEPTION_FAILED:
+ // ServiceWorker intercepted request, but something went wrong.
+ error = "corruptedContentErrorv2";
+ break;
+ case NS_ERROR_NET_INADEQUATE_SECURITY:
+ // Server negotiated bad TLS for HTTP/2.
+ error = "inadequateSecurityError";
+ addHostPort = true;
+ break;
+ case NS_ERROR_BLOCKED_BY_POLICY:
+ case NS_ERROR_DOM_COOP_FAILED:
+ case NS_ERROR_DOM_COEP_FAILED:
+ // Page blocked by policy
+ error = "blockedByPolicy";
+ break;
+ case NS_ERROR_NET_HTTP2_SENT_GOAWAY:
+ case NS_ERROR_NET_HTTP3_PROTOCOL_ERROR:
+ // HTTP/2 or HTTP/3 stack detected a protocol error
+ error = "networkProtocolError";
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ nsresult delegateErrorCode = aError;
+ // If the HTTPS-Only Mode upgraded this request and the upgrade might have
+ // caused this error, we replace the error-page with about:httpsonlyerror
+ if (nsHTTPSOnlyUtils::CouldBeHttpsOnlyError(aFailedChannel, aError)) {
+ errorPage.AssignLiteral("httpsonlyerror");
+ delegateErrorCode = NS_ERROR_HTTPS_ONLY;
+ } else if (isBadStsCertError) {
+ delegateErrorCode = NS_ERROR_BAD_HSTS_CERT;
+ }
+
+ if (nsCOMPtr<nsILoadURIDelegate> loadURIDelegate = GetLoadURIDelegate()) {
+ nsCOMPtr<nsIURI> errorPageURI;
+ rv = loadURIDelegate->HandleLoadError(
+ aURI, delegateErrorCode, NS_ERROR_GET_MODULE(delegateErrorCode),
+ getter_AddRefs(errorPageURI));
+ // If the docshell is going away there's no point in showing an error page.
+ if (NS_FAILED(rv) || mIsBeingDestroyed) {
+ *aDisplayedErrorPage = false;
+ return NS_OK;
+ }
+
+ if (errorPageURI) {
+ *aDisplayedErrorPage =
+ NS_SUCCEEDED(LoadErrorPage(errorPageURI, aURI, aFailedChannel));
+ return NS_OK;
+ }
+ }
+
+ // Test if the error should be displayed
+ if (!error) {
+ return NS_OK;
+ }
+
+ if (!errorDescriptionID) {
+ errorDescriptionID = error;
+ }
+
+ Telemetry::AccumulateCategoricalKeyed(
+ IsSubframe() ? "frame"_ns : "top"_ns,
+ mozilla::dom::LoadErrorToTelemetryLabel(aError));
+
+ // Test if the error needs to be formatted
+ if (!messageStr.IsEmpty()) {
+ // already obtained message
+ } else {
+ if (addHostPort) {
+ // Build up the host:port string.
+ nsAutoCString hostport;
+ if (aURI) {
+ aURI->GetHostPort(hostport);
+ } else {
+ hostport.Assign('?');
+ }
+ CopyUTF8toUTF16(hostport, *formatStrs.AppendElement());
+ }
+
+ nsAutoCString spec;
+ rv = NS_ERROR_NOT_AVAILABLE;
+ auto& nextFormatStr = *formatStrs.AppendElement();
+ if (aURI) {
+ // displaying "file://" is aesthetically unpleasing and could even be
+ // confusing to the user
+ if (SchemeIsFile(aURI)) {
+ aURI->GetPathQueryRef(spec);
+ } else {
+ aURI->GetSpec(spec);
+ }
+
+ nsCOMPtr<nsITextToSubURI> textToSubURI(
+ do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ rv = textToSubURI->UnEscapeURIForUI(spec, nextFormatStr);
+ }
+ } else {
+ spec.Assign('?');
+ }
+ if (NS_FAILED(rv)) {
+ CopyUTF8toUTF16(spec, nextFormatStr);
+ }
+ rv = NS_OK;
+
+ nsAutoString str;
+ rv =
+ stringBundle->FormatStringFromName(errorDescriptionID, formatStrs, str);
+ NS_ENSURE_SUCCESS(rv, rv);
+ messageStr.Assign(str);
+ }
+
+ // Display the error as a page or an alert prompt
+ NS_ENSURE_FALSE(messageStr.IsEmpty(), NS_ERROR_FAILURE);
+
+ if ((NS_ERROR_NET_INTERRUPT == aError || NS_ERROR_NET_RESET == aError) &&
+ SchemeIsHTTPS(aURI)) {
+ // Maybe TLS intolerant. Treat this as an SSL error.
+ error = "nssFailure2";
+ }
+
+ if (mBrowsingContext->GetUseErrorPages()) {
+ // Display an error page
+ nsresult loadedPage =
+ LoadErrorPage(aURI, aURL, errorPage.get(), error, messageStr.get(),
+ cssClass.get(), aFailedChannel);
+ *aDisplayedErrorPage = NS_SUCCEEDED(loadedPage);
+ } else {
+ // The prompter reqires that our private window has a document (or it
+ // asserts). Satisfy that assertion now since GetDoc will force
+ // creation of one if it hasn't already been created.
+ if (mScriptGlobal) {
+ Unused << mScriptGlobal->GetDoc();
+ }
+
+ // Display a message box
+ prompter->Alert(nullptr, messageStr.get());
+ }
+
+ return NS_OK;
+}
+
+#define PREF_SAFEBROWSING_ALLOWOVERRIDE "browser.safebrowsing.allowOverride"
+
+nsresult nsDocShell::LoadErrorPage(nsIURI* aURI, const char16_t* aURL,
+ const char* aErrorPage,
+ const char* aErrorType,
+ const char16_t* aDescription,
+ const char* aCSSClass,
+ nsIChannel* aFailedChannel) {
+ if (mIsBeingDestroyed) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+#if defined(DEBUG)
+ if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) {
+ nsAutoCString chanName;
+ if (aFailedChannel) {
+ aFailedChannel->GetName(chanName);
+ } else {
+ chanName.AssignLiteral("<no channel>");
+ }
+
+ MOZ_LOG(gDocShellLog, LogLevel::Debug,
+ ("nsDocShell[%p]::LoadErrorPage(\"%s\", \"%s\", {...}, [%s])\n",
+ this, aURI ? aURI->GetSpecOrDefault().get() : "",
+ NS_ConvertUTF16toUTF8(aURL).get(), chanName.get()));
+ }
+#endif
+
+ nsAutoCString url;
+ if (aURI) {
+ nsresult rv = aURI->GetSpec(url);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (aURL) {
+ CopyUTF16toUTF8(MakeStringSpan(aURL), url);
+ } else {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ // Create a URL to pass all the error information through to the page.
+
+#undef SAFE_ESCAPE
+#define SAFE_ESCAPE(output, input, params) \
+ if (NS_WARN_IF(!NS_Escape(input, output, params))) { \
+ return NS_ERROR_OUT_OF_MEMORY; \
+ }
+
+ nsCString escapedUrl, escapedError, escapedDescription, escapedCSSClass;
+ SAFE_ESCAPE(escapedUrl, url, url_Path);
+ SAFE_ESCAPE(escapedError, nsDependentCString(aErrorType), url_Path);
+ SAFE_ESCAPE(escapedDescription, NS_ConvertUTF16toUTF8(aDescription),
+ url_Path);
+ if (aCSSClass) {
+ nsCString cssClass(aCSSClass);
+ SAFE_ESCAPE(escapedCSSClass, cssClass, url_Path);
+ }
+ nsCString errorPageUrl("about:");
+ errorPageUrl.AppendASCII(aErrorPage);
+ errorPageUrl.AppendLiteral("?e=");
+
+ errorPageUrl.AppendASCII(escapedError.get());
+ errorPageUrl.AppendLiteral("&u=");
+ errorPageUrl.AppendASCII(escapedUrl.get());
+ if ((strcmp(aErrorPage, "blocked") == 0) &&
+ Preferences::GetBool(PREF_SAFEBROWSING_ALLOWOVERRIDE, true)) {
+ errorPageUrl.AppendLiteral("&o=1");
+ }
+ if (!escapedCSSClass.IsEmpty()) {
+ errorPageUrl.AppendLiteral("&s=");
+ errorPageUrl.AppendASCII(escapedCSSClass.get());
+ }
+ errorPageUrl.AppendLiteral("&c=UTF-8");
+
+ nsCOMPtr<nsICaptivePortalService> cps = do_GetService(NS_CAPTIVEPORTAL_CID);
+ int32_t cpsState;
+ if (cps && NS_SUCCEEDED(cps->GetState(&cpsState)) &&
+ cpsState == nsICaptivePortalService::LOCKED_PORTAL) {
+ errorPageUrl.AppendLiteral("&captive=true");
+ }
+
+ errorPageUrl.AppendLiteral("&d=");
+ errorPageUrl.AppendASCII(escapedDescription.get());
+
+ nsCOMPtr<nsIURI> errorPageURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(errorPageURI), errorPageUrl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return LoadErrorPage(errorPageURI, aURI, aFailedChannel);
+}
+
+nsresult nsDocShell::LoadErrorPage(nsIURI* aErrorURI, nsIURI* aFailedURI,
+ nsIChannel* aFailedChannel) {
+ mFailedChannel = aFailedChannel;
+ mFailedURI = aFailedURI;
+ mFailedLoadType = mLoadType;
+
+ if (mLSHE) {
+ // Abandon mLSHE's BFCache entry and create a new one. This way, if
+ // we go back or forward to another SHEntry with the same doc
+ // identifier, the error page won't persist.
+ mLSHE->AbandonBFCacheEntry();
+ }
+
+ RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(aErrorURI);
+ loadState->SetTriggeringPrincipal(nsContentUtils::GetSystemPrincipal());
+ if (mBrowsingContext) {
+ loadState->SetTriggeringSandboxFlags(mBrowsingContext->GetSandboxFlags());
+ }
+ loadState->SetLoadType(LOAD_ERROR_PAGE);
+ loadState->SetFirstParty(true);
+ loadState->SetSourceBrowsingContext(mBrowsingContext);
+ if (mozilla::SessionHistoryInParent() && mLoadingEntry) {
+ // We keep the loading entry for the load that failed here. If the user
+ // reloads we want to try to reload the original load, not the error page.
+ loadState->SetLoadingSessionHistoryInfo(
+ MakeUnique<LoadingSessionHistoryInfo>(*mLoadingEntry));
+ }
+ return InternalLoad(loadState);
+}
+
+NS_IMETHODIMP
+nsDocShell::Reload(uint32_t aReloadFlags) {
+ if (!IsNavigationAllowed()) {
+ return NS_OK; // JS may not handle returning of an error code
+ }
+
+ NS_ASSERTION(((aReloadFlags & INTERNAL_LOAD_FLAGS_LOADURI_SETUP_FLAGS) == 0),
+ "Reload command not updated to use load flags!");
+ NS_ASSERTION((aReloadFlags & EXTRA_LOAD_FLAGS) == 0,
+ "Don't pass these flags to Reload");
+
+ uint32_t loadType = MAKE_LOAD_TYPE(LOAD_RELOAD_NORMAL, aReloadFlags);
+ NS_ENSURE_TRUE(IsValidLoadType(loadType), NS_ERROR_INVALID_ARG);
+
+ // Send notifications to the HistoryListener if any, about the impending
+ // reload
+ RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
+ if (mozilla::SessionHistoryInParent()) {
+ MOZ_LOG(gSHLog, LogLevel::Debug, ("nsDocShell %p Reload", this));
+ bool forceReload = IsForceReloadType(loadType);
+ if (!XRE_IsParentProcess()) {
+ ++mPendingReloadCount;
+ RefPtr<nsDocShell> docShell(this);
+ nsCOMPtr<nsIContentViewer> cv(mContentViewer);
+ NS_ENSURE_STATE(cv);
+
+ bool okToUnload = true;
+ MOZ_TRY(cv->PermitUnload(&okToUnload));
+ if (!okToUnload) {
+ return NS_OK;
+ }
+
+ RefPtr<Document> doc(GetDocument());
+ RefPtr<BrowsingContext> browsingContext(mBrowsingContext);
+ nsCOMPtr<nsIURI> currentURI(mCurrentURI);
+ nsCOMPtr<nsIReferrerInfo> referrerInfo(mReferrerInfo);
+ RefPtr<StopDetector> stopDetector = new StopDetector();
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup) {
+ // loadGroup may be null in theory. In that case stopDetector just
+ // doesn't do anything.
+ loadGroup->AddRequest(stopDetector, nullptr);
+ }
+
+ ContentChild::GetSingleton()->SendNotifyOnHistoryReload(
+ mBrowsingContext, forceReload,
+ [docShell, doc, loadType, browsingContext, currentURI, referrerInfo,
+ loadGroup, stopDetector](
+ std::tuple<bool, Maybe<NotNull<RefPtr<nsDocShellLoadState>>>,
+ Maybe<bool>>&& aResult) {
+ auto scopeExit = MakeScopeExit([loadGroup, stopDetector]() {
+ if (loadGroup) {
+ loadGroup->RemoveRequest(stopDetector, nullptr, NS_OK);
+ }
+ });
+
+ // Decrease mPendingReloadCount before any other early returns!
+ if (--(docShell->mPendingReloadCount) > 0) {
+ return;
+ }
+
+ if (stopDetector->Canceled()) {
+ return;
+ }
+ bool canReload;
+ Maybe<NotNull<RefPtr<nsDocShellLoadState>>> loadState;
+ Maybe<bool> reloadingActiveEntry;
+
+ std::tie(canReload, loadState, reloadingActiveEntry) = aResult;
+
+ if (!canReload) {
+ return;
+ }
+
+ if (loadState.isSome()) {
+ MOZ_LOG(
+ gSHLog, LogLevel::Debug,
+ ("nsDocShell %p Reload - LoadHistoryEntry", docShell.get()));
+ loadState.ref()->SetNotifiedBeforeUnloadListeners(true);
+ docShell->LoadHistoryEntry(loadState.ref(), loadType,
+ reloadingActiveEntry.ref());
+ } else {
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell %p ReloadDocument", docShell.get()));
+ ReloadDocument(docShell, doc, loadType, browsingContext,
+ currentURI, referrerInfo,
+ /* aNotifiedBeforeUnloadListeners */ true);
+ }
+ },
+ [](mozilla::ipc::ResponseRejectReason) {});
+ } else {
+ // Parent process
+ bool canReload = false;
+ Maybe<NotNull<RefPtr<nsDocShellLoadState>>> loadState;
+ Maybe<bool> reloadingActiveEntry;
+ if (!mBrowsingContext->IsDiscarded()) {
+ mBrowsingContext->Canonical()->NotifyOnHistoryReload(
+ forceReload, canReload, loadState, reloadingActiveEntry);
+ }
+ if (canReload) {
+ if (loadState.isSome()) {
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell %p Reload - LoadHistoryEntry", this));
+ LoadHistoryEntry(loadState.ref(), loadType,
+ reloadingActiveEntry.ref());
+ } else {
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell %p ReloadDocument", this));
+ RefPtr<Document> doc = GetDocument();
+ RefPtr<BrowsingContext> bc = mBrowsingContext;
+ nsCOMPtr<nsIURI> currentURI = mCurrentURI;
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = mReferrerInfo;
+ ReloadDocument(this, doc, loadType, bc, currentURI, referrerInfo);
+ }
+ }
+ }
+ return NS_OK;
+ }
+
+ bool canReload = true;
+ if (rootSH) {
+ rootSH->LegacySHistory()->NotifyOnHistoryReload(&canReload);
+ }
+
+ if (!canReload) {
+ return NS_OK;
+ }
+
+ /* If you change this part of code, make sure bug 45297 does not re-occur */
+ if (mOSHE) {
+ nsCOMPtr<nsISHEntry> oshe = mOSHE;
+ return LoadHistoryEntry(
+ oshe, loadType,
+ aReloadFlags & nsIWebNavigation::LOAD_FLAGS_USER_ACTIVATION);
+ }
+
+ if (mLSHE) { // In case a reload happened before the current load is done
+ nsCOMPtr<nsISHEntry> lshe = mLSHE;
+ return LoadHistoryEntry(
+ lshe, loadType,
+ aReloadFlags & nsIWebNavigation::LOAD_FLAGS_USER_ACTIVATION);
+ }
+
+ RefPtr<Document> doc = GetDocument();
+ RefPtr<BrowsingContext> bc = mBrowsingContext;
+ nsCOMPtr<nsIURI> currentURI = mCurrentURI;
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = mReferrerInfo;
+ return ReloadDocument(this, doc, loadType, bc, currentURI, referrerInfo);
+}
+
+/* static */
+nsresult nsDocShell::ReloadDocument(nsDocShell* aDocShell, Document* aDocument,
+ uint32_t aLoadType,
+ BrowsingContext* aBrowsingContext,
+ nsIURI* aCurrentURI,
+ nsIReferrerInfo* aReferrerInfo,
+ bool aNotifiedBeforeUnloadListeners) {
+ if (!aDocument) {
+ return NS_OK;
+ }
+
+ // Do not inherit owner from document
+ uint32_t flags = INTERNAL_LOAD_FLAGS_NONE;
+ nsAutoString srcdoc;
+ nsIURI* baseURI = nullptr;
+ nsCOMPtr<nsIURI> originalURI;
+ nsCOMPtr<nsIURI> resultPrincipalURI;
+ bool loadReplace = false;
+
+ nsIPrincipal* triggeringPrincipal = aDocument->NodePrincipal();
+ nsCOMPtr<nsIContentSecurityPolicy> csp = aDocument->GetCsp();
+ uint32_t triggeringSandboxFlags = aDocument->GetSandboxFlags();
+
+ nsAutoString contentTypeHint;
+ aDocument->GetContentType(contentTypeHint);
+
+ if (aDocument->IsSrcdocDocument()) {
+ aDocument->GetSrcdocData(srcdoc);
+ flags |= INTERNAL_LOAD_FLAGS_IS_SRCDOC;
+ baseURI = aDocument->GetBaseURI();
+ } else {
+ srcdoc = VoidString();
+ }
+ nsCOMPtr<nsIChannel> chan = aDocument->GetChannel();
+ if (chan) {
+ uint32_t loadFlags;
+ chan->GetLoadFlags(&loadFlags);
+ loadReplace = loadFlags & nsIChannel::LOAD_REPLACE;
+ nsCOMPtr<nsIHttpChannel> httpChan(do_QueryInterface(chan));
+ if (httpChan) {
+ httpChan->GetOriginalURI(getter_AddRefs(originalURI));
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = chan->LoadInfo();
+ loadInfo->GetResultPrincipalURI(getter_AddRefs(resultPrincipalURI));
+ }
+
+ if (!triggeringPrincipal) {
+ MOZ_ASSERT(false, "Reload needs a valid triggeringPrincipal");
+ return NS_ERROR_FAILURE;
+ }
+
+ // Stack variables to ensure changes to the member variables don't affect to
+ // the call.
+ nsCOMPtr<nsIURI> currentURI = aCurrentURI;
+
+ // Reload always rewrites result principal URI.
+ Maybe<nsCOMPtr<nsIURI>> emplacedResultPrincipalURI;
+ emplacedResultPrincipalURI.emplace(std::move(resultPrincipalURI));
+
+ RefPtr<WindowContext> context = aBrowsingContext->GetCurrentWindowContext();
+ RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(currentURI);
+ loadState->SetReferrerInfo(aReferrerInfo);
+ loadState->SetOriginalURI(originalURI);
+ loadState->SetMaybeResultPrincipalURI(emplacedResultPrincipalURI);
+ loadState->SetLoadReplace(loadReplace);
+ loadState->SetTriggeringPrincipal(triggeringPrincipal);
+ loadState->SetTriggeringSandboxFlags(triggeringSandboxFlags);
+ loadState->SetPrincipalToInherit(triggeringPrincipal);
+ loadState->SetCsp(csp);
+ loadState->SetInternalLoadFlags(flags);
+ loadState->SetTypeHint(NS_ConvertUTF16toUTF8(contentTypeHint));
+ loadState->SetLoadType(aLoadType);
+ loadState->SetFirstParty(true);
+ loadState->SetSrcdocData(srcdoc);
+ loadState->SetSourceBrowsingContext(aBrowsingContext);
+ loadState->SetBaseURI(baseURI);
+ loadState->SetHasValidUserGestureActivation(
+ context && context->HasValidTransientUserGestureActivation());
+ loadState->SetNotifiedBeforeUnloadListeners(aNotifiedBeforeUnloadListeners);
+ return aDocShell->InternalLoad(loadState);
+}
+
+NS_IMETHODIMP
+nsDocShell::Stop(uint32_t aStopFlags) {
+ // Revoke any pending event related to content viewer restoration
+ mRestorePresentationEvent.Revoke();
+
+ if (mLoadType == LOAD_ERROR_PAGE) {
+ if (mLSHE) {
+ // Since error page loads never unset mLSHE, do so now
+ SetHistoryEntryAndUpdateBC(Some(nullptr), Some<nsISHEntry*>(mLSHE));
+ }
+ mActiveEntryIsLoadingFromSessionHistory = false;
+
+ mFailedChannel = nullptr;
+ mFailedURI = nullptr;
+ }
+
+ if (nsIWebNavigation::STOP_CONTENT & aStopFlags) {
+ // Stop the document loading and animations
+ if (mContentViewer) {
+ nsCOMPtr<nsIContentViewer> cv = mContentViewer;
+ cv->Stop();
+ }
+ } else if (nsIWebNavigation::STOP_NETWORK & aStopFlags) {
+ // Stop the document loading only
+ if (mContentViewer) {
+ RefPtr<Document> doc = mContentViewer->GetDocument();
+ if (doc) {
+ doc->StopDocumentLoad();
+ }
+ }
+ }
+
+ if (nsIWebNavigation::STOP_NETWORK & aStopFlags) {
+ // Suspend any timers that were set for this loader. We'll clear
+ // them out for good in CreateContentViewer.
+ if (mRefreshURIList) {
+ SuspendRefreshURIs();
+ mSavedRefreshURIList.swap(mRefreshURIList);
+ mRefreshURIList = nullptr;
+ }
+
+ // XXXbz We could also pass |this| to nsIURILoader::Stop. That will
+ // just call Stop() on us as an nsIDocumentLoader... We need fewer
+ // redundant apis!
+ Stop();
+
+ // Clear out mChannelToDisconnectOnPageHide. This page won't go in the
+ // BFCache now, and the Stop above will have removed the DocumentChannel
+ // from the loadgroup.
+ mChannelToDisconnectOnPageHide = 0;
+ }
+
+ for (auto* child : mChildList.ForwardRange()) {
+ nsCOMPtr<nsIWebNavigation> shellAsNav(do_QueryObject(child));
+ if (shellAsNav) {
+ shellAsNav->Stop(aStopFlags);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetDocument(Document** aDocument) {
+ NS_ENSURE_ARG_POINTER(aDocument);
+ NS_ENSURE_SUCCESS(EnsureContentViewer(), NS_ERROR_FAILURE);
+
+ RefPtr<Document> doc = mContentViewer->GetDocument();
+ if (!doc) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ doc.forget(aDocument);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetCurrentURI(nsIURI** aURI) {
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsCOMPtr<nsIURI> uri = mCurrentURI;
+ uri.forget(aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetSessionHistoryXPCOM(nsISupports** aSessionHistory) {
+ NS_ENSURE_ARG_POINTER(aSessionHistory);
+ RefPtr<ChildSHistory> shistory = GetSessionHistory();
+ shistory.forget(aSessionHistory);
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsDocShell::nsIWebPageDescriptor
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShell::LoadPageAsViewSource(nsIDocShell* aOtherDocShell,
+ const nsAString& aURI) {
+ if (!aOtherDocShell) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ nsCOMPtr<nsIURI> newURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(newURI), aURI);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ RefPtr<nsDocShellLoadState> loadState;
+ uint32_t cacheKey;
+ auto* otherDocShell = nsDocShell::Cast(aOtherDocShell);
+ if (mozilla::SessionHistoryInParent()) {
+ loadState = new nsDocShellLoadState(newURI);
+ if (!otherDocShell->FillLoadStateFromCurrentEntry(*loadState)) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ cacheKey = otherDocShell->GetCacheKeyFromCurrentEntry().valueOr(0);
+ } else {
+ nsCOMPtr<nsISHEntry> entry;
+ bool isOriginalSHE;
+ otherDocShell->GetCurrentSHEntry(getter_AddRefs(entry), &isOriginalSHE);
+ if (!entry) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+ rv = entry->CreateLoadInfo(getter_AddRefs(loadState));
+ NS_ENSURE_SUCCESS(rv, rv);
+ entry->GetCacheKey(&cacheKey);
+ loadState->SetURI(newURI);
+ loadState->SetSHEntry(nullptr);
+ }
+
+ // We're doing a load of the page, via an API that
+ // is only exposed to system code. The triggering principal for this load
+ // should be the system principal.
+ loadState->SetTriggeringPrincipal(nsContentUtils::GetSystemPrincipal());
+ loadState->SetOriginalURI(nullptr);
+ loadState->SetResultPrincipalURI(nullptr);
+
+ return InternalLoad(loadState, Some(cacheKey));
+}
+
+NS_IMETHODIMP
+nsDocShell::GetCurrentDescriptor(nsISupports** aPageDescriptor) {
+ MOZ_ASSERT(aPageDescriptor, "Null out param?");
+
+ *aPageDescriptor = nullptr;
+
+ nsISHEntry* src = mOSHE ? mOSHE : mLSHE;
+ if (src) {
+ nsCOMPtr<nsISHEntry> dest;
+
+ nsresult rv = src->Clone(getter_AddRefs(dest));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // null out inappropriate cloned attributes...
+ dest->SetParent(nullptr);
+ dest->SetIsSubFrame(false);
+
+ return CallQueryInterface(dest, aPageDescriptor);
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+already_AddRefed<nsIInputStream> nsDocShell::GetPostDataFromCurrentEntry()
+ const {
+ nsCOMPtr<nsIInputStream> postData;
+ if (mozilla::SessionHistoryInParent()) {
+ if (mActiveEntry) {
+ postData = mActiveEntry->GetPostData();
+ } else if (mLoadingEntry) {
+ postData = mLoadingEntry->mInfo.GetPostData();
+ }
+ } else {
+ if (mOSHE) {
+ postData = mOSHE->GetPostData();
+ } else if (mLSHE) {
+ postData = mLSHE->GetPostData();
+ }
+ }
+
+ return postData.forget();
+}
+
+Maybe<uint32_t> nsDocShell::GetCacheKeyFromCurrentEntry() const {
+ if (mozilla::SessionHistoryInParent()) {
+ if (mActiveEntry) {
+ return Some(mActiveEntry->GetCacheKey());
+ }
+
+ if (mLoadingEntry) {
+ return Some(mLoadingEntry->mInfo.GetCacheKey());
+ }
+ } else {
+ if (mOSHE) {
+ return Some(mOSHE->GetCacheKey());
+ }
+
+ if (mLSHE) {
+ return Some(mLSHE->GetCacheKey());
+ }
+ }
+
+ return Nothing();
+}
+
+bool nsDocShell::FillLoadStateFromCurrentEntry(
+ nsDocShellLoadState& aLoadState) {
+ if (mLoadingEntry) {
+ mLoadingEntry->mInfo.FillLoadInfo(aLoadState);
+ return true;
+ }
+ if (mActiveEntry) {
+ mActiveEntry->FillLoadInfo(aLoadState);
+ return true;
+ }
+ return false;
+}
+
+//*****************************************************************************
+// nsDocShell::nsIBaseWindow
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShell::InitWindow(nativeWindow aParentNativeWindow,
+ nsIWidget* aParentWidget, int32_t aX, int32_t aY,
+ int32_t aWidth, int32_t aHeight) {
+ SetParentWidget(aParentWidget);
+ SetPositionAndSize(aX, aY, aWidth, aHeight, 0);
+ NS_ENSURE_TRUE(Initialize(), NS_ERROR_FAILURE);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::Destroy() {
+ // XXX: We allow this function to be called just once. If you are going to
+ // reset new variables in this function, please make sure the variables will
+ // never be re-initialized. Adding assertions to check |mIsBeingDestroyed|
+ // in the setter functions for the variables would be enough.
+ if (mIsBeingDestroyed) {
+ return NS_ERROR_DOCSHELL_DYING;
+ }
+
+ NS_ASSERTION(mItemType == typeContent || mItemType == typeChrome,
+ "Unexpected item type in docshell");
+
+ nsCOMPtr<nsIObserverService> serv = services::GetObserverService();
+ if (serv) {
+ const char* msg = mItemType == typeContent
+ ? NS_WEBNAVIGATION_DESTROY
+ : NS_CHROME_WEBNAVIGATION_DESTROY;
+ serv->NotifyObservers(GetAsSupports(this), msg, nullptr);
+ }
+
+ mIsBeingDestroyed = true;
+
+ // Brak the cycle with the initial client, if present.
+ mInitialClientSource.reset();
+
+ // Make sure we don't record profile timeline markers anymore
+ SetRecordProfileTimelineMarkers(false);
+
+ // Make sure to blow away our mLoadingURI just in case. No loads
+ // from inside this pagehide.
+ mLoadingURI = nullptr;
+
+ // Fire unload event before we blow anything away.
+ (void)FirePageHideNotification(true);
+
+ // Clear pointers to any detached nsEditorData that's lying
+ // around in shistory entries. Breaks cycle. See bug 430921.
+ if (mOSHE) {
+ mOSHE->SetEditorData(nullptr);
+ }
+ if (mLSHE) {
+ mLSHE->SetEditorData(nullptr);
+ }
+
+ // Note: mContentListener can be null if Init() failed and we're being
+ // called from the destructor.
+ if (mContentListener) {
+ mContentListener->DropDocShellReference();
+ mContentListener->SetParentContentListener(nullptr);
+ // Note that we do NOT set mContentListener to null here; that
+ // way if someone tries to do a load in us after this point
+ // the nsDSURIContentListener will block it. All of which
+ // means that we should do this before calling Stop(), of
+ // course.
+ }
+
+ // Stop any URLs that are currently being loaded...
+ Stop(nsIWebNavigation::STOP_ALL);
+
+ mEditorData = nullptr;
+
+ // Save the state of the current document, before destroying the window.
+ // This is needed to capture the state of a frameset when the new document
+ // causes the frameset to be destroyed...
+ PersistLayoutHistoryState();
+
+ // Remove this docshell from its parent's child list
+ nsCOMPtr<nsIDocShellTreeItem> docShellParentAsItem =
+ do_QueryInterface(GetAsSupports(mParent));
+ if (docShellParentAsItem) {
+ docShellParentAsItem->RemoveChild(this);
+ }
+
+ if (mContentViewer) {
+ mContentViewer->Close(nullptr);
+ mContentViewer->Destroy();
+ mContentViewer = nullptr;
+ }
+
+ nsDocLoader::Destroy();
+
+ mParentWidget = nullptr;
+ mCurrentURI = nullptr;
+
+ if (mScriptGlobal) {
+ mScriptGlobal->DetachFromDocShell(!mWillChangeProcess);
+ mScriptGlobal = nullptr;
+ }
+
+ if (GetSessionHistory()) {
+ // We want to destroy these content viewers now rather than
+ // letting their destruction wait for the session history
+ // entries to get garbage collected. (Bug 488394)
+ GetSessionHistory()->EvictLocalContentViewers();
+ }
+
+ if (mWillChangeProcess && !mBrowsingContext->IsDiscarded()) {
+ mBrowsingContext->PrepareForProcessChange();
+ }
+
+ SetTreeOwner(nullptr);
+
+ mBrowserChild = nullptr;
+
+ mChromeEventHandler = nullptr;
+
+ // Cancel any timers that were set for this docshell; this is needed
+ // to break the cycle between us and the timers.
+ CancelRefreshURITimers();
+
+ return NS_OK;
+}
+
+double nsDocShell::GetWidgetCSSToDeviceScale() {
+ if (mParentWidget) {
+ return mParentWidget->GetDefaultScale().scale;
+ }
+ if (nsCOMPtr<nsIBaseWindow> ownerWindow = do_QueryInterface(mTreeOwner)) {
+ return ownerWindow->GetWidgetCSSToDeviceScale();
+ }
+ return 1.0;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetDevicePixelsPerDesktopPixel(double* aScale) {
+ if (mParentWidget) {
+ *aScale = mParentWidget->GetDesktopToDeviceScale().scale;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIBaseWindow> ownerWindow(do_QueryInterface(mTreeOwner));
+ if (ownerWindow) {
+ return ownerWindow->GetDevicePixelsPerDesktopPixel(aScale);
+ }
+
+ *aScale = 1.0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetPosition(int32_t aX, int32_t aY) {
+ mBounds.MoveTo(aX, aY);
+
+ if (mContentViewer) {
+ NS_ENSURE_SUCCESS(mContentViewer->Move(aX, aY), NS_ERROR_FAILURE);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetPositionDesktopPix(int32_t aX, int32_t aY) {
+ nsCOMPtr<nsIBaseWindow> ownerWindow(do_QueryInterface(mTreeOwner));
+ if (ownerWindow) {
+ return ownerWindow->SetPositionDesktopPix(aX, aY);
+ }
+
+ double scale = 1.0;
+ GetDevicePixelsPerDesktopPixel(&scale);
+ return SetPosition(NSToIntRound(aX * scale), NSToIntRound(aY * scale));
+}
+
+NS_IMETHODIMP
+nsDocShell::GetPosition(int32_t* aX, int32_t* aY) {
+ return GetPositionAndSize(aX, aY, nullptr, nullptr);
+}
+
+NS_IMETHODIMP
+nsDocShell::SetSize(int32_t aWidth, int32_t aHeight, bool aRepaint) {
+ int32_t x = 0, y = 0;
+ GetPosition(&x, &y);
+ return SetPositionAndSize(x, y, aWidth, aHeight,
+ aRepaint ? nsIBaseWindow::eRepaint : 0);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetSize(int32_t* aWidth, int32_t* aHeight) {
+ return GetPositionAndSize(nullptr, nullptr, aWidth, aHeight);
+}
+
+NS_IMETHODIMP
+nsDocShell::SetPositionAndSize(int32_t aX, int32_t aY, int32_t aWidth,
+ int32_t aHeight, uint32_t aFlags) {
+ mBounds.SetRect(aX, aY, aWidth, aHeight);
+
+ // Hold strong ref, since SetBounds can make us null out mContentViewer
+ nsCOMPtr<nsIContentViewer> viewer = mContentViewer;
+ if (viewer) {
+ uint32_t cvflags = (aFlags & nsIBaseWindow::eDelayResize)
+ ? nsIContentViewer::eDelayResize
+ : 0;
+ // XXX Border figured in here or is that handled elsewhere?
+ nsresult rv = viewer->SetBoundsWithFlags(mBounds, cvflags);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetPositionAndSize(int32_t* aX, int32_t* aY, int32_t* aWidth,
+ int32_t* aHeight) {
+ if (mParentWidget) {
+ // ensure size is up-to-date if window has changed resolution
+ LayoutDeviceIntRect r = mParentWidget->GetClientBounds();
+ SetPositionAndSize(mBounds.X(), mBounds.Y(), r.Width(), r.Height(), 0);
+ }
+
+ // We should really consider just getting this information from
+ // our window instead of duplicating the storage and code...
+ if (aWidth || aHeight) {
+ // Caller wants to know our size; make sure to give them up to
+ // date information.
+ RefPtr<Document> doc(do_GetInterface(GetAsSupports(mParent)));
+ if (doc) {
+ doc->FlushPendingNotifications(FlushType::Layout);
+ }
+ }
+
+ DoGetPositionAndSize(aX, aY, aWidth, aHeight);
+ return NS_OK;
+}
+
+void nsDocShell::DoGetPositionAndSize(int32_t* aX, int32_t* aY, int32_t* aWidth,
+ int32_t* aHeight) {
+ if (aX) {
+ *aX = mBounds.X();
+ }
+ if (aY) {
+ *aY = mBounds.Y();
+ }
+ if (aWidth) {
+ *aWidth = mBounds.Width();
+ }
+ if (aHeight) {
+ *aHeight = mBounds.Height();
+ }
+}
+
+NS_IMETHODIMP
+nsDocShell::SetDimensions(DimensionRequest&& aRequest) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetDimensions(DimensionKind aDimensionKind, int32_t* aX,
+ int32_t* aY, int32_t* aCX, int32_t* aCY) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShell::Repaint(bool aForce) {
+ PresShell* presShell = GetPresShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+
+ RefPtr<nsViewManager> viewManager = presShell->GetViewManager();
+ NS_ENSURE_TRUE(viewManager, NS_ERROR_FAILURE);
+
+ viewManager->InvalidateAllViews();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetParentWidget(nsIWidget** aParentWidget) {
+ NS_ENSURE_ARG_POINTER(aParentWidget);
+
+ *aParentWidget = mParentWidget;
+ NS_IF_ADDREF(*aParentWidget);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetParentWidget(nsIWidget* aParentWidget) {
+ MOZ_ASSERT(!mIsBeingDestroyed);
+ mParentWidget = aParentWidget;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetParentNativeWindow(nativeWindow* aParentNativeWindow) {
+ NS_ENSURE_ARG_POINTER(aParentNativeWindow);
+
+ if (mParentWidget) {
+ *aParentNativeWindow = mParentWidget->GetNativeData(NS_NATIVE_WIDGET);
+ } else {
+ *aParentNativeWindow = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetParentNativeWindow(nativeWindow aParentNativeWindow) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetNativeHandle(nsAString& aNativeHandle) {
+ // the nativeHandle should be accessed from nsIAppWindow
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetVisibility(bool* aVisibility) {
+ NS_ENSURE_ARG_POINTER(aVisibility);
+
+ *aVisibility = false;
+
+ if (!mContentViewer) {
+ return NS_OK;
+ }
+
+ PresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return NS_OK;
+ }
+
+ // get the view manager
+ nsViewManager* vm = presShell->GetViewManager();
+ NS_ENSURE_TRUE(vm, NS_ERROR_FAILURE);
+
+ // get the root view
+ nsView* view = vm->GetRootView(); // views are not ref counted
+ NS_ENSURE_TRUE(view, NS_ERROR_FAILURE);
+
+ // if our root view is hidden, we are not visible
+ if (view->GetVisibility() == ViewVisibility::Hide) {
+ return NS_OK;
+ }
+
+ // otherwise, we must walk up the document and view trees checking
+ // for a hidden view, unless we're an off screen browser, which
+ // would make this test meaningless.
+
+ RefPtr<nsDocShell> docShell = this;
+ RefPtr<nsDocShell> parentItem = docShell->GetInProcessParentDocshell();
+ while (parentItem) {
+ // Null-check for crash in bug 267804
+ if (!parentItem->GetPresShell()) {
+ MOZ_ASSERT_UNREACHABLE("parent docshell has null pres shell");
+ return NS_OK;
+ }
+
+ vm = docShell->GetPresShell()->GetViewManager();
+ if (vm) {
+ view = vm->GetRootView();
+ }
+
+ if (view) {
+ view = view->GetParent(); // anonymous inner view
+ if (view) {
+ view = view->GetParent(); // subdocumentframe's view
+ }
+ }
+
+ nsIFrame* frame = view ? view->GetFrame() : nullptr;
+ if (frame && !frame->IsVisibleConsideringAncestors(
+ nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) {
+ return NS_OK;
+ }
+
+ docShell = parentItem;
+ parentItem = docShell->GetInProcessParentDocshell();
+ }
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin(do_QueryInterface(mTreeOwner));
+ if (!treeOwnerAsWin) {
+ *aVisibility = true;
+ return NS_OK;
+ }
+
+ // Check with the tree owner as well to give embedders a chance to
+ // expose visibility as well.
+ nsresult rv = treeOwnerAsWin->GetVisibility(aVisibility);
+ if (rv == NS_ERROR_NOT_IMPLEMENTED) {
+ // The tree owner had no opinion on our visibility.
+ *aVisibility = true;
+ return NS_OK;
+ }
+ return rv;
+}
+
+void nsDocShell::ActivenessMaybeChanged() {
+ const bool isActive = mBrowsingContext->IsActive();
+ if (RefPtr<PresShell> presShell = GetPresShell()) {
+ presShell->ActivenessMaybeChanged();
+ }
+
+ // Tell the window about it
+ if (mScriptGlobal) {
+ mScriptGlobal->SetIsBackground(!isActive);
+ if (RefPtr<Document> doc = mScriptGlobal->GetExtantDoc()) {
+ // Update orientation when the top-level browsing context becomes active.
+ if (isActive && mBrowsingContext->IsTop()) {
+ // We only care about the top-level browsing context.
+ auto orientation = mBrowsingContext->GetOrientationLock();
+ ScreenOrientation::UpdateActiveOrientationLock(orientation);
+ }
+
+ doc->PostVisibilityUpdateEvent();
+ }
+ }
+
+ // Tell the nsDOMNavigationTiming about it
+ RefPtr<nsDOMNavigationTiming> timing = mTiming;
+ if (!timing && mContentViewer) {
+ if (Document* doc = mContentViewer->GetDocument()) {
+ timing = doc->GetNavigationTiming();
+ }
+ }
+ if (timing) {
+ timing->NotifyDocShellStateChanged(
+ isActive ? nsDOMNavigationTiming::DocShellState::eActive
+ : nsDOMNavigationTiming::DocShellState::eInactive);
+ }
+
+ // Restart or stop meta refresh timers if necessary
+ if (mDisableMetaRefreshWhenInactive) {
+ if (isActive) {
+ ResumeRefreshURIs();
+ } else {
+ SuspendRefreshURIs();
+ }
+ }
+
+ if (InputTaskManager::CanSuspendInputEvent()) {
+ mBrowsingContext->Group()->UpdateInputTaskManagerIfNeeded(isActive);
+ }
+}
+
+NS_IMETHODIMP
+nsDocShell::SetDefaultLoadFlags(uint32_t aDefaultLoadFlags) {
+ if (!mWillChangeProcess) {
+ // Intentionally ignoring handling discarded browsing contexts.
+ Unused << mBrowsingContext->SetDefaultLoadFlags(aDefaultLoadFlags);
+ } else {
+ // Bug 1623565: DevTools tries to clean up defaultLoadFlags on
+ // shutdown. Sorry DevTools, your DocShell is in another process.
+ NS_WARNING("nsDocShell::SetDefaultLoadFlags called on Zombie DocShell");
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetDefaultLoadFlags(uint32_t* aDefaultLoadFlags) {
+ *aDefaultLoadFlags = mBrowsingContext->GetDefaultLoadFlags();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetFailedChannel(nsIChannel** aFailedChannel) {
+ NS_ENSURE_ARG_POINTER(aFailedChannel);
+ Document* doc = GetDocument();
+ if (!doc) {
+ *aFailedChannel = nullptr;
+ return NS_OK;
+ }
+ NS_IF_ADDREF(*aFailedChannel = doc->GetFailedChannel());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetVisibility(bool aVisibility) {
+ // Show()/Hide() may change mContentViewer.
+ nsCOMPtr<nsIContentViewer> cv = mContentViewer;
+ if (!cv) {
+ return NS_OK;
+ }
+ if (aVisibility) {
+ cv->Show();
+ } else {
+ cv->Hide();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetEnabled(bool* aEnabled) {
+ NS_ENSURE_ARG_POINTER(aEnabled);
+ *aEnabled = true;
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetEnabled(bool aEnabled) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+nsDocShell::GetMainWidget(nsIWidget** aMainWidget) {
+ // We don't create our own widget, so simply return the parent one.
+ return GetParentWidget(aMainWidget);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetTitle(nsAString& aTitle) {
+ aTitle = mTitle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetTitle(const nsAString& aTitle) {
+ // Avoid unnecessary updates of the title if the URI and the title haven't
+ // changed.
+ if (mTitleValidForCurrentURI && mTitle == aTitle) {
+ return NS_OK;
+ }
+
+ // Store local title
+ mTitle = aTitle;
+ mTitleValidForCurrentURI = true;
+
+ // When title is set on the top object it should then be passed to the
+ // tree owner.
+ if (mBrowsingContext->IsTop()) {
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin(do_QueryInterface(mTreeOwner));
+ if (treeOwnerAsWin) {
+ treeOwnerAsWin->SetTitle(aTitle);
+ }
+ }
+
+ if (mCurrentURI && mLoadType != LOAD_ERROR_PAGE) {
+ UpdateGlobalHistoryTitle(mCurrentURI);
+ }
+
+ // Update SessionHistory with the document's title.
+ if (mLoadType != LOAD_BYPASS_HISTORY && mLoadType != LOAD_ERROR_PAGE) {
+ SetTitleOnHistoryEntry(true);
+ }
+
+ return NS_OK;
+}
+
+void nsDocShell::SetTitleOnHistoryEntry(bool aUpdateEntryInSessionHistory) {
+ if (mOSHE) {
+ mOSHE->SetTitle(mTitle);
+ }
+
+ if (mActiveEntry && mBrowsingContext) {
+ mActiveEntry->SetTitle(mTitle);
+ if (aUpdateEntryInSessionHistory) {
+ if (XRE_IsParentProcess()) {
+ SessionHistoryEntry* entry =
+ mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry();
+ if (entry) {
+ entry->SetTitle(mTitle);
+ }
+ } else {
+ mozilla::Unused
+ << ContentChild::GetSingleton()->SendSessionHistoryEntryTitle(
+ mBrowsingContext, mTitle);
+ }
+ }
+ }
+}
+
+nsPoint nsDocShell::GetCurScrollPos() {
+ nsPoint scrollPos;
+ if (nsIScrollableFrame* sf = GetRootScrollFrame()) {
+ scrollPos = sf->GetVisualViewportOffset();
+ }
+ return scrollPos;
+}
+
+nsresult nsDocShell::SetCurScrollPosEx(int32_t aCurHorizontalPos,
+ int32_t aCurVerticalPos) {
+ nsIScrollableFrame* sf = GetRootScrollFrame();
+ NS_ENSURE_TRUE(sf, NS_ERROR_FAILURE);
+
+ ScrollMode scrollMode =
+ sf->IsSmoothScroll() ? ScrollMode::SmoothMsd : ScrollMode::Instant;
+
+ nsPoint targetPos(aCurHorizontalPos, aCurVerticalPos);
+ sf->ScrollTo(targetPos, scrollMode);
+
+ // Set the visual viewport offset as well.
+
+ RefPtr<PresShell> presShell = GetPresShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+
+ nsPresContext* presContext = presShell->GetPresContext();
+ NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE);
+
+ // Only the root content document can have a distinct visual viewport offset.
+ if (!presContext->IsRootContentDocumentCrossProcess()) {
+ return NS_OK;
+ }
+
+ // Not on a platform with a distinct visual viewport - don't bother setting
+ // the visual viewport offset.
+ if (!presShell->IsVisualViewportSizeSet()) {
+ return NS_OK;
+ }
+
+ presShell->ScrollToVisual(targetPos, layers::FrameMetrics::eMainThread,
+ scrollMode);
+
+ return NS_OK;
+}
+
+void nsDocShell::SetScrollbarPreference(mozilla::ScrollbarPreference aPref) {
+ if (mScrollbarPref == aPref) {
+ return;
+ }
+ mScrollbarPref = aPref;
+ auto* ps = GetPresShell();
+ if (!ps) {
+ return;
+ }
+ nsIFrame* scrollFrame = ps->GetRootScrollFrame();
+ if (!scrollFrame) {
+ return;
+ }
+ ps->FrameNeedsReflow(scrollFrame,
+ IntrinsicDirty::FrameAncestorsAndDescendants,
+ NS_FRAME_IS_DIRTY);
+}
+
+//*****************************************************************************
+// nsDocShell::nsIRefreshURI
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShell::RefreshURI(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ uint32_t aDelay) {
+ MOZ_ASSERT(!mIsBeingDestroyed);
+
+ NS_ENSURE_ARG(aURI);
+
+ /* Check if Meta refresh/redirects are permitted. Some
+ * embedded applications may not want to do this.
+ * Must do this before sending out NOTIFY_REFRESH events
+ * because listeners may have side effects (e.g. displaying a
+ * button to manually trigger the refresh later).
+ */
+ bool allowRedirects = true;
+ GetAllowMetaRedirects(&allowRedirects);
+ if (!allowRedirects) {
+ return NS_OK;
+ }
+
+ // If any web progress listeners are listening for NOTIFY_REFRESH events,
+ // give them a chance to block this refresh.
+ bool sameURI;
+ nsresult rv = aURI->Equals(mCurrentURI, &sameURI);
+ if (NS_FAILED(rv)) {
+ sameURI = false;
+ }
+ if (!RefreshAttempted(this, aURI, aDelay, sameURI)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsITimerCallback> refreshTimer =
+ new nsRefreshTimer(this, aURI, aPrincipal, aDelay);
+
+ BusyFlags busyFlags = GetBusyFlags();
+
+ if (!mRefreshURIList) {
+ mRefreshURIList = nsArray::Create();
+ }
+
+ if (busyFlags & BUSY_FLAGS_BUSY ||
+ (!mBrowsingContext->IsActive() && mDisableMetaRefreshWhenInactive)) {
+ // We don't want to create the timer right now. Instead queue up the
+ // request and trigger the timer in EndPageLoad() or whenever we become
+ // active.
+ mRefreshURIList->AppendElement(refreshTimer);
+ } else {
+ // There is no page loading going on right now. Create the
+ // timer and fire it right away.
+ nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
+ NS_ENSURE_TRUE(win, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsITimer> timer;
+ MOZ_TRY_VAR(timer, NS_NewTimerWithCallback(refreshTimer, aDelay,
+ nsITimer::TYPE_ONE_SHOT));
+
+ mRefreshURIList->AppendElement(timer); // owning timer ref
+ }
+ return NS_OK;
+}
+
+nsresult nsDocShell::ForceRefreshURIFromTimer(nsIURI* aURI,
+ nsIPrincipal* aPrincipal,
+ uint32_t aDelay,
+ nsITimer* aTimer) {
+ MOZ_ASSERT(aTimer, "Must have a timer here");
+
+ // Remove aTimer from mRefreshURIList if needed
+ if (mRefreshURIList) {
+ uint32_t n = 0;
+ mRefreshURIList->GetLength(&n);
+
+ for (uint32_t i = 0; i < n; ++i) {
+ nsCOMPtr<nsITimer> timer = do_QueryElementAt(mRefreshURIList, i);
+ if (timer == aTimer) {
+ mRefreshURIList->RemoveElementAt(i);
+ break;
+ }
+ }
+ }
+
+ return ForceRefreshURI(aURI, aPrincipal, aDelay);
+}
+
+NS_IMETHODIMP
+nsDocShell::ForceRefreshURI(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ uint32_t aDelay) {
+ NS_ENSURE_ARG(aURI);
+
+ RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(aURI);
+ loadState->SetOriginalURI(mCurrentURI);
+ loadState->SetResultPrincipalURI(aURI);
+ loadState->SetResultPrincipalURIIsSome(true);
+ loadState->SetKeepResultPrincipalURIIfSet(true);
+ loadState->SetIsMetaRefresh(true);
+
+ // Set the triggering pricipal to aPrincipal if available, or current
+ // document's principal otherwise.
+ nsCOMPtr<nsIPrincipal> principal = aPrincipal;
+ RefPtr<Document> doc = GetDocument();
+ if (!principal) {
+ if (!doc) {
+ return NS_ERROR_FAILURE;
+ }
+ principal = doc->NodePrincipal();
+ }
+ loadState->SetTriggeringPrincipal(principal);
+ if (doc) {
+ loadState->SetCsp(doc->GetCsp());
+ loadState->SetHasValidUserGestureActivation(
+ doc->HasValidTransientUserGestureActivation());
+ loadState->SetTriggeringSandboxFlags(doc->GetSandboxFlags());
+ }
+
+ loadState->SetPrincipalIsExplicit(true);
+
+ /* Check if this META refresh causes a redirection
+ * to another site.
+ */
+ bool equalUri = false;
+ nsresult rv = aURI->Equals(mCurrentURI, &equalUri);
+
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+ if (NS_SUCCEEDED(rv) && !equalUri && aDelay <= REFRESH_REDIRECT_TIMER) {
+ /* It is a META refresh based redirection within the threshold time
+ * we have in mind (15000 ms as defined by REFRESH_REDIRECT_TIMER).
+ * Pass a REPLACE flag to LoadURI().
+ */
+ loadState->SetLoadType(LOAD_REFRESH_REPLACE);
+
+ /* For redirects we mimic HTTP, which passes the
+ * original referrer.
+ * We will pass in referrer but will not send to server
+ */
+ if (mReferrerInfo) {
+ referrerInfo = static_cast<ReferrerInfo*>(mReferrerInfo.get())
+ ->CloneWithNewSendReferrer(false);
+ }
+ } else {
+ loadState->SetLoadType(LOAD_REFRESH);
+ /* We do need to pass in a referrer, but we don't want it to
+ * be sent to the server.
+ * For most refreshes the current URI is an appropriate
+ * internal referrer.
+ */
+ referrerInfo = new ReferrerInfo(mCurrentURI, ReferrerPolicy::_empty, false);
+ }
+
+ loadState->SetReferrerInfo(referrerInfo);
+ loadState->SetLoadFlags(
+ nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL);
+ loadState->SetFirstParty(true);
+
+ /*
+ * LoadURI(...) will cancel all refresh timers... This causes the
+ * Timer and its refreshData instance to be released...
+ */
+ LoadURI(loadState, false);
+
+ return NS_OK;
+}
+
+static const char16_t* SkipASCIIWhitespace(const char16_t* aStart,
+ const char16_t* aEnd) {
+ const char16_t* iter = aStart;
+ while (iter != aEnd && mozilla::IsAsciiWhitespace(*iter)) {
+ ++iter;
+ }
+ return iter;
+}
+
+static std::tuple<const char16_t*, const char16_t*> ExtractURLString(
+ const char16_t* aPosition, const char16_t* aEnd) {
+ MOZ_ASSERT(aPosition != aEnd);
+
+ // 1. Let urlString be the substring of input from the code point at
+ // position to the end of the string.
+ const char16_t* urlStart = aPosition;
+ const char16_t* urlEnd = aEnd;
+
+ // 2. If the code point in input pointed to by position is U+0055 (U) or
+ // U+0075 (u), then advance position to the next code point.
+ // Otherwise, jump to the step labeled skip quotes.
+ if (*aPosition == 'U' || *aPosition == 'u') {
+ ++aPosition;
+
+ // 3. If the code point in input pointed to by position is U+0052 (R) or
+ // U+0072 (r), then advance position to the next code point.
+ // Otherwise, jump to the step labeled parse.
+ if (aPosition == aEnd || (*aPosition != 'R' && *aPosition != 'r')) {
+ return std::make_tuple(urlStart, urlEnd);
+ }
+
+ ++aPosition;
+
+ // 4. If the code point in input pointed to by position is U+004C (L) or
+ // U+006C (l), then advance position to the next code point.
+ // Otherwise, jump to the step labeled parse.
+ if (aPosition == aEnd || (*aPosition != 'L' && *aPosition != 'l')) {
+ return std::make_tuple(urlStart, urlEnd);
+ }
+
+ ++aPosition;
+
+ // 5. Skip ASCII whitespace within input given position.
+ aPosition = SkipASCIIWhitespace(aPosition, aEnd);
+
+ // 6. If the code point in input pointed to by position is U+003D (=),
+ // then advance position to the next code point. Otherwise, jump to
+ // the step labeled parse.
+ if (aPosition == aEnd || *aPosition != '=') {
+ return std::make_tuple(urlStart, urlEnd);
+ }
+
+ ++aPosition;
+
+ // 7. Skip ASCII whitespace within input given position.
+ aPosition = SkipASCIIWhitespace(aPosition, aEnd);
+ }
+
+ // 8. Skip quotes: If the code point in input pointed to by position is
+ // U+0027 (') or U+0022 ("), then let quote be that code point, and
+ // advance position to the next code point. Otherwise, let quote be
+ // the empty string.
+ Maybe<char> quote;
+ if (aPosition != aEnd && (*aPosition == '\'' || *aPosition == '"')) {
+ quote.emplace(*aPosition);
+ ++aPosition;
+ }
+
+ // 9. Set urlString to the substring of input from the code point at
+ // position to the end of the string.
+ urlStart = aPosition;
+ urlEnd = aEnd;
+
+ // 10. If quote is not the empty string, and there is a code point in
+ // urlString equal to quote, then truncate urlString at that code
+ // point, so that it and all subsequent code points are removed.
+ const char16_t* quotePos;
+ if (quote.isSome() &&
+ (quotePos = nsCharTraits<char16_t>::find(
+ urlStart, std::distance(urlStart, aEnd), quote.value()))) {
+ urlEnd = quotePos;
+ }
+
+ return std::make_tuple(urlStart, urlEnd);
+}
+
+void nsDocShell::SetupRefreshURIFromHeader(Document* aDocument,
+ const nsAString& aHeader) {
+ if (mIsBeingDestroyed) {
+ return;
+ }
+
+ const char16_t* position = aHeader.BeginReading();
+ const char16_t* end = aHeader.EndReading();
+
+ // See
+ // https://html.spec.whatwg.org/#pragma-directives:shared-declarative-refresh-steps.
+
+ // 3. Skip ASCII whitespace
+ position = SkipASCIIWhitespace(position, end);
+
+ // 4. Let time be 0.
+ CheckedInt<uint32_t> milliSeconds;
+
+ // 5. Collect a sequence of code points that are ASCII digits
+ const char16_t* digitsStart = position;
+ while (position != end && mozilla::IsAsciiDigit(*position)) {
+ ++position;
+ }
+
+ if (position == digitsStart) {
+ // 6. If timeString is the empty string, then:
+ // 1. If the code point in input pointed to by position is not U+002E
+ // (.), then return.
+ if (position == end || *position != '.') {
+ return;
+ }
+ } else {
+ // 7. Otherwise, set time to the result of parsing timeString using the
+ // rules for parsing non-negative integers.
+ nsContentUtils::ParseHTMLIntegerResultFlags result;
+ uint32_t seconds =
+ nsContentUtils::ParseHTMLInteger(digitsStart, position, &result);
+ MOZ_ASSERT(!(result & nsContentUtils::eParseHTMLInteger_Negative));
+ if (result & nsContentUtils::eParseHTMLInteger_Error) {
+ // The spec assumes no errors here (since we only pass ASCII digits in),
+ // but we can still overflow, so this block should deal with that (and
+ // only that).
+ MOZ_ASSERT(!(result & nsContentUtils::eParseHTMLInteger_ErrorOverflow));
+ return;
+ }
+ MOZ_ASSERT(
+ !(result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput));
+
+ milliSeconds = seconds;
+ milliSeconds *= 1000;
+ if (!milliSeconds.isValid()) {
+ return;
+ }
+ }
+
+ // 8. Collect a sequence of code points that are ASCII digits and U+002E FULL
+ // STOP characters (.) from input given position. Ignore any collected
+ // characters.
+ while (position != end &&
+ (mozilla::IsAsciiDigit(*position) || *position == '.')) {
+ ++position;
+ }
+
+ // 9. Let urlRecord be document's URL.
+ nsCOMPtr<nsIURI> urlRecord(aDocument->GetDocumentURI());
+
+ // 10. If position is not past the end of input
+ if (position != end) {
+ // 1. If the code point in input pointed to by position is not U+003B (;),
+ // U+002C (,), or ASCII whitespace, then return.
+ if (*position != ';' && *position != ',' &&
+ !mozilla::IsAsciiWhitespace(*position)) {
+ return;
+ }
+
+ // 2. Skip ASCII whitespace within input given position.
+ position = SkipASCIIWhitespace(position, end);
+
+ // 3. If the code point in input pointed to by position is U+003B (;) or
+ // U+002C (,), then advance position to the next code point.
+ if (position != end && (*position == ';' || *position == ',')) {
+ ++position;
+
+ // 4. Skip ASCII whitespace within input given position.
+ position = SkipASCIIWhitespace(position, end);
+ }
+
+ // 11. If position is not past the end of input, then:
+ if (position != end) {
+ const char16_t* urlStart;
+ const char16_t* urlEnd;
+
+ // 1-10. See ExtractURLString.
+ std::tie(urlStart, urlEnd) = ExtractURLString(position, end);
+
+ // 11. Parse: Parse urlString relative to document. If that fails, return.
+ // Otherwise, set urlRecord to the resulting URL record.
+ nsresult rv =
+ NS_NewURI(getter_AddRefs(urlRecord),
+ Substring(urlStart, std::distance(urlStart, urlEnd)),
+ /* charset = */ nullptr, aDocument->GetDocBaseURI());
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+ }
+
+ nsIPrincipal* principal = aDocument->NodePrincipal();
+ nsCOMPtr<nsIScriptSecurityManager> securityManager =
+ nsContentUtils::GetSecurityManager();
+ nsresult rv = securityManager->CheckLoadURIWithPrincipal(
+ principal, urlRecord,
+ nsIScriptSecurityManager::LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT,
+ aDocument->InnerWindowID());
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ bool isjs = true;
+ rv = NS_URIChainHasFlags(
+ urlRecord, nsIProtocolHandler::URI_OPENING_EXECUTES_SCRIPT, &isjs);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ if (isjs) {
+ return;
+ }
+
+ RefreshURI(urlRecord, principal, milliSeconds.value());
+}
+
+static void DoCancelRefreshURITimers(nsIMutableArray* aTimerList) {
+ if (!aTimerList) {
+ return;
+ }
+
+ uint32_t n = 0;
+ aTimerList->GetLength(&n);
+
+ while (n) {
+ nsCOMPtr<nsITimer> timer(do_QueryElementAt(aTimerList, --n));
+
+ aTimerList->RemoveElementAt(n); // bye bye owning timer ref
+
+ if (timer) {
+ timer->Cancel();
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsDocShell::CancelRefreshURITimers() {
+ DoCancelRefreshURITimers(mRefreshURIList);
+ DoCancelRefreshURITimers(mSavedRefreshURIList);
+ DoCancelRefreshURITimers(mBFCachedRefreshURIList);
+ mRefreshURIList = nullptr;
+ mSavedRefreshURIList = nullptr;
+ mBFCachedRefreshURIList = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetRefreshPending(bool* aResult) {
+ if (!mRefreshURIList) {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ uint32_t count;
+ nsresult rv = mRefreshURIList->GetLength(&count);
+ if (NS_SUCCEEDED(rv)) {
+ *aResult = (count != 0);
+ }
+ return rv;
+}
+
+void nsDocShell::RefreshURIToQueue() {
+ if (mRefreshURIList) {
+ uint32_t n = 0;
+ mRefreshURIList->GetLength(&n);
+
+ for (uint32_t i = 0; i < n; ++i) {
+ nsCOMPtr<nsITimer> timer = do_QueryElementAt(mRefreshURIList, i);
+ if (!timer) {
+ continue; // this must be a nsRefreshURI already
+ }
+
+ // Replace this timer object with a nsRefreshTimer object.
+ nsCOMPtr<nsITimerCallback> callback;
+ timer->GetCallback(getter_AddRefs(callback));
+
+ timer->Cancel();
+
+ mRefreshURIList->ReplaceElementAt(callback, i);
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsDocShell::SuspendRefreshURIs() {
+ RefreshURIToQueue();
+
+ // Suspend refresh URIs for our child shells as well.
+ for (auto* child : mChildList.ForwardRange()) {
+ nsCOMPtr<nsIDocShell> shell = do_QueryObject(child);
+ if (shell) {
+ shell->SuspendRefreshURIs();
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::ResumeRefreshURIs() {
+ RefreshURIFromQueue();
+
+ // Resume refresh URIs for our child shells as well.
+ for (auto* child : mChildList.ForwardRange()) {
+ nsCOMPtr<nsIDocShell> shell = do_QueryObject(child);
+ if (shell) {
+ shell->ResumeRefreshURIs();
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsDocShell::RefreshURIFromQueue() {
+ if (!mRefreshURIList) {
+ return NS_OK;
+ }
+ uint32_t n = 0;
+ mRefreshURIList->GetLength(&n);
+
+ while (n) {
+ nsCOMPtr<nsITimerCallback> refreshInfo =
+ do_QueryElementAt(mRefreshURIList, --n);
+
+ if (refreshInfo) {
+ // This is the nsRefreshTimer object, waiting to be
+ // setup in a timer object and fired.
+ // Create the timer and trigger it.
+ uint32_t delay = static_cast<nsRefreshTimer*>(
+ static_cast<nsITimerCallback*>(refreshInfo))
+ ->GetDelay();
+ nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
+ if (win) {
+ nsCOMPtr<nsITimer> timer;
+ NS_NewTimerWithCallback(getter_AddRefs(timer), refreshInfo, delay,
+ nsITimer::TYPE_ONE_SHOT);
+
+ if (timer) {
+ // Replace the nsRefreshTimer element in the queue with
+ // its corresponding timer object, so that in case another
+ // load comes through before the timer can go off, the timer will
+ // get cancelled in CancelRefreshURITimer()
+ mRefreshURIList->ReplaceElementAt(timer, n);
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+static bool IsFollowupPartOfMultipart(nsIRequest* aRequest) {
+ nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
+ bool firstPart = false;
+ return multiPartChannel &&
+ NS_SUCCEEDED(multiPartChannel->GetIsFirstPart(&firstPart)) &&
+ !firstPart;
+}
+
+nsresult nsDocShell::Embed(nsIContentViewer* aContentViewer,
+ WindowGlobalChild* aWindowActor,
+ bool aIsTransientAboutBlank, bool aPersist,
+ nsIRequest* aRequest, nsIURI* aPreviousURI) {
+ // Save the LayoutHistoryState of the previous document, before
+ // setting up new document
+ PersistLayoutHistoryState();
+
+ nsresult rv = SetupNewViewer(aContentViewer, aWindowActor);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // XXX What if SetupNewViewer fails?
+ if (mozilla::SessionHistoryInParent() ? !!mLoadingEntry : !!mLSHE) {
+ // Set history.state
+ SetDocCurrentStateObj(mLSHE,
+ mLoadingEntry ? &mLoadingEntry->mInfo : nullptr);
+ }
+
+ if (mLSHE) {
+ // Restore the editing state, if it's stored in session history.
+ if (mLSHE->HasDetachedEditor()) {
+ ReattachEditorToWindow(mLSHE);
+ }
+
+ SetHistoryEntryAndUpdateBC(Nothing(), Some<nsISHEntry*>(mLSHE));
+ }
+
+ if (!aIsTransientAboutBlank && mozilla::SessionHistoryInParent() &&
+ !IsFollowupPartOfMultipart(aRequest)) {
+ bool expired = false;
+ uint32_t cacheKey = 0;
+ nsCOMPtr<nsICacheInfoChannel> cacheChannel = do_QueryInterface(aRequest);
+ if (cacheChannel) {
+ // Check if the page has expired from cache
+ uint32_t expTime = 0;
+ cacheChannel->GetCacheTokenExpirationTime(&expTime);
+ uint32_t now = PRTimeToSeconds(PR_Now());
+ if (expTime <= now) {
+ expired = true;
+ }
+
+ // The checks for updating cache key are similar to the old session
+ // history in OnNewURI. Try to update the cache key if
+ // - we should update session history and aren't doing a session
+ // history load.
+ // - we're doing a forced reload.
+ if (((!mLoadingEntry || !mLoadingEntry->mLoadIsFromSessionHistory) &&
+ mBrowsingContext->ShouldUpdateSessionHistory(mLoadType)) ||
+ IsForceReloadType(mLoadType)) {
+ cacheChannel->GetCacheKey(&cacheKey);
+ }
+ }
+
+ MOZ_LOG(gSHLog, LogLevel::Debug, ("document %p Embed", this));
+ MoveLoadingToActiveEntry(aPersist, expired, cacheKey, aPreviousURI);
+ }
+
+ bool updateHistory = true;
+
+ // Determine if this type of load should update history
+ switch (mLoadType) {
+ case LOAD_NORMAL_REPLACE:
+ case LOAD_REFRESH_REPLACE:
+ case LOAD_STOP_CONTENT_AND_REPLACE:
+ case LOAD_RELOAD_BYPASS_CACHE:
+ case LOAD_RELOAD_BYPASS_PROXY:
+ case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
+ case LOAD_REPLACE_BYPASS_CACHE:
+ updateHistory = false;
+ break;
+ default:
+ break;
+ }
+
+ if (!updateHistory) {
+ SetLayoutHistoryState(nullptr);
+ }
+
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsDocShell::nsIWebProgressListener
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShell::OnProgressChange(nsIWebProgress* aProgress, nsIRequest* aRequest,
+ int32_t aCurSelfProgress, int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::OnStateChange(nsIWebProgress* aProgress, nsIRequest* aRequest,
+ uint32_t aStateFlags, nsresult aStatus) {
+ if ((~aStateFlags & (STATE_START | STATE_IS_NETWORK)) == 0) {
+ // Save timing statistics.
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ nsAutoCString aURI;
+ uri->GetAsciiSpec(aURI);
+
+ if (this == aProgress) {
+ mozilla::Unused << MaybeInitTiming();
+ mTiming->NotifyFetchStart(uri,
+ ConvertLoadTypeToNavigationType(mLoadType));
+ // If we are starting a DocumentChannel, we need to pass the timing
+ // statistics so that should a process switch occur, the starting type can
+ // be passed to the new DocShell running in the other content process.
+ if (RefPtr<DocumentChannel> docChannel = do_QueryObject(aRequest)) {
+ docChannel->SetNavigationTiming(mTiming);
+ }
+ }
+
+ // Page has begun to load
+ mBusyFlags = (BusyFlags)(BUSY_FLAGS_BUSY | BUSY_FLAGS_BEFORE_PAGE_LOAD);
+
+ if ((aStateFlags & STATE_RESTORING) == 0) {
+ // Show the progress cursor if the pref is set
+ if (StaticPrefs::ui_use_activity_cursor()) {
+ nsCOMPtr<nsIWidget> mainWidget;
+ GetMainWidget(getter_AddRefs(mainWidget));
+ if (mainWidget) {
+ mainWidget->SetCursor(nsIWidget::Cursor{eCursor_spinning});
+ }
+ }
+
+ if (StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) {
+ if (IsForceReloadType(mLoadType)) {
+ if (WindowContext* windowContext =
+ mBrowsingContext->GetCurrentWindowContext()) {
+ SessionStoreChild::From(windowContext->GetWindowGlobalChild())
+ ->ResetSessionStore(mBrowsingContext,
+ mBrowsingContext->GetSessionStoreEpoch());
+ }
+ }
+ }
+ }
+ } else if ((~aStateFlags & (STATE_TRANSFERRING | STATE_IS_DOCUMENT)) == 0) {
+ // Page is loading
+ mBusyFlags = (BusyFlags)(BUSY_FLAGS_BUSY | BUSY_FLAGS_PAGE_LOADING);
+ } else if ((aStateFlags & STATE_STOP) && (aStateFlags & STATE_IS_NETWORK)) {
+ // Page has finished loading
+ mBusyFlags = BUSY_FLAGS_NONE;
+
+ // Hide the progress cursor if the pref is set
+ if (StaticPrefs::ui_use_activity_cursor()) {
+ nsCOMPtr<nsIWidget> mainWidget;
+ GetMainWidget(getter_AddRefs(mainWidget));
+ if (mainWidget) {
+ mainWidget->SetCursor(nsIWidget::Cursor{eCursor_standard});
+ }
+ }
+ }
+
+ if ((~aStateFlags & (STATE_IS_DOCUMENT | STATE_STOP)) == 0) {
+ nsCOMPtr<nsIWebProgress> webProgress =
+ do_QueryInterface(GetAsSupports(this));
+ // Is the document stop notification for this document?
+ if (aProgress == webProgress.get()) {
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
+ EndPageLoad(aProgress, channel, aStatus);
+ }
+ }
+ // note that redirect state changes will go through here as well, but it
+ // is better to handle those in OnRedirectStateChange where more
+ // information is available.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::OnLocationChange(nsIWebProgress* aProgress, nsIRequest* aRequest,
+ nsIURI* aURI, uint32_t aFlags) {
+ // Since we've now changed Documents, notify the BrowsingContext that we've
+ // changed. Ideally we'd just let the BrowsingContext do this when it
+ // changes the current window global, but that happens before this and we
+ // have a lot of tests that depend on the specific ordering of messages.
+ bool isTopLevel = false;
+ if (XRE_IsParentProcess() &&
+ !(aFlags & nsIWebProgressListener::LOCATION_CHANGE_SAME_DOCUMENT) &&
+ NS_SUCCEEDED(aProgress->GetIsTopLevel(&isTopLevel)) && isTopLevel) {
+ GetBrowsingContext()->Canonical()->UpdateSecurityState();
+ }
+ return NS_OK;
+}
+
+void nsDocShell::OnRedirectStateChange(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel,
+ uint32_t aRedirectFlags,
+ uint32_t aStateFlags) {
+ NS_ASSERTION(aStateFlags & STATE_REDIRECTING,
+ "Calling OnRedirectStateChange when there is no redirect");
+
+ if (!(aStateFlags & STATE_IS_DOCUMENT)) {
+ return; // not a toplevel document
+ }
+
+ nsCOMPtr<nsIURI> oldURI, newURI;
+ aOldChannel->GetURI(getter_AddRefs(oldURI));
+ aNewChannel->GetURI(getter_AddRefs(newURI));
+ if (!oldURI || !newURI) {
+ return;
+ }
+
+ // DocumentChannel adds redirect chain to global history in the parent
+ // process. The redirect chain can't be queried from the content process, so
+ // there's no need to update global history here.
+ RefPtr<DocumentChannel> docChannel = do_QueryObject(aOldChannel);
+ if (!docChannel) {
+ // Below a URI visit is saved (see AddURIVisit method doc).
+ // The visit chain looks something like:
+ // ...
+ // Site N - 1
+ // => Site N
+ // (redirect to =>) Site N + 1 (we are here!)
+
+ // Get N - 1 and transition type
+ nsCOMPtr<nsIURI> previousURI;
+ uint32_t previousFlags = 0;
+ ExtractLastVisit(aOldChannel, getter_AddRefs(previousURI), &previousFlags);
+
+ if (aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL ||
+ net::ChannelIsPost(aOldChannel)) {
+ // 1. Internal redirects are ignored because they are specific to the
+ // channel implementation.
+ // 2. POSTs are not saved by global history.
+ //
+ // Regardless, we need to propagate the previous visit to the new
+ // channel.
+ SaveLastVisit(aNewChannel, previousURI, previousFlags);
+ } else {
+ // Get the HTTP response code, if available.
+ uint32_t responseStatus = 0;
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aOldChannel);
+ if (httpChannel) {
+ Unused << httpChannel->GetResponseStatus(&responseStatus);
+ }
+
+ // Add visit N -1 => N
+ AddURIVisit(oldURI, previousURI, previousFlags, responseStatus);
+
+ // Since N + 1 could be the final destination, we will not save N => N + 1
+ // here. OnNewURI will do that, so we will cache it.
+ SaveLastVisit(aNewChannel, oldURI, aRedirectFlags);
+ }
+ }
+
+ if (!(aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) &&
+ mLoadType & (LOAD_CMD_RELOAD | LOAD_CMD_HISTORY)) {
+ mLoadType = LOAD_NORMAL_REPLACE;
+ SetHistoryEntryAndUpdateBC(Some(nullptr), Nothing());
+ }
+}
+
+NS_IMETHODIMP
+nsDocShell::OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ nsresult aStatus, const char16_t* aMessage) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
+ uint32_t aState) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, uint32_t aEvent) {
+ MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)");
+ return NS_OK;
+}
+
+already_AddRefed<nsIURIFixupInfo> nsDocShell::KeywordToURI(
+ const nsACString& aKeyword, bool aIsPrivateContext) {
+ nsCOMPtr<nsIURIFixupInfo> info;
+ if (!XRE_IsContentProcess()) {
+ nsCOMPtr<nsIURIFixup> uriFixup = components::URIFixup::Service();
+ if (uriFixup) {
+ uriFixup->KeywordToURI(aKeyword, aIsPrivateContext, getter_AddRefs(info));
+ }
+ }
+ return info.forget();
+}
+
+/* static */
+already_AddRefed<nsIURI> nsDocShell::MaybeFixBadCertDomainErrorURI(
+ nsIChannel* aChannel, nsIURI* aUrl) {
+ if (!aChannel) {
+ return nullptr;
+ }
+
+ nsresult rv = NS_OK;
+ nsAutoCString host;
+ rv = aUrl->GetAsciiHost(host);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ // No point in going further if "www." is included in the hostname
+ // already. That is the only hueristic we're applying in this function.
+ if (StringBeginsWith(host, "www."_ns)) {
+ return nullptr;
+ }
+
+ // Return if fixup enable pref is turned off.
+ if (!mozilla::StaticPrefs::security_bad_cert_domain_error_url_fix_enabled()) {
+ return nullptr;
+ }
+
+ // Return if scheme is not HTTPS.
+ if (!SchemeIsHTTPS(aUrl)) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsILoadInfo> info = aChannel->LoadInfo();
+ if (!info) {
+ return nullptr;
+ }
+
+ // Skip doing the fixup if our channel was redirected, because we
+ // shouldn't be guessing things about the post-redirect URI.
+ if (!info->RedirectChain().IsEmpty()) {
+ return nullptr;
+ }
+
+ int32_t port = 0;
+ rv = aUrl->GetPort(&port);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ // Don't fix up hosts with ports.
+ if (port != -1) {
+ return nullptr;
+ }
+
+ // Don't fix up localhost url.
+ if (host == "localhost") {
+ return nullptr;
+ }
+
+ // Don't fix up hostnames with IP address.
+ if (net_IsValidIPv4Addr(host) || net_IsValidIPv6Addr(host)) {
+ return nullptr;
+ }
+
+ nsAutoCString userPass;
+ rv = aUrl->GetUserPass(userPass);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ // Security - URLs with user / password info should NOT be modified.
+ if (!userPass.IsEmpty()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsITransportSecurityInfo> tsi;
+ rv = aChannel->GetSecurityInfo(getter_AddRefs(tsi));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ if (NS_WARN_IF(!tsi)) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIX509Cert> cert;
+ rv = tsi->GetServerCert(getter_AddRefs(cert));
+ if (NS_WARN_IF(NS_FAILED(rv) || !cert)) {
+ return nullptr;
+ }
+
+ nsTArray<uint8_t> certBytes;
+ rv = cert->GetRawDER(certBytes);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ mozilla::pkix::Input serverCertInput;
+ mozilla::pkix::Result rv1 =
+ serverCertInput.Init(certBytes.Elements(), certBytes.Length());
+ if (rv1 != mozilla::pkix::Success) {
+ return nullptr;
+ }
+
+ nsAutoCString newHost("www."_ns);
+ newHost.Append(host);
+
+ mozilla::pkix::Input newHostInput;
+ rv1 = newHostInput.Init(
+ BitwiseCast<const uint8_t*, const char*>(newHost.BeginReading()),
+ newHost.Length());
+ if (rv1 != mozilla::pkix::Success) {
+ return nullptr;
+ }
+
+ // Check if adding a "www." prefix to the request's hostname will
+ // cause the response's certificate to match.
+ rv1 = mozilla::pkix::CheckCertHostname(serverCertInput, newHostInput);
+ if (rv1 != mozilla::pkix::Success) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> newURI;
+ Unused << NS_MutateURI(aUrl).SetHost(newHost).Finalize(
+ getter_AddRefs(newURI));
+
+ return newURI.forget();
+}
+
+/* static */
+already_AddRefed<nsIURI> nsDocShell::AttemptURIFixup(
+ nsIChannel* aChannel, nsresult aStatus,
+ const mozilla::Maybe<nsCString>& aOriginalURIString, uint32_t aLoadType,
+ bool aIsTopFrame, bool aAllowKeywordFixup, bool aUsePrivateBrowsing,
+ bool aNotifyKeywordSearchLoading, nsIInputStream** aNewPostData) {
+ if (aStatus != NS_ERROR_UNKNOWN_HOST && aStatus != NS_ERROR_NET_RESET &&
+ aStatus != NS_ERROR_CONNECTION_REFUSED &&
+ aStatus !=
+ mozilla::psm::GetXPCOMFromNSSError(SSL_ERROR_BAD_CERT_DOMAIN)) {
+ return nullptr;
+ }
+
+ if (!(aLoadType == LOAD_NORMAL && aIsTopFrame) && !aAllowKeywordFixup) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIURI> url;
+ nsresult rv = aChannel->GetURI(getter_AddRefs(url));
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ //
+ // Try and make an alternative URI from the old one
+ //
+ nsCOMPtr<nsIURI> newURI;
+ nsCOMPtr<nsIInputStream> newPostData;
+
+ nsAutoCString oldSpec;
+ url->GetSpec(oldSpec);
+
+ //
+ // First try keyword fixup
+ //
+ nsAutoString keywordProviderName, keywordAsSent;
+ if (aStatus == NS_ERROR_UNKNOWN_HOST && aAllowKeywordFixup) {
+ // we should only perform a keyword search under the following
+ // conditions:
+ // (0) Pref keyword.enabled is true
+ // (1) the url scheme is http (or https)
+ // (2) the url does not have a protocol scheme
+ // If we don't enforce such a policy, then we end up doing
+ // keyword searchs on urls we don't intend like imap, file,
+ // mailbox, etc. This could lead to a security problem where we
+ // send data to the keyword server that we shouldn't be.
+ // Someone needs to clean up keywords in general so we can
+ // determine on a per url basis if we want keywords
+ // enabled...this is just a bandaid...
+ nsAutoCString scheme;
+ Unused << url->GetScheme(scheme);
+ if (Preferences::GetBool("keyword.enabled", false) &&
+ StringBeginsWith(scheme, "http"_ns)) {
+ bool attemptFixup = false;
+ nsAutoCString host;
+ Unused << url->GetHost(host);
+ if (host.FindChar('.') == kNotFound) {
+ attemptFixup = true;
+ } else {
+ // For domains with dots, we check the public suffix validity.
+ nsCOMPtr<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ if (tldService) {
+ nsAutoCString suffix;
+ attemptFixup =
+ NS_SUCCEEDED(tldService->GetKnownPublicSuffix(url, suffix)) &&
+ suffix.IsEmpty();
+ }
+ }
+ if (attemptFixup) {
+ nsCOMPtr<nsIURIFixupInfo> info;
+ // only send non-qualified hosts to the keyword server
+ if (aOriginalURIString && !aOriginalURIString->IsEmpty()) {
+ info = KeywordToURI(*aOriginalURIString, aUsePrivateBrowsing);
+ } else {
+ //
+ // If this string was passed through nsStandardURL by
+ // chance, then it may have been converted from UTF-8 to
+ // ACE, which would result in a completely bogus keyword
+ // query. Here we try to recover the original Unicode
+ // value, but this is not 100% correct since the value may
+ // have been normalized per the IDN normalization rules.
+ //
+ // Since we don't have access to the exact original string
+ // that was entered by the user, this will just have to do.
+ bool isACE;
+ nsAutoCString utf8Host;
+ nsCOMPtr<nsIIDNService> idnSrv =
+ do_GetService(NS_IDNSERVICE_CONTRACTID);
+ if (idnSrv && NS_SUCCEEDED(idnSrv->IsACE(host, &isACE)) && isACE &&
+ NS_SUCCEEDED(idnSrv->ConvertACEtoUTF8(host, utf8Host))) {
+ info = KeywordToURI(utf8Host, aUsePrivateBrowsing);
+
+ } else {
+ info = KeywordToURI(host, aUsePrivateBrowsing);
+ }
+ }
+ if (info) {
+ info->GetPreferredURI(getter_AddRefs(newURI));
+ if (newURI) {
+ info->GetKeywordAsSent(keywordAsSent);
+ info->GetKeywordProviderName(keywordProviderName);
+ info->GetPostData(getter_AddRefs(newPostData));
+ }
+ }
+ }
+ }
+ }
+
+ //
+ // Now try change the address, e.g. turn http://foo into
+ // http://www.foo.com, and if that doesn't work try https with
+ // https://foo and https://www.foo.com.
+ //
+ if (aStatus == NS_ERROR_UNKNOWN_HOST || aStatus == NS_ERROR_NET_RESET) {
+ // Skip fixup for anything except a normal document load
+ // operation on the topframe.
+ bool doCreateAlternate = aLoadType == LOAD_NORMAL && aIsTopFrame;
+
+ if (doCreateAlternate) {
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ nsIPrincipal* principal = loadInfo->TriggeringPrincipal();
+ // Only do this if our channel was loaded directly by the user from the
+ // URL bar or similar (system principal) and not redirected, because we
+ // shouldn't be guessing things about links from other sites, or a
+ // post-redirect URI.
+ doCreateAlternate = principal && principal->IsSystemPrincipal() &&
+ loadInfo->RedirectChain().IsEmpty();
+ }
+ // Test if keyword lookup produced a new URI or not
+ if (doCreateAlternate && newURI) {
+ bool sameURI = false;
+ url->Equals(newURI, &sameURI);
+ if (!sameURI) {
+ // Keyword lookup made a new URI so no need to try
+ // an alternate one.
+ doCreateAlternate = false;
+ }
+ }
+ if (doCreateAlternate) {
+ newURI = nullptr;
+ newPostData = nullptr;
+ keywordProviderName.Truncate();
+ keywordAsSent.Truncate();
+ nsCOMPtr<nsIURIFixup> uriFixup = components::URIFixup::Service();
+ if (uriFixup) {
+ nsCOMPtr<nsIURIFixupInfo> fixupInfo;
+ uriFixup->GetFixupURIInfo(oldSpec,
+ nsIURIFixup::FIXUP_FLAGS_MAKE_ALTERNATE_URI,
+ getter_AddRefs(fixupInfo));
+ if (fixupInfo) {
+ fixupInfo->GetPreferredURI(getter_AddRefs(newURI));
+ }
+ }
+ }
+ } else if (aStatus == NS_ERROR_CONNECTION_REFUSED &&
+ Preferences::GetBool("browser.fixup.fallback-to-https", false)) {
+ // Try HTTPS, since http didn't work
+ if (SchemeIsHTTP(url)) {
+ int32_t port = 0;
+ url->GetPort(&port);
+
+ // Fall back to HTTPS only if port is default
+ if (port == -1) {
+ newURI = nullptr;
+ newPostData = nullptr;
+ Unused << NS_MutateURI(url)
+ .SetScheme("https"_ns)
+ .Finalize(getter_AddRefs(newURI));
+ }
+ }
+ }
+
+ // If we have a SSL_ERROR_BAD_CERT_DOMAIN error, try prefixing the domain name
+ // with www. to see if we can avoid showing the cert error page. For example,
+ // https://example.com -> https://www.example.com.
+ if (aStatus ==
+ mozilla::psm::GetXPCOMFromNSSError(SSL_ERROR_BAD_CERT_DOMAIN)) {
+ newPostData = nullptr;
+ newURI = MaybeFixBadCertDomainErrorURI(aChannel, url);
+ }
+
+ // Did we make a new URI that is different to the old one? If so
+ // load it.
+ //
+ if (newURI) {
+ // Make sure the new URI is different from the old one,
+ // otherwise there's little point trying to load it again.
+ bool sameURI = false;
+ url->Equals(newURI, &sameURI);
+ if (!sameURI) {
+ if (aNewPostData) {
+ newPostData.forget(aNewPostData);
+ }
+ if (aNotifyKeywordSearchLoading) {
+ // This notification is meant for Firefox Health Report so it
+ // can increment counts from the search engine
+ MaybeNotifyKeywordSearchLoading(keywordProviderName, keywordAsSent);
+ }
+ return newURI.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+nsresult nsDocShell::FilterStatusForErrorPage(
+ nsresult aStatus, nsIChannel* aChannel, uint32_t aLoadType,
+ bool aIsTopFrame, bool aUseErrorPages, bool aIsInitialDocument,
+ bool* aSkippedUnknownProtocolNavigation) {
+ // Errors to be shown only on top-level frames
+ if ((aStatus == NS_ERROR_UNKNOWN_HOST ||
+ aStatus == NS_ERROR_CONNECTION_REFUSED ||
+ aStatus == NS_ERROR_UNKNOWN_PROXY_HOST ||
+ aStatus == NS_ERROR_PROXY_CONNECTION_REFUSED ||
+ aStatus == NS_ERROR_PROXY_FORBIDDEN ||
+ aStatus == NS_ERROR_PROXY_NOT_IMPLEMENTED ||
+ aStatus == NS_ERROR_PROXY_AUTHENTICATION_FAILED ||
+ aStatus == NS_ERROR_PROXY_TOO_MANY_REQUESTS ||
+ aStatus == NS_ERROR_MALFORMED_URI ||
+ aStatus == NS_ERROR_BLOCKED_BY_POLICY ||
+ aStatus == NS_ERROR_DOM_COOP_FAILED ||
+ aStatus == NS_ERROR_DOM_COEP_FAILED) &&
+ (aIsTopFrame || aUseErrorPages)) {
+ return aStatus;
+ }
+
+ if (aStatus == NS_ERROR_NET_TIMEOUT ||
+ aStatus == NS_ERROR_NET_TIMEOUT_EXTERNAL ||
+ aStatus == NS_ERROR_PROXY_GATEWAY_TIMEOUT ||
+ aStatus == NS_ERROR_REDIRECT_LOOP ||
+ aStatus == NS_ERROR_UNKNOWN_SOCKET_TYPE ||
+ aStatus == NS_ERROR_NET_INTERRUPT || aStatus == NS_ERROR_NET_RESET ||
+ aStatus == NS_ERROR_PROXY_BAD_GATEWAY || aStatus == NS_ERROR_OFFLINE ||
+ aStatus == NS_ERROR_MALWARE_URI || aStatus == NS_ERROR_PHISHING_URI ||
+ aStatus == NS_ERROR_UNWANTED_URI || aStatus == NS_ERROR_HARMFUL_URI ||
+ aStatus == NS_ERROR_UNSAFE_CONTENT_TYPE ||
+ aStatus == NS_ERROR_INTERCEPTION_FAILED ||
+ aStatus == NS_ERROR_NET_INADEQUATE_SECURITY ||
+ aStatus == NS_ERROR_NET_HTTP2_SENT_GOAWAY ||
+ aStatus == NS_ERROR_NET_HTTP3_PROTOCOL_ERROR ||
+ aStatus == NS_ERROR_DOM_BAD_URI || aStatus == NS_ERROR_FILE_NOT_FOUND ||
+ aStatus == NS_ERROR_FILE_ACCESS_DENIED ||
+ aStatus == NS_ERROR_CORRUPTED_CONTENT ||
+ aStatus == NS_ERROR_INVALID_CONTENT_ENCODING ||
+ NS_ERROR_GET_MODULE(aStatus) == NS_ERROR_MODULE_SECURITY) {
+ // Errors to be shown for any frame
+ return aStatus;
+ }
+
+ if (aStatus == NS_ERROR_UNKNOWN_PROTOCOL) {
+ // For unknown protocols we only display an error if the load is triggered
+ // by the browser itself, or we're replacing the initial document (and
+ // nothing else). Showing the error for page-triggered navigations causes
+ // annoying behavior for users, see bug 1528305.
+ //
+ // We could, maybe, try to detect if this is in response to some user
+ // interaction (like clicking a link, or something else) and maybe show
+ // the error page in that case. But this allows for ctrl+clicking and such
+ // to see the error page.
+ nsCOMPtr<nsILoadInfo> info = aChannel->LoadInfo();
+ if (!info->TriggeringPrincipal()->IsSystemPrincipal() &&
+ StaticPrefs::dom_no_unknown_protocol_error_enabled() &&
+ !aIsInitialDocument) {
+ if (aSkippedUnknownProtocolNavigation) {
+ *aSkippedUnknownProtocolNavigation = true;
+ }
+ return NS_OK;
+ }
+ return aStatus;
+ }
+
+ if (aStatus == NS_ERROR_DOCUMENT_NOT_CACHED) {
+ // Non-caching channels will simply return NS_ERROR_OFFLINE.
+ // Caching channels would have to look at their flags to work
+ // out which error to return. Or we can fix up the error here.
+ if (!(aLoadType & LOAD_CMD_HISTORY)) {
+ return NS_ERROR_OFFLINE;
+ }
+ return aStatus;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsDocShell::EndPageLoad(nsIWebProgress* aProgress,
+ nsIChannel* aChannel, nsresult aStatus) {
+ MOZ_LOG(gDocShellLeakLog, LogLevel::Debug,
+ ("DOCSHELL %p EndPageLoad status: %" PRIx32 "\n", this,
+ static_cast<uint32_t>(aStatus)));
+ if (!aChannel) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ // Make sure to discard the initial client if we never created the initial
+ // about:blank document. Do this before possibly returning from the method
+ // due to an error.
+ mInitialClientSource.reset();
+
+ nsCOMPtr<nsIConsoleReportCollector> reporter = do_QueryInterface(aChannel);
+ if (reporter) {
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ aChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup) {
+ reporter->FlushConsoleReports(loadGroup);
+ } else {
+ reporter->FlushConsoleReports(GetDocument());
+ }
+ }
+
+ nsCOMPtr<nsIURI> url;
+ nsresult rv = aChannel->GetURI(getter_AddRefs(url));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsITimedChannel> timingChannel = do_QueryInterface(aChannel);
+ if (timingChannel) {
+ TimeStamp channelCreationTime;
+ rv = timingChannel->GetChannelCreation(&channelCreationTime);
+ if (NS_SUCCEEDED(rv) && !channelCreationTime.IsNull()) {
+ Telemetry::AccumulateTimeDelta(Telemetry::TOTAL_CONTENT_PAGE_LOAD_TIME,
+ channelCreationTime);
+ }
+ }
+
+ // Timing is picked up by the window, we don't need it anymore
+ mTiming = nullptr;
+
+ // clean up reload state for meta charset
+ if (eCharsetReloadRequested == mCharsetReloadState) {
+ mCharsetReloadState = eCharsetReloadStopOrigional;
+ } else {
+ mCharsetReloadState = eCharsetReloadInit;
+ }
+
+ // Save a pointer to the currently-loading history entry.
+ // nsDocShell::EndPageLoad will clear mLSHE, but we may need this history
+ // entry further down in this method.
+ nsCOMPtr<nsISHEntry> loadingSHE = mLSHE;
+ mozilla::Unused << loadingSHE; // XXX: Not sure if we need this anymore
+
+ //
+ // one of many safeguards that prevent death and destruction if
+ // someone is so very very rude as to bring this window down
+ // during this load handler.
+ //
+ nsCOMPtr<nsIDocShell> kungFuDeathGrip(this);
+
+ // Notify the ContentViewer that the Document has finished loading. This
+ // will cause any OnLoad(...) and PopState(...) handlers to fire.
+ if (!mEODForCurrentDocument && mContentViewer) {
+ mIsExecutingOnLoadHandler = true;
+ nsCOMPtr<nsIContentViewer> contentViewer = mContentViewer;
+ contentViewer->LoadComplete(aStatus);
+ mIsExecutingOnLoadHandler = false;
+
+ mEODForCurrentDocument = true;
+ }
+ /* Check if the httpChannel has any cache-control related response headers,
+ * like no-store, no-cache. If so, update SHEntry so that
+ * when a user goes back/forward to this page, we appropriately do
+ * form value restoration or load from server.
+ */
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
+ if (!httpChannel) {
+ // HttpChannel could be hiding underneath a Multipart channel.
+ GetHttpChannel(aChannel, getter_AddRefs(httpChannel));
+ }
+
+ if (httpChannel) {
+ // figure out if SH should be saving layout state.
+ bool discardLayoutState = ShouldDiscardLayoutState(httpChannel);
+ if (mLSHE && discardLayoutState && (mLoadType & LOAD_CMD_NORMAL) &&
+ (mLoadType != LOAD_BYPASS_HISTORY) && (mLoadType != LOAD_ERROR_PAGE)) {
+ mLSHE->SetSaveLayoutStateFlag(false);
+ }
+ }
+
+ // Clear mLSHE after calling the onLoadHandlers. This way, if the
+ // onLoadHandler tries to load something different in
+ // itself or one of its children, we can deal with it appropriately.
+ if (mLSHE) {
+ mLSHE->SetLoadType(LOAD_HISTORY);
+
+ // Clear the mLSHE reference to indicate document loading is done one
+ // way or another.
+ SetHistoryEntryAndUpdateBC(Some(nullptr), Nothing());
+ }
+ mActiveEntryIsLoadingFromSessionHistory = false;
+
+ // if there's a refresh header in the channel, this method
+ // will set it up for us.
+ if (mBrowsingContext->IsActive() || !mDisableMetaRefreshWhenInactive)
+ RefreshURIFromQueue();
+
+ // Test whether this is the top frame or a subframe
+ bool isTopFrame = mBrowsingContext->IsTop();
+
+ bool hadErrorStatus = false;
+ // If status code indicates an error it means that DocumentChannel already
+ // tried to fixup the uri and failed. Throw an error dialog box here.
+ if (NS_FAILED(aStatus)) {
+ // If we got CONTENT_BLOCKED from EndPageLoad, then we need to fire
+ // the error event to our embedder, since tests are relying on this.
+ // The error event is usually fired by the caller of InternalLoad, but
+ // this particular error can happen asynchronously.
+ // Bug 1629201 is filed for having much clearer decision making around
+ // which cases need error events.
+ bool fireFrameErrorEvent = (aStatus == NS_ERROR_CONTENT_BLOCKED_SHOW_ALT ||
+ aStatus == NS_ERROR_CONTENT_BLOCKED);
+ UnblockEmbedderLoadEventForFailure(fireFrameErrorEvent);
+
+ bool isInitialDocument =
+ !GetExtantDocument() || GetExtantDocument()->IsInitialDocument();
+ bool skippedUnknownProtocolNavigation = false;
+ aStatus = FilterStatusForErrorPage(aStatus, aChannel, mLoadType, isTopFrame,
+ mBrowsingContext->GetUseErrorPages(),
+ isInitialDocument,
+ &skippedUnknownProtocolNavigation);
+ hadErrorStatus = true;
+ if (NS_FAILED(aStatus)) {
+ if (!mIsBeingDestroyed) {
+ DisplayLoadError(aStatus, url, nullptr, aChannel);
+ }
+ } else if (skippedUnknownProtocolNavigation) {
+ nsTArray<nsString> params;
+ if (NS_FAILED(
+ NS_GetSanitizedURIStringFromURI(url, *params.AppendElement()))) {
+ params.LastElement().AssignLiteral(u"(unknown uri)");
+ }
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::warningFlag, "DOM"_ns, GetExtantDocument(),
+ nsContentUtils::eDOM_PROPERTIES, "UnknownProtocolNavigationPrevented",
+ params);
+ }
+ } else {
+ // If we have a host
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ PredictorLearnRedirect(url, aChannel, loadInfo->GetOriginAttributes());
+ }
+
+ if (hadErrorStatus) {
+ // Don't send session store updates if the reason EndPageLoad was called is
+ // because we are process switching. Sometimes the update takes too long and
+ // incorrectly overrides session store data from the following load.
+ return NS_OK;
+ }
+ if (StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) {
+ if (WindowContext* windowContext =
+ mBrowsingContext->GetCurrentWindowContext()) {
+ using Change = SessionStoreChangeListener::Change;
+
+ // We've finished loading the page and now we want to collect all the
+ // session store state that the page is initialized with.
+ SessionStoreChangeListener::CollectSessionStoreData(
+ windowContext,
+ EnumSet<Change>(Change::Input, Change::Scroll, Change::SessionHistory,
+ Change::WireFrame));
+ }
+ }
+
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsDocShell: Content Viewer Management
+//*****************************************************************************
+
+nsresult nsDocShell::EnsureContentViewer() {
+ if (mContentViewer) {
+ return NS_OK;
+ }
+ if (mIsBeingDestroyed) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIContentSecurityPolicy> cspToInheritForAboutBlank;
+ nsCOMPtr<nsIURI> baseURI;
+ nsIPrincipal* principal = GetInheritedPrincipal(false);
+ nsIPrincipal* partitionedPrincipal = GetInheritedPrincipal(false, true);
+
+ nsCOMPtr<nsIDocShellTreeItem> parentItem;
+ GetInProcessSameTypeParent(getter_AddRefs(parentItem));
+ if (parentItem) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> domWin = GetWindow()) {
+ nsCOMPtr<Element> parentElement = domWin->GetFrameElementInternal();
+ if (parentElement) {
+ baseURI = parentElement->GetBaseURI();
+ cspToInheritForAboutBlank = parentElement->GetCsp();
+ }
+ }
+ }
+
+ nsresult rv = CreateAboutBlankContentViewer(
+ principal, partitionedPrincipal, cspToInheritForAboutBlank, baseURI,
+ /* aIsInitialDocument */ true);
+
+ NS_ENSURE_STATE(mContentViewer);
+
+ if (NS_SUCCEEDED(rv)) {
+ RefPtr<Document> doc(GetDocument());
+ MOZ_ASSERT(doc,
+ "Should have doc if CreateAboutBlankContentViewer "
+ "succeeded!");
+ MOZ_ASSERT(doc->IsInitialDocument(), "Document should be initial document");
+
+ // Documents created using EnsureContentViewer may be transient
+ // placeholders created by framescripts before content has a
+ // chance to load. In some cases, window.open(..., "noopener")
+ // will create such a document and then synchronously tear it
+ // down, firing a "pagehide" event. Doing so violates our
+ // assertions about DocGroups. It's easier to silence the
+ // assertion here than to avoid creating the extra document.
+ doc->IgnoreDocGroupMismatches();
+ }
+
+ return rv;
+}
+
+nsresult nsDocShell::CreateAboutBlankContentViewer(
+ nsIPrincipal* aPrincipal, nsIPrincipal* aPartitionedPrincipal,
+ nsIContentSecurityPolicy* aCSP, nsIURI* aBaseURI, bool aIsInitialDocument,
+ const Maybe<nsILoadInfo::CrossOriginEmbedderPolicy>& aCOEP,
+ bool aTryToSaveOldPresentation, bool aCheckPermitUnload,
+ WindowGlobalChild* aActor) {
+ RefPtr<Document> blankDoc;
+ nsCOMPtr<nsIContentViewer> viewer;
+ nsresult rv = NS_ERROR_FAILURE;
+
+ MOZ_ASSERT_IF(aActor, aActor->DocumentPrincipal() == aPrincipal);
+
+ /* mCreatingDocument should never be true at this point. However, it's
+ a theoretical possibility. We want to know about it and make it stop,
+ and this sounds like a job for an assertion. */
+ NS_ASSERTION(!mCreatingDocument,
+ "infinite(?) loop creating document averted");
+ if (mCreatingDocument) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mBrowsingContext->AncestorsAreCurrent() ||
+ mBrowsingContext->IsInBFCache()) {
+ mBrowsingContext->RemoveRootFromBFCacheSync();
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // mContentViewer->PermitUnload may release |this| docshell.
+ nsCOMPtr<nsIDocShell> kungFuDeathGrip(this);
+
+ AutoRestore<bool> creatingDocument(mCreatingDocument);
+ mCreatingDocument = true;
+
+ if (aPrincipal && !aPrincipal->IsSystemPrincipal() &&
+ mItemType != typeChrome) {
+ MOZ_ASSERT(aPrincipal->OriginAttributesRef() ==
+ mBrowsingContext->OriginAttributesRef());
+ }
+
+ // Make sure timing is created. But first record whether we had it
+ // already, so we don't clobber the timing for an in-progress load.
+ bool hadTiming = mTiming;
+ bool toBeReset = MaybeInitTiming();
+ if (mContentViewer) {
+ if (aCheckPermitUnload) {
+ // We've got a content viewer already. Make sure the user
+ // permits us to discard the current document and replace it
+ // with about:blank. And also ensure we fire the unload events
+ // in the current document.
+
+ // Unload gets fired first for
+ // document loaded from the session history.
+ mTiming->NotifyBeforeUnload();
+
+ bool okToUnload;
+ rv = mContentViewer->PermitUnload(&okToUnload);
+
+ if (NS_SUCCEEDED(rv) && !okToUnload) {
+ // The user chose not to unload the page, interrupt the load.
+ MaybeResetInitTiming(toBeReset);
+ return NS_ERROR_FAILURE;
+ }
+ if (mTiming) {
+ mTiming->NotifyUnloadAccepted(mCurrentURI);
+ }
+ }
+
+ mSavingOldViewer =
+ aTryToSaveOldPresentation &&
+ CanSavePresentation(LOAD_NORMAL, nullptr, nullptr,
+ /* aReportBFCacheComboTelemetry */ true);
+
+ // Make sure to blow away our mLoadingURI just in case. No loads
+ // from inside this pagehide.
+ mLoadingURI = nullptr;
+
+ // Stop any in-progress loading, so that we don't accidentally trigger any
+ // PageShow notifications from Embed() interrupting our loading below.
+ Stop();
+
+ // Notify the current document that it is about to be unloaded!!
+ //
+ // It is important to fire the unload() notification *before* any state
+ // is changed within the DocShell - otherwise, javascript will get the
+ // wrong information :-(
+ //
+ (void)FirePageHideNotification(!mSavingOldViewer);
+ // pagehide notification might destroy this docshell.
+ if (mIsBeingDestroyed) {
+ return NS_ERROR_DOCSHELL_DYING;
+ }
+ }
+
+ // Now make sure we don't think we're in the middle of firing unload after
+ // this point. This will make us fire unload when the about:blank document
+ // unloads... but that's ok, more or less. Would be nice if it fired load
+ // too, of course.
+ mFiredUnloadEvent = false;
+
+ nsCOMPtr<nsIDocumentLoaderFactory> docFactory =
+ nsContentUtils::FindInternalContentViewer("text/html"_ns);
+
+ if (docFactory) {
+ nsCOMPtr<nsIPrincipal> principal, partitionedPrincipal;
+ const uint32_t sandboxFlags =
+ mBrowsingContext->GetHasLoadedNonInitialDocument()
+ ? mBrowsingContext->GetSandboxFlags()
+ : mBrowsingContext->GetInitialSandboxFlags();
+ // If we're sandboxed, then create a new null principal. We skip
+ // this if we're being created from WindowGlobalChild, since in
+ // that case we already have a null principal if required.
+ // We can't compare againt the BrowsingContext sandbox flag, since
+ // the value was taken when the load initiated and may have since
+ // changed.
+ if ((sandboxFlags & SANDBOXED_ORIGIN) && !aActor) {
+ if (aPrincipal) {
+ principal = NullPrincipal::CreateWithInheritedAttributes(aPrincipal);
+ } else {
+ principal = NullPrincipal::Create(GetOriginAttributes());
+ }
+ partitionedPrincipal = principal;
+ } else {
+ principal = aPrincipal;
+ partitionedPrincipal = aPartitionedPrincipal;
+ }
+
+ // We cannot get the foreign partitioned prinicpal for the initial
+ // about:blank page. So, we change to check if we need to use the
+ // partitioned principal for the service worker here.
+ MaybeCreateInitialClientSource(
+ StoragePrincipalHelper::ShouldUsePartitionPrincipalForServiceWorker(
+ this)
+ ? partitionedPrincipal
+ : principal);
+
+ // generate (about:blank) document to load
+ blankDoc = nsContentDLF::CreateBlankDocument(mLoadGroup, principal,
+ partitionedPrincipal, this);
+ if (blankDoc) {
+ // Hack: manually set the CSP for the new document
+ // Please create an actual copy of the CSP (do not share the same
+ // reference) otherwise appending a new policy within the new
+ // document will be incorrectly propagated to the opening doc.
+ if (aCSP) {
+ RefPtr<nsCSPContext> cspToInherit = new nsCSPContext();
+ cspToInherit->InitFromOther(static_cast<nsCSPContext*>(aCSP));
+ blankDoc->SetCsp(cspToInherit);
+ }
+
+ blankDoc->SetIsInitialDocument(aIsInitialDocument);
+
+ blankDoc->SetEmbedderPolicy(aCOEP);
+
+ // Hack: set the base URI manually, since this document never
+ // got Reset() with a channel.
+ blankDoc->SetBaseURI(aBaseURI);
+
+ // Copy our sandbox flags to the document. These are immutable
+ // after being set here.
+ blankDoc->SetSandboxFlags(sandboxFlags);
+
+ blankDoc->InitFeaturePolicy();
+
+ // create a content viewer for us and the new document
+ docFactory->CreateInstanceForDocument(
+ NS_ISUPPORTS_CAST(nsIDocShell*, this), blankDoc, "view",
+ getter_AddRefs(viewer));
+
+ // hook 'em up
+ if (viewer) {
+ viewer->SetContainer(this);
+ rv = Embed(viewer, aActor, true, false, nullptr, mCurrentURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetCurrentURI(blankDoc->GetDocumentURI(), nullptr,
+ /* aFireLocationChange */ true,
+ /* aIsInitialAboutBlank */ true,
+ /* aLocationFlags */ 0);
+ rv = mIsBeingDestroyed ? NS_ERROR_NOT_AVAILABLE : NS_OK;
+ }
+ }
+ }
+
+ // The transient about:blank viewer doesn't have a session history entry.
+ SetHistoryEntryAndUpdateBC(Nothing(), Some(nullptr));
+
+ // Clear out our mTiming like we would in EndPageLoad, if we didn't
+ // have one before entering this function.
+ if (!hadTiming) {
+ mTiming = nullptr;
+ mBlankTiming = true;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDocShell::CreateAboutBlankContentViewer(nsIPrincipal* aPrincipal,
+ nsIPrincipal* aPartitionedPrincipal,
+ nsIContentSecurityPolicy* aCSP) {
+ return CreateAboutBlankContentViewer(aPrincipal, aPartitionedPrincipal, aCSP,
+ nullptr, /* aIsInitialDocument */ false);
+}
+
+nsresult nsDocShell::CreateContentViewerForActor(
+ WindowGlobalChild* aWindowActor) {
+ MOZ_ASSERT(aWindowActor);
+
+ // FIXME: WindowGlobalChild should provide the PartitionedPrincipal.
+ // FIXME: We may want to support non-initial documents here.
+ nsresult rv = CreateAboutBlankContentViewer(
+ aWindowActor->DocumentPrincipal(), aWindowActor->DocumentPrincipal(),
+ /* aCsp */ nullptr,
+ /* aBaseURI */ nullptr,
+ /* aIsInitialDocument */ true,
+ /* aCOEP */ Nothing(),
+ /* aTryToSaveOldPresentation */ true,
+ /* aCheckPermitUnload */ true, aWindowActor);
+#ifdef DEBUG
+ if (NS_SUCCEEDED(rv)) {
+ RefPtr<Document> doc(GetDocument());
+ MOZ_ASSERT(
+ doc,
+ "Should have a document if CreateAboutBlankContentViewer succeeded");
+ MOZ_ASSERT(doc->GetOwnerGlobal() == aWindowActor->GetWindowGlobal(),
+ "New document should be in the same global as our actor");
+ MOZ_ASSERT(doc->IsInitialDocument(),
+ "New document should be an initial document");
+ }
+#endif
+
+ return rv;
+}
+
+bool nsDocShell::CanSavePresentation(uint32_t aLoadType,
+ nsIRequest* aNewRequest,
+ Document* aNewDocument,
+ bool aReportBFCacheComboTelemetry) {
+ if (!mOSHE) {
+ return false; // no entry to save into
+ }
+
+ MOZ_ASSERT(!mozilla::SessionHistoryInParent(),
+ "mOSHE cannot be non-null with SHIP");
+ nsCOMPtr<nsIContentViewer> viewer = mOSHE->GetContentViewer();
+ if (viewer) {
+ NS_WARNING("mOSHE already has a content viewer!");
+ return false;
+ }
+
+ // Only save presentation for "normal" loads and link loads. Anything else
+ // probably wants to refetch the page, so caching the old presentation
+ // would be incorrect.
+ if (aLoadType != LOAD_NORMAL && aLoadType != LOAD_HISTORY &&
+ aLoadType != LOAD_LINK && aLoadType != LOAD_STOP_CONTENT &&
+ aLoadType != LOAD_STOP_CONTENT_AND_REPLACE &&
+ aLoadType != LOAD_ERROR_PAGE) {
+ return false;
+ }
+
+ // If the session history entry has the saveLayoutState flag set to false,
+ // then we should not cache the presentation.
+ if (!mOSHE->GetSaveLayoutStateFlag()) {
+ return false;
+ }
+
+ // If the document is not done loading, don't cache it.
+ if (!mScriptGlobal || mScriptGlobal->IsLoading()) {
+ MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose,
+ ("Blocked due to document still loading"));
+ return false;
+ }
+
+ if (mScriptGlobal->WouldReuseInnerWindow(aNewDocument)) {
+ return false;
+ }
+
+ // Avoid doing the work of saving the presentation state in the case where
+ // the content viewer cache is disabled.
+ if (nsSHistory::GetMaxTotalViewers() == 0) {
+ return false;
+ }
+
+ // Don't cache the content viewer if we're in a subframe.
+ if (mBrowsingContext->GetParent()) {
+ return false; // this is a subframe load
+ }
+
+ // If the document does not want its presentation cached, then don't.
+ RefPtr<Document> doc = mScriptGlobal->GetExtantDoc();
+
+ uint32_t bfCacheCombo = 0;
+ bool canSavePresentation =
+ doc->CanSavePresentation(aNewRequest, bfCacheCombo, true);
+ MOZ_ASSERT_IF(canSavePresentation, bfCacheCombo == 0);
+ if (canSavePresentation && doc->IsTopLevelContentDocument()) {
+ auto* browsingContextGroup = mBrowsingContext->Group();
+ nsTArray<RefPtr<BrowsingContext>>& topLevelContext =
+ browsingContextGroup->Toplevels();
+
+ for (const auto& browsingContext : topLevelContext) {
+ if (browsingContext != mBrowsingContext) {
+ if (StaticPrefs::docshell_shistory_bfcache_require_no_opener()) {
+ canSavePresentation = false;
+ }
+ bfCacheCombo |= BFCacheStatus::NOT_ONLY_TOPLEVEL_IN_BCG;
+ break;
+ }
+ }
+ }
+
+ if (aReportBFCacheComboTelemetry) {
+ ReportBFCacheComboTelemetry(bfCacheCombo);
+ }
+ return doc && canSavePresentation;
+}
+
+/* static */
+void nsDocShell::ReportBFCacheComboTelemetry(uint32_t aCombo) {
+ // There are 11 possible reasons to make a request fails to use BFCache
+ // (see BFCacheStatus in dom/base/Document.h), and we'd like to record
+ // the common combinations for reasons which make requests fail to use
+ // BFCache. These combinations are generated based on some local browsings,
+ // we need to adjust them when necessary.
+ enum BFCacheStatusCombo : uint32_t {
+ BFCACHE_SUCCESS,
+ NOT_ONLY_TOPLEVEL = mozilla::dom::BFCacheStatus::NOT_ONLY_TOPLEVEL_IN_BCG,
+ // If both unload and beforeunload listeners are presented, it'll be
+ // recorded as unload
+ UNLOAD = mozilla::dom::BFCacheStatus::UNLOAD_LISTENER,
+ UNLOAD_REQUEST = mozilla::dom::BFCacheStatus::UNLOAD_LISTENER |
+ mozilla::dom::BFCacheStatus::REQUEST,
+ REQUEST = mozilla::dom::BFCacheStatus::REQUEST,
+ UNLOAD_REQUEST_PEER = mozilla::dom::BFCacheStatus::UNLOAD_LISTENER |
+ mozilla::dom::BFCacheStatus::REQUEST |
+ mozilla::dom::BFCacheStatus::ACTIVE_PEER_CONNECTION,
+ UNLOAD_REQUEST_PEER_MSE =
+ mozilla::dom::BFCacheStatus::UNLOAD_LISTENER |
+ mozilla::dom::BFCacheStatus::REQUEST |
+ mozilla::dom::BFCacheStatus::ACTIVE_PEER_CONNECTION |
+ mozilla::dom::BFCacheStatus::CONTAINS_MSE_CONTENT,
+ UNLOAD_REQUEST_MSE = mozilla::dom::BFCacheStatus::UNLOAD_LISTENER |
+ mozilla::dom::BFCacheStatus::REQUEST |
+ mozilla::dom::BFCacheStatus::CONTAINS_MSE_CONTENT,
+ SUSPENDED_UNLOAD_REQUEST_PEER =
+ mozilla::dom::BFCacheStatus::SUSPENDED |
+ mozilla::dom::BFCacheStatus::UNLOAD_LISTENER |
+ mozilla::dom::BFCacheStatus::REQUEST |
+ mozilla::dom::BFCacheStatus::ACTIVE_PEER_CONNECTION,
+ REMOTE_SUBFRAMES = mozilla::dom::BFCacheStatus::CONTAINS_REMOTE_SUBFRAMES,
+ BEFOREUNLOAD = mozilla::dom::BFCacheStatus::BEFOREUNLOAD_LISTENER,
+ };
+
+ // Beforeunload is recorded as a blocker only if it is the only one to block
+ // bfcache.
+ if (aCombo != mozilla::dom::BFCacheStatus::BEFOREUNLOAD_LISTENER) {
+ aCombo &= ~mozilla::dom::BFCacheStatus::BEFOREUNLOAD_LISTENER;
+ }
+ switch (aCombo) {
+ case BFCACHE_SUCCESS:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_BFCACHE_COMBO::BFCache_Success);
+ break;
+ case NOT_ONLY_TOPLEVEL:
+ if (StaticPrefs::docshell_shistory_bfcache_require_no_opener()) {
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_BFCACHE_COMBO::Other);
+ break;
+ }
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_BFCACHE_COMBO::BFCache_Success);
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_BFCACHE_COMBO::Success_Not_Toplevel);
+ break;
+ case UNLOAD:
+ Telemetry::AccumulateCategorical(Telemetry::LABELS_BFCACHE_COMBO::Unload);
+ break;
+ case BEFOREUNLOAD:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_BFCACHE_COMBO::Beforeunload);
+ break;
+ case UNLOAD_REQUEST:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_BFCACHE_COMBO::Unload_Req);
+ break;
+ case REQUEST:
+ Telemetry::AccumulateCategorical(Telemetry::LABELS_BFCACHE_COMBO::Req);
+ break;
+ case UNLOAD_REQUEST_PEER:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_BFCACHE_COMBO::Unload_Req_Peer);
+ break;
+ case UNLOAD_REQUEST_PEER_MSE:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_BFCACHE_COMBO::Unload_Req_Peer_MSE);
+ break;
+ case UNLOAD_REQUEST_MSE:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_BFCACHE_COMBO::Unload_Req_MSE);
+ break;
+ case SUSPENDED_UNLOAD_REQUEST_PEER:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_BFCACHE_COMBO::SPD_Unload_Req_Peer);
+ break;
+ case REMOTE_SUBFRAMES:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_BFCACHE_COMBO::Remote_Subframes);
+ break;
+ default:
+ Telemetry::AccumulateCategorical(Telemetry::LABELS_BFCACHE_COMBO::Other);
+ break;
+ }
+};
+
+void nsDocShell::ReattachEditorToWindow(nsISHEntry* aSHEntry) {
+ MOZ_ASSERT(!mozilla::SessionHistoryInParent());
+ MOZ_ASSERT(!mIsBeingDestroyed);
+
+ NS_ASSERTION(!mEditorData,
+ "Why reattach an editor when we already have one?");
+ NS_ASSERTION(aSHEntry && aSHEntry->HasDetachedEditor(),
+ "Reattaching when there's not a detached editor.");
+
+ if (mEditorData || !aSHEntry) {
+ return;
+ }
+
+ mEditorData = WrapUnique(aSHEntry->ForgetEditorData());
+ if (mEditorData) {
+#ifdef DEBUG
+ nsresult rv =
+#endif
+ mEditorData->ReattachToWindow(this);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to reattach editing session");
+ }
+}
+
+void nsDocShell::DetachEditorFromWindow() {
+ if (!mEditorData || mEditorData->WaitingForLoad()) {
+ // If there's nothing to detach, or if the editor data is actually set
+ // up for the _new_ page that's coming in, don't detach.
+ return;
+ }
+
+ NS_ASSERTION(!mOSHE || !mOSHE->HasDetachedEditor(),
+ "Detaching editor when it's already detached.");
+
+ nsresult res = mEditorData->DetachFromWindow();
+ NS_ASSERTION(NS_SUCCEEDED(res), "Failed to detach editor");
+
+ if (NS_SUCCEEDED(res)) {
+ // Make mOSHE hold the owning ref to the editor data.
+ if (mOSHE) {
+ MOZ_ASSERT(!mIsBeingDestroyed || !mOSHE->HasDetachedEditor(),
+ "We should not set the editor data again once after we "
+ "detached the editor data during destroying this docshell");
+ mOSHE->SetEditorData(mEditorData.release());
+ } else {
+ mEditorData = nullptr;
+ }
+ }
+
+#ifdef DEBUG
+ {
+ bool isEditable;
+ GetEditable(&isEditable);
+ NS_ASSERTION(!isEditable,
+ "Window is still editable after detaching editor.");
+ }
+#endif // DEBUG
+}
+
+nsresult nsDocShell::CaptureState() {
+ MOZ_ASSERT(!mozilla::SessionHistoryInParent());
+
+ if (!mOSHE || mOSHE == mLSHE) {
+ // No entry to save into, or we're replacing the existing entry.
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mScriptGlobal) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsISupports> windowState = mScriptGlobal->SaveWindowState();
+ NS_ENSURE_TRUE(windowState, NS_ERROR_FAILURE);
+
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Debug))) {
+ nsAutoCString spec;
+ nsCOMPtr<nsIURI> uri = mOSHE->GetURI();
+ if (uri) {
+ uri->GetSpec(spec);
+ }
+ MOZ_LOG(gPageCacheLog, LogLevel::Debug,
+ ("Saving presentation into session history, URI: %s", spec.get()));
+ }
+
+ mOSHE->SetWindowState(windowState);
+
+ // Suspend refresh URIs and save off the timer queue
+ mOSHE->SetRefreshURIList(mSavedRefreshURIList);
+
+ // Capture the current content viewer bounds.
+ if (mContentViewer) {
+ nsIntRect bounds;
+ mContentViewer->GetBounds(bounds);
+ mOSHE->SetViewerBounds(bounds);
+ }
+
+ // Capture the docshell hierarchy.
+ mOSHE->ClearChildShells();
+
+ uint32_t childCount = mChildList.Length();
+ for (uint32_t i = 0; i < childCount; ++i) {
+ nsCOMPtr<nsIDocShellTreeItem> childShell = do_QueryInterface(ChildAt(i));
+ NS_ASSERTION(childShell, "null child shell");
+
+ mOSHE->AddChildShell(childShell);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::RestorePresentationEvent::Run() {
+ MOZ_ASSERT(!mozilla::SessionHistoryInParent());
+
+ if (mDocShell && NS_FAILED(mDocShell->RestoreFromHistory())) {
+ NS_WARNING("RestoreFromHistory failed");
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::BeginRestore(nsIContentViewer* aContentViewer, bool aTop) {
+ MOZ_ASSERT(!mozilla::SessionHistoryInParent());
+
+ nsresult rv;
+ if (!aContentViewer) {
+ rv = EnsureContentViewer();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aContentViewer = mContentViewer;
+ }
+
+ // Dispatch events for restoring the presentation. We try to simulate
+ // the progress notifications loading the document would cause, so we add
+ // the document's channel to the loadgroup to initiate stateChange
+ // notifications.
+
+ RefPtr<Document> doc = aContentViewer->GetDocument();
+ if (doc) {
+ nsIChannel* channel = doc->GetChannel();
+ if (channel) {
+ mEODForCurrentDocument = false;
+ mIsRestoringDocument = true;
+ mLoadGroup->AddRequest(channel, nullptr);
+ mIsRestoringDocument = false;
+ }
+ }
+
+ if (!aTop) {
+ // This point corresponds to us having gotten OnStartRequest or
+ // STATE_START, so do the same thing that CreateContentViewer does at
+ // this point to ensure that unload/pagehide events for this document
+ // will fire when it's unloaded again.
+ mFiredUnloadEvent = false;
+
+ // For non-top frames, there is no notion of making sure that the
+ // previous document is in the domwindow when STATE_START notifications
+ // happen. We can just call BeginRestore for all of the child shells
+ // now.
+ rv = BeginRestoreChildren();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsDocShell::BeginRestoreChildren() {
+ MOZ_ASSERT(!mozilla::SessionHistoryInParent());
+
+ for (auto* childDocLoader : mChildList.ForwardRange()) {
+ nsCOMPtr<nsIDocShell> child = do_QueryObject(childDocLoader);
+ if (child) {
+ nsresult rv = child->BeginRestore(nullptr, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::FinishRestore() {
+ MOZ_ASSERT(!mozilla::SessionHistoryInParent());
+
+ // First we call finishRestore() on our children. In the simulated load,
+ // all of the child frames finish loading before the main document.
+
+ for (auto* childDocLoader : mChildList.ForwardRange()) {
+ nsCOMPtr<nsIDocShell> child = do_QueryObject(childDocLoader);
+ if (child) {
+ child->FinishRestore();
+ }
+ }
+
+ if (mOSHE && mOSHE->HasDetachedEditor()) {
+ ReattachEditorToWindow(mOSHE);
+ }
+
+ RefPtr<Document> doc = GetDocument();
+ if (doc) {
+ // Finally, we remove the request from the loadgroup. This will
+ // cause onStateChange(STATE_STOP) to fire, which will fire the
+ // pageshow event to the chrome.
+
+ nsIChannel* channel = doc->GetChannel();
+ if (channel) {
+ mIsRestoringDocument = true;
+ mLoadGroup->RemoveRequest(channel, nullptr, NS_OK);
+ mIsRestoringDocument = false;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetRestoringDocument(bool* aRestoring) {
+ *aRestoring = mIsRestoringDocument;
+ return NS_OK;
+}
+
+nsresult nsDocShell::RestorePresentation(nsISHEntry* aSHEntry,
+ bool* aRestoring) {
+ MOZ_ASSERT(!mozilla::SessionHistoryInParent());
+ MOZ_ASSERT(!mIsBeingDestroyed);
+
+ NS_ASSERTION(mLoadType & LOAD_CMD_HISTORY,
+ "RestorePresentation should only be called for history loads");
+
+ nsCOMPtr<nsIContentViewer> viewer = aSHEntry->GetContentViewer();
+
+ nsAutoCString spec;
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Debug))) {
+ nsCOMPtr<nsIURI> uri = aSHEntry->GetURI();
+ if (uri) {
+ uri->GetSpec(spec);
+ }
+ }
+
+ *aRestoring = false;
+
+ if (!viewer) {
+ MOZ_LOG(gPageCacheLog, LogLevel::Debug,
+ ("no saved presentation for uri: %s", spec.get()));
+ return NS_OK;
+ }
+
+ // We need to make sure the content viewer's container is this docshell.
+ // In subframe navigation, it's possible for the docshell that the
+ // content viewer was originally loaded into to be replaced with a
+ // different one. We don't currently support restoring the presentation
+ // in that case.
+
+ nsCOMPtr<nsIDocShell> container;
+ viewer->GetContainer(getter_AddRefs(container));
+ if (!::SameCOMIdentity(container, GetAsSupports(this))) {
+ MOZ_LOG(gPageCacheLog, LogLevel::Debug,
+ ("No valid container, clearing presentation"));
+ aSHEntry->SetContentViewer(nullptr);
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_ASSERTION(mContentViewer != viewer, "Restoring existing presentation");
+
+ MOZ_LOG(gPageCacheLog, LogLevel::Debug,
+ ("restoring presentation from session history: %s", spec.get()));
+
+ SetHistoryEntryAndUpdateBC(Some(aSHEntry), Nothing());
+
+ // Post an event that will remove the request after we've returned
+ // to the event loop. This mimics the way it is called by nsIChannel
+ // implementations.
+
+ // Revoke any pending restore (just in case).
+ NS_ASSERTION(!mRestorePresentationEvent.IsPending(),
+ "should only have one RestorePresentationEvent");
+ mRestorePresentationEvent.Revoke();
+
+ RefPtr<RestorePresentationEvent> evt = new RestorePresentationEvent(this);
+ nsresult rv = Dispatch(TaskCategory::Other, do_AddRef(evt));
+ if (NS_SUCCEEDED(rv)) {
+ mRestorePresentationEvent = evt.get();
+ // The rest of the restore processing will happen on our event
+ // callback.
+ *aRestoring = true;
+ }
+
+ return rv;
+}
+
+namespace {
+class MOZ_STACK_CLASS PresentationEventForgetter {
+ public:
+ explicit PresentationEventForgetter(
+ nsRevocableEventPtr<nsDocShell::RestorePresentationEvent>&
+ aRestorePresentationEvent)
+ : mRestorePresentationEvent(aRestorePresentationEvent),
+ mEvent(aRestorePresentationEvent.get()) {}
+
+ ~PresentationEventForgetter() { Forget(); }
+
+ void Forget() {
+ if (mRestorePresentationEvent.get() == mEvent) {
+ mRestorePresentationEvent.Forget();
+ mEvent = nullptr;
+ }
+ }
+
+ private:
+ nsRevocableEventPtr<nsDocShell::RestorePresentationEvent>&
+ mRestorePresentationEvent;
+ RefPtr<nsDocShell::RestorePresentationEvent> mEvent;
+};
+
+} // namespace
+
+bool nsDocShell::SandboxFlagsImplyCookies(const uint32_t& aSandboxFlags) {
+ return (aSandboxFlags & (SANDBOXED_ORIGIN | SANDBOXED_SCRIPTS)) == 0;
+}
+
+nsresult nsDocShell::RestoreFromHistory() {
+ MOZ_ASSERT(!mozilla::SessionHistoryInParent());
+ MOZ_ASSERT(mRestorePresentationEvent.IsPending());
+ PresentationEventForgetter forgetter(mRestorePresentationEvent);
+
+ // This section of code follows the same ordering as CreateContentViewer.
+ if (!mLSHE) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIContentViewer> viewer = mLSHE->GetContentViewer();
+ if (!viewer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mSavingOldViewer) {
+ // We determined that it was safe to cache the document presentation
+ // at the time we initiated the new load. We need to check whether
+ // it's still safe to do so, since there may have been DOM mutations
+ // or new requests initiated.
+ RefPtr<Document> doc = viewer->GetDocument();
+ nsIRequest* request = nullptr;
+ if (doc) {
+ request = doc->GetChannel();
+ }
+ mSavingOldViewer = CanSavePresentation(
+ mLoadType, request, doc, /* aReportBFCacheComboTelemetry */ false);
+ }
+
+ // Protect against mLSHE going away via a load triggered from
+ // pagehide or unload.
+ nsCOMPtr<nsISHEntry> origLSHE = mLSHE;
+
+ // Make sure to blow away our mLoadingURI just in case. No loads
+ // from inside this pagehide.
+ mLoadingURI = nullptr;
+
+ // Notify the old content viewer that it's being hidden.
+ FirePageHideNotification(!mSavingOldViewer);
+ // pagehide notification might destroy this docshell.
+ if (mIsBeingDestroyed) {
+ return NS_ERROR_DOCSHELL_DYING;
+ }
+
+ // If mLSHE was changed as a result of the pagehide event, then
+ // something else was loaded. Don't finish restoring.
+ if (mLSHE != origLSHE) {
+ return NS_OK;
+ }
+
+ // Add the request to our load group. We do this before swapping out
+ // the content viewers so that consumers of STATE_START can access
+ // the old document. We only deal with the toplevel load at this time --
+ // to be consistent with normal document loading, subframes cannot start
+ // loading until after data arrives, which is after STATE_START completes.
+
+ RefPtr<RestorePresentationEvent> currentPresentationRestoration =
+ mRestorePresentationEvent.get();
+ Stop();
+ // Make sure we're still restoring the same presentation.
+ // If we aren't, docshell is in process doing another load already.
+ NS_ENSURE_STATE(currentPresentationRestoration ==
+ mRestorePresentationEvent.get());
+ BeginRestore(viewer, true);
+ NS_ENSURE_STATE(currentPresentationRestoration ==
+ mRestorePresentationEvent.get());
+ forgetter.Forget();
+
+ // Set mFiredUnloadEvent = false so that the unload handler for the
+ // *new* document will fire.
+ mFiredUnloadEvent = false;
+
+ mURIResultedInDocument = true;
+ RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
+ if (rootSH) {
+ mPreviousEntryIndex = rootSH->Index();
+ rootSH->LegacySHistory()->UpdateIndex();
+ mLoadedEntryIndex = rootSH->Index();
+ MOZ_LOG(gPageCacheLog, LogLevel::Verbose,
+ ("Previous index: %d, Loaded index: %d", mPreviousEntryIndex,
+ mLoadedEntryIndex));
+ }
+
+ // Rather than call Embed(), we will retrieve the viewer from the session
+ // history entry and swap it in.
+ // XXX can we refactor this so that we can just call Embed()?
+ PersistLayoutHistoryState();
+ nsresult rv;
+ if (mContentViewer) {
+ if (mSavingOldViewer && NS_FAILED(CaptureState())) {
+ if (mOSHE) {
+ mOSHE->SyncPresentationState();
+ }
+ mSavingOldViewer = false;
+ }
+ }
+
+ mSavedRefreshURIList = nullptr;
+
+ // In cases where we use a transient about:blank viewer between loads,
+ // we never show the transient viewer, so _its_ previous viewer is never
+ // unhooked from the view hierarchy. Destroy any such previous viewer now,
+ // before we grab the root view sibling, so that we don't grab a view
+ // that's about to go away.
+
+ if (mContentViewer) {
+ // Make sure to hold a strong ref to previousViewer here while we
+ // drop the reference to it from mContentViewer.
+ nsCOMPtr<nsIContentViewer> previousViewer =
+ mContentViewer->GetPreviousViewer();
+ if (previousViewer) {
+ mContentViewer->SetPreviousViewer(nullptr);
+ previousViewer->Destroy();
+ }
+ }
+
+ // Save off the root view's parent and sibling so that we can insert the
+ // new content viewer's root view at the same position. Also save the
+ // bounds of the root view's widget.
+
+ nsView* rootViewSibling = nullptr;
+ nsView* rootViewParent = nullptr;
+ nsIntRect newBounds(0, 0, 0, 0);
+
+ PresShell* oldPresShell = GetPresShell();
+ if (oldPresShell) {
+ nsViewManager* vm = oldPresShell->GetViewManager();
+ if (vm) {
+ nsView* oldRootView = vm->GetRootView();
+
+ if (oldRootView) {
+ rootViewSibling = oldRootView->GetNextSibling();
+ rootViewParent = oldRootView->GetParent();
+
+ mContentViewer->GetBounds(newBounds);
+ }
+ }
+ }
+
+ nsCOMPtr<nsIContent> container;
+ RefPtr<Document> sibling;
+ if (rootViewParent && rootViewParent->GetParent()) {
+ nsIFrame* frame = rootViewParent->GetParent()->GetFrame();
+ container = frame ? frame->GetContent() : nullptr;
+ }
+ if (rootViewSibling) {
+ nsIFrame* frame = rootViewSibling->GetFrame();
+ sibling = frame ? frame->PresShell()->GetDocument() : nullptr;
+ }
+
+ // Transfer ownership to mContentViewer. By ensuring that either the
+ // docshell or the session history, but not both, have references to the
+ // content viewer, we prevent the viewer from being torn down after
+ // Destroy() is called.
+
+ if (mContentViewer) {
+ mContentViewer->Close(mSavingOldViewer ? mOSHE.get() : nullptr);
+ viewer->SetPreviousViewer(mContentViewer);
+ }
+ if (mOSHE && (!mContentViewer || !mSavingOldViewer)) {
+ // We don't plan to save a viewer in mOSHE; tell it to drop
+ // any other state it's holding.
+ mOSHE->SyncPresentationState();
+ }
+
+ // Order the mContentViewer setup just like Embed does.
+ mContentViewer = nullptr;
+
+ // Now that we're about to switch documents, forget all of our children.
+ // Note that we cached them as needed up in CaptureState above.
+ DestroyChildren();
+
+ mContentViewer.swap(viewer);
+
+ // Grab all of the related presentation from the SHEntry now.
+ // Clearing the viewer from the SHEntry will clear all of this state.
+ nsCOMPtr<nsISupports> windowState = mLSHE->GetWindowState();
+ mLSHE->SetWindowState(nullptr);
+
+ bool sticky = mLSHE->GetSticky();
+
+ RefPtr<Document> document = mContentViewer->GetDocument();
+
+ nsCOMArray<nsIDocShellTreeItem> childShells;
+ int32_t i = 0;
+ nsCOMPtr<nsIDocShellTreeItem> child;
+ while (NS_SUCCEEDED(mLSHE->ChildShellAt(i++, getter_AddRefs(child))) &&
+ child) {
+ childShells.AppendObject(child);
+ }
+
+ // get the previous content viewer size
+ nsIntRect oldBounds(0, 0, 0, 0);
+ mLSHE->GetViewerBounds(oldBounds);
+
+ // Restore the refresh URI list. The refresh timers will be restarted
+ // when EndPageLoad() is called.
+ nsCOMPtr<nsIMutableArray> refreshURIList = mLSHE->GetRefreshURIList();
+
+ // Reattach to the window object.
+ mIsRestoringDocument = true; // for MediaDocument::BecomeInteractive
+ rv = mContentViewer->Open(windowState, mLSHE);
+ mIsRestoringDocument = false;
+
+ // Hack to keep nsDocShellEditorData alive across the
+ // SetContentViewer(nullptr) call below.
+ UniquePtr<nsDocShellEditorData> data(mLSHE->ForgetEditorData());
+
+ // Now remove it from the cached presentation.
+ mLSHE->SetContentViewer(nullptr);
+ mEODForCurrentDocument = false;
+
+ mLSHE->SetEditorData(data.release());
+
+#ifdef DEBUG
+ {
+ nsCOMPtr<nsIMutableArray> refreshURIs = mLSHE->GetRefreshURIList();
+ nsCOMPtr<nsIDocShellTreeItem> childShell;
+ mLSHE->ChildShellAt(0, getter_AddRefs(childShell));
+ NS_ASSERTION(!refreshURIs && !childShell,
+ "SHEntry should have cleared presentation state");
+ }
+#endif
+
+ // Restore the sticky state of the viewer. The viewer has set this state
+ // on the history entry in Destroy() just before marking itself non-sticky,
+ // to avoid teardown of the presentation.
+ mContentViewer->SetSticky(sticky);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // mLSHE is now our currently-loaded document.
+ SetHistoryEntryAndUpdateBC(Nothing(), Some<nsISHEntry*>(mLSHE));
+
+ // We aren't going to restore any items from the LayoutHistoryState,
+ // but we don't want them to stay around in case the page is reloaded.
+ SetLayoutHistoryState(nullptr);
+
+ // This is the end of our Embed() replacement
+
+ mSavingOldViewer = false;
+ mEODForCurrentDocument = false;
+
+ if (document) {
+ RefPtr<nsDocShell> parent = GetInProcessParentDocshell();
+ if (parent) {
+ RefPtr<Document> d = parent->GetDocument();
+ if (d) {
+ if (d->EventHandlingSuppressed()) {
+ document->SuppressEventHandling(d->EventHandlingSuppressed());
+ }
+ }
+ }
+
+ // Use the uri from the mLSHE we had when we entered this function
+ // (which need not match the document's URI if anchors are involved),
+ // since that's the history entry we're loading. Note that if we use
+ // origLSHE we don't have to worry about whether the entry in question
+ // is still mLSHE or whether it's now mOSHE.
+ nsCOMPtr<nsIURI> uri = origLSHE->GetURI();
+ SetCurrentURI(uri, document->GetChannel(), /* aFireLocationChange */ true,
+ /* aIsInitialAboutBlank */ false,
+ /* aLocationFlags */ 0);
+ }
+
+ // This is the end of our CreateContentViewer() replacement.
+ // Now we simulate a load. First, we restore the state of the javascript
+ // window object.
+ nsCOMPtr<nsPIDOMWindowOuter> privWin = GetWindow();
+ NS_ASSERTION(privWin, "could not get nsPIDOMWindow interface");
+
+ // Now, dispatch a title change event which would happen as the
+ // <head> is parsed.
+ document->NotifyPossibleTitleChange(false);
+
+ // Now we simulate appending child docshells for subframes.
+ for (i = 0; i < childShells.Count(); ++i) {
+ nsIDocShellTreeItem* childItem = childShells.ObjectAt(i);
+ nsCOMPtr<nsIDocShell> childShell = do_QueryInterface(childItem);
+
+ // Make sure to not clobber the state of the child. Since AddChild
+ // always clobbers it, save it off first.
+ bool allowRedirects;
+ childShell->GetAllowMetaRedirects(&allowRedirects);
+
+ bool allowSubframes;
+ childShell->GetAllowSubframes(&allowSubframes);
+
+ bool allowImages;
+ childShell->GetAllowImages(&allowImages);
+
+ bool allowMedia = childShell->GetAllowMedia();
+
+ bool allowDNSPrefetch;
+ childShell->GetAllowDNSPrefetch(&allowDNSPrefetch);
+
+ bool allowContentRetargeting = childShell->GetAllowContentRetargeting();
+ bool allowContentRetargetingOnChildren =
+ childShell->GetAllowContentRetargetingOnChildren();
+
+ // this.AddChild(child) calls child.SetDocLoaderParent(this), meaning that
+ // the child inherits our state. Among other things, this means that the
+ // child inherits our mPrivateBrowsingId, which is what we want.
+ AddChild(childItem);
+
+ childShell->SetAllowMetaRedirects(allowRedirects);
+ childShell->SetAllowSubframes(allowSubframes);
+ childShell->SetAllowImages(allowImages);
+ childShell->SetAllowMedia(allowMedia);
+ childShell->SetAllowDNSPrefetch(allowDNSPrefetch);
+ childShell->SetAllowContentRetargeting(allowContentRetargeting);
+ childShell->SetAllowContentRetargetingOnChildren(
+ allowContentRetargetingOnChildren);
+
+ rv = childShell->BeginRestore(nullptr, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Make sure to restore the window state after adding the child shells back
+ // to the tree. This is necessary for Thaw() and Resume() to propagate
+ // properly.
+ rv = privWin->RestoreWindowState(windowState);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<PresShell> presShell = GetPresShell();
+
+ // We may be displayed on a different monitor (or in a different
+ // HiDPI mode) than when we got into the history list. So we need
+ // to check if this has happened. See bug 838239.
+
+ // Because the prescontext normally handles resolution changes via
+ // a runnable (see nsPresContext::UIResolutionChanged), its device
+ // context won't be -immediately- updated as a result of calling
+ // presShell->BackingScaleFactorChanged().
+
+ // But we depend on that device context when adjusting the view size
+ // via mContentViewer->SetBounds(newBounds) below. So we need to
+ // explicitly tell it to check for changed resolution here.
+ if (presShell) {
+ RefPtr<nsPresContext> pc = presShell->GetPresContext();
+ if (pc->DeviceContext()->CheckDPIChange()) {
+ presShell->BackingScaleFactorChanged();
+ }
+ // Recompute zoom and text-zoom and such.
+ pc->RecomputeBrowsingContextDependentData();
+ }
+
+ nsViewManager* newVM = presShell ? presShell->GetViewManager() : nullptr;
+ nsView* newRootView = newVM ? newVM->GetRootView() : nullptr;
+
+ // Insert the new root view at the correct location in the view tree.
+ if (container) {
+ nsSubDocumentFrame* subDocFrame =
+ do_QueryFrame(container->GetPrimaryFrame());
+ rootViewParent = subDocFrame ? subDocFrame->EnsureInnerView() : nullptr;
+ } else {
+ rootViewParent = nullptr;
+ }
+ if (sibling && sibling->GetPresShell() &&
+ sibling->GetPresShell()->GetViewManager()) {
+ rootViewSibling = sibling->GetPresShell()->GetViewManager()->GetRootView();
+ } else {
+ rootViewSibling = nullptr;
+ }
+ if (rootViewParent && newRootView &&
+ newRootView->GetParent() != rootViewParent) {
+ nsViewManager* parentVM = rootViewParent->GetViewManager();
+ if (parentVM) {
+ // InsertChild(parent, child, sib, true) inserts the child after
+ // sib in content order, which is before sib in view order. BUT
+ // when sib is null it inserts at the end of the the document
+ // order, i.e., first in view order. But when oldRootSibling is
+ // null, the old root as at the end of the view list --- last in
+ // content order --- and we want to call InsertChild(parent, child,
+ // nullptr, false) in that case.
+ parentVM->InsertChild(rootViewParent, newRootView, rootViewSibling,
+ rootViewSibling ? true : false);
+
+ NS_ASSERTION(newRootView->GetNextSibling() == rootViewSibling,
+ "error in InsertChild");
+ }
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> privWinInner = privWin->GetCurrentInnerWindow();
+
+ // If parent is suspended, increase suspension count.
+ // This can't be done as early as event suppression since this
+ // depends on docshell tree.
+ privWinInner->SyncStateFromParentWindow();
+
+ // Now that all of the child docshells have been put into place, we can
+ // restart the timers for the window and all of the child frames.
+ privWinInner->Resume();
+
+ // Now that we have found the inner window of the page restored
+ // from the history, we have to make sure that
+ // performance.navigation.type is 2.
+ Performance* performance = privWinInner->GetPerformance();
+ if (performance) {
+ performance->GetDOMTiming()->NotifyRestoreStart();
+ }
+
+ // Restore the refresh URI list. The refresh timers will be restarted
+ // when EndPageLoad() is called.
+ mRefreshURIList = refreshURIList;
+
+ // Meta-refresh timers have been restarted for this shell, but not
+ // for our children. Walk the child shells and restart their timers.
+ for (auto* childDocLoader : mChildList.ForwardRange()) {
+ nsCOMPtr<nsIDocShell> child = do_QueryObject(childDocLoader);
+ if (child) {
+ child->ResumeRefreshURIs();
+ }
+ }
+
+ // Make sure this presentation is the same size as the previous
+ // presentation. If this is not the same size we showed it at last time,
+ // then we need to resize the widget.
+
+ // XXXbryner This interacts poorly with Firefox's infobar. If the old
+ // presentation had the infobar visible, then we will resize the new
+ // presentation to that smaller size. However, firing the locationchanged
+ // event will hide the infobar, which will immediately resize the window
+ // back to the larger size. A future optimization might be to restore
+ // the presentation at the "wrong" size, then fire the locationchanged
+ // event and check whether the docshell's new size is the same as the
+ // cached viewer size (skipping the resize if they are equal).
+
+ if (newRootView) {
+ if (!newBounds.IsEmpty() && !newBounds.IsEqualEdges(oldBounds)) {
+ MOZ_LOG(gPageCacheLog, LogLevel::Debug,
+ ("resize widget(%d, %d, %d, %d)", newBounds.x, newBounds.y,
+ newBounds.width, newBounds.height));
+ mContentViewer->SetBounds(newBounds);
+ } else {
+ nsIScrollableFrame* rootScrollFrame =
+ presShell->GetRootScrollFrameAsScrollable();
+ if (rootScrollFrame) {
+ rootScrollFrame->PostScrolledAreaEventForCurrentArea();
+ }
+ }
+ }
+
+ // The FinishRestore call below can kill these, null them out so we don't
+ // have invalid pointer lying around.
+ newRootView = rootViewSibling = rootViewParent = nullptr;
+ newVM = nullptr;
+
+ // If the IsUnderHiddenEmbedderElement() state has been changed, we need to
+ // update it.
+ if (oldPresShell && presShell &&
+ presShell->IsUnderHiddenEmbedderElement() !=
+ oldPresShell->IsUnderHiddenEmbedderElement()) {
+ presShell->SetIsUnderHiddenEmbedderElement(
+ oldPresShell->IsUnderHiddenEmbedderElement());
+ }
+
+ // Simulate the completion of the load.
+ nsDocShell::FinishRestore();
+
+ // Restart plugins, and paint the content.
+ if (presShell) {
+ presShell->Thaw();
+ }
+
+ return privWin->FireDelayedDOMEvents(true);
+}
+
+nsresult nsDocShell::CreateContentViewer(const nsACString& aContentType,
+ nsIRequest* aRequest,
+ nsIStreamListener** aContentHandler) {
+ if (DocGroup::TryToLoadIframesInBackground()) {
+ ResetToFirstLoad();
+ }
+
+ *aContentHandler = nullptr;
+
+ if (!mTreeOwner || mIsBeingDestroyed) {
+ // If we don't have a tree owner, then we're in the process of being
+ // destroyed. Rather than continue trying to load something, just give up.
+ return NS_ERROR_DOCSHELL_DYING;
+ }
+
+ if (!mBrowsingContext->AncestorsAreCurrent() ||
+ mBrowsingContext->IsInBFCache()) {
+ mBrowsingContext->RemoveRootFromBFCacheSync();
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Can we check the content type of the current content viewer
+ // and reuse it without destroying it and re-creating it?
+
+ NS_ASSERTION(mLoadGroup, "Someone ignored return from Init()?");
+
+ // Instantiate the content viewer object
+ nsCOMPtr<nsIContentViewer> viewer;
+ nsresult rv = NewContentViewerObj(aContentType, aRequest, mLoadGroup,
+ aContentHandler, getter_AddRefs(viewer));
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Notify the current document that it is about to be unloaded!!
+ //
+ // It is important to fire the unload() notification *before* any state
+ // is changed within the DocShell - otherwise, javascript will get the
+ // wrong information :-(
+ //
+
+ if (mSavingOldViewer) {
+ // We determined that it was safe to cache the document presentation
+ // at the time we initiated the new load. We need to check whether
+ // it's still safe to do so, since there may have been DOM mutations
+ // or new requests initiated.
+ RefPtr<Document> doc = viewer->GetDocument();
+ mSavingOldViewer = CanSavePresentation(
+ mLoadType, aRequest, doc, /* aReportBFCacheComboTelemetry */ false);
+ }
+
+ NS_ASSERTION(!mLoadingURI, "Re-entering unload?");
+
+ nsCOMPtr<nsIChannel> aOpenedChannel = do_QueryInterface(aRequest);
+ if (aOpenedChannel) {
+ aOpenedChannel->GetURI(getter_AddRefs(mLoadingURI));
+ }
+
+ // Grab the current URI, we need to pass it to Embed, and OnNewURI will reset
+ // it before we do call Embed.
+ nsCOMPtr<nsIURI> previousURI = mCurrentURI;
+
+ FirePageHideNotification(!mSavingOldViewer);
+ if (mIsBeingDestroyed) {
+ // Force to stop the newly created orphaned viewer.
+ viewer->Stop();
+ return NS_ERROR_DOCSHELL_DYING;
+ }
+ mLoadingURI = nullptr;
+
+ // Set mFiredUnloadEvent = false so that the unload handler for the
+ // *new* document will fire.
+ mFiredUnloadEvent = false;
+
+ // we've created a new document so go ahead and call
+ // OnNewURI(), but don't fire OnLocationChange()
+ // notifications before we've called Embed(). See bug 284993.
+ mURIResultedInDocument = true;
+ bool errorOnLocationChangeNeeded = false;
+ nsCOMPtr<nsIChannel> failedChannel = mFailedChannel;
+ nsCOMPtr<nsIURI> failedURI;
+
+ if (mLoadType == LOAD_ERROR_PAGE) {
+ // We need to set the SH entry and our current URI here and not
+ // at the moment we load the page. We want the same behavior
+ // of Stop() as for a normal page load. See bug 514232 for details.
+
+ // Revert mLoadType to load type to state the page load failed,
+ // following function calls need it.
+ mLoadType = mFailedLoadType;
+
+ Document* doc = viewer->GetDocument();
+ if (doc) {
+ doc->SetFailedChannel(failedChannel);
+ }
+
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal;
+ if (failedChannel) {
+ // Make sure we have a URI to set currentURI.
+ NS_GetFinalChannelURI(failedChannel, getter_AddRefs(failedURI));
+ } else {
+ // if there is no failed channel we have to explicitly provide
+ // a triggeringPrincipal for the history entry.
+ triggeringPrincipal = nsContentUtils::GetSystemPrincipal();
+ }
+
+ if (!failedURI) {
+ failedURI = mFailedURI;
+ }
+ if (!failedURI) {
+ // We need a URI object to store a session history entry, so make up a URI
+ NS_NewURI(getter_AddRefs(failedURI), "about:blank");
+ }
+
+ // When we don't have failedURI, something wrong will happen. See
+ // bug 291876.
+ MOZ_ASSERT(failedURI, "We don't have a URI for history APIs.");
+
+ mFailedChannel = nullptr;
+ mFailedURI = nullptr;
+
+ // Create an shistory entry for the old load.
+ if (failedURI) {
+ errorOnLocationChangeNeeded =
+ OnNewURI(failedURI, failedChannel, triggeringPrincipal, nullptr,
+ nullptr, nullptr, false, false, false);
+ }
+
+ // Be sure to have a correct mLSHE, it may have been cleared by
+ // EndPageLoad. See bug 302115.
+ ChildSHistory* shistory = GetSessionHistory();
+ if (!mozilla::SessionHistoryInParent() && shistory && !mLSHE) {
+ int32_t idx = shistory->LegacySHistory()->GetRequestedIndex();
+ if (idx == -1) {
+ idx = shistory->Index();
+ }
+ shistory->LegacySHistory()->GetEntryAtIndex(idx, getter_AddRefs(mLSHE));
+ }
+
+ mLoadType = LOAD_ERROR_PAGE;
+ }
+
+ nsCOMPtr<nsIURI> finalURI;
+ // If this a redirect, use the final url (uri)
+ // else use the original url
+ //
+ // Note that this should match what documents do (see Document::Reset).
+ NS_GetFinalChannelURI(aOpenedChannel, getter_AddRefs(finalURI));
+
+ bool onLocationChangeNeeded = false;
+ if (finalURI) {
+ // Pass false for aCloneSHChildren, since we're loading a new page here.
+ onLocationChangeNeeded =
+ OnNewURI(finalURI, aOpenedChannel, nullptr, nullptr, nullptr, nullptr,
+ false, true, false);
+ }
+
+ // let's try resetting the load group if we need to...
+ nsCOMPtr<nsILoadGroup> currentLoadGroup;
+ NS_ENSURE_SUCCESS(
+ aOpenedChannel->GetLoadGroup(getter_AddRefs(currentLoadGroup)),
+ NS_ERROR_FAILURE);
+
+ if (currentLoadGroup != mLoadGroup) {
+ nsLoadFlags loadFlags = 0;
+
+ // Cancel any URIs that are currently loading...
+ // XXX: Need to do this eventually Stop();
+ //
+ // Retarget the document to this loadgroup...
+ //
+ /* First attach the channel to the right loadgroup
+ * and then remove from the old loadgroup. This
+ * puts the notifications in the right order and
+ * we don't null-out mLSHE in OnStateChange() for
+ * all redirected urls
+ */
+ aOpenedChannel->SetLoadGroup(mLoadGroup);
+
+ // Mark the channel as being a document URI...
+ aOpenedChannel->GetLoadFlags(&loadFlags);
+ loadFlags |= nsIChannel::LOAD_DOCUMENT_URI;
+ nsCOMPtr<nsILoadInfo> loadInfo = aOpenedChannel->LoadInfo();
+ if (SandboxFlagsImplyCookies(loadInfo->GetSandboxFlags())) {
+ loadFlags |= nsIRequest::LOAD_DOCUMENT_NEEDS_COOKIE;
+ }
+
+ aOpenedChannel->SetLoadFlags(loadFlags);
+
+ mLoadGroup->AddRequest(aRequest, nullptr);
+ if (currentLoadGroup) {
+ currentLoadGroup->RemoveRequest(aRequest, nullptr, NS_BINDING_RETARGETED);
+ }
+
+ // Update the notification callbacks, so that progress and
+ // status information are sent to the right docshell...
+ aOpenedChannel->SetNotificationCallbacks(this);
+ }
+
+ if (DocGroup::TryToLoadIframesInBackground()) {
+ if ((!mContentViewer || GetDocument()->IsInitialDocument()) &&
+ IsSubframe()) {
+ // At this point, we know we just created a new iframe document based on
+ // the response from the server, and we check if it's a cross-domain
+ // iframe
+
+ RefPtr<Document> newDoc = viewer->GetDocument();
+
+ RefPtr<nsDocShell> parent = GetInProcessParentDocshell();
+ nsCOMPtr<nsIPrincipal> parentPrincipal =
+ parent->GetDocument()->NodePrincipal();
+ nsCOMPtr<nsIPrincipal> thisPrincipal = newDoc->NodePrincipal();
+
+ SiteIdentifier parentSite;
+ SiteIdentifier thisSite;
+
+ nsresult rv =
+ BasePrincipal::Cast(parentPrincipal)->GetSiteIdentifier(parentSite);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = BasePrincipal::Cast(thisPrincipal)->GetSiteIdentifier(thisSite);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!parentSite.Equals(thisSite)) {
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ nsCOMPtr<nsIURI> prinURI;
+ BasePrincipal::Cast(thisPrincipal)->GetURI(getter_AddRefs(prinURI));
+ nsPrintfCString marker(
+ "Iframe loaded in background: %s",
+ nsContentUtils::TruncatedURLForDisplay(prinURI).get());
+ PROFILER_MARKER_TEXT("Background Iframe", DOM, {}, marker);
+ }
+ SetBackgroundLoadIframe();
+ }
+ }
+ }
+
+ NS_ENSURE_SUCCESS(Embed(viewer, nullptr, false,
+ ShouldAddToSessionHistory(finalURI, aOpenedChannel),
+ aOpenedChannel, previousURI),
+ NS_ERROR_FAILURE);
+
+ if (!mBrowsingContext->GetHasLoadedNonInitialDocument()) {
+ MOZ_ALWAYS_SUCCEEDS(mBrowsingContext->SetHasLoadedNonInitialDocument(true));
+ }
+
+ if (TreatAsBackgroundLoad()) {
+ nsCOMPtr<nsIRunnable> triggerParentCheckDocShell =
+ NewRunnableMethod("nsDocShell::TriggerParentCheckDocShellIsEmpty", this,
+ &nsDocShell::TriggerParentCheckDocShellIsEmpty);
+ nsresult rv = NS_DispatchToCurrentThread(triggerParentCheckDocShell);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mSavedRefreshURIList = nullptr;
+ mSavingOldViewer = false;
+ mEODForCurrentDocument = false;
+
+ // if this document is part of a multipart document,
+ // the ID can be used to distinguish it from the other parts.
+ nsCOMPtr<nsIMultiPartChannel> multiPartChannel(do_QueryInterface(aRequest));
+ if (multiPartChannel) {
+ if (PresShell* presShell = GetPresShell()) {
+ if (Document* doc = presShell->GetDocument()) {
+ uint32_t partID;
+ multiPartChannel->GetPartID(&partID);
+ doc->SetPartID(partID);
+ }
+ }
+ }
+
+ if (errorOnLocationChangeNeeded) {
+ FireOnLocationChange(this, failedChannel, failedURI,
+ LOCATION_CHANGE_ERROR_PAGE);
+ } else if (onLocationChangeNeeded) {
+ uint32_t locationFlags =
+ (mLoadType & LOAD_CMD_RELOAD) ? uint32_t(LOCATION_CHANGE_RELOAD) : 0;
+ FireOnLocationChange(this, aRequest, mCurrentURI, locationFlags);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsDocShell::NewContentViewerObj(const nsACString& aContentType,
+ nsIRequest* aRequest,
+ nsILoadGroup* aLoadGroup,
+ nsIStreamListener** aContentHandler,
+ nsIContentViewer** aViewer) {
+ nsCOMPtr<nsIChannel> aOpenedChannel = do_QueryInterface(aRequest);
+
+ nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
+ nsContentUtils::FindInternalContentViewer(aContentType);
+ if (!docLoaderFactory) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Now create an instance of the content viewer nsLayoutDLF makes the
+ // determination if it should be a "view-source" instead of "view"
+ nsresult rv = docLoaderFactory->CreateInstance(
+ "view", aOpenedChannel, aLoadGroup, aContentType, this, nullptr,
+ aContentHandler, aViewer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ (*aViewer)->SetContainer(this);
+ return NS_OK;
+}
+
+nsresult nsDocShell::SetupNewViewer(nsIContentViewer* aNewViewer,
+ WindowGlobalChild* aWindowActor) {
+ MOZ_ASSERT(!mIsBeingDestroyed);
+
+ //
+ // Copy content viewer state from previous or parent content viewer.
+ //
+ // The following logic is mirrored in nsHTMLDocument::StartDocumentLoad!
+ //
+ // Do NOT to maintain a reference to the old content viewer outside
+ // of this "copying" block, or it will not be destroyed until the end of
+ // this routine and all <SCRIPT>s and event handlers fail! (bug 20315)
+ //
+ // In this block of code, if we get an error result, we return it
+ // but if we get a null pointer, that's perfectly legal for parent
+ // and parentContentViewer.
+ //
+
+ int32_t x = 0;
+ int32_t y = 0;
+ int32_t cx = 0;
+ int32_t cy = 0;
+
+ // This will get the size from the current content viewer or from the
+ // Init settings
+ DoGetPositionAndSize(&x, &y, &cx, &cy);
+
+ nsCOMPtr<nsIDocShellTreeItem> parentAsItem;
+ NS_ENSURE_SUCCESS(GetInProcessSameTypeParent(getter_AddRefs(parentAsItem)),
+ NS_ERROR_FAILURE);
+ nsCOMPtr<nsIDocShell> parent(do_QueryInterface(parentAsItem));
+
+ const Encoding* reloadEncoding = nullptr;
+ int32_t reloadEncodingSource = kCharsetUninitialized;
+ // |newMUDV| also serves as a flag to set the data from the above vars
+ nsCOMPtr<nsIContentViewer> newCv;
+
+ if (mContentViewer || parent) {
+ nsCOMPtr<nsIContentViewer> oldCv;
+ if (mContentViewer) {
+ // Get any interesting state from old content viewer
+ // XXX: it would be far better to just reuse the document viewer ,
+ // since we know we're just displaying the same document as before
+ oldCv = mContentViewer;
+
+ // Tell the old content viewer to hibernate in session history when
+ // it is destroyed.
+
+ if (mSavingOldViewer && NS_FAILED(CaptureState())) {
+ if (mOSHE) {
+ mOSHE->SyncPresentationState();
+ }
+ mSavingOldViewer = false;
+ }
+ } else {
+ // No old content viewer, so get state from parent's content viewer
+ parent->GetContentViewer(getter_AddRefs(oldCv));
+ }
+
+ if (oldCv) {
+ newCv = aNewViewer;
+ if (newCv) {
+ reloadEncoding =
+ oldCv->GetReloadEncodingAndSource(&reloadEncodingSource);
+ }
+ }
+ }
+
+ nscolor bgcolor = NS_RGBA(0, 0, 0, 0);
+ bool isUnderHiddenEmbedderElement = false;
+ // Ensure that the content viewer is destroyed *after* the GC - bug 71515
+ nsCOMPtr<nsIContentViewer> contentViewer = mContentViewer;
+ if (contentViewer) {
+ // Stop any activity that may be happening in the old document before
+ // releasing it...
+ contentViewer->Stop();
+
+ // Try to extract the canvas background color from the old
+ // presentation shell, so we can use it for the next document.
+ if (PresShell* presShell = contentViewer->GetPresShell()) {
+ bgcolor = presShell->GetCanvasBackground();
+ isUnderHiddenEmbedderElement = presShell->IsUnderHiddenEmbedderElement();
+ }
+
+ contentViewer->Close(mSavingOldViewer ? mOSHE.get() : nullptr);
+ aNewViewer->SetPreviousViewer(contentViewer);
+ }
+ if (mOSHE && (!mContentViewer || !mSavingOldViewer)) {
+ // We don't plan to save a viewer in mOSHE; tell it to drop
+ // any other state it's holding.
+ mOSHE->SyncPresentationState();
+ }
+
+ mContentViewer = nullptr;
+
+ // Now that we're about to switch documents, forget all of our children.
+ // Note that we cached them as needed up in CaptureState above.
+ DestroyChildren();
+
+ mContentViewer = aNewViewer;
+
+ nsCOMPtr<nsIWidget> widget;
+ NS_ENSURE_SUCCESS(GetMainWidget(getter_AddRefs(widget)), NS_ERROR_FAILURE);
+
+ nsIntRect bounds(x, y, cx, cy);
+
+ mContentViewer->SetNavigationTiming(mTiming);
+
+ if (NS_FAILED(mContentViewer->Init(widget, bounds, aWindowActor))) {
+ nsCOMPtr<nsIContentViewer> viewer = mContentViewer;
+ viewer->Close(nullptr);
+ viewer->Destroy();
+ mContentViewer = nullptr;
+ mCurrentURI = nullptr;
+ NS_WARNING("ContentViewer Initialization failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ // If we have old state to copy, set the old state onto the new content
+ // viewer
+ if (newCv) {
+ newCv->SetReloadEncodingAndSource(reloadEncoding, reloadEncodingSource);
+ }
+
+ NS_ENSURE_TRUE(mContentViewer, NS_ERROR_FAILURE);
+
+ // Stuff the bgcolor from the old pres shell into the new
+ // pres shell. This improves page load continuity.
+ if (RefPtr<PresShell> presShell = mContentViewer->GetPresShell()) {
+ presShell->SetCanvasBackground(bgcolor);
+ presShell->ActivenessMaybeChanged();
+ if (isUnderHiddenEmbedderElement) {
+ presShell->SetIsUnderHiddenEmbedderElement(isUnderHiddenEmbedderElement);
+ }
+ }
+
+ // XXX: It looks like the LayoutState gets restored again in Embed()
+ // right after the call to SetupNewViewer(...)
+
+ // We don't show the mContentViewer yet, since we want to draw the old page
+ // until we have enough of the new page to show. Just return with the new
+ // viewer still set to hidden.
+
+ return NS_OK;
+}
+
+void nsDocShell::SetDocCurrentStateObj(nsISHEntry* aShEntry,
+ SessionHistoryInfo* aInfo) {
+ NS_ENSURE_TRUE_VOID(mContentViewer);
+
+ RefPtr<Document> document = GetDocument();
+ NS_ENSURE_TRUE_VOID(document);
+
+ nsCOMPtr<nsIStructuredCloneContainer> scContainer;
+ if (mozilla::SessionHistoryInParent()) {
+ // If aInfo is null, just set the document's state object to null.
+ if (aInfo) {
+ scContainer = aInfo->GetStateData();
+ }
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell %p SetCurrentDocState %p", this, scContainer.get()));
+ } else {
+ if (aShEntry) {
+ scContainer = aShEntry->GetStateData();
+
+ // If aShEntry is null, just set the document's state object to null.
+ }
+ }
+
+ // It's OK for scContainer too be null here; that just means there's no
+ // state data associated with this history entry.
+ document->SetStateObject(scContainer);
+}
+
+nsresult nsDocShell::CheckLoadingPermissions() {
+ // This method checks whether the caller may load content into
+ // this docshell. Even though we've done our best to hide windows
+ // from code that doesn't have the right to access them, it's
+ // still possible for an evil site to open a window and access
+ // frames in the new window through window.frames[] (which is
+ // allAccess for historic reasons), so we still need to do this
+ // check on load.
+ nsresult rv = NS_OK;
+
+ if (!IsSubframe()) {
+ // We're not a frame. Permit all loads.
+ return rv;
+ }
+
+ // Note - The check for a current JSContext here isn't necessarily sensical.
+ // It's just designed to preserve the old semantics during a mass-conversion
+ // patch.
+ if (!nsContentUtils::GetCurrentJSContext()) {
+ return NS_OK;
+ }
+
+ // Check if the caller is from the same origin as this docshell,
+ // or any of its ancestors.
+ for (RefPtr<BrowsingContext> bc = mBrowsingContext; bc;
+ bc = bc->GetParent()) {
+ // If the BrowsingContext is not in process, then it
+ // is true by construction that its principal will not
+ // subsume the current docshell principal.
+ if (!bc->IsInProcess()) {
+ continue;
+ }
+
+ nsCOMPtr<nsIScriptGlobalObject> sgo =
+ bc->GetDocShell()->GetScriptGlobalObject();
+ nsCOMPtr<nsIScriptObjectPrincipal> sop(do_QueryInterface(sgo));
+
+ nsIPrincipal* p;
+ if (!sop || !(p = sop->GetPrincipal())) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (nsContentUtils::SubjectPrincipal()->Subsumes(p)) {
+ // Same origin, permit load
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_DOM_PROP_ACCESS_DENIED;
+}
+
+//*****************************************************************************
+// nsDocShell: Site Loading
+//*****************************************************************************
+
+void nsDocShell::CopyFavicon(nsIURI* aOldURI, nsIURI* aNewURI,
+ bool aInPrivateBrowsing) {
+ if (XRE_IsContentProcess()) {
+ dom::ContentChild* contentChild = dom::ContentChild::GetSingleton();
+ if (contentChild) {
+ contentChild->SendCopyFavicon(aOldURI, aNewURI, aInPrivateBrowsing);
+ }
+ return;
+ }
+
+#ifdef MOZ_PLACES
+ nsCOMPtr<nsIFaviconService> favSvc =
+ do_GetService("@mozilla.org/browser/favicon-service;1");
+ if (favSvc) {
+ favSvc->CopyFavicons(aOldURI, aNewURI,
+ aInPrivateBrowsing
+ ? nsIFaviconService::FAVICON_LOAD_PRIVATE
+ : nsIFaviconService::FAVICON_LOAD_NON_PRIVATE,
+ nullptr);
+ }
+#endif
+}
+
+class InternalLoadEvent : public Runnable {
+ public:
+ InternalLoadEvent(nsDocShell* aDocShell, nsDocShellLoadState* aLoadState)
+ : mozilla::Runnable("InternalLoadEvent"),
+ mDocShell(aDocShell),
+ mLoadState(aLoadState) {
+ // For events, both target and filename should be the version of "null" they
+ // expect. By the time the event is fired, both window targeting and file
+ // downloading have been handled, so we should never have an internal load
+ // event that retargets or had a download.
+ mLoadState->SetTarget(u""_ns);
+ mLoadState->SetFileName(VoidString());
+ }
+
+ NS_IMETHOD
+ Run() override {
+#ifndef ANDROID
+ MOZ_ASSERT(mLoadState->TriggeringPrincipal(),
+ "InternalLoadEvent: Should always have a principal here");
+#endif
+ return mDocShell->InternalLoad(mLoadState);
+ }
+
+ private:
+ RefPtr<nsDocShell> mDocShell;
+ RefPtr<nsDocShellLoadState> mLoadState;
+};
+
+/**
+ * Returns true if we started an asynchronous load (i.e., from the network), but
+ * the document we're loading there hasn't yet become this docshell's active
+ * document.
+ *
+ * When JustStartedNetworkLoad is true, you should be careful about modifying
+ * mLoadType and mLSHE. These are both set when the asynchronous load first
+ * starts, and the load expects that, when it eventually runs InternalLoad,
+ * mLoadType and mLSHE will have their original values.
+ */
+bool nsDocShell::JustStartedNetworkLoad() {
+ return mDocumentRequest && mDocumentRequest != GetCurrentDocChannel();
+}
+
+// The contentType will be INTERNAL_(I)FRAME if this docshell is for a
+// non-toplevel browsing context in spec terms. (frame, iframe, <object>,
+// <embed>, etc)
+//
+// This return value will be used when we call NS_CheckContentLoadPolicy, and
+// later when we call DoURILoad.
+nsContentPolicyType nsDocShell::DetermineContentType() {
+ if (!IsSubframe()) {
+ return nsIContentPolicy::TYPE_DOCUMENT;
+ }
+
+ const auto& maybeEmbedderElementType =
+ GetBrowsingContext()->GetEmbedderElementType();
+ if (!maybeEmbedderElementType) {
+ // If the EmbedderElementType hasn't been set yet, just assume we're
+ // an iframe since that's more common.
+ return nsIContentPolicy::TYPE_INTERNAL_IFRAME;
+ }
+
+ return maybeEmbedderElementType->EqualsLiteral("iframe")
+ ? nsIContentPolicy::TYPE_INTERNAL_IFRAME
+ : nsIContentPolicy::TYPE_INTERNAL_FRAME;
+}
+
+bool nsDocShell::NoopenerForceEnabled() {
+ // If current's top-level browsing context's active document's
+ // cross-origin-opener-policy is "same-origin" or "same-origin + COEP" then
+ // if currentDoc's origin is not same origin with currentDoc's top-level
+ // origin, noopener is force enabled, and name is cleared to "_blank".
+ auto topPolicy = mBrowsingContext->Top()->GetOpenerPolicy();
+ return (topPolicy == nsILoadInfo::OPENER_POLICY_SAME_ORIGIN ||
+ topPolicy ==
+ nsILoadInfo::
+ OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP) &&
+ !mBrowsingContext->SameOriginWithTop();
+}
+
+nsresult nsDocShell::PerformRetargeting(nsDocShellLoadState* aLoadState) {
+ MOZ_ASSERT(aLoadState, "need a load state!");
+ MOZ_ASSERT(!aLoadState->Target().IsEmpty(), "should have a target here!");
+ MOZ_ASSERT(aLoadState->TargetBrowsingContext().IsNull(),
+ "should not have picked target yet");
+
+ nsresult rv = NS_OK;
+ RefPtr<BrowsingContext> targetContext;
+
+ // Only _self, _parent, and _top are supported in noopener case. But we
+ // have to be careful to not apply that to the noreferrer case. See bug
+ // 1358469.
+ bool allowNamedTarget =
+ !aLoadState->HasInternalLoadFlags(INTERNAL_LOAD_FLAGS_NO_OPENER) ||
+ aLoadState->HasInternalLoadFlags(INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER);
+ if (allowNamedTarget ||
+ aLoadState->Target().LowerCaseEqualsLiteral("_self") ||
+ aLoadState->Target().LowerCaseEqualsLiteral("_parent") ||
+ aLoadState->Target().LowerCaseEqualsLiteral("_top")) {
+ Document* document = GetDocument();
+ NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
+ WindowGlobalChild* wgc = document->GetWindowGlobalChild();
+ NS_ENSURE_TRUE(wgc, NS_ERROR_FAILURE);
+ targetContext = wgc->FindBrowsingContextWithName(
+ aLoadState->Target(), /* aUseEntryGlobalForAccessCheck */ false);
+ }
+
+ if (!targetContext) {
+ // If the targetContext doesn't exist, then this is a new docShell and we
+ // should consider this a TYPE_DOCUMENT load
+ //
+ // For example, when target="_blank"
+
+ // If there's no targetContext, that means we are about to create a new
+ // window. Perform a content policy check before creating the window. Please
+ // note for all other docshell loads content policy checks are performed
+ // within the contentSecurityManager when the channel is about to be
+ // openend.
+ nsISupports* requestingContext = nullptr;
+ if (XRE_IsContentProcess()) {
+ // In e10s the child process doesn't have access to the element that
+ // contains the browsing context (because that element is in the chrome
+ // process). So we just pass mScriptGlobal.
+ requestingContext = ToSupports(mScriptGlobal);
+ } else {
+ // This is for loading non-e10s tabs and toplevel windows of various
+ // sorts.
+ // For the toplevel window cases, requestingElement will be null.
+ nsCOMPtr<Element> requestingElement =
+ mScriptGlobal->GetFrameElementInternal();
+ requestingContext = requestingElement;
+ }
+
+ // Ideally we should use the same loadinfo as within DoURILoad which
+ // should match this one when both are applicable.
+ nsCOMPtr<nsILoadInfo> secCheckLoadInfo =
+ new LoadInfo(mScriptGlobal, aLoadState->URI(),
+ aLoadState->TriggeringPrincipal(), requestingContext,
+ nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, 0);
+
+ // Since Content Policy checks are performed within docShell as well as
+ // the ContentSecurityManager we need a reliable way to let certain
+ // nsIContentPolicy consumers ignore duplicate calls.
+ secCheckLoadInfo->SetSkipContentPolicyCheckForWebRequest(true);
+
+ int16_t shouldLoad = nsIContentPolicy::ACCEPT;
+ rv = NS_CheckContentLoadPolicy(aLoadState->URI(), secCheckLoadInfo,
+ ""_ns, // mime guess
+ &shouldLoad);
+
+ if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
+ if (NS_SUCCEEDED(rv)) {
+ if (shouldLoad == nsIContentPolicy::REJECT_TYPE) {
+ return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT;
+ }
+ if (shouldLoad == nsIContentPolicy::REJECT_POLICY) {
+ return NS_ERROR_BLOCKED_BY_POLICY;
+ }
+ }
+
+ return NS_ERROR_CONTENT_BLOCKED;
+ }
+ }
+
+ //
+ // Resolve the window target before going any further...
+ // If the load has been targeted to another DocShell, then transfer the
+ // load to it...
+ //
+
+ // We've already done our owner-inheriting. Mask out that bit, so we
+ // don't try inheriting an owner from the target window if we came up
+ // with a null owner above.
+ aLoadState->UnsetInternalLoadFlag(INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL);
+
+ if (!targetContext) {
+ // If the docshell's document is sandboxed, only open a new window
+ // if the document's SANDBOXED_AUXILLARY_NAVIGATION flag is not set.
+ // (i.e. if allow-popups is specified)
+ NS_ENSURE_TRUE(mContentViewer, NS_ERROR_FAILURE);
+ Document* doc = mContentViewer->GetDocument();
+
+ const bool isDocumentAuxSandboxed =
+ doc && (doc->GetSandboxFlags() & SANDBOXED_AUXILIARY_NAVIGATION);
+
+ if (isDocumentAuxSandboxed) {
+ return NS_ERROR_DOM_INVALID_ACCESS_ERR;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
+ NS_ENSURE_TRUE(win, NS_ERROR_NOT_AVAILABLE);
+
+ RefPtr<BrowsingContext> newBC;
+ nsAutoCString spec;
+ aLoadState->URI()->GetSpec(spec);
+
+ // If we are a noopener load, we just hand the whole thing over to our
+ // window.
+ if (aLoadState->HasInternalLoadFlags(INTERNAL_LOAD_FLAGS_NO_OPENER) ||
+ NoopenerForceEnabled()) {
+ // Various asserts that we know to hold because NO_OPENER loads can only
+ // happen for links.
+ MOZ_ASSERT(!aLoadState->LoadReplace());
+ MOZ_ASSERT(aLoadState->PrincipalToInherit() ==
+ aLoadState->TriggeringPrincipal());
+ MOZ_ASSERT(!(aLoadState->InternalLoadFlags() &
+ ~(INTERNAL_LOAD_FLAGS_NO_OPENER |
+ INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER)),
+ "Only INTERNAL_LOAD_FLAGS_NO_OPENER and "
+ "INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER can be set");
+ MOZ_ASSERT_IF(aLoadState->PostDataStream(),
+ aLoadState->IsFormSubmission());
+ MOZ_ASSERT(!aLoadState->HeadersStream());
+ // If OnLinkClickSync was invoked inside the onload handler, the load
+ // type would be set to LOAD_NORMAL_REPLACE; otherwise it should be
+ // LOAD_LINK.
+ MOZ_ASSERT(aLoadState->LoadType() == LOAD_LINK ||
+ aLoadState->LoadType() == LOAD_NORMAL_REPLACE);
+ MOZ_ASSERT(!aLoadState->LoadIsFromSessionHistory());
+ MOZ_ASSERT(aLoadState->FirstParty()); // Windowwatcher will assume this.
+
+ RefPtr<nsDocShellLoadState> loadState =
+ new nsDocShellLoadState(aLoadState->URI());
+
+ // Set up our loadinfo so it will do the load as much like we would have
+ // as possible.
+ loadState->SetReferrerInfo(aLoadState->GetReferrerInfo());
+ loadState->SetOriginalURI(aLoadState->OriginalURI());
+
+ Maybe<nsCOMPtr<nsIURI>> resultPrincipalURI;
+ aLoadState->GetMaybeResultPrincipalURI(resultPrincipalURI);
+
+ loadState->SetMaybeResultPrincipalURI(resultPrincipalURI);
+ loadState->SetKeepResultPrincipalURIIfSet(
+ aLoadState->KeepResultPrincipalURIIfSet());
+ // LoadReplace will always be false due to asserts above, skip setting
+ // it.
+ loadState->SetTriggeringPrincipal(aLoadState->TriggeringPrincipal());
+ loadState->SetTriggeringSandboxFlags(
+ aLoadState->TriggeringSandboxFlags());
+ loadState->SetCsp(aLoadState->Csp());
+ loadState->SetInheritPrincipal(aLoadState->HasInternalLoadFlags(
+ INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL));
+ // Explicit principal because we do not want any guesses as to what the
+ // principal to inherit is: it should be aTriggeringPrincipal.
+ loadState->SetPrincipalIsExplicit(true);
+ loadState->SetLoadType(LOAD_LINK);
+ loadState->SetForceAllowDataURI(aLoadState->HasInternalLoadFlags(
+ INTERNAL_LOAD_FLAGS_FORCE_ALLOW_DATA_URI));
+
+ loadState->SetHasValidUserGestureActivation(
+ aLoadState->HasValidUserGestureActivation());
+
+ // Propagate POST data to the new load.
+ loadState->SetPostDataStream(aLoadState->PostDataStream());
+ loadState->SetIsFormSubmission(aLoadState->IsFormSubmission());
+
+ rv = win->Open(NS_ConvertUTF8toUTF16(spec),
+ aLoadState->Target(), // window name
+ u""_ns, // Features
+ loadState,
+ true, // aForceNoOpener
+ getter_AddRefs(newBC));
+ MOZ_ASSERT(!newBC);
+ return rv;
+ }
+
+ rv = win->OpenNoNavigate(NS_ConvertUTF8toUTF16(spec),
+ aLoadState->Target(), // window name
+ u""_ns, // Features
+ getter_AddRefs(newBC));
+
+ // In some cases the Open call doesn't actually result in a new
+ // window being opened. We can detect these cases by examining the
+ // document in |newBC|, if any.
+ nsCOMPtr<nsPIDOMWindowOuter> piNewWin =
+ newBC ? newBC->GetDOMWindow() : nullptr;
+ if (piNewWin) {
+ RefPtr<Document> newDoc = piNewWin->GetExtantDoc();
+ if (!newDoc || newDoc->IsInitialDocument()) {
+ aLoadState->SetInternalLoadFlag(INTERNAL_LOAD_FLAGS_FIRST_LOAD);
+ }
+ }
+
+ if (newBC) {
+ targetContext = newBC;
+ }
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(targetContext, rv);
+
+ // If our target BrowsingContext is still pending initialization, ignore the
+ // navigation request targeting it.
+ if (NS_WARN_IF(targetContext->GetPendingInitialization())) {
+ return NS_OK;
+ }
+
+ aLoadState->SetTargetBrowsingContext(targetContext);
+ //
+ // Transfer the load to the target BrowsingContext... Clear the window target
+ // name to the empty string to prevent recursive retargeting!
+ //
+ // No window target
+ aLoadState->SetTarget(u""_ns);
+ // No forced download
+ aLoadState->SetFileName(VoidString());
+ return targetContext->InternalLoad(aLoadState);
+}
+
+static nsAutoCString RefMaybeNull(nsIURI* aURI) {
+ nsAutoCString result;
+ if (NS_FAILED(aURI->GetRef(result))) {
+ result.SetIsVoid(true);
+ }
+ return result;
+}
+
+uint32_t nsDocShell::GetSameDocumentNavigationFlags(nsIURI* aNewURI) {
+ uint32_t flags = LOCATION_CHANGE_SAME_DOCUMENT;
+
+ bool equal = false;
+ if (mCurrentURI &&
+ NS_SUCCEEDED(mCurrentURI->EqualsExceptRef(aNewURI, &equal)) && equal &&
+ RefMaybeNull(mCurrentURI) != RefMaybeNull(aNewURI)) {
+ flags |= LOCATION_CHANGE_HASHCHANGE;
+ }
+
+ return flags;
+}
+
+bool nsDocShell::IsSameDocumentNavigation(nsDocShellLoadState* aLoadState,
+ SameDocumentNavigationState& aState) {
+ MOZ_ASSERT(aLoadState);
+ if (!(aLoadState->LoadType() == LOAD_NORMAL ||
+ aLoadState->LoadType() == LOAD_STOP_CONTENT ||
+ LOAD_TYPE_HAS_FLAGS(aLoadState->LoadType(),
+ LOAD_FLAGS_REPLACE_HISTORY) ||
+ aLoadState->LoadType() == LOAD_HISTORY ||
+ aLoadState->LoadType() == LOAD_LINK)) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> currentURI = mCurrentURI;
+
+ nsresult rvURINew = aLoadState->URI()->GetRef(aState.mNewHash);
+ if (NS_SUCCEEDED(rvURINew)) {
+ rvURINew = aLoadState->URI()->GetHasRef(&aState.mNewURIHasRef);
+ }
+
+ if (currentURI && NS_SUCCEEDED(rvURINew)) {
+ nsresult rvURIOld = currentURI->GetRef(aState.mCurrentHash);
+ if (NS_SUCCEEDED(rvURIOld)) {
+ rvURIOld = currentURI->GetHasRef(&aState.mCurrentURIHasRef);
+ }
+ if (NS_SUCCEEDED(rvURIOld)) {
+ if (NS_FAILED(currentURI->EqualsExceptRef(aLoadState->URI(),
+ &aState.mSameExceptHashes))) {
+ aState.mSameExceptHashes = false;
+ }
+ }
+ }
+
+ if (!aState.mSameExceptHashes && currentURI && NS_SUCCEEDED(rvURINew)) {
+ // Maybe aLoadState->URI() came from the exposable form of currentURI?
+ nsCOMPtr<nsIURI> currentExposableURI =
+ nsIOService::CreateExposableURI(currentURI);
+ nsresult rvURIOld = currentExposableURI->GetRef(aState.mCurrentHash);
+ if (NS_SUCCEEDED(rvURIOld)) {
+ rvURIOld = currentExposableURI->GetHasRef(&aState.mCurrentURIHasRef);
+ }
+ if (NS_SUCCEEDED(rvURIOld)) {
+ if (NS_FAILED(currentExposableURI->EqualsExceptRef(
+ aLoadState->URI(), &aState.mSameExceptHashes))) {
+ aState.mSameExceptHashes = false;
+ }
+ // HTTPS-Only Mode upgrades schemes from http to https in Necko, hence we
+ // have to perform a special check here to avoid an actual navigation. If
+ // HTTPS-Only Mode is enabled and the two URIs are same-origin (modulo the
+ // fact that the new URI is currently http), then set mSameExceptHashes to
+ // true and only perform a fragment navigation.
+ if (!aState.mSameExceptHashes) {
+ if (nsCOMPtr<nsIChannel> docChannel = GetCurrentDocChannel()) {
+ nsCOMPtr<nsILoadInfo> docLoadInfo = docChannel->LoadInfo();
+ if (!docLoadInfo->GetLoadErrorPage() &&
+ nsHTTPSOnlyUtils::IsEqualURIExceptSchemeAndRef(
+ currentExposableURI, aLoadState->URI(), docLoadInfo)) {
+ uint32_t status = docLoadInfo->GetHttpsOnlyStatus();
+ if (status & (nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_REGISTERED |
+ nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST)) {
+ // At this point the requested URI is for sure a fragment
+ // navigation via HTTP and HTTPS-Only mode or HTTPS-First is
+ // enabled. Also it is not interfering the upgrade order of
+ // https://searchfox.org/mozilla-central/source/netwerk/base/nsNetUtil.cpp#2948-2953.
+ // Since we are on an HTTPS site the fragment
+ // navigation should also be an HTTPS.
+ // For that reason we should upgrade the URI to HTTPS.
+ aState.mSecureUpgradeURI = true;
+ aState.mSameExceptHashes = true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (mozilla::SessionHistoryInParent()) {
+ if (mActiveEntry && aLoadState->LoadIsFromSessionHistory()) {
+ aState.mHistoryNavBetweenSameDoc = mActiveEntry->SharesDocumentWith(
+ aLoadState->GetLoadingSessionHistoryInfo()->mInfo);
+ }
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell::IsSameDocumentNavigation %p NavBetweenSameDoc=%d",
+ this, aState.mHistoryNavBetweenSameDoc));
+ } else {
+ if (mOSHE && aLoadState->LoadIsFromSessionHistory()) {
+ // We're doing a history load.
+
+ mOSHE->SharesDocumentWith(aLoadState->SHEntry(),
+ &aState.mHistoryNavBetweenSameDoc);
+ }
+ }
+
+ // A same document navigation happens when we navigate between two SHEntries
+ // for the same document. We do a same document navigation under two
+ // circumstances. Either
+ //
+ // a) we're navigating between two different SHEntries which share a
+ // document, or
+ //
+ // b) we're navigating to a new shentry whose URI differs from the
+ // current URI only in its hash, the new hash is non-empty, and
+ // we're not doing a POST.
+ //
+ // The restriction that the SHEntries in (a) must be different ensures
+ // that history.go(0) and the like trigger full refreshes, rather than
+ // same document navigations.
+ if (!mozilla::SessionHistoryInParent()) {
+ bool doSameDocumentNavigation =
+ (aState.mHistoryNavBetweenSameDoc && mOSHE != aLoadState->SHEntry()) ||
+ (!aLoadState->SHEntry() && !aLoadState->PostDataStream() &&
+ aState.mSameExceptHashes && aState.mNewURIHasRef);
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell %p NavBetweenSameDoc=%d is same doc = %d", this,
+ aState.mHistoryNavBetweenSameDoc, doSameDocumentNavigation));
+ return doSameDocumentNavigation;
+ }
+
+ if (aState.mHistoryNavBetweenSameDoc &&
+ !aLoadState->GetLoadingSessionHistoryInfo()->mLoadingCurrentEntry) {
+ return true;
+ }
+
+ MOZ_LOG(
+ gSHLog, LogLevel::Debug,
+ ("nsDocShell::IsSameDocumentNavigation %p !LoadIsFromSessionHistory=%s "
+ "!PostDataStream: %s mSameExceptHashes: %s mNewURIHasRef: %s",
+ this, !aLoadState->LoadIsFromSessionHistory() ? "true" : "false",
+ !aLoadState->PostDataStream() ? "true" : "false",
+ aState.mSameExceptHashes ? "true" : "false",
+ aState.mNewURIHasRef ? "true" : "false"));
+ return !aLoadState->LoadIsFromSessionHistory() &&
+ !aLoadState->PostDataStream() && aState.mSameExceptHashes &&
+ aState.mNewURIHasRef;
+}
+
+nsresult nsDocShell::HandleSameDocumentNavigation(
+ nsDocShellLoadState* aLoadState, SameDocumentNavigationState& aState) {
+#ifdef DEBUG
+ SameDocumentNavigationState state;
+ MOZ_ASSERT(IsSameDocumentNavigation(aLoadState, state));
+#endif
+
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell::HandleSameDocumentNavigation %p %s -> %s", this,
+ mCurrentURI->GetSpecOrDefault().get(),
+ aLoadState->URI()->GetSpecOrDefault().get()));
+
+ RefPtr<Document> doc = GetDocument();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+ doc->DoNotifyPossibleTitleChange();
+
+ nsCOMPtr<nsIURI> currentURI = mCurrentURI;
+
+ // We need to upgrade the new URI from http: to https:
+ nsCOMPtr<nsIURI> newURI = aLoadState->URI();
+ if (aState.mSecureUpgradeURI) {
+ MOZ_TRY(NS_GetSecureUpgradedURI(aLoadState->URI(), getter_AddRefs(newURI)));
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("Upgraded URI to %s", newURI->GetSpecOrDefault().get()));
+ }
+
+#ifdef DEBUG
+ if (aState.mSameExceptHashes) {
+ bool sameExceptHashes = false;
+ currentURI->EqualsExceptRef(newURI, &sameExceptHashes);
+ MOZ_ASSERT(sameExceptHashes);
+ }
+#endif
+
+ // Save the position of the scrollers.
+ nsPoint scrollPos = GetCurScrollPos();
+
+ // Reset mLoadType to its original value once we exit this block, because this
+ // same document navigation might have started after a normal, network load,
+ // and we don't want to clobber its load type. See bug 737307.
+ AutoRestore<uint32_t> loadTypeResetter(mLoadType);
+
+ // If a non-same-document-navigation (i.e., a network load) is pending, make
+ // this a replacement load, so that we don't add a SHEntry here and the
+ // network load goes into the SHEntry it expects to.
+ if (JustStartedNetworkLoad() && (aLoadState->LoadType() & LOAD_CMD_NORMAL)) {
+ mLoadType = LOAD_NORMAL_REPLACE;
+ } else {
+ mLoadType = aLoadState->LoadType();
+ }
+
+ mURIResultedInDocument = true;
+
+ nsCOMPtr<nsISHEntry> oldLSHE = mLSHE;
+
+ // we need to assign aLoadState->SHEntry() to mLSHE right here, so that on
+ // History loads, SetCurrentURI() called from OnNewURI() will send proper
+ // onLocationChange() notifications to the browser to update back/forward
+ // buttons.
+ SetHistoryEntryAndUpdateBC(Some<nsISHEntry*>(aLoadState->SHEntry()),
+ Nothing());
+ UniquePtr<mozilla::dom::LoadingSessionHistoryInfo> oldLoadingEntry;
+ mLoadingEntry.swap(oldLoadingEntry);
+ if (aLoadState->GetLoadingSessionHistoryInfo()) {
+ mLoadingEntry = MakeUnique<LoadingSessionHistoryInfo>(
+ *aLoadState->GetLoadingSessionHistoryInfo());
+ mNeedToReportActiveAfterLoadingBecomesActive = false;
+ }
+
+ // Set the doc's URI according to the new history entry's URI.
+ doc->SetDocumentURI(newURI);
+
+ /* This is a anchor traversal within the same page.
+ * call OnNewURI() so that, this traversal will be
+ * recorded in session and global history.
+ */
+ nsCOMPtr<nsIPrincipal> newURITriggeringPrincipal, newURIPrincipalToInherit,
+ newURIPartitionedPrincipalToInherit;
+ nsCOMPtr<nsIContentSecurityPolicy> newCsp;
+ if (mozilla::SessionHistoryInParent() ? !!mActiveEntry : !!mOSHE) {
+ if (mozilla::SessionHistoryInParent()) {
+ newURITriggeringPrincipal = mActiveEntry->GetTriggeringPrincipal();
+ newURIPrincipalToInherit = mActiveEntry->GetPrincipalToInherit();
+ newURIPartitionedPrincipalToInherit =
+ mActiveEntry->GetPartitionedPrincipalToInherit();
+ newCsp = mActiveEntry->GetCsp();
+ } else {
+ newURITriggeringPrincipal = mOSHE->GetTriggeringPrincipal();
+ newURIPrincipalToInherit = mOSHE->GetPrincipalToInherit();
+ newURIPartitionedPrincipalToInherit =
+ mOSHE->GetPartitionedPrincipalToInherit();
+ newCsp = mOSHE->GetCsp();
+ }
+ } else {
+ newURITriggeringPrincipal = aLoadState->TriggeringPrincipal();
+ newURIPrincipalToInherit = doc->NodePrincipal();
+ newURIPartitionedPrincipalToInherit = doc->PartitionedPrincipal();
+ newCsp = doc->GetCsp();
+ }
+
+ uint32_t locationChangeFlags = GetSameDocumentNavigationFlags(newURI);
+
+ // Pass true for aCloneSHChildren, since we're not
+ // changing documents here, so all of our subframes are
+ // still relevant to the new session history entry.
+ //
+ // It also makes OnNewURI(...) set LOCATION_CHANGE_SAME_DOCUMENT
+ // flag on firing onLocationChange(...).
+ // Anyway, aCloneSHChildren param is simply reflecting
+ // doSameDocumentNavigation in this scope.
+ //
+ // Note: we'll actually fire onLocationChange later, in order to preserve
+ // ordering of HistoryCommit() in the parent vs onLocationChange (bug
+ // 1668126)
+ bool locationChangeNeeded = OnNewURI(
+ newURI, nullptr, newURITriggeringPrincipal, newURIPrincipalToInherit,
+ newURIPartitionedPrincipalToInherit, newCsp, false, true, true);
+
+ nsCOMPtr<nsIInputStream> postData;
+ uint32_t cacheKey = 0;
+
+ bool scrollRestorationIsManual = false;
+ if (!mozilla::SessionHistoryInParent()) {
+ if (mOSHE) {
+ /* save current position of scroller(s) (bug 59774) */
+ mOSHE->SetScrollPosition(scrollPos.x, scrollPos.y);
+ scrollRestorationIsManual = mOSHE->GetScrollRestorationIsManual();
+ // Get the postdata and page ident from the current page, if
+ // the new load is being done via normal means. Note that
+ // "normal means" can be checked for just by checking for
+ // LOAD_CMD_NORMAL, given the loadType and allowScroll check
+ // above -- it filters out some LOAD_CMD_NORMAL cases that we
+ // wouldn't want here.
+ if (aLoadState->LoadType() & LOAD_CMD_NORMAL) {
+ postData = mOSHE->GetPostData();
+ cacheKey = mOSHE->GetCacheKey();
+ }
+
+ // Link our new SHEntry to the old SHEntry's back/forward
+ // cache data, since the two SHEntries correspond to the
+ // same document.
+ if (mLSHE) {
+ if (!aLoadState->LoadIsFromSessionHistory()) {
+ // If we're not doing a history load, scroll restoration
+ // should be inherited from the previous session history entry.
+ SetScrollRestorationIsManualOnHistoryEntry(mLSHE,
+ scrollRestorationIsManual);
+ }
+ mLSHE->AdoptBFCacheEntry(mOSHE);
+ }
+ }
+ } else {
+ if (mActiveEntry) {
+ mActiveEntry->SetScrollPosition(scrollPos.x, scrollPos.y);
+ if (mBrowsingContext) {
+ CollectWireframe();
+ if (XRE_IsParentProcess()) {
+ SessionHistoryEntry* entry =
+ mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry();
+ if (entry) {
+ entry->SetScrollPosition(scrollPos.x, scrollPos.y);
+ }
+ } else {
+ mozilla::Unused << ContentChild::GetSingleton()
+ ->SendSessionHistoryEntryScrollPosition(
+ mBrowsingContext, scrollPos.x,
+ scrollPos.y);
+ }
+ }
+ }
+ if (mLoadingEntry) {
+ if (!mLoadingEntry->mLoadIsFromSessionHistory) {
+ // If we're not doing a history load, scroll restoration
+ // should be inherited from the previous session history entry.
+ // XXX This needs most probably tweaks once fragment navigation is
+ // fixed to work with session-history-in-parent.
+ SetScrollRestorationIsManualOnHistoryEntry(nullptr,
+ scrollRestorationIsManual);
+ }
+ }
+ }
+
+ // If we're doing a history load, use its scroll restoration state.
+ if (aLoadState->LoadIsFromSessionHistory()) {
+ if (mozilla::SessionHistoryInParent()) {
+ scrollRestorationIsManual = aLoadState->GetLoadingSessionHistoryInfo()
+ ->mInfo.GetScrollRestorationIsManual();
+ } else {
+ scrollRestorationIsManual =
+ aLoadState->SHEntry()->GetScrollRestorationIsManual();
+ }
+ }
+
+ /* Assign mLSHE to mOSHE. This will either be a new entry created
+ * by OnNewURI() for normal loads or aLoadState->SHEntry() for history
+ * loads.
+ */
+ if (!mozilla::SessionHistoryInParent()) {
+ if (mLSHE) {
+ SetHistoryEntryAndUpdateBC(Nothing(), Some<nsISHEntry*>(mLSHE));
+ // Save the postData obtained from the previous page
+ // in to the session history entry created for the
+ // anchor page, so that any history load of the anchor
+ // page will restore the appropriate postData.
+ if (postData) {
+ mOSHE->SetPostData(postData);
+ }
+
+ // Make sure we won't just repost without hitting the
+ // cache first
+ if (cacheKey != 0) {
+ mOSHE->SetCacheKey(cacheKey);
+ }
+ }
+
+ /* Set the title for the SH entry for this target url so that
+ * SH menus in go/back/forward buttons won't be empty for this.
+ * Note, this happens on mOSHE (and mActiveEntry in the future) because of
+ * the code above.
+ * Note, when session history lives in the parent process, this does not
+ * update the title there.
+ */
+ SetTitleOnHistoryEntry(false);
+ } else {
+ if (aLoadState->LoadIsFromSessionHistory()) {
+ MOZ_LOG(
+ gSHLog, LogLevel::Debug,
+ ("Moving the loading entry to the active entry on nsDocShell %p to "
+ "%s",
+ this, mLoadingEntry->mInfo.GetURI()->GetSpecOrDefault().get()));
+
+ nsCOMPtr<nsILayoutHistoryState> currentLayoutHistoryState;
+ if (mActiveEntry) {
+ currentLayoutHistoryState = mActiveEntry->GetLayoutHistoryState();
+ }
+
+ UniquePtr<SessionHistoryInfo> previousActiveEntry(mActiveEntry.release());
+ mActiveEntry = MakeUnique<SessionHistoryInfo>(mLoadingEntry->mInfo);
+ if (currentLayoutHistoryState) {
+ // Restore the existing nsILayoutHistoryState object, since it is
+ // possibly being used by the layout. When doing a new load, the
+ // shared state is copied from the existing active entry, so this
+ // special case is needed only with the history loads.
+ mActiveEntry->SetLayoutHistoryState(currentLayoutHistoryState);
+ }
+
+ if (cacheKey != 0) {
+ mActiveEntry->SetCacheKey(cacheKey);
+ }
+ // We're passing in mCurrentURI, which could be null. SessionHistoryCommit
+ // does require a non-null uri if this is for a refresh load of the same
+ // URI, but in that case mCurrentURI won't be null here.
+ mBrowsingContext->SessionHistoryCommit(
+ *mLoadingEntry, mLoadType, mCurrentURI, previousActiveEntry.get(),
+ true, true,
+ /* No expiration update on the same document loads*/
+ false, cacheKey);
+ // FIXME Need to set postdata.
+
+ // Set the title for the SH entry for this target url so that
+ // SH menus in go/back/forward buttons won't be empty for this.
+ // Note, when session history lives in the parent process, this does not
+ // update the title there.
+ SetTitleOnHistoryEntry(false);
+ } else {
+ Maybe<bool> scrollRestorationIsManual;
+ if (mActiveEntry) {
+ scrollRestorationIsManual.emplace(
+ mActiveEntry->GetScrollRestorationIsManual());
+
+ // Get the postdata and page ident from the current page, if the new
+ // load is being done via normal means. Note that "normal means" can be
+ // checked for just by checking for LOAD_CMD_NORMAL, given the loadType
+ // and allowScroll check above -- it filters out some LOAD_CMD_NORMAL
+ // cases that we wouldn't want here.
+ if (aLoadState->LoadType() & LOAD_CMD_NORMAL) {
+ postData = mActiveEntry->GetPostData();
+ cacheKey = mActiveEntry->GetCacheKey();
+ }
+ }
+
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("Creating an active entry on nsDocShell %p to %s", this,
+ newURI->GetSpecOrDefault().get()));
+ if (mActiveEntry) {
+ mActiveEntry = MakeUnique<SessionHistoryInfo>(*mActiveEntry, newURI);
+ } else {
+ mActiveEntry = MakeUnique<SessionHistoryInfo>(
+ newURI, newURITriggeringPrincipal, newURIPrincipalToInherit,
+ newURIPartitionedPrincipalToInherit, newCsp, mContentTypeHint);
+ }
+
+ // Save the postData obtained from the previous page in to the session
+ // history entry created for the anchor page, so that any history load of
+ // the anchor page will restore the appropriate postData.
+ if (postData) {
+ mActiveEntry->SetPostData(postData);
+ }
+
+ // Make sure we won't just repost without hitting the
+ // cache first
+ if (cacheKey != 0) {
+ mActiveEntry->SetCacheKey(cacheKey);
+ }
+
+ // Set the title for the SH entry for this target url so that
+ // SH menus in go/back/forward buttons won't be empty for this.
+ mActiveEntry->SetTitle(mTitle);
+
+ if (scrollRestorationIsManual.isSome()) {
+ mActiveEntry->SetScrollRestorationIsManual(
+ scrollRestorationIsManual.value());
+ }
+
+ if (LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY)) {
+ mBrowsingContext->ReplaceActiveSessionHistoryEntry(mActiveEntry.get());
+ } else {
+ mBrowsingContext->IncrementHistoryEntryCountForBrowsingContext();
+ // FIXME We should probably just compute mChildOffset in the parent
+ // instead of passing it over IPC here.
+ mBrowsingContext->SetActiveSessionHistoryEntry(
+ Some(scrollPos), mActiveEntry.get(), mLoadType, cacheKey);
+ // FIXME Do we need to update mPreviousEntryIndex and mLoadedEntryIndex?
+ }
+ }
+ }
+
+ if (locationChangeNeeded) {
+ FireOnLocationChange(this, nullptr, newURI, locationChangeFlags);
+ }
+
+ /* Restore the original LSHE if we were loading something
+ * while same document navigation was initiated.
+ */
+ SetHistoryEntryAndUpdateBC(Some<nsISHEntry*>(oldLSHE), Nothing());
+ mLoadingEntry.swap(oldLoadingEntry);
+
+ /* Set the title for the Global History entry for this anchor url.
+ */
+ UpdateGlobalHistoryTitle(newURI);
+
+ SetDocCurrentStateObj(mOSHE, mActiveEntry.get());
+
+ // Inform the favicon service that the favicon for oldURI also
+ // applies to newURI.
+ CopyFavicon(currentURI, newURI, UsePrivateBrowsing());
+
+ RefPtr<nsGlobalWindowOuter> scriptGlobal = mScriptGlobal;
+ RefPtr<nsGlobalWindowInner> win =
+ scriptGlobal ? scriptGlobal->GetCurrentInnerWindowInternal() : nullptr;
+
+ // ScrollToAnchor doesn't necessarily cause us to scroll the window;
+ // the function decides whether a scroll is appropriate based on the
+ // arguments it receives. But even if we don't end up scrolling,
+ // ScrollToAnchor performs other important tasks, such as informing
+ // the presShell that we have a new hash. See bug 680257.
+ nsresult rv = ScrollToAnchor(aState.mCurrentURIHasRef, aState.mNewURIHasRef,
+ aState.mNewHash, aLoadState->LoadType());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /* restore previous position of scroller(s), if we're moving
+ * back in history (bug 59774)
+ */
+ nscoord bx = 0;
+ nscoord by = 0;
+ bool needsScrollPosUpdate = false;
+ if ((mozilla::SessionHistoryInParent() ? !!mActiveEntry : !!mOSHE) &&
+ (aLoadState->LoadType() == LOAD_HISTORY ||
+ aLoadState->LoadType() == LOAD_RELOAD_NORMAL) &&
+ !scrollRestorationIsManual) {
+ needsScrollPosUpdate = true;
+ if (mozilla::SessionHistoryInParent()) {
+ mActiveEntry->GetScrollPosition(&bx, &by);
+ } else {
+ mOSHE->GetScrollPosition(&bx, &by);
+ }
+ }
+
+ // Dispatch the popstate and hashchange events, as appropriate.
+ //
+ // The event dispatch below can cause us to re-enter script and
+ // destroy the docshell, nulling out mScriptGlobal. Hold a stack
+ // reference to avoid null derefs. See bug 914521.
+ if (win) {
+ // Fire a hashchange event URIs differ, and only in their hashes.
+ bool doHashchange = aState.mSameExceptHashes &&
+ (aState.mCurrentURIHasRef != aState.mNewURIHasRef ||
+ !aState.mCurrentHash.Equals(aState.mNewHash));
+
+ if (aState.mHistoryNavBetweenSameDoc || doHashchange) {
+ win->DispatchSyncPopState();
+ }
+
+ if (needsScrollPosUpdate && win->HasActiveDocument()) {
+ SetCurScrollPosEx(bx, by);
+ }
+
+ if (doHashchange) {
+ // Note that currentURI hasn't changed because it's on the
+ // stack, so we can just use it directly as the old URI.
+ win->DispatchAsyncHashchange(currentURI, newURI);
+ }
+ }
+
+ return NS_OK;
+}
+
+static bool NavigationShouldTakeFocus(nsDocShell* aDocShell,
+ nsDocShellLoadState* aLoadState) {
+ if (!aLoadState->AllowFocusMove()) {
+ return false;
+ }
+ if (!aLoadState->HasValidUserGestureActivation()) {
+ return false;
+ }
+ const auto& sourceBC = aLoadState->SourceBrowsingContext();
+ if (!sourceBC || !sourceBC->IsActive()) {
+ // If the navigation didn't come from a foreground tab, then we don't steal
+ // focus.
+ return false;
+ }
+ auto* bc = aDocShell->GetBrowsingContext();
+ if (sourceBC.get() == bc) {
+ // If it comes from the same tab / frame, don't steal focus either.
+ return false;
+ }
+ auto* fm = nsFocusManager::GetFocusManager();
+ if (fm && bc->IsActive() && fm->IsInActiveWindow(bc)) {
+ // If we're already on the foreground tab of the foreground window, then we
+ // don't need to do this. This helps to e.g. not steal focus from the
+ // browser chrome unnecessarily.
+ return false;
+ }
+ if (auto* doc = aDocShell->GetExtantDocument()) {
+ if (doc->IsInitialDocument()) {
+ // If we're the initial load for the browsing context, the browser
+ // chrome determines what to focus. This is important because the
+ // browser chrome may want to e.g focus the url-bar
+ return false;
+ }
+ }
+ // Take loadDivertedInBackground into account so the behavior would be the
+ // same as how the tab first opened.
+ return !Preferences::GetBool("browser.tabs.loadDivertedInBackground", false);
+}
+
+nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState,
+ Maybe<uint32_t> aCacheKey) {
+ MOZ_ASSERT(aLoadState, "need a load state!");
+ MOZ_ASSERT(aLoadState->TriggeringPrincipal(),
+ "need a valid TriggeringPrincipal");
+
+ if (!aLoadState->TriggeringPrincipal()) {
+ MOZ_ASSERT(false, "InternalLoad needs a valid triggeringPrincipal");
+ return NS_ERROR_FAILURE;
+ }
+ if (NS_WARN_IF(mBrowsingContext->GetPendingInitialization())) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ const bool shouldTakeFocus = NavigationShouldTakeFocus(this, aLoadState);
+
+ mOriginalUriString.Truncate();
+
+ MOZ_LOG(gDocShellLeakLog, LogLevel::Debug,
+ ("DOCSHELL %p InternalLoad %s\n", this,
+ aLoadState->URI()->GetSpecOrDefault().get()));
+
+ NS_ENSURE_TRUE(IsValidLoadType(aLoadState->LoadType()), NS_ERROR_INVALID_ARG);
+
+ // Cancel loads coming from Docshells that are being destroyed.
+ if (mIsBeingDestroyed) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = EnsureScriptEnvironment();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // If we have a target to move to, do that now.
+ if (!aLoadState->Target().IsEmpty()) {
+ return PerformRetargeting(aLoadState);
+ }
+
+ if (aLoadState->TargetBrowsingContext().IsNull()) {
+ aLoadState->SetTargetBrowsingContext(GetBrowsingContext());
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ aLoadState->TargetBrowsingContext() == GetBrowsingContext(),
+ "Load must be targeting this BrowsingContext");
+
+ MOZ_TRY(CheckDisallowedJavascriptLoad(aLoadState));
+
+ // If we don't have a target, we're loading into ourselves, and our load
+ // delegate may want to intercept that load.
+ SameDocumentNavigationState sameDocumentNavigationState;
+ bool sameDocument =
+ IsSameDocumentNavigation(aLoadState, sameDocumentNavigationState) &&
+ !aLoadState->GetPendingRedirectedChannel();
+
+ // Note: We do this check both here and in BrowsingContext::
+ // LoadURI/InternalLoad, since document-specific sandbox flags are only
+ // available in the process triggering the load, and we don't want the target
+ // process to have to trust the triggering process to do the appropriate
+ // checks for the BrowsingContext's sandbox flags.
+ MOZ_TRY(mBrowsingContext->CheckSandboxFlags(aLoadState));
+
+ NS_ENSURE_STATE(!HasUnloadedParent());
+
+ rv = CheckLoadingPermissions();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (mFiredUnloadEvent) {
+ if (IsOKToLoadURI(aLoadState->URI())) {
+ MOZ_ASSERT(aLoadState->Target().IsEmpty(),
+ "Shouldn't have a window target here!");
+
+ // If this is a replace load, make whatever load triggered
+ // the unload event also a replace load, so we don't
+ // create extra history entries.
+ if (LOAD_TYPE_HAS_FLAGS(aLoadState->LoadType(),
+ LOAD_FLAGS_REPLACE_HISTORY)) {
+ mLoadType = LOAD_NORMAL_REPLACE;
+ }
+
+ // Do this asynchronously
+ nsCOMPtr<nsIRunnable> ev = new InternalLoadEvent(this, aLoadState);
+ return Dispatch(TaskCategory::Other, ev.forget());
+ }
+
+ // Just ignore this load attempt
+ return NS_OK;
+ }
+
+ // If we are loading a URI that should inherit a security context (basically
+ // javascript: at this point), and the caller has said that principal
+ // inheritance is allowed, there are a few possible cases:
+ //
+ // 1) We are provided with the principal to inherit. In that case, we just use
+ // it.
+ //
+ // 2) The load is coming from some other application. In this case we don't
+ // want to inherit from whatever document we have loaded now, since the
+ // load is unrelated to it.
+ //
+ // 3) It's a load from our application, but does not provide an explicit
+ // principal to inherit. In that case, we want to inherit the principal of
+ // our current document, or of our parent document (if any) if we don't
+ // have a current document.
+ {
+ bool inherits;
+
+ if (!aLoadState->HasLoadFlags(LOAD_FLAGS_FROM_EXTERNAL) &&
+ !aLoadState->PrincipalToInherit() &&
+ (aLoadState->HasInternalLoadFlags(
+ INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL)) &&
+ NS_SUCCEEDED(nsContentUtils::URIInheritsSecurityContext(
+ aLoadState->URI(), &inherits)) &&
+ inherits) {
+ aLoadState->SetPrincipalToInherit(GetInheritedPrincipal(true));
+ }
+ // If principalToInherit is still null (e.g. if some of the conditions of
+ // were not satisfied), then no inheritance of any sort will happen: the
+ // load will just get a principal based on the URI being loaded.
+ }
+
+ // If this docshell is owned by a frameloader, make sure to cancel
+ // possible frameloader initialization before loading a new page.
+ nsCOMPtr<nsIDocShellTreeItem> parent = GetInProcessParentDocshell();
+ if (parent) {
+ RefPtr<Document> doc = parent->GetDocument();
+ if (doc) {
+ doc->TryCancelFrameLoaderInitialization(this);
+ }
+ }
+
+ // Before going any further vet loads initiated by external programs.
+ if (aLoadState->HasLoadFlags(LOAD_FLAGS_FROM_EXTERNAL)) {
+ MOZ_DIAGNOSTIC_ASSERT(aLoadState->LoadType() == LOAD_NORMAL);
+
+ // Disallow external chrome: loads targetted at content windows
+ if (SchemeIsChrome(aLoadState->URI())) {
+ NS_WARNING("blocked external chrome: url -- use '--chrome' option");
+ return NS_ERROR_FAILURE;
+ }
+
+ // clear the decks to prevent context bleed-through (bug 298255)
+ rv = CreateAboutBlankContentViewer(nullptr, nullptr, nullptr, nullptr,
+ /* aIsInitialDocument */ false);
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ mAllowKeywordFixup = aLoadState->HasInternalLoadFlags(
+ INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP);
+ mURIResultedInDocument = false; // reset the clock...
+
+ // See if this is actually a load between two history entries for the same
+ // document. If the process fails, or if we successfully navigate within the
+ // same document, return.
+ if (sameDocument) {
+ nsresult rv =
+ HandleSameDocumentNavigation(aLoadState, sameDocumentNavigationState);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (shouldTakeFocus) {
+ mBrowsingContext->Focus(CallerType::System, IgnoreErrors());
+ }
+ return rv;
+ }
+
+ // mContentViewer->PermitUnload can destroy |this| docShell, which
+ // causes the next call of CanSavePresentation to crash.
+ // Hold onto |this| until we return, to prevent a crash from happening.
+ // (bug#331040)
+ nsCOMPtr<nsIDocShell> kungFuDeathGrip(this);
+
+ // Don't init timing for javascript:, since it generally doesn't
+ // actually start a load or anything. If it does, we'll init
+ // timing then, from OnStateChange.
+
+ // XXXbz mTiming should know what channel it's for, so we don't
+ // need this hackery.
+ bool toBeReset = false;
+ bool isJavaScript = SchemeIsJavascript(aLoadState->URI());
+
+ if (!isJavaScript) {
+ toBeReset = MaybeInitTiming();
+ }
+ bool isNotDownload = aLoadState->FileName().IsVoid();
+ if (mTiming && isNotDownload) {
+ mTiming->NotifyBeforeUnload();
+ }
+ // Check if the page doesn't want to be unloaded. The javascript:
+ // protocol handler deals with this for javascript: URLs.
+ if (!isJavaScript && isNotDownload &&
+ !aLoadState->NotifiedBeforeUnloadListeners() && mContentViewer) {
+ bool okToUnload;
+
+ // Check if request is exempted from HTTPSOnlyMode and if https-first is
+ // enabled, if so it means:
+ // * https-first failed to upgrade request to https
+ // * we already asked for permission to unload and the user accepted
+ // otherwise we wouldn't be here.
+ bool isPrivateWin = GetOriginAttributes().mPrivateBrowsingId > 0;
+ bool isHistoryOrReload = false;
+ uint32_t loadType = aLoadState->LoadType();
+
+ // Check if request is a reload.
+ if (loadType == LOAD_RELOAD_NORMAL ||
+ loadType == LOAD_RELOAD_BYPASS_CACHE ||
+ loadType == LOAD_RELOAD_BYPASS_PROXY ||
+ loadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE ||
+ loadType == LOAD_HISTORY) {
+ isHistoryOrReload = true;
+ }
+
+ // If it isn't a reload, the request already failed to be upgraded and
+ // https-first is enabled then don't ask the user again for permission to
+ // unload and just unload.
+ if (!isHistoryOrReload && aLoadState->IsExemptFromHTTPSOnlyMode() &&
+ nsHTTPSOnlyUtils::IsHttpsFirstModeEnabled(isPrivateWin)) {
+ rv = mContentViewer->PermitUnload(
+ nsIContentViewer::PermitUnloadAction::eDontPromptAndUnload,
+ &okToUnload);
+ } else {
+ rv = mContentViewer->PermitUnload(&okToUnload);
+ }
+
+ if (NS_SUCCEEDED(rv) && !okToUnload) {
+ // The user chose not to unload the page, interrupt the
+ // load.
+ MaybeResetInitTiming(toBeReset);
+ return NS_OK;
+ }
+ }
+
+ if (mTiming && isNotDownload) {
+ mTiming->NotifyUnloadAccepted(mCurrentURI);
+ }
+
+ // In e10s, in the parent process, we refuse to load anything other than
+ // "safe" resources that we ship or trust enough to give "special" URLs.
+ // Similar check will be performed by the ParentProcessDocumentChannel if in
+ // use.
+ if (XRE_IsE10sParentProcess() &&
+ !DocumentChannel::CanUseDocumentChannel(aLoadState->URI()) &&
+ !CanLoadInParentProcess(aLoadState->URI())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Whenever a top-level browsing context is navigated, the user agent MUST
+ // lock the orientation of the document to the document's default
+ // orientation. We don't explicitly check for a top-level browsing context
+ // here because orientation is only set on top-level browsing contexts.
+ if (mBrowsingContext->GetOrientationLock() != hal::ScreenOrientation::None) {
+ MOZ_ASSERT(mBrowsingContext->IsTop());
+ MOZ_ALWAYS_SUCCEEDS(
+ mBrowsingContext->SetOrientationLock(hal::ScreenOrientation::None));
+ if (mBrowsingContext->IsActive()) {
+ ScreenOrientation::UpdateActiveOrientationLock(
+ hal::ScreenOrientation::None);
+ }
+ }
+
+ // Check for saving the presentation here, before calling Stop().
+ // This is necessary so that we can catch any pending requests.
+ // Since the new request has not been created yet, we pass null for the
+ // new request parameter.
+ // Also pass nullptr for the document, since it doesn't affect the return
+ // value for our purposes here.
+ bool savePresentation =
+ CanSavePresentation(aLoadState->LoadType(), nullptr, nullptr,
+ /* aReportBFCacheComboTelemetry */ true);
+
+ // nsDocShell::CanSavePresentation is for non-SHIP version only. Do a
+ // separate check for SHIP so that we know if there are ongoing requests
+ // before calling Stop() below.
+ if (mozilla::SessionHistoryInParent()) {
+ Document* document = GetDocument();
+ uint32_t flags = 0;
+ if (document && !document->CanSavePresentation(nullptr, flags, true)) {
+ // This forces some flags into the WindowGlobalParent's mBFCacheStatus,
+ // which we'll then use in CanonicalBrowsingContext::AllowedInBFCache,
+ // and in particular we'll store BFCacheStatus::REQUEST if needed.
+ // Also, we want to report all the flags to the parent process here (and
+ // not just BFCacheStatus::NOT_ALLOWED), so that it can update the
+ // telemetry data correctly.
+ document->DisallowBFCaching(flags);
+ }
+ }
+
+ // Don't stop current network activity for javascript: URL's since
+ // they might not result in any data, and thus nothing should be
+ // stopped in those cases. In the case where they do result in
+ // data, the javascript: URL channel takes care of stopping
+ // current network activity.
+ if (!isJavaScript && isNotDownload) {
+ // Stop any current network activity.
+ // Also stop content if this is a zombie doc. otherwise
+ // the onload will be delayed by other loads initiated in the
+ // background by the first document that
+ // didn't fully load before the next load was initiated.
+ // If not a zombie, don't stop content until data
+ // starts arriving from the new URI...
+
+ if ((mContentViewer && mContentViewer->GetPreviousViewer()) ||
+ LOAD_TYPE_HAS_FLAGS(aLoadState->LoadType(), LOAD_FLAGS_STOP_CONTENT)) {
+ rv = Stop(nsIWebNavigation::STOP_ALL);
+ } else {
+ rv = Stop(nsIWebNavigation::STOP_NETWORK);
+ }
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ mLoadType = aLoadState->LoadType();
+
+ // aLoadState->SHEntry() should be assigned to mLSHE, only after Stop() has
+ // been called. But when loading an error page, do not clear the
+ // mLSHE for the real page.
+ if (mLoadType != LOAD_ERROR_PAGE) {
+ SetHistoryEntryAndUpdateBC(Some<nsISHEntry*>(aLoadState->SHEntry()),
+ Nothing());
+ if (aLoadState->LoadIsFromSessionHistory() &&
+ !mozilla::SessionHistoryInParent()) {
+ // We're making history navigation or a reload. Make sure our history ID
+ // points to the same ID as SHEntry's docshell ID.
+ nsID historyID = {};
+ aLoadState->SHEntry()->GetDocshellID(historyID);
+
+ Unused << mBrowsingContext->SetHistoryID(historyID);
+ }
+ }
+
+ mSavingOldViewer = savePresentation;
+
+ // If we have a saved content viewer in history, restore and show it now.
+ if (aLoadState->LoadIsFromSessionHistory() &&
+ (mLoadType & LOAD_CMD_HISTORY)) {
+ // https://html.spec.whatwg.org/#history-traversal:
+ // To traverse the history
+ // "If entry has a different Document object than the current entry, then
+ // run the following substeps: Remove any tasks queued by the history
+ // traversal task source..."
+ // Same document object case was handled already above with
+ // HandleSameDocumentNavigation call.
+ RefPtr<ChildSHistory> shistory = GetRootSessionHistory();
+ if (shistory) {
+ shistory->RemovePendingHistoryNavigations();
+ }
+ if (!mozilla::SessionHistoryInParent()) {
+ // It's possible that the previous viewer of mContentViewer is the
+ // viewer that will end up in aLoadState->SHEntry() when it gets closed.
+ // If that's the case, we need to go ahead and force it into its shentry
+ // so we can restore it.
+ if (mContentViewer) {
+ nsCOMPtr<nsIContentViewer> prevViewer =
+ mContentViewer->GetPreviousViewer();
+ if (prevViewer) {
+#ifdef DEBUG
+ nsCOMPtr<nsIContentViewer> prevPrevViewer =
+ prevViewer->GetPreviousViewer();
+ NS_ASSERTION(!prevPrevViewer, "Should never have viewer chain here");
+#endif
+ nsCOMPtr<nsISHEntry> viewerEntry;
+ prevViewer->GetHistoryEntry(getter_AddRefs(viewerEntry));
+ if (viewerEntry == aLoadState->SHEntry()) {
+ // Make sure this viewer ends up in the right place
+ mContentViewer->SetPreviousViewer(nullptr);
+ prevViewer->Destroy();
+ }
+ }
+ }
+ nsCOMPtr<nsISHEntry> oldEntry = mOSHE;
+ bool restoring;
+ rv = RestorePresentation(aLoadState->SHEntry(), &restoring);
+ if (restoring) {
+ Telemetry::Accumulate(Telemetry::BFCACHE_PAGE_RESTORED, true);
+ return rv;
+ }
+ Telemetry::Accumulate(Telemetry::BFCACHE_PAGE_RESTORED, false);
+
+ // We failed to restore the presentation, so clean up.
+ // Both the old and new history entries could potentially be in
+ // an inconsistent state.
+ if (NS_FAILED(rv)) {
+ if (oldEntry) {
+ oldEntry->SyncPresentationState();
+ }
+
+ aLoadState->SHEntry()->SyncPresentationState();
+ }
+ }
+ }
+
+ bool isTopLevelDoc = mBrowsingContext->IsTopContent();
+
+ OriginAttributes attrs = GetOriginAttributes();
+ attrs.SetFirstPartyDomain(isTopLevelDoc, aLoadState->URI());
+
+ PredictorLearn(aLoadState->URI(), nullptr,
+ nsINetworkPredictor::LEARN_LOAD_TOPLEVEL, attrs);
+ PredictorPredict(aLoadState->URI(), nullptr,
+ nsINetworkPredictor::PREDICT_LOAD, attrs, nullptr);
+
+ nsCOMPtr<nsIRequest> req;
+ rv = DoURILoad(aLoadState, aCacheKey, getter_AddRefs(req));
+
+ if (NS_SUCCEEDED(rv)) {
+ if (shouldTakeFocus) {
+ mBrowsingContext->Focus(CallerType::System, IgnoreErrors());
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ nsCOMPtr<nsIChannel> chan(do_QueryInterface(req));
+ UnblockEmbedderLoadEventForFailure();
+ nsCOMPtr<nsIURI> uri = aLoadState->URI();
+ if (DisplayLoadError(rv, uri, nullptr, chan) &&
+ // FIXME: At this point code was using internal load flags, but checking
+ // non-internal load flags?
+ aLoadState->HasLoadFlags(LOAD_FLAGS_ERROR_LOAD_CHANGES_RV)) {
+ return NS_ERROR_LOAD_SHOWED_ERRORPAGE;
+ }
+
+ // We won't report any error if this is an unknown protocol error. The
+ // reason behind this is that it will allow enumeration of external
+ // protocols if we report an error for each unknown protocol.
+ if (NS_ERROR_UNKNOWN_PROTOCOL == rv) {
+ return NS_OK;
+ }
+ }
+
+ return rv;
+}
+
+/* static */
+bool nsDocShell::CanLoadInParentProcess(nsIURI* aURI) {
+ nsCOMPtr<nsIURI> uri = aURI;
+ // In e10s, in the parent process, we refuse to load anything other than
+ // "safe" resources that we ship or trust enough to give "special" URLs.
+ bool canLoadInParent = false;
+ if (NS_SUCCEEDED(NS_URIChainHasFlags(
+ uri, nsIProtocolHandler::URI_IS_UI_RESOURCE, &canLoadInParent)) &&
+ canLoadInParent) {
+ // We allow UI resources.
+ return true;
+ }
+ // For about: and extension-based URIs, which don't get
+ // URI_IS_UI_RESOURCE, first remove layers of view-source:, if present.
+ while (uri && uri->SchemeIs("view-source")) {
+ nsCOMPtr<nsINestedURI> nested = do_QueryInterface(uri);
+ if (nested) {
+ nested->GetInnerURI(getter_AddRefs(uri));
+ } else {
+ break;
+ }
+ }
+ // Allow about: URIs, and allow moz-extension ones if we're running
+ // extension content in the parent process.
+ if (!uri || uri->SchemeIs("about") ||
+ (!StaticPrefs::extensions_webextensions_remote() &&
+ uri->SchemeIs("moz-extension"))) {
+ return true;
+ }
+#ifdef MOZ_THUNDERBIRD
+ if (uri->SchemeIs("imap") || uri->SchemeIs("mailbox") ||
+ uri->SchemeIs("news") || uri->SchemeIs("nntp") ||
+ uri->SchemeIs("snews")) {
+ return true;
+ }
+#endif
+ nsAutoCString scheme;
+ uri->GetScheme(scheme);
+ // Allow ext+foo URIs (extension-registered custom protocols). See
+ // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/protocol_handlers
+ if (StringBeginsWith(scheme, "ext+"_ns) &&
+ !StaticPrefs::extensions_webextensions_remote()) {
+ return true;
+ }
+ // Final exception for some legacy automated tests:
+ if (xpc::IsInAutomation() &&
+ StaticPrefs::security_allow_unsafe_parent_loads()) {
+ return true;
+ }
+ return false;
+}
+
+nsIPrincipal* nsDocShell::GetInheritedPrincipal(
+ bool aConsiderCurrentDocument, bool aConsiderPartitionedPrincipal) {
+ RefPtr<Document> document;
+ bool inheritedFromCurrent = false;
+
+ if (aConsiderCurrentDocument && mContentViewer) {
+ document = mContentViewer->GetDocument();
+ inheritedFromCurrent = true;
+ }
+
+ if (!document) {
+ nsCOMPtr<nsIDocShellTreeItem> parentItem;
+ GetInProcessSameTypeParent(getter_AddRefs(parentItem));
+ if (parentItem) {
+ document = parentItem->GetDocument();
+ }
+ }
+
+ if (!document) {
+ if (!aConsiderCurrentDocument) {
+ return nullptr;
+ }
+
+ // Make sure we end up with _something_ as the principal no matter
+ // what.If this fails, we'll just get a null docViewer and bail.
+ EnsureContentViewer();
+ if (!mContentViewer) {
+ return nullptr;
+ }
+ document = mContentViewer->GetDocument();
+ }
+
+ //-- Get the document's principal
+ if (document) {
+ nsIPrincipal* docPrincipal = aConsiderPartitionedPrincipal
+ ? document->PartitionedPrincipal()
+ : document->NodePrincipal();
+
+ // Don't allow loads in typeContent docShells to inherit the system
+ // principal from existing documents.
+ if (inheritedFromCurrent && mItemType == typeContent &&
+ docPrincipal->IsSystemPrincipal()) {
+ return nullptr;
+ }
+
+ return docPrincipal;
+ }
+
+ return nullptr;
+}
+
+/* static */ nsresult nsDocShell::CreateRealChannelForDocument(
+ nsIChannel** aChannel, nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ nsIInterfaceRequestor* aCallbacks, nsLoadFlags aLoadFlags,
+ const nsAString& aSrcdoc, nsIURI* aBaseURI) {
+ nsCOMPtr<nsIChannel> channel;
+ if (aSrcdoc.IsVoid()) {
+ MOZ_TRY(NS_NewChannelInternal(getter_AddRefs(channel), aURI, aLoadInfo,
+ nullptr, // PerformanceStorage
+ nullptr, // loadGroup
+ aCallbacks, aLoadFlags));
+
+ if (aBaseURI) {
+ nsCOMPtr<nsIViewSourceChannel> vsc = do_QueryInterface(channel);
+ if (vsc) {
+ MOZ_ALWAYS_SUCCEEDS(vsc->SetBaseURI(aBaseURI));
+ }
+ }
+ } else if (SchemeIsViewSource(aURI)) {
+ // Instantiate view source handler protocol, if it doesn't exist already.
+ nsCOMPtr<nsIIOService> io(do_GetIOService());
+ MOZ_ASSERT(io);
+ nsCOMPtr<nsIProtocolHandler> handler;
+ nsresult rv =
+ io->GetProtocolHandler("view-source", getter_AddRefs(handler));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsViewSourceHandler* vsh = nsViewSourceHandler::GetInstance();
+ if (!vsh) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_TRY(vsh->NewSrcdocChannel(aURI, aBaseURI, aSrcdoc, aLoadInfo,
+ getter_AddRefs(channel)));
+ } else {
+ MOZ_TRY(NS_NewInputStreamChannelInternal(getter_AddRefs(channel), aURI,
+ aSrcdoc, "text/html"_ns, aLoadInfo,
+ true));
+ nsCOMPtr<nsIInputStreamChannel> isc = do_QueryInterface(channel);
+ MOZ_ASSERT(isc);
+ isc->SetBaseURI(aBaseURI);
+ }
+
+ if (aLoadFlags != nsIRequest::LOAD_NORMAL) {
+ nsresult rv = channel->SetLoadFlags(aLoadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ channel.forget(aChannel);
+ return NS_OK;
+}
+
+/* static */ bool nsDocShell::CreateAndConfigureRealChannelForLoadState(
+ BrowsingContext* aBrowsingContext, nsDocShellLoadState* aLoadState,
+ LoadInfo* aLoadInfo, nsIInterfaceRequestor* aCallbacks,
+ nsDocShell* aDocShell, const OriginAttributes& aOriginAttributes,
+ nsLoadFlags aLoadFlags, uint32_t aCacheKey, nsresult& aRv,
+ nsIChannel** aChannel) {
+ MOZ_ASSERT(aLoadInfo);
+
+ nsString srcdoc = VoidString();
+ bool isSrcdoc =
+ aLoadState->HasInternalLoadFlags(INTERNAL_LOAD_FLAGS_IS_SRCDOC);
+ if (isSrcdoc) {
+ srcdoc = aLoadState->SrcdocData();
+ }
+
+ aLoadInfo->SetTriggeringRemoteType(
+ aLoadState->GetEffectiveTriggeringRemoteType());
+
+ if (aLoadState->PrincipalToInherit()) {
+ aLoadInfo->SetPrincipalToInherit(aLoadState->PrincipalToInherit());
+ }
+ aLoadInfo->SetLoadTriggeredFromExternal(
+ aLoadState->HasLoadFlags(LOAD_FLAGS_FROM_EXTERNAL));
+ aLoadInfo->SetForceAllowDataURI(aLoadState->HasInternalLoadFlags(
+ INTERNAL_LOAD_FLAGS_FORCE_ALLOW_DATA_URI));
+ aLoadInfo->SetOriginalFrameSrcLoad(
+ aLoadState->HasInternalLoadFlags(INTERNAL_LOAD_FLAGS_ORIGINAL_FRAME_SRC));
+
+ bool inheritAttrs = false;
+ if (aLoadState->PrincipalToInherit()) {
+ inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal(
+ aLoadState->PrincipalToInherit(), aLoadState->URI(),
+ true, // aInheritForAboutBlank
+ isSrcdoc);
+ }
+
+ // Strip the target query parameters before creating the channel.
+ aLoadState->MaybeStripTrackerQueryStrings(aBrowsingContext);
+
+ OriginAttributes attrs;
+
+ // Inherit origin attributes from PrincipalToInherit if inheritAttrs is
+ // true. Otherwise we just use the origin attributes from docshell.
+ if (inheritAttrs) {
+ MOZ_ASSERT(aLoadState->PrincipalToInherit(),
+ "We should have PrincipalToInherit here.");
+ attrs = aLoadState->PrincipalToInherit()->OriginAttributesRef();
+ // If firstPartyIsolation is not enabled, then PrincipalToInherit should
+ // have the same origin attributes with docshell.
+ MOZ_ASSERT_IF(!OriginAttributes::IsFirstPartyEnabled(),
+ attrs == aOriginAttributes);
+ } else {
+ attrs = aOriginAttributes;
+ attrs.SetFirstPartyDomain(IsTopLevelDoc(aBrowsingContext, aLoadInfo),
+ aLoadState->URI());
+ }
+
+ aRv = aLoadInfo->SetOriginAttributes(attrs);
+ if (NS_WARN_IF(NS_FAILED(aRv))) {
+ return false;
+ }
+
+ if (aLoadState->GetIsFromProcessingFrameAttributes()) {
+ aLoadInfo->SetIsFromProcessingFrameAttributes();
+ }
+
+ // Propagate the IsFormSubmission flag to the loadInfo.
+ if (aLoadState->IsFormSubmission()) {
+ aLoadInfo->SetIsFormSubmission(true);
+ }
+
+ aLoadInfo->SetUnstrippedURI(aLoadState->GetUnstrippedURI());
+
+ nsCOMPtr<nsIChannel> channel;
+ aRv = CreateRealChannelForDocument(getter_AddRefs(channel), aLoadState->URI(),
+ aLoadInfo, aCallbacks, aLoadFlags, srcdoc,
+ aLoadState->BaseURI());
+ NS_ENSURE_SUCCESS(aRv, false);
+
+ if (!channel) {
+ return false;
+ }
+
+ // If the HTTPS-Only mode is enabled, every insecure request gets upgraded to
+ // HTTPS by default. This behavior can be disabled through the loadinfo flag
+ // HTTPS_ONLY_EXEMPT.
+ nsHTTPSOnlyUtils::TestSitePermissionAndPotentiallyAddExemption(channel);
+
+ // hack
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
+ nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal(
+ do_QueryInterface(channel));
+ nsCOMPtr<nsIURI> referrer;
+ nsIReferrerInfo* referrerInfo = aLoadState->GetReferrerInfo();
+ if (referrerInfo) {
+ referrerInfo->GetOriginalReferrer(getter_AddRefs(referrer));
+ }
+ if (httpChannelInternal) {
+ if (aLoadState->HasInternalLoadFlags(
+ INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES)) {
+ aRv = httpChannelInternal->SetThirdPartyFlags(
+ nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
+ MOZ_ASSERT(NS_SUCCEEDED(aRv));
+ }
+ if (aLoadState->FirstParty()) {
+ aRv = httpChannelInternal->SetDocumentURI(aLoadState->URI());
+ MOZ_ASSERT(NS_SUCCEEDED(aRv));
+ } else {
+ aRv = httpChannelInternal->SetDocumentURI(referrer);
+ MOZ_ASSERT(NS_SUCCEEDED(aRv));
+ }
+ aRv = httpChannelInternal->SetRedirectMode(
+ nsIHttpChannelInternal::REDIRECT_MODE_MANUAL);
+ MOZ_ASSERT(NS_SUCCEEDED(aRv));
+ }
+
+ if (httpChannel) {
+ if (aLoadState->HeadersStream()) {
+ aRv = AddHeadersToChannel(aLoadState->HeadersStream(), httpChannel);
+ }
+ // Set the referrer explicitly
+ // Referrer is currenly only set for link clicks here.
+ if (referrerInfo) {
+ aRv = httpChannel->SetReferrerInfo(referrerInfo);
+ MOZ_ASSERT(NS_SUCCEEDED(aRv));
+ }
+
+ // Mark the http channel as UrgentStart for top level document loading in
+ // active tab.
+ if (IsUrgentStart(aBrowsingContext, aLoadInfo, aLoadState->LoadType())) {
+ nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
+ if (cos) {
+ cos->AddClassFlags(nsIClassOfService::UrgentStart);
+ }
+ }
+ }
+
+ channel->SetOriginalURI(aLoadState->OriginalURI() ? aLoadState->OriginalURI()
+ : aLoadState->URI());
+
+ const nsACString& typeHint = aLoadState->TypeHint();
+ if (!typeHint.IsVoid()) {
+ channel->SetContentType(typeHint);
+ }
+
+ const nsAString& fileName = aLoadState->FileName();
+ if (!fileName.IsVoid()) {
+ aRv = channel->SetContentDisposition(nsIChannel::DISPOSITION_ATTACHMENT);
+ NS_ENSURE_SUCCESS(aRv, false);
+ if (!fileName.IsEmpty()) {
+ aRv = channel->SetContentDispositionFilename(fileName);
+ NS_ENSURE_SUCCESS(aRv, false);
+ }
+ }
+
+ if (nsCOMPtr<nsIWritablePropertyBag2> props = do_QueryInterface(channel)) {
+ nsCOMPtr<nsIURI> referrer;
+ nsIReferrerInfo* referrerInfo = aLoadState->GetReferrerInfo();
+ if (referrerInfo) {
+ referrerInfo->GetOriginalReferrer(getter_AddRefs(referrer));
+ }
+ // save true referrer for those who need it (e.g. xpinstall whitelisting)
+ // Currently only http and ftp channels support this.
+ props->SetPropertyAsInterface(u"docshell.internalReferrer"_ns, referrer);
+
+ if (aLoadState->HasInternalLoadFlags(INTERNAL_LOAD_FLAGS_FIRST_LOAD)) {
+ props->SetPropertyAsBool(u"docshell.newWindowTarget"_ns, true);
+ }
+ }
+
+ nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(channel));
+ auto loadType = aLoadState->LoadType();
+
+ if (loadType == LOAD_RELOAD_NORMAL &&
+ StaticPrefs::
+ browser_soft_reload_only_force_validate_top_level_document()) {
+ nsCOMPtr<nsICacheInfoChannel> cachingChannel = do_QueryInterface(channel);
+ if (cachingChannel) {
+ cachingChannel->SetForceValidateCacheContent(true);
+ }
+ }
+
+ // figure out if we need to set the post data stream on the channel...
+ if (aLoadState->PostDataStream()) {
+ if (nsCOMPtr<nsIFormPOSTActionChannel> postChannel =
+ do_QueryInterface(channel)) {
+ // XXX it's a bit of a hack to rewind the postdata stream here but
+ // it has to be done in case the post data is being reused multiple
+ // times.
+ nsCOMPtr<nsISeekableStream> postDataSeekable =
+ do_QueryInterface(aLoadState->PostDataStream());
+ if (postDataSeekable) {
+ aRv = postDataSeekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+ NS_ENSURE_SUCCESS(aRv, false);
+ }
+
+ // we really need to have a content type associated with this stream!!
+ postChannel->SetUploadStream(aLoadState->PostDataStream(), ""_ns, -1);
+
+ // Ownership of the stream has transferred to the channel, clear our
+ // reference.
+ aLoadState->SetPostDataStream(nullptr);
+ }
+
+ /* If there is a valid postdata *and* it is a History Load,
+ * set up the cache key on the channel, to retrieve the
+ * data *only* from the cache. If it is a normal reload, the
+ * cache is free to go to the server for updated postdata.
+ */
+ if (cacheChannel && aCacheKey != 0) {
+ if (loadType == LOAD_HISTORY || loadType == LOAD_RELOAD_CHARSET_CHANGE) {
+ cacheChannel->SetCacheKey(aCacheKey);
+ uint32_t loadFlags;
+ if (NS_SUCCEEDED(channel->GetLoadFlags(&loadFlags))) {
+ channel->SetLoadFlags(loadFlags |
+ nsICachingChannel::LOAD_ONLY_FROM_CACHE);
+ }
+ } else if (loadType == LOAD_RELOAD_NORMAL) {
+ cacheChannel->SetCacheKey(aCacheKey);
+ }
+ }
+ } else {
+ /* If there is no postdata, set the cache key on the channel, and
+ * do not set the LOAD_ONLY_FROM_CACHE flag, so that the channel
+ * will be free to get it from net if it is not found in cache.
+ * New cache may use it creatively on CGI pages with GET
+ * method and even on those that say "no-cache"
+ */
+ if (loadType == LOAD_HISTORY || loadType == LOAD_RELOAD_NORMAL ||
+ loadType == LOAD_RELOAD_CHARSET_CHANGE ||
+ loadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE ||
+ loadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE) {
+ if (cacheChannel && aCacheKey != 0) {
+ cacheChannel->SetCacheKey(aCacheKey);
+ }
+ }
+ }
+
+ if (nsCOMPtr<nsIScriptChannel> scriptChannel = do_QueryInterface(channel)) {
+ // Allow execution against our context if the principals match
+ scriptChannel->SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL);
+ }
+
+ if (nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(channel)) {
+ timedChannel->SetTimingEnabled(true);
+
+ nsString initiatorType;
+ switch (aLoadInfo->InternalContentPolicyType()) {
+ case nsIContentPolicy::TYPE_INTERNAL_EMBED:
+ initiatorType = u"embed"_ns;
+ break;
+ case nsIContentPolicy::TYPE_INTERNAL_OBJECT:
+ initiatorType = u"object"_ns;
+ break;
+ default: {
+ const auto& embedderElementType =
+ aBrowsingContext->GetEmbedderElementType();
+ if (embedderElementType) {
+ initiatorType = *embedderElementType;
+ }
+ break;
+ }
+ }
+
+ if (!initiatorType.IsEmpty()) {
+ timedChannel->SetInitiatorType(initiatorType);
+ }
+ }
+
+ nsCOMPtr<nsIURI> rpURI;
+ aLoadInfo->GetResultPrincipalURI(getter_AddRefs(rpURI));
+ Maybe<nsCOMPtr<nsIURI>> originalResultPrincipalURI;
+ aLoadState->GetMaybeResultPrincipalURI(originalResultPrincipalURI);
+ if (originalResultPrincipalURI &&
+ (!aLoadState->KeepResultPrincipalURIIfSet() || !rpURI)) {
+ // Unconditionally override, we want the replay to be equal to what has
+ // been captured.
+ aLoadInfo->SetResultPrincipalURI(originalResultPrincipalURI.ref());
+ }
+
+ if (aLoadState->OriginalURI() && aLoadState->LoadReplace()) {
+ // The LOAD_REPLACE flag and its handling here will be removed as part
+ // of bug 1319110. For now preserve its restoration here to not break
+ // any code expecting it being set specially on redirected channels.
+ // If the flag has originally been set to change result of
+ // NS_GetFinalChannelURI it won't have any effect and also won't cause
+ // any harm.
+ uint32_t loadFlags;
+ aRv = channel->GetLoadFlags(&loadFlags);
+ NS_ENSURE_SUCCESS(aRv, false);
+ channel->SetLoadFlags(loadFlags | nsIChannel::LOAD_REPLACE);
+ }
+
+ nsCOMPtr<nsIContentSecurityPolicy> csp = aLoadState->Csp();
+ if (csp) {
+ // Navigational requests that are same origin need to be upgraded in case
+ // upgrade-insecure-requests is present. Please note that for document
+ // navigations that bit is re-computed in case we encounter a server
+ // side redirect so the navigation is not same-origin anymore.
+ bool upgradeInsecureRequests = false;
+ csp->GetUpgradeInsecureRequests(&upgradeInsecureRequests);
+ if (upgradeInsecureRequests) {
+ // only upgrade if the navigation is same origin
+ nsCOMPtr<nsIPrincipal> resultPrincipal;
+ aRv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ channel, getter_AddRefs(resultPrincipal));
+ NS_ENSURE_SUCCESS(aRv, false);
+ if (nsContentSecurityUtils::IsConsideredSameOriginForUIR(
+ aLoadState->TriggeringPrincipal(), resultPrincipal)) {
+ aLoadInfo->SetUpgradeInsecureRequests(true);
+ }
+ }
+
+ // For document loads we store the CSP that potentially needs to
+ // be inherited by the new document, e.g. in case we are loading
+ // an opaque origin like a data: URI. The actual inheritance
+ // check happens within Document::InitCSP().
+ // Please create an actual copy of the CSP (do not share the same
+ // reference) otherwise a Meta CSP of an opaque origin will
+ // incorrectly be propagated to the embedding document.
+ RefPtr<nsCSPContext> cspToInherit = new nsCSPContext();
+ cspToInherit->InitFromOther(static_cast<nsCSPContext*>(csp.get()));
+ aLoadInfo->SetCSPToInherit(cspToInherit);
+ }
+
+ channel.forget(aChannel);
+ return true;
+}
+
+bool nsDocShell::IsAboutBlankLoadOntoInitialAboutBlank(
+ nsIURI* aURI, bool aInheritPrincipal, nsIPrincipal* aPrincipalToInherit) {
+ return NS_IsAboutBlank(aURI) && aInheritPrincipal &&
+ (aPrincipalToInherit == GetInheritedPrincipal(false)) &&
+ (!mContentViewer || !mContentViewer->GetDocument() ||
+ mContentViewer->GetDocument()->IsInitialDocument());
+}
+
+nsresult nsDocShell::DoURILoad(nsDocShellLoadState* aLoadState,
+ Maybe<uint32_t> aCacheKey,
+ nsIRequest** aRequest) {
+ // Double-check that we're still around to load this URI.
+ if (mIsBeingDestroyed) {
+ // Return NS_OK despite not doing anything to avoid throwing exceptions
+ // from nsLocation::SetHref if the unload handler of the existing page
+ // tears us down.
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURILoader> uriLoader = components::URILoader::Service();
+ if (NS_WARN_IF(!uriLoader)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // Persist and sync layout history state before we load a new uri, as this
+ // might be our last chance to do so, in the content process.
+ PersistLayoutHistoryState();
+ SynchronizeLayoutHistoryState();
+
+ nsresult rv;
+ nsContentPolicyType contentPolicyType = DetermineContentType();
+
+ if (IsSubframe()) {
+ MOZ_ASSERT(contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_IFRAME ||
+ contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_FRAME,
+ "DoURILoad thinks this is a frame and InternalLoad does not");
+
+ if (StaticPrefs::dom_block_external_protocol_in_iframes()) {
+ // Only allow URLs able to return data in iframes.
+ if (nsContentUtils::IsExternalProtocol(aLoadState->URI())) {
+ // The context to check user-interaction with for the purposes of
+ // popup-blocking.
+ //
+ // We generally want to check the context that initiated the navigation.
+ WindowContext* sourceWindowContext = [&] {
+ const MaybeDiscardedBrowsingContext& sourceBC =
+ aLoadState->SourceBrowsingContext();
+ if (!sourceBC.IsNullOrDiscarded()) {
+ if (WindowContext* wc = sourceBC.get()->GetCurrentWindowContext()) {
+ return wc;
+ }
+ }
+ return mBrowsingContext->GetParentWindowContext();
+ }();
+
+ MOZ_ASSERT(sourceWindowContext);
+ // FIXME: We can't check user-interaction against an OOP window. This is
+ // the next best thing we can really do. The load state keeps whether
+ // the navigation had a user interaction in process
+ // (aLoadState->HasValidUserGestureActivation()), but we can't really
+ // consume it, which we want to prevent popup-spamming from the same
+ // click event.
+ WindowContext* context =
+ sourceWindowContext->IsInProcess()
+ ? sourceWindowContext
+ : mBrowsingContext->GetCurrentWindowContext();
+ const bool popupBlocked = [&] {
+ const bool active = mBrowsingContext->IsActive();
+
+ // For same-origin-with-top windows, we grant a single free popup
+ // without user activation, see bug 1680721.
+ //
+ // We consume the flag now even if there's no user activation.
+ const bool hasFreePass = [&] {
+ if (!active ||
+ !(context->IsInProcess() && context->SameOriginWithTop())) {
+ return false;
+ }
+ nsGlobalWindowInner* win =
+ context->TopWindowContext()->GetInnerWindow();
+ return win && win->TryOpenExternalProtocolIframe();
+ }();
+
+ if (context->IsInProcess() &&
+ context->ConsumeTransientUserGestureActivation()) {
+ // If the user has interacted with the page, consume it.
+ return false;
+ }
+
+ // TODO(emilio): Can we remove this check? It seems like what prompted
+ // this code (bug 1514547) should be covered by transient user
+ // activation, see bug 1514547.
+ if (active &&
+ PopupBlocker::ConsumeTimerTokenForExternalProtocolIframe()) {
+ return false;
+ }
+
+ if (sourceWindowContext->CanShowPopup()) {
+ return false;
+ }
+
+ if (hasFreePass) {
+ return false;
+ }
+
+ return true;
+ }();
+
+ // No error must be returned when iframes are blocked.
+ if (popupBlocked) {
+ nsAutoString message;
+ nsresult rv = nsContentUtils::GetLocalizedString(
+ nsContentUtils::eDOM_PROPERTIES,
+ "ExternalProtocolFrameBlockedNoUserActivation", message);
+ if (NS_SUCCEEDED(rv)) {
+ nsContentUtils::ReportToConsoleByWindowID(
+ message, nsIScriptError::warningFlag, "DOM"_ns,
+ context->InnerWindowId());
+ }
+ return NS_OK;
+ }
+ }
+ }
+
+ // Only allow view-source scheme in top-level docshells. view-source is
+ // the only scheme to which this applies at the moment due to potential
+ // timing attacks to read data from cross-origin iframes. If this widens
+ // we should add a protocol flag for whether the scheme is allowed in
+ // frames and use something like nsNetUtil::NS_URIChainHasFlags.
+ nsCOMPtr<nsIURI> tempURI = aLoadState->URI();
+ nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(tempURI);
+ while (nestedURI) {
+ // view-source should always be an nsINestedURI, loop and check the
+ // scheme on this and all inner URIs that are also nested URIs.
+ if (SchemeIsViewSource(tempURI)) {
+ return NS_ERROR_UNKNOWN_PROTOCOL;
+ }
+ nestedURI->GetInnerURI(getter_AddRefs(tempURI));
+ nestedURI = do_QueryInterface(tempURI);
+ }
+ } else {
+ MOZ_ASSERT(contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT,
+ "DoURILoad thinks this is a document and InternalLoad does not");
+ }
+
+ // We want to inherit aLoadState->PrincipalToInherit() when:
+ // 1. ChannelShouldInheritPrincipal returns true.
+ // 2. aLoadState->URI() is not data: URI, or data: URI is not
+ // configured as unique opaque origin.
+ bool inheritPrincipal = false;
+
+ if (aLoadState->PrincipalToInherit()) {
+ bool isSrcdoc =
+ aLoadState->HasInternalLoadFlags(INTERNAL_LOAD_FLAGS_IS_SRCDOC);
+ bool inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal(
+ aLoadState->PrincipalToInherit(), aLoadState->URI(),
+ true, // aInheritForAboutBlank
+ isSrcdoc);
+
+ inheritPrincipal = inheritAttrs && !SchemeIsData(aLoadState->URI());
+ }
+
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1736570
+ const bool isAboutBlankLoadOntoInitialAboutBlank =
+ IsAboutBlankLoadOntoInitialAboutBlank(aLoadState->URI(), inheritPrincipal,
+ aLoadState->PrincipalToInherit());
+
+ // FIXME We still have a ton of codepaths that don't pass through
+ // DocumentLoadListener, so probably need to create session history info
+ // in more places.
+ if (aLoadState->GetLoadingSessionHistoryInfo()) {
+ SetLoadingSessionHistoryInfo(*aLoadState->GetLoadingSessionHistoryInfo());
+ } else if (isAboutBlankLoadOntoInitialAboutBlank &&
+ mozilla::SessionHistoryInParent()) {
+ // Materialize LoadingSessionHistoryInfo here, because DocumentChannel
+ // loads have it, and later history behavior depends on it existing.
+ UniquePtr<SessionHistoryInfo> entry = MakeUnique<SessionHistoryInfo>(
+ aLoadState->URI(), aLoadState->TriggeringPrincipal(),
+ aLoadState->PrincipalToInherit(),
+ aLoadState->PartitionedPrincipalToInherit(), aLoadState->Csp(),
+ mContentTypeHint);
+ mozilla::dom::LoadingSessionHistoryInfo info(*entry);
+ SetLoadingSessionHistoryInfo(info, true);
+ }
+
+ // open a channel for the url
+
+ // If we have a pending channel, use the channel we've already created here.
+ // We don't need to set up load flags for our channel, as it has already been
+ // created.
+
+ if (nsCOMPtr<nsIChannel> channel =
+ aLoadState->GetPendingRedirectedChannel()) {
+ // If we have a request outparameter, shove our channel into it.
+ if (aRequest) {
+ nsCOMPtr<nsIRequest> outRequest = channel;
+ outRequest.forget(aRequest);
+ }
+
+ return OpenRedirectedChannel(aLoadState);
+ }
+
+ // There are two cases we care about:
+ // * Top-level load: In this case, loadingNode is null, but loadingWindow
+ // is our mScriptGlobal. We pass null for loadingPrincipal in this case.
+ // * Subframe load: loadingWindow is null, but loadingNode is the frame
+ // element for the load. loadingPrincipal is the NodePrincipal of the
+ // frame element.
+ nsCOMPtr<nsINode> loadingNode;
+ nsCOMPtr<nsPIDOMWindowOuter> loadingWindow;
+ nsCOMPtr<nsIPrincipal> loadingPrincipal;
+ nsCOMPtr<nsISupports> topLevelLoadingContext;
+
+ if (contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT) {
+ loadingNode = nullptr;
+ loadingPrincipal = nullptr;
+ loadingWindow = mScriptGlobal;
+ if (XRE_IsContentProcess()) {
+ // In e10s the child process doesn't have access to the element that
+ // contains the browsing context (because that element is in the chrome
+ // process).
+ nsCOMPtr<nsIBrowserChild> browserChild = GetBrowserChild();
+ topLevelLoadingContext = ToSupports(browserChild);
+ } else {
+ // This is for loading non-e10s tabs and toplevel windows of various
+ // sorts.
+ // For the toplevel window cases, requestingElement will be null.
+ nsCOMPtr<Element> requestingElement =
+ loadingWindow->GetFrameElementInternal();
+ topLevelLoadingContext = requestingElement;
+ }
+ } else {
+ loadingWindow = nullptr;
+ loadingNode = mScriptGlobal->GetFrameElementInternal();
+ if (loadingNode) {
+ // If we have a loading node, then use that as our loadingPrincipal.
+ loadingPrincipal = loadingNode->NodePrincipal();
+#ifdef DEBUG
+ // Get the docshell type for requestingElement.
+ RefPtr<Document> requestingDoc = loadingNode->OwnerDoc();
+ nsCOMPtr<nsIDocShell> elementDocShell = requestingDoc->GetDocShell();
+ // requestingElement docshell type = current docshell type.
+ MOZ_ASSERT(
+ mItemType == elementDocShell->ItemType(),
+ "subframes should have the same docshell type as their parent");
+#endif
+ } else {
+ if (mIsBeingDestroyed) {
+ // If this isn't a top-level load and mScriptGlobal's frame element is
+ // null, then the element got removed from the DOM while we were trying
+ // to load this resource. This docshell is scheduled for destruction
+ // already, so bail out here.
+ return NS_OK;
+ }
+ // If we are not being destroyed and we do not have access to the loading
+ // node, then we are a remote subframe. Set the loading principal
+ // to be a null principal and then set it correctly in the parent.
+ loadingPrincipal = NullPrincipal::Create(GetOriginAttributes(), nullptr);
+ }
+ }
+
+ if (!aLoadState->TriggeringPrincipal()) {
+ MOZ_ASSERT(false, "DoURILoad needs a valid triggeringPrincipal");
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t sandboxFlags = mBrowsingContext->GetSandboxFlags();
+ nsSecurityFlags securityFlags =
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
+
+ if (mLoadType == LOAD_ERROR_PAGE) {
+ securityFlags |= nsILoadInfo::SEC_LOAD_ERROR_PAGE;
+ }
+
+ if (inheritPrincipal) {
+ securityFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
+ }
+
+ // Must never have a parent for TYPE_DOCUMENT loads
+ MOZ_ASSERT_IF(contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT,
+ !mBrowsingContext->GetParent());
+ // Subdocuments must have a parent
+ MOZ_ASSERT_IF(contentPolicyType == nsIContentPolicy::TYPE_SUBDOCUMENT,
+ mBrowsingContext->GetParent());
+ mBrowsingContext->SetTriggeringAndInheritPrincipals(
+ aLoadState->TriggeringPrincipal(), aLoadState->PrincipalToInherit(),
+ aLoadState->GetLoadIdentifier());
+ RefPtr<LoadInfo> loadInfo =
+ (contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT)
+ ? new LoadInfo(loadingWindow, aLoadState->URI(),
+ aLoadState->TriggeringPrincipal(),
+ topLevelLoadingContext, securityFlags, sandboxFlags)
+ : new LoadInfo(loadingPrincipal, aLoadState->TriggeringPrincipal(),
+ loadingNode, securityFlags, contentPolicyType,
+ Maybe<mozilla::dom::ClientInfo>(),
+ Maybe<mozilla::dom::ServiceWorkerDescriptor>(),
+ sandboxFlags);
+ RefPtr<WindowContext> context = mBrowsingContext->GetCurrentWindowContext();
+
+ if (isAboutBlankLoadOntoInitialAboutBlank) {
+ // Match the DocumentChannel case where the default for third-partiness
+ // differs from the default in LoadInfo construction here.
+ // toolkit/components/antitracking/test/browser/browser_aboutblank.js
+ // fails without this.
+ BrowsingContext* top = mBrowsingContext->Top();
+ if (top == mBrowsingContext) {
+ // If we're at the top, this must be a window.open()ed
+ // window, and we can't be third-party relative to ourselves.
+ loadInfo->SetIsThirdPartyContextToTopWindow(false);
+ } else {
+ if (Document* topDoc = top->GetDocument()) {
+ bool thirdParty = false;
+ mozilla::Unused << topDoc->GetPrincipal()->IsThirdPartyPrincipal(
+ aLoadState->PrincipalToInherit(), &thirdParty);
+ loadInfo->SetIsThirdPartyContextToTopWindow(thirdParty);
+ } else {
+ // If top is in a different process, we have to be third-party relative
+ // to it.
+ loadInfo->SetIsThirdPartyContextToTopWindow(true);
+ }
+ }
+ }
+
+ if (mLoadType != LOAD_ERROR_PAGE && context && context->IsInProcess() &&
+ context->HasValidTransientUserGestureActivation()) {
+ aLoadState->SetHasValidUserGestureActivation(true);
+ }
+
+ // in case this docshell load was triggered by a valid transient user gesture,
+ // or also the load originates from external, then we pass that information on
+ // to the loadinfo, which allows e.g. setting Sec-Fetch-User request headers.
+ if (aLoadState->HasValidUserGestureActivation() ||
+ aLoadState->HasLoadFlags(LOAD_FLAGS_FROM_EXTERNAL)) {
+ loadInfo->SetHasValidUserGestureActivation(true);
+ }
+ loadInfo->SetTriggeringSandboxFlags(aLoadState->TriggeringSandboxFlags());
+ loadInfo->SetIsMetaRefresh(aLoadState->IsMetaRefresh());
+
+ uint32_t cacheKey = 0;
+ if (aCacheKey) {
+ cacheKey = *aCacheKey;
+ } else if (mozilla::SessionHistoryInParent()) {
+ if (mLoadingEntry) {
+ cacheKey = mLoadingEntry->mInfo.GetCacheKey();
+ } else if (mActiveEntry) { // for reload cases
+ cacheKey = mActiveEntry->GetCacheKey();
+ }
+ } else {
+ if (mLSHE) {
+ cacheKey = mLSHE->GetCacheKey();
+ } else if (mOSHE) { // for reload cases
+ cacheKey = mOSHE->GetCacheKey();
+ }
+ }
+
+ bool uriModified;
+ if (mLSHE || mLoadingEntry) {
+ if (mLoadingEntry) {
+ uriModified = mLoadingEntry->mInfo.GetURIWasModified();
+ } else {
+ uriModified = mLSHE->GetURIWasModified();
+ }
+ } else {
+ uriModified = false;
+ }
+
+ bool isXFOError = false;
+ if (mFailedChannel) {
+ nsresult status;
+ mFailedChannel->GetStatus(&status);
+ isXFOError = status == NS_ERROR_XFO_VIOLATION;
+ }
+
+ nsLoadFlags loadFlags = aLoadState->CalculateChannelLoadFlags(
+ mBrowsingContext, Some(uriModified), Some(isXFOError));
+
+ nsCOMPtr<nsIChannel> channel;
+ if (DocumentChannel::CanUseDocumentChannel(aLoadState->URI()) &&
+ !isAboutBlankLoadOntoInitialAboutBlank) {
+ channel = DocumentChannel::CreateForDocument(aLoadState, loadInfo,
+ loadFlags, this, cacheKey,
+ uriModified, isXFOError);
+ MOZ_ASSERT(channel);
+
+ // Disable keyword fixup when using DocumentChannel, since
+ // DocumentLoadListener will handle this for us (in the parent process).
+ mAllowKeywordFixup = false;
+ } else if (!CreateAndConfigureRealChannelForLoadState(
+ mBrowsingContext, aLoadState, loadInfo, this, this,
+ GetOriginAttributes(), loadFlags, cacheKey, rv,
+ getter_AddRefs(channel))) {
+ return rv;
+ }
+
+ // Make sure to give the caller a channel if we managed to create one
+ // This is important for correct error page/session history interaction
+ if (aRequest) {
+ NS_ADDREF(*aRequest = channel);
+ }
+
+ nsCOMPtr<nsIContentSecurityPolicy> csp = aLoadState->Csp();
+ if (csp) {
+ // Check CSP navigate-to
+ bool allowsNavigateTo = false;
+ rv = csp->GetAllowsNavigateTo(aLoadState->URI(),
+ aLoadState->IsFormSubmission(),
+ false, /* aWasRedirected */
+ false, /* aEnforceWhitelist */
+ &allowsNavigateTo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!allowsNavigateTo) {
+ return NS_ERROR_CSP_NAVIGATE_TO_VIOLATION;
+ }
+ }
+
+ const nsACString& typeHint = aLoadState->TypeHint();
+ if (!typeHint.IsVoid()) {
+ mContentTypeHint = typeHint;
+ } else {
+ mContentTypeHint.Truncate();
+ }
+
+ // Load attributes depend on load type...
+ if (mLoadType == LOAD_RELOAD_CHARSET_CHANGE) {
+ // Use SetAllowStaleCacheContent (not LOAD_FROM_CACHE flag) since we
+ // only want to force cache load for this channel, not the whole
+ // loadGroup.
+ nsCOMPtr<nsICacheInfoChannel> cachingChannel = do_QueryInterface(channel);
+ if (cachingChannel) {
+ cachingChannel->SetAllowStaleCacheContent(true);
+ }
+ }
+
+ uint32_t openFlags =
+ nsDocShell::ComputeURILoaderFlags(mBrowsingContext, mLoadType);
+ return OpenInitializedChannel(channel, uriLoader, openFlags);
+}
+
+static nsresult AppendSegmentToString(nsIInputStream* aIn, void* aClosure,
+ const char* aFromRawSegment,
+ uint32_t aToOffset, uint32_t aCount,
+ uint32_t* aWriteCount) {
+ // aFromSegment now contains aCount bytes of data.
+
+ nsAutoCString* buf = static_cast<nsAutoCString*>(aClosure);
+ buf->Append(aFromRawSegment, aCount);
+
+ // Indicate that we have consumed all of aFromSegment
+ *aWriteCount = aCount;
+ return NS_OK;
+}
+
+/* static */ nsresult nsDocShell::AddHeadersToChannel(
+ nsIInputStream* aHeadersData, nsIChannel* aGenericChannel) {
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aGenericChannel);
+ NS_ENSURE_STATE(httpChannel);
+
+ uint32_t numRead;
+ nsAutoCString headersString;
+ nsresult rv = aHeadersData->ReadSegments(
+ AppendSegmentToString, &headersString, UINT32_MAX, &numRead);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // used during the manipulation of the String from the InputStream
+ nsAutoCString headerName;
+ nsAutoCString headerValue;
+ int32_t crlf;
+ int32_t colon;
+
+ //
+ // Iterate over the headersString: for each "\r\n" delimited chunk,
+ // add the value as a header to the nsIHttpChannel
+ //
+
+ static const char kWhitespace[] = "\b\t\r\n ";
+ while (true) {
+ crlf = headersString.Find("\r\n");
+ if (crlf == kNotFound) {
+ return NS_OK;
+ }
+
+ const nsACString& oneHeader = StringHead(headersString, crlf);
+
+ colon = oneHeader.FindChar(':');
+ if (colon == kNotFound) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ headerName = StringHead(oneHeader, colon);
+ headerValue = Substring(oneHeader, colon + 1);
+
+ headerName.Trim(kWhitespace);
+ headerValue.Trim(kWhitespace);
+
+ headersString.Cut(0, crlf + 2);
+
+ //
+ // FINALLY: we can set the header!
+ //
+
+ rv = httpChannel->SetRequestHeader(headerName, headerValue, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ MOZ_ASSERT_UNREACHABLE("oops");
+ return NS_ERROR_UNEXPECTED;
+}
+
+/* static */ uint32_t nsDocShell::ComputeURILoaderFlags(
+ BrowsingContext* aBrowsingContext, uint32_t aLoadType) {
+ MOZ_ASSERT(aBrowsingContext);
+
+ uint32_t openFlags = 0;
+ if (aLoadType == LOAD_LINK) {
+ openFlags |= nsIURILoader::IS_CONTENT_PREFERRED;
+ }
+ if (!aBrowsingContext->GetAllowContentRetargeting()) {
+ openFlags |= nsIURILoader::DONT_RETARGET;
+ }
+
+ return openFlags;
+}
+
+nsresult nsDocShell::OpenInitializedChannel(nsIChannel* aChannel,
+ nsIURILoader* aURILoader,
+ uint32_t aOpenFlags) {
+ nsresult rv = NS_OK;
+
+ // If anything fails here, make sure to clear our initial ClientSource.
+ auto cleanupInitialClient =
+ MakeScopeExit([&] { mInitialClientSource.reset(); });
+
+ nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
+ NS_ENSURE_TRUE(win, NS_ERROR_FAILURE);
+
+ MaybeCreateInitialClientSource();
+
+ // Let the client channel helper know if we are using DocumentChannel,
+ // since redirects get handled in the parent process in that case.
+ RefPtr<net::DocumentChannel> docChannel = do_QueryObject(aChannel);
+ if (docChannel && XRE_IsContentProcess()) {
+ // Tell the content process nsDocumentOpenInfo to not try to do
+ // any sort of targeting.
+ aOpenFlags |= nsIURILoader::DONT_RETARGET;
+ }
+
+ // Since we are loading a document we need to make sure the proper reserved
+ // and initial client data is stored on the nsILoadInfo. The
+ // ClientChannelHelper does this and ensures that it is propagated properly
+ // on redirects. We pass no reserved client here so that the helper will
+ // create the reserved ClientSource if necessary.
+ Maybe<ClientInfo> noReservedClient;
+ if (docChannel) {
+ // When using DocumentChannel, all redirect handling is done in the parent,
+ // so we just need the child variant to watch for the internal redirect
+ // to the final channel.
+ rv = AddClientChannelHelperInChild(
+ aChannel, win->EventTargetFor(TaskCategory::Other));
+ docChannel->SetInitialClientInfo(GetInitialClientInfo());
+ } else {
+ rv = AddClientChannelHelper(aChannel, std::move(noReservedClient),
+ GetInitialClientInfo(),
+ win->EventTargetFor(TaskCategory::Other));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aURILoader->OpenURI(aChannel, aOpenFlags, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We're about to load a new page and it may take time before necko
+ // gives back any data, so main thread might have a chance to process a
+ // collector slice
+ nsJSContext::MaybeRunNextCollectorSlice(this, JS::GCReason::DOCSHELL);
+
+ // Success. Keep the initial ClientSource if it exists.
+ cleanupInitialClient.release();
+
+ return NS_OK;
+}
+
+nsresult nsDocShell::OpenRedirectedChannel(nsDocShellLoadState* aLoadState) {
+ nsCOMPtr<nsIChannel> channel = aLoadState->GetPendingRedirectedChannel();
+ MOZ_ASSERT(channel);
+
+ // If anything fails here, make sure to clear our initial ClientSource.
+ auto cleanupInitialClient =
+ MakeScopeExit([&] { mInitialClientSource.reset(); });
+
+ nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow();
+ NS_ENSURE_TRUE(win, NS_ERROR_FAILURE);
+
+ MaybeCreateInitialClientSource();
+
+ nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
+
+ LoadInfo* li = static_cast<LoadInfo*>(loadInfo.get());
+ if (loadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_DOCUMENT) {
+ li->UpdateBrowsingContextID(mBrowsingContext->Id());
+ } else if (loadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ li->UpdateFrameBrowsingContextID(mBrowsingContext->Id());
+ }
+ // TODO: more attributes need to be updated on the LoadInfo (bug 1561706)
+
+ // If we did a process switch, then we should have an existing allocated
+ // ClientInfo, so we just need to allocate a corresponding ClientSource.
+ CreateReservedSourceIfNeeded(channel,
+ win->EventTargetFor(TaskCategory::Other));
+
+ RefPtr<nsDocumentOpenInfo> loader =
+ new nsDocumentOpenInfo(this, nsIURILoader::DONT_RETARGET, nullptr);
+ channel->SetLoadGroup(mLoadGroup);
+
+ MOZ_ALWAYS_SUCCEEDS(loader->Prepare());
+
+ nsresult rv = NS_OK;
+ if (XRE_IsParentProcess()) {
+ // If we're in the parent, the we don't have an nsIChildChannel, just
+ // the original channel, which is already open in this process.
+
+ // DocumentLoadListener expects to get an nsIParentChannel, so
+ // we create a wrapper around the channel and nsIStreamListener
+ // that forwards functionality as needed, and then we register
+ // it under the provided identifier.
+ RefPtr<ParentChannelWrapper> wrapper =
+ new ParentChannelWrapper(channel, loader);
+ wrapper->Register(aLoadState->GetPendingRedirectChannelRegistrarId());
+
+ mLoadGroup->AddRequest(channel, nullptr);
+ } else if (nsCOMPtr<nsIChildChannel> childChannel =
+ do_QueryInterface(channel)) {
+ // Our channel was redirected from another process, so doesn't need to
+ // be opened again. However, it does need its listener hooked up
+ // correctly.
+ rv = childChannel->CompleteRedirectSetup(loader);
+ } else {
+ // It's possible for the redirected channel to not implement
+ // nsIChildChannel and be entirely local (like srcdoc). In that case we
+ // can just open the local instance and it will work.
+ rv = channel->AsyncOpen(loader);
+ }
+ if (rv == NS_ERROR_NO_CONTENT) {
+ return NS_OK;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Success. Keep the initial ClientSource if it exists.
+ cleanupInitialClient.release();
+ return NS_OK;
+}
+
+nsresult nsDocShell::ScrollToAnchor(bool aCurHasRef, bool aNewHasRef,
+ nsACString& aNewHash, uint32_t aLoadType) {
+ if (!mCurrentURI) {
+ return NS_OK;
+ }
+
+ RefPtr<PresShell> presShell = GetPresShell();
+ if (!presShell) {
+ // If we failed to get the shell, or if there is no shell,
+ // nothing left to do here.
+ return NS_OK;
+ }
+
+ nsIScrollableFrame* rootScroll = presShell->GetRootScrollFrameAsScrollable();
+ if (rootScroll) {
+ rootScroll->ClearDidHistoryRestore();
+ }
+
+ // If we have no new anchor, we do not want to scroll, unless there is a
+ // current anchor and we are doing a history load. So return if we have no
+ // new anchor, and there is no current anchor or the load is not a history
+ // load.
+ if ((!aCurHasRef || aLoadType != LOAD_HISTORY) && !aNewHasRef) {
+ return NS_OK;
+ }
+
+ // Both the new and current URIs refer to the same page. We can now
+ // browse to the hash stored in the new URI.
+
+ if (!aNewHash.IsEmpty()) {
+ // anchor is there, but if it's a load from history,
+ // we don't have any anchor jumping to do
+ bool scroll = aLoadType != LOAD_HISTORY && aLoadType != LOAD_RELOAD_NORMAL;
+
+ // We assume that the bytes are in UTF-8, as it says in the
+ // spec:
+ // http://www.w3.org/TR/html4/appendix/notes.html#h-B.2.1
+
+ // We try the UTF-8 string first, and then try the document's
+ // charset (see below). If the string is not UTF-8,
+ // conversion will fail and give us an empty Unicode string.
+ // In that case, we should just fall through to using the
+ // page's charset.
+ nsresult rv = NS_ERROR_FAILURE;
+ NS_ConvertUTF8toUTF16 uStr(aNewHash);
+ if (!uStr.IsEmpty()) {
+ rv = presShell->GoToAnchor(uStr, scroll, ScrollFlags::ScrollSmoothAuto);
+ }
+
+ if (NS_FAILED(rv)) {
+ char* str = ToNewCString(aNewHash, mozilla::fallible);
+ if (!str) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ nsUnescape(str);
+ NS_ConvertUTF8toUTF16 utf16Str(str);
+ if (!utf16Str.IsEmpty()) {
+ rv = presShell->GoToAnchor(utf16Str, scroll,
+ ScrollFlags::ScrollSmoothAuto);
+ }
+ free(str);
+ }
+
+ // Above will fail if the anchor name is not UTF-8. Need to
+ // convert from document charset to unicode.
+ if (NS_FAILED(rv)) {
+ // Get a document charset
+ NS_ENSURE_TRUE(mContentViewer, NS_ERROR_FAILURE);
+ Document* doc = mContentViewer->GetDocument();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+ nsAutoCString charset;
+ doc->GetDocumentCharacterSet()->Name(charset);
+
+ nsCOMPtr<nsITextToSubURI> textToSubURI =
+ do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Unescape and convert to unicode
+ nsAutoString uStr;
+
+ rv = textToSubURI->UnEscapeAndConvert(charset, aNewHash, uStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Ignore return value of GoToAnchor, since it will return an error
+ // if there is no such anchor in the document, which is actually a
+ // success condition for us (we want to update the session history
+ // with the new URI no matter whether we actually scrolled
+ // somewhere).
+ //
+ // When aNewHash contains "%00", unescaped string may be empty.
+ // And GoToAnchor asserts if we ask it to scroll to an empty ref.
+ presShell->GoToAnchor(uStr, scroll && !uStr.IsEmpty(),
+ ScrollFlags::ScrollSmoothAuto);
+ }
+ } else {
+ // Tell the shell it's at an anchor, without scrolling.
+ presShell->GoToAnchor(u""_ns, false);
+
+ // An empty anchor was found, but if it's a load from history,
+ // we don't have to jump to the top of the page. Scrollbar
+ // position will be restored by the caller, based on positions
+ // stored in session history.
+ if (aLoadType == LOAD_HISTORY || aLoadType == LOAD_RELOAD_NORMAL) {
+ return NS_OK;
+ }
+ // An empty anchor. Scroll to the top of the page. Ignore the
+ // return value; failure to scroll here (e.g. if there is no
+ // root scrollframe) is not grounds for canceling the load!
+ SetCurScrollPosEx(0, 0);
+ }
+
+ return NS_OK;
+}
+
+bool nsDocShell::OnNewURI(nsIURI* aURI, nsIChannel* aChannel,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp,
+ bool aFireOnLocationChange, bool aAddToGlobalHistory,
+ bool aCloneSHChildren) {
+ MOZ_ASSERT(aURI, "uri is null");
+ MOZ_ASSERT(!aChannel || !aTriggeringPrincipal, "Shouldn't have both set");
+
+ MOZ_ASSERT(!aPrincipalToInherit ||
+ (aPrincipalToInherit && aTriggeringPrincipal));
+
+#if defined(DEBUG)
+ if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) {
+ nsAutoCString chanName;
+ if (aChannel) {
+ aChannel->GetName(chanName);
+ } else {
+ chanName.AssignLiteral("<no channel>");
+ }
+
+ MOZ_LOG(gDocShellLog, LogLevel::Debug,
+ ("nsDocShell[%p]::OnNewURI(\"%s\", [%s], 0x%x)\n", this,
+ aURI->GetSpecOrDefault().get(), chanName.get(), mLoadType));
+ }
+#endif
+
+ bool equalUri = false;
+
+ // Get the post data and the HTTP response code from the channel.
+ uint32_t responseStatus = 0;
+ nsCOMPtr<nsIInputStream> inputStream;
+ if (aChannel) {
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
+
+ // Check if the HTTPChannel is hiding under a multiPartChannel
+ if (!httpChannel) {
+ GetHttpChannel(aChannel, getter_AddRefs(httpChannel));
+ }
+
+ if (httpChannel) {
+ nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel));
+ if (uploadChannel) {
+ uploadChannel->GetUploadStream(getter_AddRefs(inputStream));
+ }
+
+ // If the response status indicates an error, unlink this session
+ // history entry from any entries sharing its document.
+ nsresult rv = httpChannel->GetResponseStatus(&responseStatus);
+ if (mLSHE && NS_SUCCEEDED(rv) && responseStatus >= 400) {
+ mLSHE->AbandonBFCacheEntry();
+ // FIXME Do the same for mLoadingEntry
+ }
+ }
+ }
+
+ // Determine if this type of load should update history.
+ bool updateGHistory = ShouldUpdateGlobalHistory(mLoadType);
+
+ // We don't update session history on reload unless we're loading
+ // an iframe in shift-reload case.
+ bool updateSHistory = mBrowsingContext->ShouldUpdateSessionHistory(mLoadType);
+
+ // Create SH Entry (mLSHE) only if there is a SessionHistory object in the
+ // root browsing context.
+ // FIXME If session history in the parent is enabled then we only do this if
+ // the session history object is in process, otherwise we can't really
+ // use the mLSHE anyway. Once session history is only stored in the
+ // parent then this code will probably be removed anyway.
+ RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
+ if (!rootSH) {
+ updateSHistory = false;
+ updateGHistory = false; // XXX Why global history too?
+ }
+
+ // Check if the url to be loaded is the same as the one already loaded.
+ if (mCurrentURI) {
+ aURI->Equals(mCurrentURI, &equalUri);
+ }
+
+#ifdef DEBUG
+ bool shAvailable = (rootSH != nullptr);
+
+ // XXX This log message is almost useless because |updateSHistory|
+ // and |updateGHistory| are not correct at this point.
+
+ MOZ_LOG(gDocShellLog, LogLevel::Debug,
+ (" shAvailable=%i updateSHistory=%i updateGHistory=%i"
+ " equalURI=%i\n",
+ shAvailable, updateSHistory, updateGHistory, equalUri));
+#endif
+
+ /* If the url to be loaded is the same as the one already there,
+ * and the original loadType is LOAD_NORMAL, LOAD_LINK, or
+ * LOAD_STOP_CONTENT, set loadType to LOAD_NORMAL_REPLACE so that
+ * AddToSessionHistory() won't mess with the current SHEntry and
+ * if this page has any frame children, it also will be handled
+ * properly. see bug 83684
+ *
+ * NB: If mOSHE is null but we have a current URI, then it probably
+ * means that we must be at the transient about:blank content viewer;
+ * we should let the normal load continue, since there's nothing to
+ * replace. Sometimes this happens after a session restore (eg process
+ * switch) and mCurrentURI is not about:blank; we assume we can let the load
+ * continue (Bug 1301399).
+ *
+ * XXX Hopefully changing the loadType at this time will not hurt
+ * anywhere. The other way to take care of sequentially repeating
+ * frameset pages is to add new methods to nsIDocShellTreeItem.
+ * Hopefully I don't have to do that.
+ */
+ if (equalUri &&
+ (mozilla::SessionHistoryInParent() ? !!mActiveEntry : !!mOSHE) &&
+ (mLoadType == LOAD_NORMAL || mLoadType == LOAD_LINK ||
+ mLoadType == LOAD_STOP_CONTENT) &&
+ !inputStream) {
+ mLoadType = LOAD_NORMAL_REPLACE;
+ }
+
+ // If this is a refresh to the currently loaded url, we don't
+ // have to update session or global history.
+ if (mLoadType == LOAD_REFRESH && !inputStream && equalUri) {
+ SetHistoryEntryAndUpdateBC(Some<nsISHEntry*>(mOSHE), Nothing());
+ }
+
+ /* If the user pressed shift-reload, cache will create a new cache key
+ * for the page. Save the new cacheKey in Session History.
+ * see bug 90098
+ */
+ if (aChannel && IsForceReloadType(mLoadType)) {
+ MOZ_ASSERT(!updateSHistory || IsSubframe(),
+ "We shouldn't be updating session history for forced"
+ " reloads unless we're in a newly created iframe!");
+
+ nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(aChannel));
+ uint32_t cacheKey = 0;
+ // Get the Cache Key and store it in SH.
+ if (cacheChannel) {
+ cacheChannel->GetCacheKey(&cacheKey);
+ }
+ // If we already have a loading history entry, store the new cache key
+ // in it. Otherwise, since we're doing a reload and won't be updating
+ // our history entry, store the cache key in our current history entry.
+ SetCacheKeyOnHistoryEntry(mLSHE ? mLSHE : mOSHE, cacheKey);
+
+ if (!mozilla::SessionHistoryInParent()) {
+ // Since we're force-reloading, clear all the sub frame history.
+ ClearFrameHistory(mLSHE);
+ ClearFrameHistory(mOSHE);
+ }
+ }
+
+ if (!mozilla::SessionHistoryInParent()) {
+ // Clear subframe history on refresh.
+ // XXX: history.go(0) won't go this path as mLoadType is LOAD_HISTORY in
+ // this case. One should re-validate after bug 1331865 fixed.
+ if (mLoadType == LOAD_REFRESH) {
+ ClearFrameHistory(mLSHE);
+ ClearFrameHistory(mOSHE);
+ }
+
+ if (updateSHistory) {
+ // Update session history if necessary...
+ if (!mLSHE && (mItemType == typeContent) && mURIResultedInDocument) {
+ /* This is a fresh page getting loaded for the first time
+ *.Create a Entry for it and add it to SH, if this is the
+ * rootDocShell
+ */
+ (void)AddToSessionHistory(aURI, aChannel, aTriggeringPrincipal,
+ aPrincipalToInherit,
+ aPartitionedPrincipalToInherit, aCsp,
+ aCloneSHChildren, getter_AddRefs(mLSHE));
+ }
+ } else if (GetSessionHistory() && mLSHE && mURIResultedInDocument) {
+ // Even if we don't add anything to SHistory, ensure the current index
+ // points to the same SHEntry as our mLSHE.
+
+ GetSessionHistory()->LegacySHistory()->EnsureCorrectEntryAtCurrIndex(
+ mLSHE);
+ }
+ }
+
+ // If this is a POST request, we do not want to include this in global
+ // history.
+ if (ShouldAddURIVisit(aChannel) && updateGHistory && aAddToGlobalHistory &&
+ !net::ChannelIsPost(aChannel)) {
+ nsCOMPtr<nsIURI> previousURI;
+ uint32_t previousFlags = 0;
+
+ if (mLoadType & LOAD_CMD_RELOAD) {
+ // On a reload request, we don't set redirecting flags.
+ previousURI = aURI;
+ } else {
+ ExtractLastVisit(aChannel, getter_AddRefs(previousURI), &previousFlags);
+ }
+
+ AddURIVisit(aURI, previousURI, previousFlags, responseStatus);
+ }
+
+ // If this was a history load or a refresh, or it was a history load but
+ // later changed to LOAD_NORMAL_REPLACE due to redirection, update the index
+ // in session history.
+ if (!mozilla::SessionHistoryInParent() && rootSH &&
+ ((mLoadType & (LOAD_CMD_HISTORY | LOAD_CMD_RELOAD)) ||
+ mLoadType == LOAD_NORMAL_REPLACE || mLoadType == LOAD_REFRESH_REPLACE)) {
+ mPreviousEntryIndex = rootSH->Index();
+ if (!mozilla::SessionHistoryInParent()) {
+ rootSH->LegacySHistory()->UpdateIndex();
+ }
+ mLoadedEntryIndex = rootSH->Index();
+ MOZ_LOG(gPageCacheLog, LogLevel::Verbose,
+ ("Previous index: %d, Loaded index: %d", mPreviousEntryIndex,
+ mLoadedEntryIndex));
+ }
+
+ // aCloneSHChildren exactly means "we are not loading a new document".
+ uint32_t locationFlags =
+ aCloneSHChildren ? uint32_t(LOCATION_CHANGE_SAME_DOCUMENT) : 0;
+
+ bool onLocationChangeNeeded =
+ SetCurrentURI(aURI, aChannel, aFireOnLocationChange,
+ /* aIsInitialAboutBlank */ false, locationFlags);
+ // Make sure to store the referrer from the channel, if any
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
+ if (httpChannel) {
+ mReferrerInfo = httpChannel->GetReferrerInfo();
+ }
+ return onLocationChangeNeeded;
+}
+
+Maybe<Wireframe> nsDocShell::GetWireframe() {
+ const bool collectWireFrame =
+ mozilla::SessionHistoryInParent() &&
+ StaticPrefs::browser_history_collectWireframes() &&
+ mBrowsingContext->IsTopContent() && mActiveEntry;
+
+ if (!collectWireFrame) {
+ return Nothing();
+ }
+
+ RefPtr<Document> doc = mContentViewer->GetDocument();
+ Nullable<Wireframe> wireframe;
+ doc->GetWireframeWithoutFlushing(false, wireframe);
+ if (wireframe.IsNull()) {
+ return Nothing();
+ }
+ return Some(wireframe.Value());
+}
+
+bool nsDocShell::CollectWireframe() {
+ Maybe<Wireframe> wireframe = GetWireframe();
+ if (wireframe.isNothing()) {
+ return false;
+ }
+
+ if (XRE_IsParentProcess()) {
+ SessionHistoryEntry* entry =
+ mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry();
+ if (entry) {
+ entry->SetWireframe(wireframe);
+ }
+ } else {
+ mozilla::Unused
+ << ContentChild::GetSingleton()->SendSessionHistoryEntryWireframe(
+ mBrowsingContext, wireframe.ref());
+ }
+
+ return true;
+}
+
+//*****************************************************************************
+// nsDocShell: Session History
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle,
+ const nsAString& aURL, bool aReplace, JSContext* aCx) {
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell[%p]: AddState(..., %s, %s, %d)", this,
+ NS_ConvertUTF16toUTF8(aTitle).get(),
+ NS_ConvertUTF16toUTF8(aURL).get(), aReplace));
+ // Implements History.pushState and History.replaceState
+
+ // Here's what we do, roughly in the order specified by HTML5. The specific
+ // steps we are executing are at
+ // <https://html.spec.whatwg.org/multipage/history.html#dom-history-pushstate>
+ // and
+ // <https://html.spec.whatwg.org/multipage/history.html#url-and-history-update-steps>.
+ // This function basically implements #dom-history-pushstate and
+ // UpdateURLAndHistory implements #url-and-history-update-steps.
+ //
+ // A. Serialize aData using structured clone. This is #dom-history-pushstate
+ // step 5.
+ // B. If the third argument is present, #dom-history-pushstate step 7.
+ // 7.1. Resolve the url, relative to our document.
+ // 7.2. If (a) fails, raise a SECURITY_ERR
+ // 7.4. Compare the resulting absolute URL to the document's address. If
+ // any part of the URLs difer other than the <path>, <query>, and
+ // <fragment> components, raise a SECURITY_ERR and abort.
+ // C. If !aReplace, #url-and-history-update-steps steps 2.1-2.3:
+ // Remove from the session history all entries after the current entry,
+ // as we would after a regular navigation, and save the current
+ // entry's scroll position (bug 590573).
+ // D. #url-and-history-update-steps step 2.4 or step 3. As apropriate,
+ // either add a state object entry to the session history after the
+ // current entry with the following properties, or modify the current
+ // session history entry to set
+ // a. cloned data as the state object,
+ // b. if the third argument was present, the absolute URL found in
+ // step 2
+ // Also clear the new history entry's POST data (see bug 580069).
+ // E. If aReplace is false (i.e. we're doing a pushState instead of a
+ // replaceState), notify bfcache that we've navigated to a new page.
+ // F. If the third argument is present, set the document's current address
+ // to the absolute URL found in step B. This is
+ // #url-and-history-update-steps step 4.
+ //
+ // It's important that this function not run arbitrary scripts after step A
+ // and before completing step E. For example, if a script called
+ // history.back() before we completed step E, bfcache might destroy an
+ // active content viewer. Since EvictOutOfRangeContentViewers at the end of
+ // step E might run script, we can't just put a script blocker around the
+ // critical section.
+ //
+ // Note that we completely ignore the aTitle parameter.
+
+ nsresult rv;
+
+ // Don't clobber the load type of an existing network load.
+ AutoRestore<uint32_t> loadTypeResetter(mLoadType);
+
+ // pushState effectively becomes replaceState when we've started a network
+ // load but haven't adopted its document yet. This mirrors what we do with
+ // changes to the hash at this stage of the game.
+ if (JustStartedNetworkLoad()) {
+ aReplace = true;
+ }
+
+ RefPtr<Document> document = GetDocument();
+ NS_ENSURE_TRUE(document, NS_ERROR_FAILURE);
+
+ // Step A: Serialize aData using structured clone.
+ // https://html.spec.whatwg.org/multipage/history.html#dom-history-pushstate
+ // step 5.
+ nsCOMPtr<nsIStructuredCloneContainer> scContainer;
+
+ // scContainer->Init might cause arbitrary JS to run, and this code might
+ // navigate the page we're on, potentially to a different origin! (bug
+ // 634834) To protect against this, we abort if our principal changes due
+ // to the InitFromJSVal() call.
+ {
+ RefPtr<Document> origDocument = GetDocument();
+ if (!origDocument) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ nsCOMPtr<nsIPrincipal> origPrincipal = origDocument->NodePrincipal();
+
+ scContainer = new nsStructuredCloneContainer();
+ rv = scContainer->InitFromJSVal(aData, aCx);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<Document> newDocument = GetDocument();
+ if (!newDocument) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ nsCOMPtr<nsIPrincipal> newPrincipal = newDocument->NodePrincipal();
+
+ bool principalsEqual = false;
+ origPrincipal->Equals(newPrincipal, &principalsEqual);
+ NS_ENSURE_TRUE(principalsEqual, NS_ERROR_DOM_SECURITY_ERR);
+ }
+
+ // Check that the state object isn't too long.
+ int32_t maxStateObjSize = StaticPrefs::browser_history_maxStateObjectSize();
+ if (maxStateObjSize < 0) {
+ maxStateObjSize = 0;
+ }
+
+ uint64_t scSize;
+ rv = scContainer->GetSerializedNBytes(&scSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_TRUE(scSize <= (uint32_t)maxStateObjSize, NS_ERROR_ILLEGAL_VALUE);
+
+ // Step B: Resolve aURL.
+ // https://html.spec.whatwg.org/multipage/history.html#dom-history-pushstate
+ // step 7.
+ bool equalURIs = true;
+ nsCOMPtr<nsIURI> currentURI;
+ if (mCurrentURI) {
+ currentURI = nsIOService::CreateExposableURI(mCurrentURI);
+ } else {
+ currentURI = mCurrentURI;
+ }
+ nsCOMPtr<nsIURI> newURI;
+ if (aURL.Length() == 0) {
+ newURI = currentURI;
+ } else {
+ // 7.1: Resolve aURL relative to mURI
+
+ nsIURI* docBaseURI = document->GetDocBaseURI();
+ if (!docBaseURI) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoCString spec;
+ docBaseURI->GetSpec(spec);
+
+ rv = NS_NewURI(getter_AddRefs(newURI), aURL,
+ document->GetDocumentCharacterSet(), docBaseURI);
+
+ // 7.2: If 2a fails, raise a SECURITY_ERR
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ // 7.4 and 7.5: Same-origin check.
+ if (!nsContentUtils::URIIsLocalFile(newURI)) {
+ // In addition to checking that the security manager says that
+ // the new URI has the same origin as our current URI, we also
+ // check that the two URIs have the same userpass. (The
+ // security manager says that |http://foo.com| and
+ // |http://me@foo.com| have the same origin.) currentURI
+ // won't contain the password part of the userpass, so this
+ // means that it's never valid to specify a password in a
+ // pushState or replaceState URI.
+
+ nsCOMPtr<nsIScriptSecurityManager> secMan =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
+ NS_ENSURE_TRUE(secMan, NS_ERROR_FAILURE);
+
+ // It's very important that we check that newURI is of the same
+ // origin as currentURI, not docBaseURI, because a page can
+ // set docBaseURI arbitrarily to any domain.
+ nsAutoCString currentUserPass, newUserPass;
+ NS_ENSURE_SUCCESS(currentURI->GetUserPass(currentUserPass),
+ NS_ERROR_FAILURE);
+ NS_ENSURE_SUCCESS(newURI->GetUserPass(newUserPass), NS_ERROR_FAILURE);
+ bool isPrivateWin =
+ document->NodePrincipal()->OriginAttributesRef().mPrivateBrowsingId >
+ 0;
+ if (NS_FAILED(secMan->CheckSameOriginURI(currentURI, newURI, true,
+ isPrivateWin)) ||
+ !currentUserPass.Equals(newUserPass)) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ } else {
+ // It's a file:// URI
+ nsCOMPtr<nsIPrincipal> principal = document->GetPrincipal();
+
+ if (!principal || NS_FAILED(principal->CheckMayLoadWithReporting(
+ newURI, false, document->InnerWindowID()))) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ }
+
+ if (currentURI) {
+ currentURI->Equals(newURI, &equalURIs);
+ } else {
+ equalURIs = false;
+ }
+
+ } // end of same-origin check
+
+ // Step 8: call "URL and history update steps"
+ rv = UpdateURLAndHistory(document, newURI, scContainer, aTitle, aReplace,
+ currentURI, equalURIs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult nsDocShell::UpdateURLAndHistory(Document* aDocument, nsIURI* aNewURI,
+ nsIStructuredCloneContainer* aData,
+ const nsAString& aTitle, bool aReplace,
+ nsIURI* aCurrentURI, bool aEqualURIs) {
+ // Implements
+ // https://html.spec.whatwg.org/multipage/history.html#url-and-history-update-steps
+
+ // If we have a pending title change, handle it before creating a new entry.
+ aDocument->DoNotifyPossibleTitleChange();
+
+ // Step 2, if aReplace is false: Create a new entry in the session
+ // history. This will erase all SHEntries after the new entry and make this
+ // entry the current one. This operation may modify mOSHE, which we need
+ // later, so we keep a reference here.
+ NS_ENSURE_TRUE(mOSHE || mActiveEntry || aReplace, NS_ERROR_FAILURE);
+ nsCOMPtr<nsISHEntry> oldOSHE = mOSHE;
+
+ // If this push/replaceState changed the document's current URI and the new
+ // URI differs from the old URI in more than the hash, or if the old
+ // SHEntry's URI was modified in this way by a push/replaceState call
+ // set URIWasModified to true for the current SHEntry (bug 669671).
+ bool sameExceptHashes = true;
+ aNewURI->EqualsExceptRef(aCurrentURI, &sameExceptHashes);
+ bool uriWasModified;
+ if (sameExceptHashes) {
+ if (mozilla::SessionHistoryInParent()) {
+ uriWasModified = mActiveEntry && mActiveEntry->GetURIWasModified();
+ } else {
+ uriWasModified = oldOSHE && oldOSHE->GetURIWasModified();
+ }
+ } else {
+ uriWasModified = true;
+ }
+
+ mLoadType = LOAD_PUSHSTATE;
+
+ nsCOMPtr<nsISHEntry> newSHEntry;
+ if (!aReplace) {
+ // Step 2.
+
+ // Step 2.2, "Remove any tasks queued by the history traversal task
+ // source that are associated with any Document objects in the
+ // top-level browsing context's document family." This is very hard in
+ // SessionHistoryInParent since we can't synchronously access the
+ // pending navigations that are already sent to the parent. We can
+ // abort any AsyncGo navigations that are waiting to be sent. If we
+ // send a message to the parent, it would be processed after any
+ // navigations previously sent. So long as we consider the "history
+ // traversal task source" to be the list in this process we match the
+ // spec. If we move the entire list to the parent, we can handle the
+ // aborting of loads there, but we don't have a way to synchronously
+ // remove entries as we do here for non-SHIP.
+ RefPtr<ChildSHistory> shistory = GetRootSessionHistory();
+ if (shistory) {
+ shistory->RemovePendingHistoryNavigations();
+ }
+
+ nsPoint scrollPos = GetCurScrollPos();
+
+ bool scrollRestorationIsManual;
+ if (mozilla::SessionHistoryInParent()) {
+ // FIXME Need to save the current scroll position on mActiveEntry.
+ scrollRestorationIsManual = mActiveEntry->GetScrollRestorationIsManual();
+ } else {
+ // Save the current scroll position (bug 590573). Step 2.3.
+ mOSHE->SetScrollPosition(scrollPos.x, scrollPos.y);
+
+ scrollRestorationIsManual = mOSHE->GetScrollRestorationIsManual();
+ }
+
+ nsCOMPtr<nsIContentSecurityPolicy> csp = aDocument->GetCsp();
+
+ if (mozilla::SessionHistoryInParent()) {
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell %p UpdateActiveEntry (not replacing)", this));
+ nsString title(mActiveEntry->GetTitle());
+ UpdateActiveEntry(false,
+ /* aPreviousScrollPos = */ Some(scrollPos), aNewURI,
+ /* aOriginalURI = */ nullptr,
+ /* aReferrerInfo = */ nullptr,
+ /* aTriggeringPrincipal = */ aDocument->NodePrincipal(),
+ csp, title, scrollRestorationIsManual, aData,
+ uriWasModified);
+ } else {
+ // Since we're not changing which page we have loaded, pass
+ // true for aCloneChildren.
+ nsresult rv = AddToSessionHistory(
+ aNewURI, nullptr,
+ aDocument->NodePrincipal(), // triggeringPrincipal
+ nullptr, nullptr, csp, true, getter_AddRefs(newSHEntry));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_TRUE(newSHEntry, NS_ERROR_FAILURE);
+
+ // Session history entries created by pushState inherit scroll restoration
+ // mode from the current entry.
+ newSHEntry->SetScrollRestorationIsManual(scrollRestorationIsManual);
+
+ nsString title;
+ mOSHE->GetTitle(title);
+
+ // Set the new SHEntry's title (bug 655273).
+ newSHEntry->SetTitle(title);
+
+ // Link the new SHEntry to the old SHEntry's BFCache entry, since the
+ // two entries correspond to the same document.
+ NS_ENSURE_SUCCESS(newSHEntry->AdoptBFCacheEntry(oldOSHE),
+ NS_ERROR_FAILURE);
+
+ // AddToSessionHistory may not modify mOSHE. In case it doesn't,
+ // we'll just set mOSHE here.
+ mOSHE = newSHEntry;
+ }
+ } else if (mozilla::SessionHistoryInParent()) {
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell %p UpdateActiveEntry (replacing) mActiveEntry %p",
+ this, mActiveEntry.get()));
+ // Setting the resultPrincipalURI to nullptr is fine here: it will cause
+ // NS_GetFinalChannelURI to use the originalURI as the URI, which is aNewURI
+ // in our case. We could also set it to aNewURI, with the same result.
+ // We don't use aTitle here, see bug 544535.
+ nsString title;
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+ if (mActiveEntry) {
+ title = mActiveEntry->GetTitle();
+ referrerInfo = mActiveEntry->GetReferrerInfo();
+ } else {
+ referrerInfo = nullptr;
+ }
+ UpdateActiveEntry(
+ true, /* aPreviousScrollPos = */ Nothing(), aNewURI, aNewURI,
+ /* aReferrerInfo = */ referrerInfo, aDocument->NodePrincipal(),
+ aDocument->GetCsp(), title,
+ mActiveEntry && mActiveEntry->GetScrollRestorationIsManual(), aData,
+ uriWasModified);
+ } else {
+ // Step 3.
+ newSHEntry = mOSHE;
+
+ MOZ_LOG(gSHLog, LogLevel::Debug, ("nsDocShell %p step 3", this));
+ // Since we're not changing which page we have loaded, pass
+ // true for aCloneChildren.
+ if (!newSHEntry) {
+ nsresult rv = AddToSessionHistory(
+ aNewURI, nullptr,
+ aDocument->NodePrincipal(), // triggeringPrincipal
+ nullptr, nullptr, aDocument->GetCsp(), true,
+ getter_AddRefs(newSHEntry));
+ NS_ENSURE_SUCCESS(rv, rv);
+ mOSHE = newSHEntry;
+ }
+
+ newSHEntry->SetURI(aNewURI);
+ newSHEntry->SetOriginalURI(aNewURI);
+ // We replaced the URI of the entry, clear the unstripped URI as it
+ // shouldn't be used for reloads anymore.
+ newSHEntry->SetUnstrippedURI(nullptr);
+ // Setting the resultPrincipalURI to nullptr is fine here: it will cause
+ // NS_GetFinalChannelURI to use the originalURI as the URI, which is aNewURI
+ // in our case. We could also set it to aNewURI, with the same result.
+ newSHEntry->SetResultPrincipalURI(nullptr);
+ newSHEntry->SetLoadReplace(false);
+ }
+
+ if (!mozilla::SessionHistoryInParent()) {
+ // Step 2.4 and 3: Modify new/original session history entry and clear its
+ // POST data, if there is any.
+ newSHEntry->SetStateData(aData);
+ newSHEntry->SetPostData(nullptr);
+
+ newSHEntry->SetURIWasModified(uriWasModified);
+
+ // Step E as described at the top of AddState: If aReplace is false,
+ // indicating that we're doing a pushState rather than a replaceState,
+ // notify bfcache that we've added a page to the history so it can evict
+ // content viewers if appropriate. Otherwise call ReplaceEntry so that we
+ // notify nsIHistoryListeners that an entry was replaced. We may not have a
+ // root session history if this call is coming from a document.open() in a
+ // docshell subtree that disables session history.
+ RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
+ if (rootSH) {
+ rootSH->LegacySHistory()->EvictContentViewersOrReplaceEntry(newSHEntry,
+ aReplace);
+ }
+ }
+
+ // Step 4: If the document's URI changed, update document's URI and update
+ // global history.
+ //
+ // We need to call FireOnLocationChange so that the browser's address bar
+ // gets updated and the back button is enabled, but we only need to
+ // explicitly call FireOnLocationChange if we're not calling SetCurrentURI,
+ // since SetCurrentURI will call FireOnLocationChange for us.
+ //
+ // Both SetCurrentURI(...) and FireDummyOnLocationChange() pass
+ // nullptr for aRequest param to FireOnLocationChange(...). Such an update
+ // notification is allowed only when we know docshell is not loading a new
+ // document and it requires LOCATION_CHANGE_SAME_DOCUMENT flag. Otherwise,
+ // FireOnLocationChange(...) breaks security UI.
+ //
+ // If the docshell is shutting down, don't update the document URI, as we
+ // can't load into a docshell that is being destroyed.
+ if (!aEqualURIs && !mIsBeingDestroyed) {
+ aDocument->SetDocumentURI(aNewURI);
+ SetCurrentURI(aNewURI, nullptr, /* aFireLocationChange */ true,
+ /* aIsInitialAboutBlank */ false,
+ GetSameDocumentNavigationFlags(aNewURI));
+
+ AddURIVisit(aNewURI, aCurrentURI, 0);
+
+ // AddURIVisit doesn't set the title for the new URI in global history,
+ // so do that here.
+ UpdateGlobalHistoryTitle(aNewURI);
+
+ // Inform the favicon service that our old favicon applies to this new
+ // URI.
+ CopyFavicon(aCurrentURI, aNewURI, UsePrivateBrowsing());
+ } else {
+ FireDummyOnLocationChange();
+ }
+ aDocument->SetStateObject(aData);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetCurrentScrollRestorationIsManual(bool* aIsManual) {
+ if (mozilla::SessionHistoryInParent()) {
+ *aIsManual = mActiveEntry && mActiveEntry->GetScrollRestorationIsManual();
+ return NS_OK;
+ }
+
+ *aIsManual = false;
+ if (mOSHE) {
+ return mOSHE->GetScrollRestorationIsManual(aIsManual);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetCurrentScrollRestorationIsManual(bool aIsManual) {
+ SetScrollRestorationIsManualOnHistoryEntry(mOSHE, aIsManual);
+
+ return NS_OK;
+}
+
+void nsDocShell::SetScrollRestorationIsManualOnHistoryEntry(
+ nsISHEntry* aSHEntry, bool aIsManual) {
+ if (aSHEntry) {
+ aSHEntry->SetScrollRestorationIsManual(aIsManual);
+ }
+
+ if (mActiveEntry && mBrowsingContext) {
+ mActiveEntry->SetScrollRestorationIsManual(aIsManual);
+ if (XRE_IsParentProcess()) {
+ SessionHistoryEntry* entry =
+ mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry();
+ if (entry) {
+ entry->SetScrollRestorationIsManual(aIsManual);
+ }
+ } else {
+ mozilla::Unused << ContentChild::GetSingleton()
+ ->SendSessionHistoryEntryScrollRestorationIsManual(
+ mBrowsingContext, aIsManual);
+ }
+ }
+}
+
+void nsDocShell::SetCacheKeyOnHistoryEntry(nsISHEntry* aSHEntry,
+ uint32_t aCacheKey) {
+ if (aSHEntry) {
+ aSHEntry->SetCacheKey(aCacheKey);
+ }
+
+ if (mActiveEntry && mBrowsingContext) {
+ mActiveEntry->SetCacheKey(aCacheKey);
+ if (XRE_IsParentProcess()) {
+ SessionHistoryEntry* entry =
+ mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry();
+ if (entry) {
+ entry->SetCacheKey(aCacheKey);
+ }
+ } else {
+ mozilla::Unused
+ << ContentChild::GetSingleton()->SendSessionHistoryEntryCacheKey(
+ mBrowsingContext, aCacheKey);
+ }
+ }
+}
+
+/* static */
+bool nsDocShell::ShouldAddToSessionHistory(nsIURI* aURI, nsIChannel* aChannel) {
+ // I believe none of the about: urls should go in the history. But then
+ // that could just be me... If the intent is only deny about:blank then we
+ // should just do a spec compare, rather than two gets of the scheme and
+ // then the path. -Gagan
+ nsresult rv;
+ nsAutoCString buf;
+
+ rv = aURI->GetScheme(buf);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ if (buf.EqualsLiteral("about")) {
+ rv = aURI->GetPathQueryRef(buf);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ if (buf.EqualsLiteral("blank")) {
+ return false;
+ }
+ // We only want to add about:newtab if it's not privileged, and
+ // if it is not configured to show the blank page.
+ if (buf.EqualsLiteral("newtab")) {
+ if (!StaticPrefs::browser_newtabpage_enabled()) {
+ return false;
+ }
+
+ NS_ENSURE_TRUE(aChannel, false);
+ nsCOMPtr<nsIPrincipal> resultPrincipal;
+ rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ aChannel, getter_AddRefs(resultPrincipal));
+ NS_ENSURE_SUCCESS(rv, false);
+ return !resultPrincipal->IsSystemPrincipal();
+ }
+ }
+
+ return true;
+}
+
+nsresult nsDocShell::AddToSessionHistory(
+ nsIURI* aURI, nsIChannel* aChannel, nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp, bool aCloneChildren,
+ nsISHEntry** aNewEntry) {
+ MOZ_ASSERT(aURI, "uri is null");
+ MOZ_ASSERT(!aChannel || !aTriggeringPrincipal, "Shouldn't have both set");
+ MOZ_DIAGNOSTIC_ASSERT(!mozilla::SessionHistoryInParent());
+
+#if defined(DEBUG)
+ if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) {
+ nsAutoCString chanName;
+ if (aChannel) {
+ aChannel->GetName(chanName);
+ } else {
+ chanName.AssignLiteral("<no channel>");
+ }
+
+ MOZ_LOG(gDocShellLog, LogLevel::Debug,
+ ("nsDocShell[%p]::AddToSessionHistory(\"%s\", [%s])\n", this,
+ aURI->GetSpecOrDefault().get(), chanName.get()));
+ }
+#endif
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsISHEntry> entry;
+
+ /*
+ * If this is a LOAD_FLAGS_REPLACE_HISTORY in a subframe, we use
+ * the existing SH entry in the page and replace the url and
+ * other vitalities.
+ */
+ if (LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY) &&
+ !mBrowsingContext->IsTop()) {
+ // This is a subframe
+ entry = mOSHE;
+ if (entry) {
+ entry->ClearEntry();
+ }
+ }
+
+ // Create a new entry if necessary.
+ if (!entry) {
+ entry = new nsSHEntry();
+ }
+
+ // Get the post data & referrer
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsCOMPtr<nsIURI> originalURI;
+ nsCOMPtr<nsIURI> resultPrincipalURI;
+ nsCOMPtr<nsIURI> unstrippedURI;
+ bool loadReplace = false;
+ nsCOMPtr<nsIReferrerInfo> referrerInfo;
+ uint32_t cacheKey = 0;
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal = aTriggeringPrincipal;
+ nsCOMPtr<nsIPrincipal> principalToInherit = aPrincipalToInherit;
+ nsCOMPtr<nsIPrincipal> partitionedPrincipalToInherit =
+ aPartitionedPrincipalToInherit;
+ nsCOMPtr<nsIContentSecurityPolicy> csp = aCsp;
+ bool expired = false; // by default the page is not expired
+ bool discardLayoutState = false;
+ nsCOMPtr<nsICacheInfoChannel> cacheChannel;
+ bool userActivation = false;
+
+ if (aChannel) {
+ cacheChannel = do_QueryInterface(aChannel);
+
+ /* If there is a caching channel, get the Cache Key and store it
+ * in SH.
+ */
+ if (cacheChannel) {
+ cacheChannel->GetCacheKey(&cacheKey);
+ }
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
+
+ // Check if the httpChannel is hiding under a multipartChannel
+ if (!httpChannel) {
+ GetHttpChannel(aChannel, getter_AddRefs(httpChannel));
+ }
+ if (httpChannel) {
+ nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel));
+ if (uploadChannel) {
+ uploadChannel->GetUploadStream(getter_AddRefs(inputStream));
+ }
+ httpChannel->GetOriginalURI(getter_AddRefs(originalURI));
+ uint32_t loadFlags;
+ aChannel->GetLoadFlags(&loadFlags);
+ loadReplace = loadFlags & nsIChannel::LOAD_REPLACE;
+ rv = httpChannel->GetReferrerInfo(getter_AddRefs(referrerInfo));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ discardLayoutState = ShouldDiscardLayoutState(httpChannel);
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ if (!triggeringPrincipal) {
+ triggeringPrincipal = loadInfo->TriggeringPrincipal();
+ }
+ if (!csp) {
+ csp = loadInfo->GetCspToInherit();
+ }
+
+ loadInfo->GetResultPrincipalURI(getter_AddRefs(resultPrincipalURI));
+
+ loadInfo->GetUnstrippedURI(getter_AddRefs(unstrippedURI));
+
+ userActivation = loadInfo->GetHasValidUserGestureActivation();
+
+ // For now keep storing just the principal in the SHEntry.
+ if (!principalToInherit) {
+ if (loadInfo->GetLoadingSandboxed()) {
+ if (loadInfo->GetLoadingPrincipal()) {
+ principalToInherit = NullPrincipal::CreateWithInheritedAttributes(
+ loadInfo->GetLoadingPrincipal());
+ } else {
+ // get the OriginAttributes
+ OriginAttributes attrs;
+ loadInfo->GetOriginAttributes(&attrs);
+ principalToInherit = NullPrincipal::Create(attrs);
+ }
+ } else {
+ principalToInherit = loadInfo->PrincipalToInherit();
+ }
+ }
+
+ if (!partitionedPrincipalToInherit) {
+ // XXXehsan is it correct to fall back to the principal to inherit in all
+ // cases? For example, what about the cases where we are using the load
+ // info's principal to inherit? Do we need to add a similar concept to
+ // load info for partitioned principal?
+ partitionedPrincipalToInherit = principalToInherit;
+ }
+ }
+
+ nsAutoString srcdoc;
+ bool srcdocEntry = false;
+ nsCOMPtr<nsIURI> baseURI;
+
+ nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(aChannel);
+ if (inStrmChan) {
+ bool isSrcdocChannel;
+ inStrmChan->GetIsSrcdocChannel(&isSrcdocChannel);
+ if (isSrcdocChannel) {
+ inStrmChan->GetSrcdocData(srcdoc);
+ srcdocEntry = true;
+ inStrmChan->GetBaseURI(getter_AddRefs(baseURI));
+ } else {
+ srcdoc.SetIsVoid(true);
+ }
+ }
+ /* If cache got a 'no-store', ask SH not to store
+ * HistoryLayoutState. By default, SH will set this
+ * flag to true and save HistoryLayoutState.
+ */
+ bool saveLayoutState = !discardLayoutState;
+
+ if (cacheChannel) {
+ // Check if the page has expired from cache
+ uint32_t expTime = 0;
+ cacheChannel->GetCacheTokenExpirationTime(&expTime);
+ uint32_t now = PRTimeToSeconds(PR_Now());
+ if (expTime <= now) {
+ expired = true;
+ }
+ }
+
+ // Title is set in nsDocShell::SetTitle()
+ entry->Create(aURI, // uri
+ u""_ns, // Title
+ inputStream, // Post data stream
+ cacheKey, // CacheKey
+ mContentTypeHint, // Content-type
+ triggeringPrincipal, // Channel or provided principal
+ principalToInherit, partitionedPrincipalToInherit, csp,
+ HistoryID(), GetCreatedDynamically(), originalURI,
+ resultPrincipalURI, unstrippedURI, loadReplace, referrerInfo,
+ srcdoc, srcdocEntry, baseURI, saveLayoutState, expired,
+ userActivation);
+
+ if (mBrowsingContext->IsTop() && GetSessionHistory()) {
+ bool shouldPersist = ShouldAddToSessionHistory(aURI, aChannel);
+ Maybe<int32_t> previousEntryIndex;
+ Maybe<int32_t> loadedEntryIndex;
+ rv = GetSessionHistory()->LegacySHistory()->AddToRootSessionHistory(
+ aCloneChildren, mOSHE, mBrowsingContext, entry, mLoadType,
+ shouldPersist, &previousEntryIndex, &loadedEntryIndex);
+
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Could not add entry to root session history");
+ if (previousEntryIndex.isSome()) {
+ mPreviousEntryIndex = previousEntryIndex.value();
+ }
+ if (loadedEntryIndex.isSome()) {
+ mLoadedEntryIndex = loadedEntryIndex.value();
+ }
+
+ // aCloneChildren implies that we are retaining the same document, thus we
+ // need to signal to the top WC that the new SHEntry may receive a fresh
+ // user interaction flag.
+ if (aCloneChildren) {
+ WindowContext* topWc = mBrowsingContext->GetTopWindowContext();
+ if (topWc && !topWc->IsDiscarded()) {
+ MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(false));
+ }
+ }
+ } else {
+ // This is a subframe, make sure that this new SHEntry will be
+ // marked with user interaction.
+ WindowContext* topWc = mBrowsingContext->GetTopWindowContext();
+ if (topWc && !topWc->IsDiscarded()) {
+ MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(false));
+ }
+ if (!mOSHE || !LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY)) {
+ rv = AddChildSHEntryToParent(entry, mBrowsingContext->ChildOffset(),
+ aCloneChildren);
+ }
+ }
+
+ // Return the new SH entry...
+ if (aNewEntry) {
+ *aNewEntry = nullptr;
+ if (NS_SUCCEEDED(rv)) {
+ entry.forget(aNewEntry);
+ }
+ }
+
+ return rv;
+}
+
+void nsDocShell::UpdateActiveEntry(
+ bool aReplace, const Maybe<nsPoint>& aPreviousScrollPos, nsIURI* aURI,
+ nsIURI* aOriginalURI, nsIReferrerInfo* aReferrerInfo,
+ nsIPrincipal* aTriggeringPrincipal, nsIContentSecurityPolicy* aCsp,
+ const nsAString& aTitle, bool aScrollRestorationIsManual,
+ nsIStructuredCloneContainer* aData, bool aURIWasModified) {
+ MOZ_ASSERT(mozilla::SessionHistoryInParent());
+ MOZ_ASSERT(aURI, "uri is null");
+ MOZ_ASSERT(mLoadType == LOAD_PUSHSTATE,
+ "This code only deals with pushState");
+ MOZ_ASSERT_IF(aPreviousScrollPos.isSome(), !aReplace);
+
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("Creating an active entry on nsDocShell %p to %s", this,
+ aURI->GetSpecOrDefault().get()));
+
+ // Even if we're replacing an existing entry we create new a
+ // SessionHistoryInfo. In the parent process we'll keep the existing
+ // SessionHistoryEntry, but just replace its SessionHistoryInfo, that way the
+ // entry keeps identity but its data is replaced.
+ bool replace = aReplace && mActiveEntry;
+
+ if (!replace) {
+ CollectWireframe();
+ }
+
+ if (mActiveEntry) {
+ // Link this entry to the previous active entry.
+ mActiveEntry = MakeUnique<SessionHistoryInfo>(*mActiveEntry, aURI);
+ } else {
+ mActiveEntry = MakeUnique<SessionHistoryInfo>(
+ aURI, aTriggeringPrincipal, nullptr, nullptr, aCsp, mContentTypeHint);
+ }
+ mActiveEntry->SetOriginalURI(aOriginalURI);
+ mActiveEntry->SetUnstrippedURI(nullptr);
+ mActiveEntry->SetReferrerInfo(aReferrerInfo);
+ mActiveEntry->SetTitle(aTitle);
+ mActiveEntry->SetStateData(static_cast<nsStructuredCloneContainer*>(aData));
+ mActiveEntry->SetURIWasModified(aURIWasModified);
+ mActiveEntry->SetScrollRestorationIsManual(aScrollRestorationIsManual);
+
+ if (replace) {
+ mBrowsingContext->ReplaceActiveSessionHistoryEntry(mActiveEntry.get());
+ } else {
+ mBrowsingContext->IncrementHistoryEntryCountForBrowsingContext();
+ // FIXME We should probably just compute mChildOffset in the parent
+ // instead of passing it over IPC here.
+ mBrowsingContext->SetActiveSessionHistoryEntry(
+ aPreviousScrollPos, mActiveEntry.get(), mLoadType,
+ /* aCacheKey = */ 0);
+ // FIXME Do we need to update mPreviousEntryIndex and mLoadedEntryIndex?
+ }
+}
+
+nsresult nsDocShell::LoadHistoryEntry(nsISHEntry* aEntry, uint32_t aLoadType,
+ bool aUserActivation) {
+ NS_ENSURE_TRUE(aEntry, NS_ERROR_FAILURE);
+
+ nsresult rv;
+ RefPtr<nsDocShellLoadState> loadState;
+ rv = aEntry->CreateLoadInfo(getter_AddRefs(loadState));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Calling CreateAboutBlankContentViewer can set mOSHE to null, and if
+ // that's the only thing holding a ref to aEntry that will cause aEntry to
+ // die while we're loading it. So hold a strong ref to aEntry here, just
+ // in case.
+ nsCOMPtr<nsISHEntry> kungFuDeathGrip(aEntry);
+
+ loadState->SetHasValidUserGestureActivation(
+ loadState->HasValidUserGestureActivation() || aUserActivation);
+
+ return LoadHistoryEntry(loadState, aLoadType, aEntry == mOSHE);
+}
+
+nsresult nsDocShell::LoadHistoryEntry(const LoadingSessionHistoryInfo& aEntry,
+ uint32_t aLoadType,
+ bool aUserActivation) {
+ RefPtr<nsDocShellLoadState> loadState = aEntry.CreateLoadInfo();
+ loadState->SetHasValidUserGestureActivation(
+ loadState->HasValidUserGestureActivation() || aUserActivation);
+
+ return LoadHistoryEntry(loadState, aLoadType, aEntry.mLoadingCurrentEntry);
+}
+
+nsresult nsDocShell::LoadHistoryEntry(nsDocShellLoadState* aLoadState,
+ uint32_t aLoadType,
+ bool aLoadingCurrentEntry) {
+ if (!IsNavigationAllowed()) {
+ return NS_OK;
+ }
+
+ // We are setting load type afterwards so we don't have to
+ // send it in an IPC message
+ aLoadState->SetLoadType(aLoadType);
+
+ nsresult rv;
+ if (SchemeIsJavascript(aLoadState->URI())) {
+ // We're loading a URL that will execute script from inside asyncOpen.
+ // Replace the current document with about:blank now to prevent
+ // anything from the current document from leaking into any JavaScript
+ // code in the URL.
+ // Don't cache the presentation if we're going to just reload the
+ // current entry. Caching would lead to trying to save the different
+ // content viewers in the same nsISHEntry object.
+ rv = CreateAboutBlankContentViewer(
+ aLoadState->PrincipalToInherit(),
+ aLoadState->PartitionedPrincipalToInherit(), nullptr, nullptr,
+ /* aIsInitialDocument */ false, Nothing(), !aLoadingCurrentEntry);
+
+ if (NS_FAILED(rv)) {
+ // The creation of the intermittent about:blank content
+ // viewer failed for some reason (potentially because the
+ // user prevented it). Interrupt the history load.
+ return NS_OK;
+ }
+
+ if (!aLoadState->TriggeringPrincipal()) {
+ // Ensure that we have a triggeringPrincipal. Otherwise javascript:
+ // URIs will pick it up from the about:blank page we just loaded,
+ // and we don't really want even that in this case.
+ nsCOMPtr<nsIPrincipal> principal =
+ NullPrincipal::Create(GetOriginAttributes());
+ aLoadState->SetTriggeringPrincipal(principal);
+ }
+ }
+
+ /* If there is a valid postdata *and* the user pressed
+ * reload or shift-reload, take user's permission before we
+ * repost the data to the server.
+ */
+ if ((aLoadType & LOAD_CMD_RELOAD) && aLoadState->PostDataStream()) {
+ bool repost;
+ rv = ConfirmRepost(&repost);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // If the user pressed cancel in the dialog, return. We're done here.
+ if (!repost) {
+ return NS_BINDING_ABORTED;
+ }
+ }
+
+ // If there is no valid triggeringPrincipal, we deny the load
+ MOZ_ASSERT(aLoadState->TriggeringPrincipal(),
+ "need a valid triggeringPrincipal to load from history");
+ if (!aLoadState->TriggeringPrincipal()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return InternalLoad(aLoadState); // No nsIRequest
+}
+
+NS_IMETHODIMP
+nsDocShell::PersistLayoutHistoryState() {
+ nsresult rv = NS_OK;
+
+ if (mozilla::SessionHistoryInParent() ? !!mActiveEntry : !!mOSHE) {
+ bool scrollRestorationIsManual;
+ if (mozilla::SessionHistoryInParent()) {
+ scrollRestorationIsManual = mActiveEntry->GetScrollRestorationIsManual();
+ } else {
+ scrollRestorationIsManual = mOSHE->GetScrollRestorationIsManual();
+ }
+ nsCOMPtr<nsILayoutHistoryState> layoutState;
+ if (RefPtr<PresShell> presShell = GetPresShell()) {
+ rv = presShell->CaptureHistoryState(getter_AddRefs(layoutState));
+ } else if (scrollRestorationIsManual) {
+ // Even if we don't have layout anymore, we may want to reset the
+ // current scroll state in layout history.
+ GetLayoutHistoryState(getter_AddRefs(layoutState));
+ }
+
+ if (scrollRestorationIsManual && layoutState) {
+ layoutState->ResetScrollState();
+ }
+ }
+
+ return rv;
+}
+
+void nsDocShell::SwapHistoryEntries(nsISHEntry* aOldEntry,
+ nsISHEntry* aNewEntry) {
+ if (aOldEntry == mOSHE) {
+ mOSHE = aNewEntry;
+ }
+
+ if (aOldEntry == mLSHE) {
+ mLSHE = aNewEntry;
+ }
+}
+
+void nsDocShell::SetHistoryEntryAndUpdateBC(const Maybe<nsISHEntry*>& aLSHE,
+ const Maybe<nsISHEntry*>& aOSHE) {
+ // We want to hold on to the reference in mLSHE before we update it.
+ // Otherwise, SetHistoryEntry could release the last reference to
+ // the entry while aOSHE is pointing to it.
+ nsCOMPtr<nsISHEntry> deathGripOldLSHE;
+ if (aLSHE.isSome()) {
+ deathGripOldLSHE = SetHistoryEntry(&mLSHE, aLSHE.value());
+ MOZ_ASSERT(mLSHE.get() == aLSHE.value());
+ }
+ nsCOMPtr<nsISHEntry> deathGripOldOSHE;
+ if (aOSHE.isSome()) {
+ deathGripOldOSHE = SetHistoryEntry(&mOSHE, aOSHE.value());
+ MOZ_ASSERT(mOSHE.get() == aOSHE.value());
+ }
+}
+
+already_AddRefed<nsISHEntry> nsDocShell::SetHistoryEntry(
+ nsCOMPtr<nsISHEntry>* aPtr, nsISHEntry* aEntry) {
+ // We need to sync up the docshell and session history trees for
+ // subframe navigation. If the load was in a subframe, we forward up to
+ // the root docshell, which will then recursively sync up all docshells
+ // to their corresponding entries in the new session history tree.
+ // If we don't do this, then we can cache a content viewer on the wrong
+ // cloned entry, and subsequently restore it at the wrong time.
+ RefPtr<BrowsingContext> topBC = mBrowsingContext->Top();
+ if (topBC->IsDiscarded()) {
+ topBC = nullptr;
+ }
+ RefPtr<BrowsingContext> currBC =
+ mBrowsingContext->IsDiscarded() ? nullptr : mBrowsingContext;
+ if (topBC && *aPtr) {
+ (*aPtr)->SyncTreesForSubframeNavigation(aEntry, topBC, currBC);
+ }
+ nsCOMPtr<nsISHEntry> entry(aEntry);
+ entry.swap(*aPtr);
+ return entry.forget();
+}
+
+already_AddRefed<ChildSHistory> nsDocShell::GetRootSessionHistory() {
+ RefPtr<ChildSHistory> childSHistory =
+ mBrowsingContext->Top()->GetChildSessionHistory();
+ return childSHistory.forget();
+}
+
+nsresult nsDocShell::GetHttpChannel(nsIChannel* aChannel,
+ nsIHttpChannel** aReturn) {
+ NS_ENSURE_ARG_POINTER(aReturn);
+ if (!aChannel) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIMultiPartChannel> multiPartChannel(do_QueryInterface(aChannel));
+ if (multiPartChannel) {
+ nsCOMPtr<nsIChannel> baseChannel;
+ multiPartChannel->GetBaseChannel(getter_AddRefs(baseChannel));
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(baseChannel));
+ *aReturn = httpChannel;
+ NS_IF_ADDREF(*aReturn);
+ }
+ return NS_OK;
+}
+
+bool nsDocShell::ShouldDiscardLayoutState(nsIHttpChannel* aChannel) {
+ // By default layout State will be saved.
+ if (!aChannel) {
+ return false;
+ }
+
+ // figure out if SH should be saving layout state
+ bool noStore = false;
+ Unused << aChannel->IsNoStoreResponse(&noStore);
+ return noStore;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetEditor(nsIEditor** aEditor) {
+ NS_ENSURE_ARG_POINTER(aEditor);
+ RefPtr<HTMLEditor> htmlEditor = GetHTMLEditorInternal();
+ htmlEditor.forget(aEditor);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetEditor(nsIEditor* aEditor) {
+ HTMLEditor* htmlEditor = aEditor ? aEditor->GetAsHTMLEditor() : nullptr;
+ // If TextEditor comes, throw an error.
+ if (aEditor && !htmlEditor) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ return SetHTMLEditorInternal(htmlEditor);
+}
+
+HTMLEditor* nsDocShell::GetHTMLEditorInternal() {
+ return mEditorData ? mEditorData->GetHTMLEditor() : nullptr;
+}
+
+nsresult nsDocShell::SetHTMLEditorInternal(HTMLEditor* aHTMLEditor) {
+ if (!aHTMLEditor && !mEditorData) {
+ return NS_OK;
+ }
+
+ nsresult rv = EnsureEditorData();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return mEditorData->SetHTMLEditor(aHTMLEditor);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetEditable(bool* aEditable) {
+ NS_ENSURE_ARG_POINTER(aEditable);
+ *aEditable = mEditorData && mEditorData->GetEditable();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetHasEditingSession(bool* aHasEditingSession) {
+ NS_ENSURE_ARG_POINTER(aHasEditingSession);
+
+ if (mEditorData) {
+ *aHasEditingSession = !!mEditorData->GetEditingSession();
+ } else {
+ *aHasEditingSession = false;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::MakeEditable(bool aInWaitForUriLoad) {
+ nsresult rv = EnsureEditorData();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return mEditorData->MakeEditable(aInWaitForUriLoad);
+}
+
+/* static */ bool nsDocShell::ShouldAddURIVisit(nsIChannel* aChannel) {
+ bool needToAddURIVisit = true;
+ nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(aChannel));
+ if (props) {
+ mozilla::Unused << props->GetPropertyAsBool(
+ u"docshell.needToAddURIVisit"_ns, &needToAddURIVisit);
+ }
+
+ return needToAddURIVisit;
+}
+
+/* static */ void nsDocShell::ExtractLastVisit(
+ nsIChannel* aChannel, nsIURI** aURI, uint32_t* aChannelRedirectFlags) {
+ nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(aChannel));
+ if (!props) {
+ return;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIURI> uri(do_GetProperty(props, u"docshell.previousURI"_ns, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ uri.forget(aURI);
+
+ rv = props->GetPropertyAsUint32(u"docshell.previousFlags"_ns,
+ aChannelRedirectFlags);
+
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "Could not fetch previous flags, URI will be treated like referrer");
+
+ } else {
+ // There is no last visit for this channel, so this must be the first
+ // link. Link the visit to the referrer of this request, if any.
+ // Treat referrer as null if there is an error getting it.
+ NS_GetReferrerFromChannel(aChannel, aURI);
+ }
+}
+
+void nsDocShell::SaveLastVisit(nsIChannel* aChannel, nsIURI* aURI,
+ uint32_t aChannelRedirectFlags) {
+ nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(aChannel));
+ if (!props || !aURI) {
+ return;
+ }
+
+ props->SetPropertyAsInterface(u"docshell.previousURI"_ns, aURI);
+ props->SetPropertyAsUint32(u"docshell.previousFlags"_ns,
+ aChannelRedirectFlags);
+}
+
+/* static */ void nsDocShell::InternalAddURIVisit(
+ nsIURI* aURI, nsIURI* aPreviousURI, uint32_t aChannelRedirectFlags,
+ uint32_t aResponseStatus, BrowsingContext* aBrowsingContext,
+ nsIWidget* aWidget, uint32_t aLoadType) {
+ MOZ_ASSERT(aURI, "Visited URI is null!");
+ MOZ_ASSERT(aLoadType != LOAD_ERROR_PAGE && aLoadType != LOAD_BYPASS_HISTORY,
+ "Do not add error or bypass pages to global history");
+
+ bool usePrivateBrowsing = false;
+ aBrowsingContext->GetUsePrivateBrowsing(&usePrivateBrowsing);
+
+ // Only content-type docshells save URI visits. Also don't do
+ // anything here if we're not supposed to use global history.
+ if (!aBrowsingContext->IsContent() ||
+ !aBrowsingContext->GetUseGlobalHistory() || usePrivateBrowsing) {
+ return;
+ }
+
+ nsCOMPtr<IHistory> history = components::History::Service();
+
+ if (history) {
+ uint32_t visitURIFlags = 0;
+
+ if (aBrowsingContext->IsTop()) {
+ visitURIFlags |= IHistory::TOP_LEVEL;
+ }
+
+ if (aChannelRedirectFlags & nsIChannelEventSink::REDIRECT_TEMPORARY) {
+ visitURIFlags |= IHistory::REDIRECT_TEMPORARY;
+ } else if (aChannelRedirectFlags &
+ nsIChannelEventSink::REDIRECT_PERMANENT) {
+ visitURIFlags |= IHistory::REDIRECT_PERMANENT;
+ } else {
+ MOZ_ASSERT(!aChannelRedirectFlags,
+ "One of REDIRECT_TEMPORARY or REDIRECT_PERMANENT must be set "
+ "if any flags in aChannelRedirectFlags is set.");
+ }
+
+ if (aResponseStatus >= 300 && aResponseStatus < 400) {
+ visitURIFlags |= IHistory::REDIRECT_SOURCE;
+ if (aResponseStatus == 301 || aResponseStatus == 308) {
+ visitURIFlags |= IHistory::REDIRECT_SOURCE_PERMANENT;
+ }
+ }
+ // Errors 400-501 and 505 are considered unrecoverable, in the sense a
+ // simple retry attempt by the user is unlikely to solve them.
+ // 408 is special cased, since may actually indicate a temporary
+ // connection problem.
+ else if (aResponseStatus != 408 &&
+ ((aResponseStatus >= 400 && aResponseStatus <= 501) ||
+ aResponseStatus == 505)) {
+ visitURIFlags |= IHistory::UNRECOVERABLE_ERROR;
+ }
+
+ mozilla::Unused << history->VisitURI(aWidget, aURI, aPreviousURI,
+ visitURIFlags,
+ aBrowsingContext->BrowserId());
+ }
+}
+
+void nsDocShell::AddURIVisit(nsIURI* aURI, nsIURI* aPreviousURI,
+ uint32_t aChannelRedirectFlags,
+ uint32_t aResponseStatus) {
+ nsPIDOMWindowOuter* outer = GetWindow();
+ nsCOMPtr<nsIWidget> widget = widget::WidgetUtils::DOMWindowToWidget(outer);
+
+ InternalAddURIVisit(aURI, aPreviousURI, aChannelRedirectFlags,
+ aResponseStatus, mBrowsingContext, widget, mLoadType);
+}
+
+//*****************************************************************************
+// nsDocShell: Helper Routines
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShell::SetLoadType(uint32_t aLoadType) {
+ mLoadType = aLoadType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetLoadType(uint32_t* aLoadType) {
+ *aLoadType = mLoadType;
+ return NS_OK;
+}
+
+nsresult nsDocShell::ConfirmRepost(bool* aRepost) {
+ if (StaticPrefs::dom_confirm_repost_testing_always_accept()) {
+ *aRepost = true;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPromptCollection> prompter =
+ do_GetService("@mozilla.org/embedcomp/prompt-collection;1");
+ if (!prompter) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return prompter->ConfirmRepost(mBrowsingContext, aRepost);
+}
+
+nsresult nsDocShell::GetPromptAndStringBundle(nsIPrompt** aPrompt,
+ nsIStringBundle** aStringBundle) {
+ NS_ENSURE_SUCCESS(GetInterface(NS_GET_IID(nsIPrompt), (void**)aPrompt),
+ NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(stringBundleService, NS_ERROR_FAILURE);
+
+ NS_ENSURE_SUCCESS(
+ stringBundleService->CreateBundle(kAppstringsBundleURL, aStringBundle),
+ NS_ERROR_FAILURE);
+
+ return NS_OK;
+}
+
+nsIScrollableFrame* nsDocShell::GetRootScrollFrame() {
+ PresShell* presShell = GetPresShell();
+ NS_ENSURE_TRUE(presShell, nullptr);
+
+ return presShell->GetRootScrollFrameAsScrollable();
+}
+
+nsresult nsDocShell::EnsureScriptEnvironment() {
+ if (mScriptGlobal) {
+ return NS_OK;
+ }
+
+ if (mIsBeingDestroyed) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+#ifdef DEBUG
+ NS_ASSERTION(!mInEnsureScriptEnv,
+ "Infinite loop! Calling EnsureScriptEnvironment() from "
+ "within EnsureScriptEnvironment()!");
+
+ // Yeah, this isn't re-entrant safe, but that's ok since if we
+ // re-enter this method, we'll infinitely loop...
+ AutoRestore<bool> boolSetter(mInEnsureScriptEnv);
+ mInEnsureScriptEnv = true;
+#endif
+
+ nsCOMPtr<nsIWebBrowserChrome> browserChrome(do_GetInterface(mTreeOwner));
+ NS_ENSURE_TRUE(browserChrome, NS_ERROR_NOT_AVAILABLE);
+
+ uint32_t chromeFlags;
+ browserChrome->GetChromeFlags(&chromeFlags);
+
+ // If our window is modal and we're not opened as chrome, make
+ // this window a modal content window.
+ mScriptGlobal = nsGlobalWindowOuter::Create(this, mItemType == typeChrome);
+ MOZ_ASSERT(mScriptGlobal);
+
+ // Ensure the script object is set up to run script.
+ return mScriptGlobal->EnsureScriptEnvironment();
+}
+
+nsresult nsDocShell::EnsureEditorData() {
+ MOZ_ASSERT(!mIsBeingDestroyed);
+
+ bool openDocHasDetachedEditor = mOSHE && mOSHE->HasDetachedEditor();
+ if (!mEditorData && !mIsBeingDestroyed && !openDocHasDetachedEditor) {
+ // We shouldn't recreate the editor data if it already exists, or
+ // we're shutting down, or we already have a detached editor data
+ // stored in the session history. We should only have one editordata
+ // per docshell.
+ mEditorData = MakeUnique<nsDocShellEditorData>(this);
+ }
+
+ return mEditorData ? NS_OK : NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult nsDocShell::EnsureFind() {
+ if (!mFind) {
+ mFind = new nsWebBrowserFind();
+ }
+
+ // we promise that the nsIWebBrowserFind that we return has been set
+ // up to point to the focused, or content window, so we have to
+ // set that up each time.
+
+ nsIScriptGlobalObject* scriptGO = GetScriptGlobalObject();
+ NS_ENSURE_TRUE(scriptGO, NS_ERROR_UNEXPECTED);
+
+ // default to our window
+ nsCOMPtr<nsPIDOMWindowOuter> ourWindow = do_QueryInterface(scriptGO);
+ nsCOMPtr<nsPIDOMWindowOuter> windowToSearch;
+ nsFocusManager::GetFocusedDescendant(ourWindow,
+ nsFocusManager::eIncludeAllDescendants,
+ getter_AddRefs(windowToSearch));
+
+ nsCOMPtr<nsIWebBrowserFindInFrames> findInFrames = do_QueryInterface(mFind);
+ if (!findInFrames) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ nsresult rv = findInFrames->SetRootSearchFrame(ourWindow);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = findInFrames->SetCurrentSearchFrame(windowToSearch);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::IsBeingDestroyed(bool* aDoomed) {
+ NS_ENSURE_ARG(aDoomed);
+ *aDoomed = mIsBeingDestroyed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetIsExecutingOnLoadHandler(bool* aResult) {
+ NS_ENSURE_ARG(aResult);
+ *aResult = mIsExecutingOnLoadHandler;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetLayoutHistoryState(nsILayoutHistoryState** aLayoutHistoryState) {
+ nsCOMPtr<nsILayoutHistoryState> state;
+ if (mozilla::SessionHistoryInParent()) {
+ if (mActiveEntry) {
+ state = mActiveEntry->GetLayoutHistoryState();
+ }
+ } else {
+ if (mOSHE) {
+ state = mOSHE->GetLayoutHistoryState();
+ }
+ }
+ state.forget(aLayoutHistoryState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetLayoutHistoryState(nsILayoutHistoryState* aLayoutHistoryState) {
+ if (mOSHE) {
+ mOSHE->SetLayoutHistoryState(aLayoutHistoryState);
+ }
+ if (mActiveEntry) {
+ mActiveEntry->SetLayoutHistoryState(aLayoutHistoryState);
+ }
+ return NS_OK;
+}
+
+nsDocShell::InterfaceRequestorProxy::InterfaceRequestorProxy(
+ nsIInterfaceRequestor* aRequestor) {
+ if (aRequestor) {
+ mWeakPtr = do_GetWeakReference(aRequestor);
+ }
+}
+
+nsDocShell::InterfaceRequestorProxy::~InterfaceRequestorProxy() {
+ mWeakPtr = nullptr;
+}
+
+NS_IMPL_ISUPPORTS(nsDocShell::InterfaceRequestorProxy, nsIInterfaceRequestor)
+
+NS_IMETHODIMP
+nsDocShell::InterfaceRequestorProxy::GetInterface(const nsIID& aIID,
+ void** aSink) {
+ NS_ENSURE_ARG_POINTER(aSink);
+ nsCOMPtr<nsIInterfaceRequestor> ifReq = do_QueryReferent(mWeakPtr);
+ if (ifReq) {
+ return ifReq->GetInterface(aIID, aSink);
+ }
+ *aSink = nullptr;
+ return NS_NOINTERFACE;
+}
+
+//*****************************************************************************
+// nsDocShell::nsIAuthPromptProvider
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShell::GetAuthPrompt(uint32_t aPromptReason, const nsIID& aIID,
+ void** aResult) {
+ // a priority prompt request will override a false mAllowAuth setting
+ bool priorityPrompt = (aPromptReason == PROMPT_PROXY);
+
+ if (!mAllowAuth && !priorityPrompt) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // we're either allowing auth, or it's a proxy request
+ nsresult rv;
+ nsCOMPtr<nsIPromptFactory> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = EnsureScriptEnvironment();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the an auth prompter for our window so that the parenting
+ // of the dialogs works as it should when using tabs.
+
+ return wwatch->GetPrompt(mScriptGlobal, aIID,
+ reinterpret_cast<void**>(aResult));
+}
+
+//*****************************************************************************
+// nsDocShell::nsILoadContext
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShell::GetAssociatedWindow(mozIDOMWindowProxy** aWindow) {
+ CallGetInterface(this, aWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetTopWindow(mozIDOMWindowProxy** aWindow) {
+ return mBrowsingContext->GetTopWindow(aWindow);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetTopFrameElement(Element** aElement) {
+ return mBrowsingContext->GetTopFrameElement(aElement);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetUseTrackingProtection(bool* aUseTrackingProtection) {
+ return mBrowsingContext->GetUseTrackingProtection(aUseTrackingProtection);
+}
+
+NS_IMETHODIMP
+nsDocShell::SetUseTrackingProtection(bool aUseTrackingProtection) {
+ return mBrowsingContext->SetUseTrackingProtection(aUseTrackingProtection);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetIsContent(bool* aIsContent) {
+ *aIsContent = (mItemType == typeContent);
+ return NS_OK;
+}
+
+bool nsDocShell::IsOKToLoadURI(nsIURI* aURI) {
+ MOZ_ASSERT(aURI, "Must have a URI!");
+
+ if (!mFiredUnloadEvent) {
+ return true;
+ }
+
+ if (!mLoadingURI) {
+ return false;
+ }
+
+ bool isPrivateWin = false;
+ Document* doc = GetDocument();
+ if (doc) {
+ isPrivateWin =
+ doc->NodePrincipal()->OriginAttributesRef().mPrivateBrowsingId > 0;
+ }
+
+ nsCOMPtr<nsIScriptSecurityManager> secMan =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
+ return secMan && NS_SUCCEEDED(secMan->CheckSameOriginURI(
+ aURI, mLoadingURI, false, isPrivateWin));
+}
+
+//
+// Routines for selection and clipboard
+//
+nsresult nsDocShell::GetControllerForCommand(const char* aCommand,
+ nsIController** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = nullptr;
+
+ NS_ENSURE_TRUE(mScriptGlobal, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsPIWindowRoot> root = mScriptGlobal->GetTopWindowRoot();
+ NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);
+
+ return root->GetControllerForCommand(aCommand, false /* for any window */,
+ aResult);
+}
+
+NS_IMETHODIMP
+nsDocShell::IsCommandEnabled(const char* aCommand, bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIController> controller;
+ rv = GetControllerForCommand(aCommand, getter_AddRefs(controller));
+ if (controller) {
+ rv = controller->IsCommandEnabled(aCommand, aResult);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDocShell::DoCommand(const char* aCommand) {
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIController> controller;
+ rv = GetControllerForCommand(aCommand, getter_AddRefs(controller));
+ if (controller) {
+ rv = controller->DoCommand(aCommand);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDocShell::DoCommandWithParams(const char* aCommand,
+ nsICommandParams* aParams) {
+ nsCOMPtr<nsIController> controller;
+ nsresult rv = GetControllerForCommand(aCommand, getter_AddRefs(controller));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsICommandController> commandController =
+ do_QueryInterface(controller, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return commandController->DoCommandWithParams(aCommand, aParams);
+}
+
+nsresult nsDocShell::EnsureCommandHandler() {
+ if (!mCommandManager) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> domWindow = GetWindow()) {
+ mCommandManager = new nsCommandManager(domWindow);
+ }
+ }
+ return mCommandManager ? NS_OK : NS_ERROR_FAILURE;
+}
+
+// link handling
+
+class OnLinkClickEvent : public Runnable {
+ public:
+ OnLinkClickEvent(nsDocShell* aHandler, nsIContent* aContent,
+ nsDocShellLoadState* aLoadState, bool aNoOpenerImplied,
+ bool aIsTrusted, nsIPrincipal* aTriggeringPrincipal);
+
+ NS_IMETHOD Run() override {
+ AutoPopupStatePusher popupStatePusher(mPopupState);
+
+ // We need to set up an AutoJSAPI here for the following reason: When we
+ // do OnLinkClickSync we'll eventually end up in
+ // nsGlobalWindow::OpenInternal which only does popup blocking if
+ // !LegacyIsCallerChromeOrNativeCode(). So we need to fake things so that
+ // we don't look like native code as far as LegacyIsCallerNativeCode() is
+ // concerned.
+ AutoJSAPI jsapi;
+ if (mIsTrusted || jsapi.Init(mContent->OwnerDoc()->GetScopeObject())) {
+ mHandler->OnLinkClickSync(mContent, mLoadState, mNoOpenerImplied,
+ mTriggeringPrincipal);
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsDocShell> mHandler;
+ nsCOMPtr<nsIContent> mContent;
+ RefPtr<nsDocShellLoadState> mLoadState;
+ nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
+ PopupBlocker::PopupControlState mPopupState;
+ bool mNoOpenerImplied;
+ bool mIsTrusted;
+};
+
+OnLinkClickEvent::OnLinkClickEvent(nsDocShell* aHandler, nsIContent* aContent,
+ nsDocShellLoadState* aLoadState,
+ bool aNoOpenerImplied, bool aIsTrusted,
+ nsIPrincipal* aTriggeringPrincipal)
+ : mozilla::Runnable("OnLinkClickEvent"),
+ mHandler(aHandler),
+ mContent(aContent),
+ mLoadState(aLoadState),
+ mTriggeringPrincipal(aTriggeringPrincipal),
+ mPopupState(PopupBlocker::GetPopupControlState()),
+ mNoOpenerImplied(aNoOpenerImplied),
+ mIsTrusted(aIsTrusted) {}
+
+nsresult nsDocShell::OnLinkClick(
+ nsIContent* aContent, nsIURI* aURI, const nsAString& aTargetSpec,
+ const nsAString& aFileName, nsIInputStream* aPostDataStream,
+ nsIInputStream* aHeadersDataStream, bool aIsUserTriggered, bool aIsTrusted,
+ nsIPrincipal* aTriggeringPrincipal, nsIContentSecurityPolicy* aCsp) {
+#ifndef ANDROID
+ MOZ_ASSERT(aTriggeringPrincipal, "Need a valid triggeringPrincipal");
+#endif
+ NS_ASSERTION(NS_IsMainThread(), "wrong thread");
+
+ if (!IsNavigationAllowed() || !IsOKToLoadURI(aURI)) {
+ return NS_OK;
+ }
+
+ // On history navigation through Back/Forward buttons, don't execute
+ // automatic JavaScript redirection such as |anchorElement.click()| or
+ // |formElement.submit()|.
+ //
+ // XXX |formElement.submit()| bypasses this checkpoint because it calls
+ // nsDocShell::OnLinkClickSync(...) instead.
+ if (ShouldBlockLoadingForBackButton()) {
+ return NS_OK;
+ }
+
+ if (aContent->IsEditable()) {
+ return NS_OK;
+ }
+
+ Document* ownerDoc = aContent->OwnerDoc();
+ if (nsContentUtils::IsExternalProtocol(aURI)) {
+ ownerDoc->EnsureNotEnteringAndExitFullscreen();
+ }
+
+ bool noOpenerImplied = false;
+ nsAutoString target(aTargetSpec);
+ if (aFileName.IsVoid() &&
+ ShouldOpenInBlankTarget(aTargetSpec, aURI, aContent, aIsUserTriggered)) {
+ target = u"_blank";
+ if (!aTargetSpec.Equals(target)) {
+ noOpenerImplied = true;
+ }
+ }
+
+ RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(aURI);
+ loadState->SetTarget(target);
+ loadState->SetFileName(aFileName);
+ loadState->SetPostDataStream(aPostDataStream);
+ loadState->SetHeadersStream(aHeadersDataStream);
+ loadState->SetFirstParty(true);
+ loadState->SetTriggeringPrincipal(
+ aTriggeringPrincipal ? aTriggeringPrincipal : aContent->NodePrincipal());
+ loadState->SetPrincipalToInherit(aContent->NodePrincipal());
+ loadState->SetCsp(aCsp ? aCsp : aContent->GetCsp());
+ loadState->SetAllowFocusMove(UserActivation::IsHandlingUserInput());
+
+ nsCOMPtr<nsIRunnable> ev =
+ new OnLinkClickEvent(this, aContent, loadState, noOpenerImplied,
+ aIsTrusted, aTriggeringPrincipal);
+ return Dispatch(TaskCategory::UI, ev.forget());
+}
+
+bool nsDocShell::ShouldOpenInBlankTarget(const nsAString& aOriginalTarget,
+ nsIURI* aLinkURI, nsIContent* aContent,
+ bool aIsUserTriggered) {
+ if (net::SchemeIsJavascript(aLinkURI)) {
+ return false;
+ }
+
+ // External links from within app tabs should always open in new tabs
+ // instead of replacing the app tab's page (Bug 575561)
+ // nsIURI.host can throw for non-nsStandardURL nsIURIs. If we fail to
+ // get either host, just return false to use the original target.
+ nsAutoCString linkHost;
+ if (NS_FAILED(aLinkURI->GetHost(linkHost))) {
+ return false;
+ }
+
+ // The targetTopLevelLinkClicksToBlank property on BrowsingContext allows
+ // privileged code to change the default targeting behaviour. In particular,
+ // if a user-initiated link click for the (or targetting the) top-level frame
+ // is detected, we default the target to "_blank" to give it a new
+ // top-level BrowsingContext.
+ if (mBrowsingContext->TargetTopLevelLinkClicksToBlank() && aIsUserTriggered &&
+ ((aOriginalTarget.IsEmpty() && mBrowsingContext->IsTop()) ||
+ aOriginalTarget == u"_top"_ns)) {
+ return true;
+ }
+
+ // Don't modify non-default targets.
+ if (!aOriginalTarget.IsEmpty()) {
+ return false;
+ }
+
+ // Only check targets that are in extension panels or app tabs.
+ // (isAppTab will be false for app tab subframes).
+ nsString mmGroup = mBrowsingContext->Top()->GetMessageManagerGroup();
+ if (!mmGroup.EqualsLiteral("webext-browsers") &&
+ !mBrowsingContext->IsAppTab()) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> docURI = aContent->OwnerDoc()->GetDocumentURIObject();
+ if (!docURI) {
+ return false;
+ }
+
+ nsAutoCString docHost;
+ if (NS_FAILED(docURI->GetHost(docHost))) {
+ return false;
+ }
+
+ if (linkHost.Equals(docHost)) {
+ return false;
+ }
+
+ // Special case: ignore "www" prefix if it is part of host string
+ return linkHost.Length() < docHost.Length()
+ ? !docHost.Equals("www."_ns + linkHost)
+ : !linkHost.Equals("www."_ns + docHost);
+}
+
+static bool ElementCanHaveNoopener(nsIContent* aContent) {
+ // Make sure we are dealing with either an <A>, <AREA>, or <FORM> element in
+ // the HTML, XHTML, or SVG namespace.
+ return aContent->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area,
+ nsGkAtoms::form) ||
+ aContent->IsSVGElement(nsGkAtoms::a);
+}
+
+nsresult nsDocShell::OnLinkClickSync(nsIContent* aContent,
+ nsDocShellLoadState* aLoadState,
+ bool aNoOpenerImplied,
+ nsIPrincipal* aTriggeringPrincipal) {
+ if (!IsNavigationAllowed() || !IsOKToLoadURI(aLoadState->URI())) {
+ return NS_OK;
+ }
+
+ // XXX When the linking node was HTMLFormElement, it is synchronous event.
+ // That is, the caller of this method is not |OnLinkClickEvent::Run()|
+ // but |HTMLFormElement::SubmitSubmission(...)|.
+ if (aContent->IsHTMLElement(nsGkAtoms::form) &&
+ ShouldBlockLoadingForBackButton()) {
+ return NS_OK;
+ }
+
+ if (aContent->IsEditable()) {
+ return NS_OK;
+ }
+
+ // if the triggeringPrincipal is not passed explicitly, then we
+ // fall back to using doc->NodePrincipal() as the triggeringPrincipal.
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal =
+ aTriggeringPrincipal ? aTriggeringPrincipal : aContent->NodePrincipal();
+
+ {
+ // defer to an external protocol handler if necessary...
+ nsCOMPtr<nsIExternalProtocolService> extProtService =
+ do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID);
+ if (extProtService) {
+ nsAutoCString scheme;
+ aLoadState->URI()->GetScheme(scheme);
+ if (!scheme.IsEmpty()) {
+ // if the URL scheme does not correspond to an exposed protocol, then
+ // we need to hand this link click over to the external protocol
+ // handler.
+ bool isExposed;
+ nsresult rv =
+ extProtService->IsExposedProtocol(scheme.get(), &isExposed);
+ if (NS_SUCCEEDED(rv) && !isExposed) {
+ return extProtService->LoadURI(
+ aLoadState->URI(), triggeringPrincipal, nullptr, mBrowsingContext,
+ /* aTriggeredExternally */
+ false,
+ /* aHasValidUserGestureActivation */
+ aContent->OwnerDoc()->HasValidTransientUserGestureActivation());
+ }
+ }
+ }
+ }
+ uint32_t triggeringSandboxFlags = 0;
+ if (mBrowsingContext) {
+ triggeringSandboxFlags = aContent->OwnerDoc()->GetSandboxFlags();
+ }
+
+ uint32_t flags = INTERNAL_LOAD_FLAGS_NONE;
+ bool elementCanHaveNoopener = ElementCanHaveNoopener(aContent);
+ bool triggeringPrincipalIsSystemPrincipal =
+ aLoadState->TriggeringPrincipal()->IsSystemPrincipal();
+ if (elementCanHaveNoopener) {
+ MOZ_ASSERT(aContent->IsHTMLElement() || aContent->IsSVGElement());
+ nsAutoString relString;
+ aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::rel,
+ relString);
+ nsWhitespaceTokenizerTemplate<nsContentUtils::IsHTMLWhitespace> tok(
+ relString);
+
+ bool targetBlank = aLoadState->Target().LowerCaseEqualsLiteral("_blank");
+ bool explicitOpenerSet = false;
+
+ // The opener behaviour follows a hierarchy, such that if a higher
+ // priority behaviour is specified, it always takes priority. That
+ // priority is currently: norefrerer > noopener > opener > default
+
+ while (tok.hasMoreTokens()) {
+ const nsAString& token = tok.nextToken();
+ if (token.LowerCaseEqualsLiteral("noreferrer")) {
+ flags |= INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER |
+ INTERNAL_LOAD_FLAGS_NO_OPENER;
+ // noreferrer cannot be overwritten by a 'rel=opener'.
+ explicitOpenerSet = true;
+ break;
+ }
+
+ if (token.LowerCaseEqualsLiteral("noopener")) {
+ flags |= INTERNAL_LOAD_FLAGS_NO_OPENER;
+ explicitOpenerSet = true;
+ }
+
+ if (targetBlank && StaticPrefs::dom_targetBlankNoOpener_enabled() &&
+ token.LowerCaseEqualsLiteral("opener") && !explicitOpenerSet) {
+ explicitOpenerSet = true;
+ }
+ }
+
+ if (targetBlank && StaticPrefs::dom_targetBlankNoOpener_enabled() &&
+ !explicitOpenerSet && !triggeringPrincipalIsSystemPrincipal) {
+ flags |= INTERNAL_LOAD_FLAGS_NO_OPENER;
+ }
+
+ if (aNoOpenerImplied) {
+ flags |= INTERNAL_LOAD_FLAGS_NO_OPENER;
+ }
+ }
+
+ // Get the owner document of the link that was clicked, this will be
+ // the document that the link is in, or the last document that the
+ // link was in. From that document, we'll get the URI to use as the
+ // referrer, since the current URI in this docshell may be a
+ // new document that we're in the process of loading.
+ RefPtr<Document> referrerDoc = aContent->OwnerDoc();
+
+ // Now check that the referrerDoc's inner window is the current inner
+ // window for mScriptGlobal. If it's not, then we don't want to
+ // follow this link.
+ nsPIDOMWindowInner* referrerInner = referrerDoc->GetInnerWindow();
+ if (!mScriptGlobal || !referrerInner ||
+ mScriptGlobal->GetCurrentInnerWindow() != referrerInner) {
+ // We're no longer the current inner window
+ return NS_OK;
+ }
+
+ // referrer could be null here in some odd cases, but that's ok,
+ // we'll just load the link w/o sending a referrer in those cases.
+
+ // If this is an anchor element, grab its type property to use as a hint
+ nsAutoString typeHint;
+ RefPtr<HTMLAnchorElement> anchor = HTMLAnchorElement::FromNode(aContent);
+ if (anchor) {
+ anchor->GetType(typeHint);
+ NS_ConvertUTF16toUTF8 utf8Hint(typeHint);
+ nsAutoCString type, dummy;
+ NS_ParseRequestContentType(utf8Hint, type, dummy);
+ CopyUTF8toUTF16(type, typeHint);
+ }
+
+ // Link click (or form submission) can be triggered inside an onload
+ // handler, and we don't want to add history entry in this case.
+ bool inOnLoadHandler = false;
+ GetIsExecutingOnLoadHandler(&inOnLoadHandler);
+ uint32_t loadType = inOnLoadHandler ? LOAD_NORMAL_REPLACE : LOAD_LINK;
+
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ elementCanHaveNoopener ? new ReferrerInfo(*aContent->AsElement())
+ : new ReferrerInfo(*referrerDoc);
+ RefPtr<WindowContext> context = mBrowsingContext->GetCurrentWindowContext();
+
+ aLoadState->SetTriggeringSandboxFlags(triggeringSandboxFlags);
+ aLoadState->SetReferrerInfo(referrerInfo);
+ aLoadState->SetInternalLoadFlags(flags);
+ aLoadState->SetTypeHint(NS_ConvertUTF16toUTF8(typeHint));
+ aLoadState->SetLoadType(loadType);
+ aLoadState->SetSourceBrowsingContext(mBrowsingContext);
+ aLoadState->SetHasValidUserGestureActivation(
+ context && context->HasValidTransientUserGestureActivation());
+
+ nsresult rv = InternalLoad(aLoadState);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsPingListener::DispatchPings(this, aContent, aLoadState->URI(),
+ referrerInfo);
+ }
+
+ return rv;
+}
+
+nsresult nsDocShell::OnOverLink(nsIContent* aContent, nsIURI* aURI,
+ const nsAString& aTargetSpec) {
+ if (aContent->IsEditable()) {
+ return NS_OK;
+ }
+
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIWebBrowserChrome> browserChrome = do_GetInterface(mTreeOwner);
+ if (!browserChrome) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIURI> exposableURI = nsIOService::CreateExposableURI(aURI);
+ nsAutoCString spec;
+ rv = exposableURI->GetDisplaySpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ConvertUTF8toUTF16 uStr(spec);
+
+ PredictorPredict(aURI, mCurrentURI, nsINetworkPredictor::PREDICT_LINK,
+ aContent->NodePrincipal()->OriginAttributesRef(), nullptr);
+
+ rv = browserChrome->SetLinkStatus(uStr);
+ return rv;
+}
+
+nsresult nsDocShell::OnLeaveLink() {
+ nsCOMPtr<nsIWebBrowserChrome> browserChrome(do_GetInterface(mTreeOwner));
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (browserChrome) {
+ rv = browserChrome->SetLinkStatus(u""_ns);
+ }
+ return rv;
+}
+
+bool nsDocShell::ShouldBlockLoadingForBackButton() {
+ if (!(mLoadType & LOAD_CMD_HISTORY) ||
+ UserActivation::IsHandlingUserInput() ||
+ !Preferences::GetBool("accessibility.blockjsredirection")) {
+ return false;
+ }
+
+ bool canGoForward = false;
+ GetCanGoForward(&canGoForward);
+ return canGoForward;
+}
+
+bool nsDocShell::PluginsAllowedInCurrentDoc() {
+ if (!mContentViewer) {
+ return false;
+ }
+
+ Document* doc = mContentViewer->GetDocument();
+ if (!doc) {
+ return false;
+ }
+
+ return doc->GetAllowPlugins();
+}
+
+//----------------------------------------------------------------------
+// Web Shell Services API
+
+// This functions is only called when a new charset is detected in loading a
+// document.
+nsresult nsDocShell::CharsetChangeReloadDocument(
+ mozilla::NotNull<const mozilla::Encoding*> aEncoding, int32_t aSource) {
+ // XXX hack. keep the aCharset and aSource wait to pick it up
+ nsCOMPtr<nsIContentViewer> cv;
+ NS_ENSURE_SUCCESS(GetContentViewer(getter_AddRefs(cv)), NS_ERROR_FAILURE);
+ if (cv) {
+ int32_t source;
+ Unused << cv->GetReloadEncodingAndSource(&source);
+ if (aSource > source) {
+ cv->SetReloadEncodingAndSource(aEncoding, aSource);
+ if (eCharsetReloadRequested != mCharsetReloadState) {
+ mCharsetReloadState = eCharsetReloadRequested;
+ switch (mLoadType) {
+ case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
+ return Reload(LOAD_FLAGS_CHARSET_CHANGE | LOAD_FLAGS_BYPASS_CACHE |
+ LOAD_FLAGS_BYPASS_PROXY);
+ case LOAD_RELOAD_BYPASS_CACHE:
+ return Reload(LOAD_FLAGS_CHARSET_CHANGE | LOAD_FLAGS_BYPASS_CACHE);
+ default:
+ return Reload(LOAD_FLAGS_CHARSET_CHANGE);
+ }
+ }
+ }
+ }
+ // return failure if this request is not accepted due to mCharsetReloadState
+ return NS_ERROR_DOCSHELL_REQUEST_REJECTED;
+}
+
+nsresult nsDocShell::CharsetChangeStopDocumentLoad() {
+ if (eCharsetReloadRequested != mCharsetReloadState) {
+ Stop(nsIWebNavigation::STOP_ALL);
+ return NS_OK;
+ }
+ // return failer if this request is not accepted due to mCharsetReloadState
+ return NS_ERROR_DOCSHELL_REQUEST_REJECTED;
+}
+
+NS_IMETHODIMP nsDocShell::ExitPrintPreview() {
+#if NS_PRINT_PREVIEW
+ nsCOMPtr<nsIWebBrowserPrint> viewer = do_QueryInterface(mContentViewer);
+ return viewer->ExitPrintPreview();
+#else
+ return NS_OK;
+#endif
+}
+
+/* [infallible] */
+NS_IMETHODIMP nsDocShell::GetIsTopLevelContentDocShell(
+ bool* aIsTopLevelContentDocShell) {
+ *aIsTopLevelContentDocShell = false;
+
+ if (mItemType == typeContent) {
+ *aIsTopLevelContentDocShell = mBrowsingContext->IsTopContent();
+ }
+
+ return NS_OK;
+}
+
+// Implements nsILoadContext.originAttributes
+NS_IMETHODIMP
+nsDocShell::GetScriptableOriginAttributes(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aVal) {
+ return mBrowsingContext->GetScriptableOriginAttributes(aCx, aVal);
+}
+
+// Implements nsIDocShell.GetOriginAttributes()
+NS_IMETHODIMP
+nsDocShell::GetOriginAttributes(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aVal) {
+ return mBrowsingContext->GetScriptableOriginAttributes(aCx, aVal);
+}
+
+bool nsDocShell::ServiceWorkerAllowedToControlWindow(nsIPrincipal* aPrincipal,
+ nsIURI* aURI) {
+ MOZ_ASSERT(aPrincipal);
+ MOZ_ASSERT(aURI);
+
+ if (UsePrivateBrowsing() || mBrowsingContext->GetSandboxFlags()) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> parent;
+ GetInProcessSameTypeParent(getter_AddRefs(parent));
+ nsPIDOMWindowOuter* parentOuter = parent ? parent->GetWindow() : nullptr;
+ nsPIDOMWindowInner* parentInner =
+ parentOuter ? parentOuter->GetCurrentInnerWindow() : nullptr;
+
+ StorageAccess storage =
+ StorageAllowedForNewWindow(aPrincipal, aURI, parentInner);
+
+ // If the partitioned service worker is enabled, service worker is allowed to
+ // control the window if partition is enabled.
+ if (StaticPrefs::privacy_partition_serviceWorkers() && parentInner) {
+ RefPtr<Document> doc = parentInner->GetExtantDoc();
+
+ if (doc && StoragePartitioningEnabled(storage, doc->CookieJarSettings())) {
+ return true;
+ }
+ }
+
+ return storage == StorageAccess::eAllow;
+}
+
+nsresult nsDocShell::SetOriginAttributes(const OriginAttributes& aAttrs) {
+ MOZ_ASSERT(!mIsBeingDestroyed);
+ return mBrowsingContext->SetOriginAttributes(aAttrs);
+}
+
+NS_IMETHODIMP
+nsDocShell::ResumeRedirectedLoad(uint64_t aIdentifier, int32_t aHistoryIndex) {
+ RefPtr<nsDocShell> self = this;
+ RefPtr<ChildProcessChannelListener> cpcl =
+ ChildProcessChannelListener::GetSingleton();
+
+ // Call into InternalLoad with the pending channel when it is received.
+ cpcl->RegisterCallback(
+ aIdentifier, [self, aHistoryIndex](
+ nsDocShellLoadState* aLoadState,
+ nsTArray<Endpoint<extensions::PStreamFilterParent>>&&
+ aStreamFilterEndpoints,
+ nsDOMNavigationTiming* aTiming) {
+ MOZ_ASSERT(aLoadState->GetPendingRedirectedChannel());
+ if (NS_WARN_IF(self->mIsBeingDestroyed)) {
+ aLoadState->GetPendingRedirectedChannel()->CancelWithReason(
+ NS_BINDING_ABORTED, "nsDocShell::mIsBeingDestroyed"_ns);
+ return NS_BINDING_ABORTED;
+ }
+
+ self->mLoadType = aLoadState->LoadType();
+ nsCOMPtr<nsIURI> previousURI;
+ uint32_t previousFlags = 0;
+ ExtractLastVisit(aLoadState->GetPendingRedirectedChannel(),
+ getter_AddRefs(previousURI), &previousFlags);
+ self->SaveLastVisit(aLoadState->GetPendingRedirectedChannel(),
+ previousURI, previousFlags);
+
+ if (aTiming) {
+ self->mTiming = new nsDOMNavigationTiming(self, aTiming);
+ self->mBlankTiming = false;
+ }
+
+ // If we're performing a history load, locate the correct history entry,
+ // and set the relevant bits on our loadState.
+ if (aHistoryIndex >= 0 && self->GetSessionHistory() &&
+ !mozilla::SessionHistoryInParent()) {
+ nsCOMPtr<nsISHistory> legacySHistory =
+ self->GetSessionHistory()->LegacySHistory();
+
+ nsCOMPtr<nsISHEntry> entry;
+ nsresult rv = legacySHistory->GetEntryAtIndex(aHistoryIndex,
+ getter_AddRefs(entry));
+ if (NS_SUCCEEDED(rv)) {
+ legacySHistory->InternalSetRequestedIndex(aHistoryIndex);
+ aLoadState->SetLoadType(LOAD_HISTORY);
+ aLoadState->SetSHEntry(entry);
+ }
+ }
+
+ self->InternalLoad(aLoadState);
+
+ if (aLoadState->GetOriginalURIString().isSome()) {
+ // Save URI string in case it's needed later when
+ // sending to search engine service in EndPageLoad()
+ self->mOriginalUriString = *aLoadState->GetOriginalURIString();
+ }
+
+ for (auto& endpoint : aStreamFilterEndpoints) {
+ extensions::StreamFilterParent::Attach(
+ aLoadState->GetPendingRedirectedChannel(), std::move(endpoint));
+ }
+
+ // If the channel isn't pending, then it means that InternalLoad
+ // never connected it, and we shouldn't try to continue. This
+ // can happen even if InternalLoad returned NS_OK.
+ bool pending = false;
+ aLoadState->GetPendingRedirectedChannel()->IsPending(&pending);
+ NS_ASSERTION(pending, "We should have connected the pending channel!");
+ if (!pending) {
+ return NS_BINDING_ABORTED;
+ }
+ return NS_OK;
+ });
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::SetOriginAttributes(JS::Handle<JS::Value> aOriginAttributes,
+ JSContext* aCx) {
+ OriginAttributes attrs;
+ if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return SetOriginAttributes(attrs);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetAsyncPanZoomEnabled(bool* aOut) {
+ if (PresShell* presShell = GetPresShell()) {
+ *aOut = presShell->AsyncPanZoomEnabled();
+ return NS_OK;
+ }
+
+ // If we don't have a presShell, fall back to the default platform value of
+ // whether or not APZ is enabled.
+ *aOut = gfxPlatform::AsyncPanZoomEnabled();
+ return NS_OK;
+}
+
+bool nsDocShell::HasUnloadedParent() {
+ for (WindowContext* wc = GetBrowsingContext()->GetParentWindowContext(); wc;
+ wc = wc->GetParentWindowContext()) {
+ if (!wc->IsCurrent() || wc->IsDiscarded() ||
+ wc->GetBrowsingContext()->IsDiscarded()) {
+ // If a parent is OOP and the parent WindowContext is no
+ // longer current, we can assume the parent was unloaded.
+ return true;
+ }
+
+ if (wc->GetBrowsingContext()->IsInProcess() &&
+ (!wc->GetBrowsingContext()->GetDocShell() ||
+ wc->GetBrowsingContext()->GetDocShell()->GetIsInUnload())) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* static */
+bool nsDocShell::ShouldUpdateGlobalHistory(uint32_t aLoadType) {
+ return !(aLoadType == LOAD_BYPASS_HISTORY || aLoadType == LOAD_ERROR_PAGE ||
+ aLoadType & LOAD_CMD_HISTORY);
+}
+
+void nsDocShell::UpdateGlobalHistoryTitle(nsIURI* aURI) {
+ if (!mBrowsingContext->GetUseGlobalHistory() || UsePrivateBrowsing()) {
+ return;
+ }
+
+ // Global history is interested into sub-frame visits only for link-coloring
+ // purposes, thus title updates are skipped for those.
+ //
+ // Moreover, some iframe documents (such as the ones created via
+ // document.open()) inherit the document uri of the caller, which would cause
+ // us to override a previously set page title with one from the subframe.
+ if (IsSubframe()) {
+ return;
+ }
+
+ if (nsCOMPtr<IHistory> history = components::History::Service()) {
+ history->SetURITitle(aURI, mTitle);
+ }
+}
+
+bool nsDocShell::IsInvisible() { return mInvisible; }
+
+void nsDocShell::SetInvisible(bool aInvisible) { mInvisible = aInvisible; }
+
+// The caller owns |aAsyncCause| here.
+void nsDocShell::NotifyJSRunToCompletionStart(const char* aReason,
+ const nsAString& aFunctionName,
+ const nsAString& aFilename,
+ const uint32_t aLineNumber,
+ JS::Handle<JS::Value> aAsyncStack,
+ const char* aAsyncCause) {
+ // If first start, mark interval start.
+ if (mJSRunToCompletionDepth == 0 && TimelineConsumers::HasConsumer(this)) {
+ TimelineConsumers::AddMarkerForDocShell(
+ this, mozilla::MakeUnique<JavascriptTimelineMarker>(
+ aReason, aFunctionName, aFilename, aLineNumber,
+ MarkerTracingType::START, aAsyncStack, aAsyncCause));
+ }
+
+ mJSRunToCompletionDepth++;
+}
+
+void nsDocShell::NotifyJSRunToCompletionStop() {
+ mJSRunToCompletionDepth--;
+
+ // If last stop, mark interval end.
+ if (mJSRunToCompletionDepth == 0 && TimelineConsumers::HasConsumer(this)) {
+ TimelineConsumers::AddMarkerForDocShell(this, "Javascript",
+ MarkerTracingType::END);
+ }
+}
+
+/* static */
+void nsDocShell::MaybeNotifyKeywordSearchLoading(const nsString& aProvider,
+ const nsString& aKeyword) {
+ if (aProvider.IsEmpty()) {
+ return;
+ }
+ nsresult rv;
+ nsCOMPtr<nsISupportsString> isupportsString =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ rv = isupportsString->SetData(aProvider);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService();
+ if (obsSvc) {
+ // Note that "keyword-search" refers to a search via the url
+ // bar, not a bookmarks keyword search.
+ obsSvc->NotifyObservers(isupportsString, "keyword-search", aKeyword.get());
+ }
+}
+
+NS_IMETHODIMP
+nsDocShell::ShouldPrepareForIntercept(nsIURI* aURI, nsIChannel* aChannel,
+ bool* aShouldIntercept) {
+ return mInterceptController->ShouldPrepareForIntercept(aURI, aChannel,
+ aShouldIntercept);
+}
+
+NS_IMETHODIMP
+nsDocShell::ChannelIntercepted(nsIInterceptedChannel* aChannel) {
+ return mInterceptController->ChannelIntercepted(aChannel);
+}
+
+bool nsDocShell::InFrameSwap() {
+ RefPtr<nsDocShell> shell = this;
+ do {
+ if (shell->mInFrameSwap) {
+ return true;
+ }
+ shell = shell->GetInProcessParentDocshell();
+ } while (shell);
+ return false;
+}
+
+UniquePtr<ClientSource> nsDocShell::TakeInitialClientSource() {
+ return std::move(mInitialClientSource);
+}
+
+NS_IMETHODIMP
+nsDocShell::GetEditingSession(nsIEditingSession** aEditSession) {
+ if (!NS_SUCCEEDED(EnsureEditorData())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aEditSession = do_AddRef(mEditorData->GetEditingSession()).take();
+ return *aEditSession ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetScriptableBrowserChild(nsIBrowserChild** aBrowserChild) {
+ *aBrowserChild = GetBrowserChild().take();
+ return *aBrowserChild ? NS_OK : NS_ERROR_FAILURE;
+}
+
+already_AddRefed<nsIBrowserChild> nsDocShell::GetBrowserChild() {
+ nsCOMPtr<nsIBrowserChild> tc = do_QueryReferent(mBrowserChild);
+ return tc.forget();
+}
+
+nsCommandManager* nsDocShell::GetCommandManager() {
+ NS_ENSURE_SUCCESS(EnsureCommandHandler(), nullptr);
+ return mCommandManager;
+}
+
+NS_IMETHODIMP_(void)
+nsDocShell::GetOriginAttributes(mozilla::OriginAttributes& aAttrs) {
+ mBrowsingContext->GetOriginAttributes(aAttrs);
+}
+
+HTMLEditor* nsIDocShell::GetHTMLEditor() {
+ nsDocShell* docShell = static_cast<nsDocShell*>(this);
+ return docShell->GetHTMLEditorInternal();
+}
+
+nsresult nsIDocShell::SetHTMLEditor(HTMLEditor* aHTMLEditor) {
+ nsDocShell* docShell = static_cast<nsDocShell*>(this);
+ return docShell->SetHTMLEditorInternal(aHTMLEditor);
+}
+
+#define MATRIX_LENGTH 20
+
+NS_IMETHODIMP
+nsDocShell::SetColorMatrix(const nsTArray<float>& aMatrix) {
+ if (aMatrix.Length() == MATRIX_LENGTH) {
+ mColorMatrix.reset(new gfx::Matrix5x4());
+ static_assert(
+ MATRIX_LENGTH * sizeof(float) == sizeof(mColorMatrix->components),
+ "Size mismatch for our memcpy");
+ memcpy(mColorMatrix->components, aMatrix.Elements(),
+ sizeof(mColorMatrix->components));
+ } else if (aMatrix.Length() == 0) {
+ mColorMatrix.reset();
+ } else {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ PresShell* presShell = GetPresShell();
+ if (!presShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsIFrame* frame = presShell->GetRootFrame();
+ if (!frame) {
+ return NS_ERROR_FAILURE;
+ }
+
+ frame->SchedulePaint();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShell::GetColorMatrix(nsTArray<float>& aMatrix) {
+ if (mColorMatrix) {
+ aMatrix.SetLength(MATRIX_LENGTH);
+ static_assert(
+ MATRIX_LENGTH * sizeof(float) == sizeof(mColorMatrix->components),
+ "Size mismatch for our memcpy");
+ memcpy(aMatrix.Elements(), mColorMatrix->components,
+ MATRIX_LENGTH * sizeof(float));
+ }
+
+ return NS_OK;
+}
+
+#undef MATRIX_LENGTH
+
+NS_IMETHODIMP
+nsDocShell::GetIsForceReloading(bool* aForceReload) {
+ *aForceReload = IsForceReloading();
+ return NS_OK;
+}
+
+bool nsDocShell::IsForceReloading() { return IsForceReloadType(mLoadType); }
+
+NS_IMETHODIMP
+nsDocShell::GetBrowsingContextXPCOM(BrowsingContext** aBrowsingContext) {
+ *aBrowsingContext = do_AddRef(mBrowsingContext).take();
+ return NS_OK;
+}
+
+BrowsingContext* nsDocShell::GetBrowsingContext() { return mBrowsingContext; }
+
+bool nsDocShell::GetIsAttemptingToNavigate() {
+ // XXXbz the document.open spec says to abort even if there's just a
+ // queued navigation task, sort of. It's not clear whether browsers
+ // actually do that, and we didn't use to do it, so for now let's
+ // not do that.
+ // https://github.com/whatwg/html/issues/3447 tracks the spec side of this.
+ if (mDocumentRequest) {
+ // There's definitely a navigation in progress.
+ return true;
+ }
+
+ // javascript: channels have slightly weird behavior: they're LOAD_BACKGROUND
+ // until the script runs, which means they're not sending loadgroup
+ // notifications and hence not getting set as mDocumentRequest. Look through
+ // our loadgroup for document-level javascript: loads.
+ if (!mLoadGroup) {
+ return false;
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> requests;
+ mLoadGroup->GetRequests(getter_AddRefs(requests));
+ bool hasMore = false;
+ while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> elem;
+ requests->GetNext(getter_AddRefs(elem));
+ nsCOMPtr<nsIScriptChannel> scriptChannel(do_QueryInterface(elem));
+ if (!scriptChannel) {
+ continue;
+ }
+
+ if (scriptChannel->GetIsDocumentLoad()) {
+ // This is a javascript: load that might lead to a new document,
+ // hence a navigation.
+ return true;
+ }
+ }
+
+ return mCheckingSessionHistory;
+}
+
+void nsDocShell::SetLoadingSessionHistoryInfo(
+ const mozilla::dom::LoadingSessionHistoryInfo& aLoadingInfo,
+ bool aNeedToReportActiveAfterLoadingBecomesActive) {
+ // FIXME Would like to assert this, but can't yet.
+ // MOZ_ASSERT(!mLoadingEntry);
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("Setting the loading entry on nsDocShell %p to %s", this,
+ aLoadingInfo.mInfo.GetURI()->GetSpecOrDefault().get()));
+ mLoadingEntry = MakeUnique<LoadingSessionHistoryInfo>(aLoadingInfo);
+ mNeedToReportActiveAfterLoadingBecomesActive =
+ aNeedToReportActiveAfterLoadingBecomesActive;
+}
+
+void nsDocShell::MoveLoadingToActiveEntry(bool aPersist, bool aExpired,
+ uint32_t aCacheKey,
+ nsIURI* aPreviousURI) {
+ MOZ_ASSERT(mozilla::SessionHistoryInParent());
+
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("nsDocShell %p MoveLoadingToActiveEntry", this));
+
+ UniquePtr<SessionHistoryInfo> previousActiveEntry(mActiveEntry.release());
+ mozilla::UniquePtr<mozilla::dom::LoadingSessionHistoryInfo> loadingEntry;
+ mActiveEntryIsLoadingFromSessionHistory =
+ mLoadingEntry && mLoadingEntry->mLoadIsFromSessionHistory;
+ if (mLoadingEntry) {
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("Moving the loading entry to the active entry on nsDocShell %p "
+ "to %s",
+ this, mLoadingEntry->mInfo.GetURI()->GetSpecOrDefault().get()));
+ mActiveEntry = MakeUnique<SessionHistoryInfo>(mLoadingEntry->mInfo);
+ mLoadingEntry.swap(loadingEntry);
+ if (!mActiveEntryIsLoadingFromSessionHistory) {
+ if (mNeedToReportActiveAfterLoadingBecomesActive) {
+ // Needed to pass various history length WPTs.
+ mBrowsingContext->SetActiveSessionHistoryEntry(
+ mozilla::Nothing(), mActiveEntry.get(), mLoadType,
+ /* aUpdatedCacheKey = */ 0, false);
+ }
+ mBrowsingContext->IncrementHistoryEntryCountForBrowsingContext();
+ }
+ }
+ mNeedToReportActiveAfterLoadingBecomesActive = false;
+
+ if (mActiveEntry) {
+ if (aCacheKey != 0) {
+ mActiveEntry->SetCacheKey(aCacheKey);
+ }
+ MOZ_ASSERT(loadingEntry);
+ uint32_t loadType =
+ mLoadType == LOAD_ERROR_PAGE ? mFailedLoadType : mLoadType;
+
+ if (loadingEntry->mLoadId != UINT64_MAX) {
+ // We're passing in mCurrentURI, which could be null. SessionHistoryCommit
+ // does require a non-null uri if this is for a refresh load of the same
+ // URI, but in that case mCurrentURI won't be null here.
+ mBrowsingContext->SessionHistoryCommit(
+ *loadingEntry, loadType, aPreviousURI, previousActiveEntry.get(),
+ aPersist, false, aExpired, aCacheKey);
+ }
+ }
+}
+
+static bool IsFaviconLoad(nsIRequest* aRequest) {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ if (!channel) {
+ return false;
+ }
+
+ nsCOMPtr<nsILoadInfo> li = channel->LoadInfo();
+ return li && li->InternalContentPolicyType() ==
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON;
+}
+
+void nsDocShell::RecordSingleChannelId(bool aStartRequest,
+ nsIRequest* aRequest) {
+ // Ignore favicon loads, they don't need to block caching.
+ if (IsFaviconLoad(aRequest)) {
+ return;
+ }
+
+ MOZ_ASSERT_IF(!aStartRequest, mRequestForBlockingFromBFCacheCount > 0);
+
+ mRequestForBlockingFromBFCacheCount += aStartRequest ? 1 : -1;
+
+ if (mBrowsingContext->GetCurrentWindowContext()) {
+ // We have three states: no request, one request with an id and
+ // eiher one request without an id or multiple requests. Nothing() is no
+ // request, Some(non-zero) is one request with an id and Some(0) is one
+ // request without an id or multiple requests.
+ Maybe<uint64_t> singleChannelId;
+ if (mRequestForBlockingFromBFCacheCount > 1) {
+ singleChannelId = Some(0);
+ } else if (mRequestForBlockingFromBFCacheCount == 1) {
+ nsCOMPtr<nsIIdentChannel> identChannel;
+ if (aStartRequest) {
+ identChannel = do_QueryInterface(aRequest);
+ } else {
+ // aChannel is the channel that's being removed, but we need to check if
+ // the remaining channel in the loadgroup has an id.
+ nsCOMPtr<nsISimpleEnumerator> requests;
+ mLoadGroup->GetRequests(getter_AddRefs(requests));
+ for (const auto& request : SimpleEnumerator<nsIRequest>(requests)) {
+ if (!IsFaviconLoad(request) &&
+ !!(identChannel = do_QueryInterface(request))) {
+ break;
+ }
+ }
+ }
+
+ if (identChannel) {
+ singleChannelId = Some(identChannel->ChannelId());
+ } else {
+ singleChannelId = Some(0);
+ }
+ } else {
+ MOZ_ASSERT(mRequestForBlockingFromBFCacheCount == 0);
+ singleChannelId = Nothing();
+ }
+
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Verbose))) {
+ nsAutoCString uri("[no uri]");
+ if (mCurrentURI) {
+ uri = mCurrentURI->GetSpecOrDefault();
+ }
+ if (singleChannelId.isNothing()) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Verbose,
+ ("Loadgroup for %s doesn't have any requests relevant for "
+ "blocking BFCache",
+ uri.get()));
+ } else if (singleChannelId.value() == 0) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Verbose,
+ ("Loadgroup for %s has multiple requests relevant for blocking "
+ "BFCache",
+ uri.get()));
+ } else {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Verbose,
+ ("Loadgroup for %s has one request with id %" PRIu64
+ " relevant for blocking BFCache",
+ uri.get(), singleChannelId.value()));
+ }
+ }
+
+ if (mSingleChannelId != singleChannelId) {
+ mSingleChannelId = singleChannelId;
+ WindowGlobalChild* wgc =
+ mBrowsingContext->GetCurrentWindowContext()->GetWindowGlobalChild();
+ if (wgc) {
+ wgc->SendSetSingleChannelId(singleChannelId);
+ }
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsDocShell::OnStartRequest(nsIRequest* aRequest) {
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Verbose))) {
+ nsAutoCString uri("[no uri]");
+ if (mCurrentURI) {
+ uri = mCurrentURI->GetSpecOrDefault();
+ }
+ nsAutoCString name;
+ aRequest->GetName(name);
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Verbose,
+ ("Adding request %s to loadgroup for %s", name.get(), uri.get()));
+ }
+ RecordSingleChannelId(true, aRequest);
+ return nsDocLoader::OnStartRequest(aRequest);
+}
+
+NS_IMETHODIMP
+nsDocShell::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Verbose))) {
+ nsAutoCString uri("[no uri]");
+ if (mCurrentURI) {
+ uri = mCurrentURI->GetSpecOrDefault();
+ }
+ nsAutoCString name;
+ aRequest->GetName(name);
+ MOZ_LOG(
+ gSHIPBFCacheLog, LogLevel::Verbose,
+ ("Removing request %s from loadgroup for %s", name.get(), uri.get()));
+ }
+ RecordSingleChannelId(false, aRequest);
+ return nsDocLoader::OnStopRequest(aRequest, aStatusCode);
+}
+
+void nsDocShell::MaybeDisconnectChildListenersOnPageHide() {
+ MOZ_RELEASE_ASSERT(XRE_IsContentProcess());
+
+ if (mChannelToDisconnectOnPageHide != 0 && mLoadGroup) {
+ nsCOMPtr<nsISimpleEnumerator> requests;
+ mLoadGroup->GetRequests(getter_AddRefs(requests));
+ for (const auto& request : SimpleEnumerator<nsIRequest>(requests)) {
+ RefPtr<DocumentChannel> channel = do_QueryObject(request);
+ if (channel && channel->ChannelId() == mChannelToDisconnectOnPageHide) {
+ static_cast<DocumentChannelChild*>(channel.get())
+ ->DisconnectChildListeners(NS_BINDING_ABORTED, NS_BINDING_ABORTED);
+ }
+ }
+ mChannelToDisconnectOnPageHide = 0;
+ }
+}
diff --git a/docshell/base/nsDocShell.h b/docshell/base/nsDocShell.h
new file mode 100644
index 0000000000..67090146dc
--- /dev/null
+++ b/docshell/base/nsDocShell.h
@@ -0,0 +1,1388 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDocShell_h__
+#define nsDocShell_h__
+
+#include "Units.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/ScrollbarPreferences.h"
+#include "mozilla/TimelineConsumers.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/WindowProxyHolder.h"
+#include "nsCOMPtr.h"
+#include "nsCharsetSource.h"
+#include "nsDocLoader.h"
+#include "nsIAuthPromptProvider.h"
+#include "nsIBaseWindow.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsILoadContext.h"
+#include "nsINetworkInterceptController.h"
+#include "nsIRefreshURI.h"
+#include "nsIWebNavigation.h"
+#include "nsIWebPageDescriptor.h"
+#include "nsIWebProgressListener.h"
+#include "nsPoint.h" // mCurrent/mDefaultScrollbarPreferences
+#include "nsRect.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "prtime.h"
+
+// Interfaces Needed
+
+namespace mozilla {
+class Encoding;
+class HTMLEditor;
+class ObservedDocShell;
+enum class TaskCategory;
+namespace dom {
+class ClientInfo;
+class ClientSource;
+class EventTarget;
+class SessionHistoryInfo;
+struct LoadingSessionHistoryInfo;
+struct Wireframe;
+} // namespace dom
+namespace net {
+class LoadInfo;
+class DocumentLoadListener;
+} // namespace net
+} // namespace mozilla
+
+class nsIContentViewer;
+class nsIController;
+class nsIDocShellTreeOwner;
+class nsIHttpChannel;
+class nsIMutableArray;
+class nsIPrompt;
+class nsIScrollableFrame;
+class nsIStringBundle;
+class nsIURIFixup;
+class nsIURIFixupInfo;
+class nsIURILoader;
+class nsIWebBrowserFind;
+class nsIWidget;
+class nsIReferrerInfo;
+
+class nsCommandManager;
+class nsDocShellEditorData;
+class nsDOMNavigationTiming;
+class nsDSURIContentListener;
+class nsGlobalWindowOuter;
+
+class FramingChecker;
+class OnLinkClickEvent;
+
+/* internally used ViewMode types */
+enum ViewMode { viewNormal = 0x0, viewSource = 0x1 };
+
+enum eCharsetReloadState {
+ eCharsetReloadInit,
+ eCharsetReloadRequested,
+ eCharsetReloadStopOrigional
+};
+
+class nsDocShell final : public nsDocLoader,
+ public nsIDocShell,
+ public nsIWebNavigation,
+ public nsIBaseWindow,
+ public nsIRefreshURI,
+ public nsIWebProgressListener,
+ public nsIWebPageDescriptor,
+ public nsIAuthPromptProvider,
+ public nsILoadContext,
+ public nsINetworkInterceptController,
+ public mozilla::SupportsWeakPtr {
+ public:
+ enum InternalLoad : uint32_t {
+ INTERNAL_LOAD_FLAGS_NONE = 0x0,
+ INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL = 0x1,
+ INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER = 0x2,
+ INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP = 0x4,
+
+ // This flag marks the first load in this object
+ // @see nsIWebNavigation::LOAD_FLAGS_FIRST_LOAD
+ INTERNAL_LOAD_FLAGS_FIRST_LOAD = 0x8,
+
+ // The set of flags that should not be set before calling into
+ // nsDocShell::LoadURI and other nsDocShell loading functions.
+ INTERNAL_LOAD_FLAGS_LOADURI_SETUP_FLAGS = 0xf,
+
+ INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER = 0x10,
+ INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES = 0x20,
+
+ // Whether the load should be treated as srcdoc load, rather than a URI one.
+ INTERNAL_LOAD_FLAGS_IS_SRCDOC = 0x40,
+
+ // Whether this is the load of a frame's original src attribute
+ INTERNAL_LOAD_FLAGS_ORIGINAL_FRAME_SRC = 0x80,
+
+ INTERNAL_LOAD_FLAGS_NO_OPENER = 0x100,
+
+ // Whether a top-level data URI navigation is allowed for that load
+ INTERNAL_LOAD_FLAGS_FORCE_ALLOW_DATA_URI = 0x200,
+
+ // Whether the load should go through LoadURIDelegate.
+ INTERNAL_LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE = 0x2000,
+ };
+
+ // Event type dispatched by RestorePresentation
+ class RestorePresentationEvent : public mozilla::Runnable {
+ public:
+ NS_DECL_NSIRUNNABLE
+ explicit RestorePresentationEvent(nsDocShell* aDs)
+ : mozilla::Runnable("nsDocShell::RestorePresentationEvent"),
+ mDocShell(aDs) {}
+ void Revoke() { mDocShell = nullptr; }
+
+ private:
+ RefPtr<nsDocShell> mDocShell;
+ };
+
+ class InterfaceRequestorProxy : public nsIInterfaceRequestor {
+ public:
+ explicit InterfaceRequestorProxy(nsIInterfaceRequestor* aRequestor);
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ private:
+ virtual ~InterfaceRequestorProxy();
+ InterfaceRequestorProxy() = default;
+ nsWeakPtr mWeakPtr;
+ };
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsDocShell, nsDocLoader)
+ NS_DECL_NSIDOCSHELL
+ NS_DECL_NSIDOCSHELLTREEITEM
+ NS_DECL_NSIWEBNAVIGATION
+ NS_DECL_NSIBASEWINDOW
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSIWEBPROGRESSLISTENER
+ NS_DECL_NSIREFRESHURI
+ NS_DECL_NSIWEBPAGEDESCRIPTOR
+ NS_DECL_NSIAUTHPROMPTPROVIDER
+ NS_DECL_NSINETWORKINTERCEPTCONTROLLER
+
+ // Create a new nsDocShell object.
+ static already_AddRefed<nsDocShell> Create(
+ mozilla::dom::BrowsingContext* aBrowsingContext,
+ uint64_t aContentWindowID = 0);
+
+ bool Initialize();
+
+ NS_IMETHOD Stop() override {
+ // Need this here because otherwise nsIWebNavigation::Stop
+ // overrides the docloader's Stop()
+ return nsDocLoader::Stop();
+ }
+
+ mozilla::ScrollbarPreference ScrollbarPreference() const {
+ return mScrollbarPref;
+ }
+ void SetScrollbarPreference(mozilla::ScrollbarPreference);
+
+ /*
+ * The size, in CSS pixels, of the margins for the <body> of an HTML document
+ * in this docshell; used to implement the marginwidth attribute on HTML
+ * <frame>/<iframe> elements. A value smaller than zero indicates that the
+ * attribute was not set.
+ */
+ const mozilla::CSSIntSize& GetFrameMargins() const { return mFrameMargins; }
+
+ bool UpdateFrameMargins(const mozilla::CSSIntSize& aMargins) {
+ if (mFrameMargins == aMargins) {
+ return false;
+ }
+ mFrameMargins = aMargins;
+ return true;
+ }
+
+ /**
+ * Process a click on a link.
+ *
+ * @param aContent the content object used for triggering the link.
+ * @param aURI a URI object that defines the destination for the link
+ * @param aTargetSpec indicates where the link is targeted (may be an empty
+ * string)
+ * @param aFileName non-null when the link should be downloaded as the given
+ * file
+ * @param aPostDataStream the POST data to send
+ * @param aHeadersDataStream ??? (only used for plugins)
+ * @param aIsTrusted false if the triggerer is an untrusted DOM event.
+ * @param aTriggeringPrincipal, if not passed explicitly we fall back to
+ * the document's principal.
+ * @param aCsp, the CSP to be used for the load, that is the CSP of the
+ * entity responsible for causing the load to occur. Most likely
+ * this is the CSP of the document that started the load. In case
+ * aCsp was not passed explicitly we fall back to using
+ * aContent's document's CSP if that document holds any.
+ */
+ nsresult OnLinkClick(nsIContent* aContent, nsIURI* aURI,
+ const nsAString& aTargetSpec, const nsAString& aFileName,
+ nsIInputStream* aPostDataStream,
+ nsIInputStream* aHeadersDataStream,
+ bool aIsUserTriggered, bool aIsTrusted,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsIContentSecurityPolicy* aCsp);
+ /**
+ * Process a click on a link.
+ *
+ * Works the same as OnLinkClick() except it happens immediately rather than
+ * through an event.
+ *
+ * @param aContent the content object used for triggering the link.
+ * @param aDocShellLoadState the extended load info for this load.
+ * @param aNoOpenerImplied if the link implies "noopener"
+ * @param aTriggeringPrincipal, if not passed explicitly we fall back to
+ * the document's principal.
+ */
+ nsresult OnLinkClickSync(nsIContent* aContent,
+ nsDocShellLoadState* aLoadState,
+ bool aNoOpenerImplied,
+ nsIPrincipal* aTriggeringPrincipal);
+
+ /**
+ * Process a mouse-over a link.
+ *
+ * @param aContent the linked content.
+ * @param aURI an URI object that defines the destination for the link
+ * @param aTargetSpec indicates where the link is targeted (it may be an empty
+ * string)
+ */
+ nsresult OnOverLink(nsIContent* aContent, nsIURI* aURI,
+ const nsAString& aTargetSpec);
+ /**
+ * Process the mouse leaving a link.
+ */
+ nsresult OnLeaveLink();
+
+ // Don't use NS_DECL_NSILOADCONTEXT because some of nsILoadContext's methods
+ // are shared with nsIDocShell and can't be declared twice.
+ NS_IMETHOD GetAssociatedWindow(mozIDOMWindowProxy**) override;
+ NS_IMETHOD GetTopWindow(mozIDOMWindowProxy**) override;
+ NS_IMETHOD GetTopFrameElement(mozilla::dom::Element**) override;
+ NS_IMETHOD GetIsContent(bool*) override;
+ NS_IMETHOD GetUsePrivateBrowsing(bool*) override;
+ NS_IMETHOD SetUsePrivateBrowsing(bool) override;
+ NS_IMETHOD SetPrivateBrowsing(bool) override;
+ NS_IMETHOD GetUseRemoteTabs(bool*) override;
+ NS_IMETHOD SetRemoteTabs(bool) override;
+ NS_IMETHOD GetUseRemoteSubframes(bool*) override;
+ NS_IMETHOD SetRemoteSubframes(bool) override;
+ NS_IMETHOD GetScriptableOriginAttributes(
+ JSContext*, JS::MutableHandle<JS::Value>) override;
+ NS_IMETHOD_(void)
+ GetOriginAttributes(mozilla::OriginAttributes& aAttrs) override;
+
+ // Restores a cached presentation from history (mLSHE).
+ // This method swaps out the content viewer and simulates loads for
+ // subframes. It then simulates the completion of the toplevel load.
+ nsresult RestoreFromHistory();
+
+ /**
+ * Parses the passed in header string and sets up a refreshURI if a "refresh"
+ * header is found. If docshell is busy loading a page currently, the request
+ * will be queued and executed when the current page finishes loading.
+ *
+ * @param aDocument document to which the refresh header applies.
+ * @param aHeader The meta refresh header string.
+ */
+ void SetupRefreshURIFromHeader(mozilla::dom::Document* aDocument,
+ const nsAString& aHeader);
+
+ // Perform a URI load from a refresh timer. This is just like the
+ // ForceRefreshURI method on nsIRefreshURI, but makes sure to take
+ // the timer involved out of mRefreshURIList if it's there.
+ // aTimer must not be null.
+ nsresult ForceRefreshURIFromTimer(nsIURI* aURI, nsIPrincipal* aPrincipal,
+ uint32_t aDelay, nsITimer* aTimer);
+
+ // We need dummy OnLocationChange in some cases to update the UI without
+ // updating security info.
+ void FireDummyOnLocationChange() {
+ FireOnLocationChange(this, nullptr, mCurrentURI,
+ LOCATION_CHANGE_SAME_DOCUMENT);
+ }
+
+ // This function is created exclusively for dom.background_loading_iframe is
+ // set. As soon as the current DocShell knows itself can be treated as
+ // background loading, it triggers the parent docshell to see if the parent
+ // document can fire load event earlier.
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void TriggerParentCheckDocShellIsEmpty();
+
+ nsresult HistoryEntryRemoved(int32_t aIndex);
+
+ // Notify Scroll observers when an async panning/zooming transform
+ // has started being applied
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ void NotifyAsyncPanZoomStarted();
+
+ // Notify Scroll observers when an async panning/zooming transform
+ // is no longer applied
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ void NotifyAsyncPanZoomStopped();
+
+ void SetInFrameSwap(bool aInSwap) { mInFrameSwap = aInSwap; }
+ bool InFrameSwap();
+
+ bool GetForcedAutodetection() { return mForcedAutodetection; }
+
+ void ResetForcedAutodetection() { mForcedAutodetection = false; }
+
+ mozilla::HTMLEditor* GetHTMLEditorInternal();
+ nsresult SetHTMLEditorInternal(mozilla::HTMLEditor* aHTMLEditor);
+
+ // Handle page navigation due to charset changes
+ nsresult CharsetChangeReloadDocument(
+ mozilla::NotNull<const mozilla::Encoding*> aEncoding, int32_t aSource);
+ nsresult CharsetChangeStopDocumentLoad();
+
+ nsDOMNavigationTiming* GetNavigationTiming() const;
+
+ nsresult SetOriginAttributes(const mozilla::OriginAttributes& aAttrs);
+
+ const mozilla::OriginAttributes& GetOriginAttributes() {
+ return mBrowsingContext->OriginAttributesRef();
+ }
+
+ // Determine whether this docshell corresponds to the given history entry,
+ // via having a pointer to it in mOSHE or mLSHE.
+ bool HasHistoryEntry(nsISHEntry* aEntry) const {
+ return aEntry && (aEntry == mOSHE || aEntry == mLSHE);
+ }
+
+ // Update any pointers (mOSHE or mLSHE) to aOldEntry to point to aNewEntry
+ void SwapHistoryEntries(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry);
+
+ bool GetCreatedDynamically() const {
+ return mBrowsingContext && mBrowsingContext->CreatedDynamically();
+ }
+
+ mozilla::gfx::Matrix5x4* GetColorMatrix() { return mColorMatrix.get(); }
+
+ static bool SandboxFlagsImplyCookies(const uint32_t& aSandboxFlags);
+
+ // Tell the favicon service that aNewURI has the same favicon as aOldURI.
+ static void CopyFavicon(nsIURI* aOldURI, nsIURI* aNewURI,
+ bool aInPrivateBrowsing);
+
+ static nsDocShell* Cast(nsIDocShell* aDocShell) {
+ return static_cast<nsDocShell*>(aDocShell);
+ }
+
+ static bool CanLoadInParentProcess(nsIURI* aURI);
+
+ // Returns true if the current load is a force reload (started by holding
+ // shift while triggering reload)
+ bool IsForceReloading();
+
+ mozilla::dom::WindowProxyHolder GetWindowProxy() {
+ EnsureScriptEnvironment();
+ return mozilla::dom::WindowProxyHolder(mBrowsingContext);
+ }
+
+ /**
+ * Loads the given URI. See comments on nsDocShellLoadState members for more
+ * information on information used.
+ * `aCacheKey` gets passed to DoURILoad call.
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ nsresult InternalLoad(
+ nsDocShellLoadState* aLoadState,
+ mozilla::Maybe<uint32_t> aCacheKey = mozilla::Nothing());
+
+ void MaybeRestoreWindowName();
+
+ void StoreWindowNameToSHEntries();
+
+ void SetWillChangeProcess() { mWillChangeProcess = true; }
+ bool WillChangeProcess() { return mWillChangeProcess; }
+
+ // Create a content viewer within this nsDocShell for the given
+ // `WindowGlobalChild` actor.
+ nsresult CreateContentViewerForActor(
+ mozilla::dom::WindowGlobalChild* aWindowActor);
+
+ // Creates a real network channel (not a DocumentChannel) using the specified
+ // parameters.
+ // Used by nsDocShell when not using DocumentChannel, by DocumentLoadListener
+ // (parent-process DocumentChannel), and by DocumentChannelChild/ContentChild
+ // to transfer the resulting channel into the final process.
+ static nsresult CreateRealChannelForDocument(
+ nsIChannel** aChannel, nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ nsIInterfaceRequestor* aCallbacks, nsLoadFlags aLoadFlags,
+ const nsAString& aSrcdoc, nsIURI* aBaseURI);
+
+ // Creates a real (not DocumentChannel) channel, and configures it using the
+ // supplied nsDocShellLoadState.
+ // Configuration options here are ones that should be applied to only the
+ // real channel, especially ones that need to QI to channel subclasses.
+ static bool CreateAndConfigureRealChannelForLoadState(
+ mozilla::dom::BrowsingContext* aBrowsingContext,
+ nsDocShellLoadState* aLoadState, mozilla::net::LoadInfo* aLoadInfo,
+ nsIInterfaceRequestor* aCallbacks, nsDocShell* aDocShell,
+ const mozilla::OriginAttributes& aOriginAttributes,
+ nsLoadFlags aLoadFlags, uint32_t aCacheKey, nsresult& rv,
+ nsIChannel** aChannel);
+
+ // This is used to deal with errors resulting from a failed page load.
+ // Errors are handled as follows:
+ // 1. Check to see if it's a file not found error or bad content
+ // encoding error.
+ // 2. Send the URI to a keyword server (if enabled)
+ // 3. If the error was DNS failure, then add www and .com to the URI
+ // (if appropriate).
+ // 4. If the www .com additions don't work, try those with an HTTPS scheme
+ // (if appropriate).
+ static already_AddRefed<nsIURI> AttemptURIFixup(
+ nsIChannel* aChannel, nsresult aStatus,
+ const mozilla::Maybe<nsCString>& aOriginalURIString, uint32_t aLoadType,
+ bool aIsTopFrame, bool aAllowKeywordFixup, bool aUsePrivateBrowsing,
+ bool aNotifyKeywordSearchLoading = false,
+ nsIInputStream** aNewPostData = nullptr);
+
+ static already_AddRefed<nsIURI> MaybeFixBadCertDomainErrorURI(
+ nsIChannel* aChannel, nsIURI* aUrl);
+
+ // Takes aStatus and filters out results that should not display
+ // an error page.
+ // If this returns a failed result, then we should display an error
+ // page with that result.
+ // aSkippedUnknownProtocolNavigation will be set to true if we chose
+ // to skip displaying an error page for an NS_ERROR_UNKNOWN_PROTOCOL
+ // navigation.
+ static nsresult FilterStatusForErrorPage(
+ nsresult aStatus, nsIChannel* aChannel, uint32_t aLoadType,
+ bool aIsTopFrame, bool aUseErrorPages, bool aIsInitialDocument,
+ bool* aSkippedUnknownProtocolNavigation = nullptr);
+
+ // Notify consumers of a search being loaded through the observer service:
+ static void MaybeNotifyKeywordSearchLoading(const nsString& aProvider,
+ const nsString& aKeyword);
+
+ nsDocShell* GetInProcessChildAt(int32_t aIndex);
+
+ static bool ShouldAddURIVisit(nsIChannel* aChannel);
+
+ /**
+ * Helper function that finds the last URI and its transition flags for a
+ * channel.
+ *
+ * This method first checks the channel's property bag to see if previous
+ * info has been saved. If not, it gives back the referrer of the channel.
+ *
+ * @param aChannel
+ * The channel we are transitioning to
+ * @param aURI
+ * Output parameter with the previous URI, not addref'd
+ * @param aChannelRedirectFlags
+ * If a redirect, output parameter with the previous redirect flags
+ * from nsIChannelEventSink
+ */
+ static void ExtractLastVisit(nsIChannel* aChannel, nsIURI** aURI,
+ uint32_t* aChannelRedirectFlags);
+
+ bool HasContentViewer() const { return !!mContentViewer; }
+
+ static uint32_t ComputeURILoaderFlags(
+ mozilla::dom::BrowsingContext* aBrowsingContext, uint32_t aLoadType);
+
+ void SetLoadingSessionHistoryInfo(
+ const mozilla::dom::LoadingSessionHistoryInfo& aLoadingInfo,
+ bool aNeedToReportActiveAfterLoadingBecomesActive = false);
+ const mozilla::dom::LoadingSessionHistoryInfo*
+ GetLoadingSessionHistoryInfo() {
+ return mLoadingEntry.get();
+ }
+
+ already_AddRefed<nsIInputStream> GetPostDataFromCurrentEntry() const;
+ mozilla::Maybe<uint32_t> GetCacheKeyFromCurrentEntry() const;
+
+ // Loading and/or active entries are only set when session history
+ // in the parent is on.
+ bool FillLoadStateFromCurrentEntry(nsDocShellLoadState& aLoadState);
+
+ static bool ShouldAddToSessionHistory(nsIURI* aURI, nsIChannel* aChannel);
+
+ bool IsOSHE(nsISHEntry* aEntry) const { return mOSHE == aEntry; }
+
+ mozilla::dom::ChildSHistory* GetSessionHistory() {
+ return mBrowsingContext->GetChildSessionHistory();
+ }
+
+ // This returns true only when using session history in parent.
+ bool IsLoadingFromSessionHistory();
+
+ NS_IMETHODIMP OnStartRequest(nsIRequest* aRequest) override;
+ NS_IMETHODIMP OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) override;
+
+ private: // member functions
+ friend class nsAppShellService;
+ friend class nsDSURIContentListener;
+ friend class FramingChecker;
+ friend class OnLinkClickEvent;
+ friend class nsIDocShell;
+ friend class mozilla::dom::BrowsingContext;
+ friend class mozilla::net::DocumentLoadListener;
+ friend class nsGlobalWindowOuter;
+
+ // It is necessary to allow adding a timeline marker wherever a docshell
+ // instance is available. This operation happens frequently and needs to
+ // be very fast, so instead of using a Map or having to search for some
+ // docshell-specific markers storage, a pointer to an `ObservedDocShell` is
+ // is stored on docshells directly.
+ friend void mozilla::TimelineConsumers::AddConsumer(nsDocShell*);
+ friend void mozilla::TimelineConsumers::RemoveConsumer(nsDocShell*);
+ friend void mozilla::TimelineConsumers::AddMarkerForDocShell(
+ nsDocShell*, const char*, MarkerTracingType, MarkerStackRequest);
+ friend void mozilla::TimelineConsumers::AddMarkerForDocShell(
+ nsDocShell*, const char*, const TimeStamp&, MarkerTracingType,
+ MarkerStackRequest);
+ friend void mozilla::TimelineConsumers::AddMarkerForDocShell(
+ nsDocShell*, UniquePtr<AbstractTimelineMarker>&&);
+ friend void mozilla::TimelineConsumers::PopMarkers(
+ nsDocShell*, JSContext*, nsTArray<dom::ProfileTimelineMarker>&);
+
+ nsDocShell(mozilla::dom::BrowsingContext* aBrowsingContext,
+ uint64_t aContentWindowID);
+
+ static inline uint32_t PRTimeToSeconds(PRTime aTimeUsec) {
+ return uint32_t(aTimeUsec / PR_USEC_PER_SEC);
+ }
+
+ virtual ~nsDocShell();
+
+ //
+ // nsDocLoader
+ //
+
+ virtual void DestroyChildren() override;
+
+ // Overridden from nsDocLoader, this provides more information than the
+ // normal OnStateChange with flags STATE_REDIRECTING
+ virtual void OnRedirectStateChange(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel,
+ uint32_t aRedirectFlags,
+ uint32_t aStateFlags) override;
+
+ // Override the parent setter from nsDocLoader
+ virtual nsresult SetDocLoaderParent(nsDocLoader* aLoader) override;
+
+ //
+ // Content Viewer Management
+ //
+
+ nsresult EnsureContentViewer();
+
+ // aPrincipal can be passed in if the caller wants. If null is
+ // passed in, the about:blank principal will end up being used.
+ // aCSP, if any, will be used for the new about:blank load.
+ nsresult CreateAboutBlankContentViewer(
+ nsIPrincipal* aPrincipal, nsIPrincipal* aPartitionedPrincipal,
+ nsIContentSecurityPolicy* aCSP, nsIURI* aBaseURI, bool aIsInitialDocument,
+ const mozilla::Maybe<nsILoadInfo::CrossOriginEmbedderPolicy>& aCOEP =
+ mozilla::Nothing(),
+ bool aTryToSaveOldPresentation = true, bool aCheckPermitUnload = true,
+ mozilla::dom::WindowGlobalChild* aActor = nullptr);
+
+ nsresult CreateContentViewer(const nsACString& aContentType,
+ nsIRequest* aRequest,
+ nsIStreamListener** aContentHandler);
+
+ nsresult NewContentViewerObj(const nsACString& aContentType,
+ nsIRequest* aRequest, nsILoadGroup* aLoadGroup,
+ nsIStreamListener** aContentHandler,
+ nsIContentViewer** aViewer);
+
+ already_AddRefed<nsILoadURIDelegate> GetLoadURIDelegate();
+
+ nsresult SetupNewViewer(
+ nsIContentViewer* aNewViewer,
+ mozilla::dom::WindowGlobalChild* aWindowActor = nullptr);
+
+ //
+ // Session History
+ //
+
+ // Either aChannel or aOwner must be null. If aChannel is
+ // present, the owner should be gotten from it.
+ // If aCloneChildren is true, then our current session history's
+ // children will be cloned onto the new entry. This should be
+ // used when we aren't actually changing the document while adding
+ // the new session history entry.
+ // aCsp is the CSP to be used for the load. That is *not* the CSP
+ // that will be applied to subresource loads within that document
+ // but the CSP for the document load itself. E.g. if that CSP
+ // includes upgrade-insecure-requests, then the new top-level load
+ // will be upgraded to HTTPS.
+ nsresult AddToSessionHistory(nsIURI* aURI, nsIChannel* aChannel,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp,
+ bool aCloneChildren, nsISHEntry** aNewEntry);
+
+ void UpdateActiveEntry(
+ bool aReplace, const mozilla::Maybe<nsPoint>& aPreviousScrollPos,
+ nsIURI* aURI, nsIURI* aOriginalURI, nsIReferrerInfo* aReferrerInfo,
+ nsIPrincipal* aTriggeringPrincipal, nsIContentSecurityPolicy* aCsp,
+ const nsAString& aTitle, bool aScrollRestorationIsManual,
+ nsIStructuredCloneContainer* aData, bool aURIWasModified);
+
+ nsresult AddChildSHEntry(nsISHEntry* aCloneRef, nsISHEntry* aNewEntry,
+ int32_t aChildOffset, uint32_t aLoadType,
+ bool aCloneChildren);
+
+ nsresult AddChildSHEntryToParent(nsISHEntry* aNewEntry, int32_t aChildOffset,
+ bool aCloneChildren);
+
+ // Call this method to swap in a new history entry to m[OL]SHE, rather than
+ // setting it directly. This completes the navigation in all docshells
+ // in the case of a subframe navigation.
+ // Returns old mOSHE/mLSHE.
+ already_AddRefed<nsISHEntry> SetHistoryEntry(nsCOMPtr<nsISHEntry>* aPtr,
+ nsISHEntry* aEntry);
+
+ // This method calls SetHistoryEntry and updates mOSHE and mLSHE in BC to be
+ // the same as in docshell
+ void SetHistoryEntryAndUpdateBC(const mozilla::Maybe<nsISHEntry*>& aLSHE,
+ const mozilla::Maybe<nsISHEntry*>& aOSHE);
+
+ // If aNotifiedBeforeUnloadListeners is true, "beforeunload" event listeners
+ // were notified by the caller and given the chance to abort the navigation,
+ // and should not be notified again.
+ static nsresult ReloadDocument(
+ nsDocShell* aDocShell, mozilla::dom::Document* aDocument,
+ uint32_t aLoadType, mozilla::dom::BrowsingContext* aBrowsingContext,
+ nsIURI* aCurrentURI, nsIReferrerInfo* aReferrerInfo,
+ bool aNotifiedBeforeUnloadListeners = false);
+
+ public:
+ bool IsAboutBlankLoadOntoInitialAboutBlank(nsIURI* aURI,
+ bool aInheritPrincipal,
+ nsIPrincipal* aPrincipalToInherit);
+
+ private:
+ //
+ // URI Load
+ //
+
+ // Actually open a channel and perform a URI load. Callers need to pass a
+ // non-null aLoadState->TriggeringPrincipal() which initiated the URI load.
+ // Please note that the TriggeringPrincipal will be used for performing
+ // security checks. If aLoadState->URI() is provided by the web, then please
+ // do not pass a SystemPrincipal as the triggeringPrincipal. If
+ // aLoadState()->PrincipalToInherit is null, then no inheritance of any sort
+ // will happen and the load will get a principal based on the URI being
+ // loaded. If the Srcdoc flag is set (INTERNAL_LOAD_FLAGS_IS_SRCDOC), the load
+ // will be considered as a srcdoc load, and the contents of Srcdoc will be
+ // loaded instead of the URI. aLoadState->OriginalURI() will be set as the
+ // originalURI on the channel that does the load. If OriginalURI is null, URI
+ // will be set as the originalURI. If LoadReplace is true, LOAD_REPLACE flag
+ // will be set on the nsIChannel.
+ // If `aCacheKey` is supplied, use it for the session history entry.
+ nsresult DoURILoad(nsDocShellLoadState* aLoadState,
+ mozilla::Maybe<uint32_t> aCacheKey, nsIRequest** aRequest);
+
+ static nsresult AddHeadersToChannel(nsIInputStream* aHeadersData,
+ nsIChannel* aChannel);
+
+ nsresult OpenInitializedChannel(nsIChannel* aChannel,
+ nsIURILoader* aURILoader,
+ uint32_t aOpenFlags);
+ nsresult OpenRedirectedChannel(nsDocShellLoadState* aLoadState);
+
+ void UpdateMixedContentChannelForNewLoad(nsIChannel* aChannel);
+
+ MOZ_CAN_RUN_SCRIPT
+ nsresult ScrollToAnchor(bool aCurHasRef, bool aNewHasRef,
+ nsACString& aNewHash, uint32_t aLoadType);
+
+ private:
+ // Returns true if would have called FireOnLocationChange,
+ // but did not because aFireOnLocationChange was false on entry.
+ // In this case it is the caller's responsibility to ensure
+ // FireOnLocationChange is called.
+ // In all other cases false is returned.
+ // Either aChannel or aTriggeringPrincipal must be null. If aChannel is
+ // present, the owner should be gotten from it.
+ // If OnNewURI calls AddToSessionHistory, it will pass its
+ // aCloneSHChildren argument as aCloneChildren.
+ // aCsp is the CSP to be used for the load. That is *not* the CSP
+ // that will be applied to subresource loads within that document
+ // but the CSP for the document load itself. E.g. if that CSP
+ // includes upgrade-insecure-requests, then the new top-level load
+ // will be upgraded to HTTPS.
+ bool OnNewURI(nsIURI* aURI, nsIChannel* aChannel,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInehrit,
+ nsIContentSecurityPolicy* aCsp, bool aFireOnLocationChange,
+ bool aAddToGlobalHistory, bool aCloneSHChildren);
+
+ public:
+ // If wireframe collection is enabled, will attempt to gather the
+ // wireframe for the document.
+ mozilla::Maybe<mozilla::dom::Wireframe> GetWireframe();
+
+ // If wireframe collection is enabled, will attempt to gather the
+ // wireframe for the document and stash it inside of the active history
+ // entry. Returns true if wireframes were collected.
+ bool CollectWireframe();
+
+ // Helper method that is called when a new document (including any
+ // sub-documents - ie. frames) has been completely loaded.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ nsresult EndPageLoad(nsIWebProgress* aProgress, nsIChannel* aChannel,
+ nsresult aResult);
+
+ // Builds an error page URI (e.g. about:neterror?etc) for the given aURI
+ // and displays it via the LoadErrorPage() overload below.
+ nsresult LoadErrorPage(nsIURI* aURI, const char16_t* aURL,
+ const char* aErrorPage, const char* aErrorType,
+ const char16_t* aDescription, const char* aCSSClass,
+ nsIChannel* aFailedChannel);
+
+ // This method directly loads aErrorURI as an error page. aFailedURI and
+ // aFailedChannel come from DisplayLoadError() or the LoadErrorPage() overload
+ // above.
+ nsresult LoadErrorPage(nsIURI* aErrorURI, nsIURI* aFailedURI,
+ nsIChannel* aFailedChannel);
+
+ bool DisplayLoadError(nsresult aError, nsIURI* aURI, const char16_t* aURL,
+ nsIChannel* aFailedChannel) {
+ bool didDisplayLoadError = false;
+ DisplayLoadError(aError, aURI, aURL, aFailedChannel, &didDisplayLoadError);
+ return didDisplayLoadError;
+ }
+
+ //
+ // Uncategorized
+ //
+
+ // Get the principal that we'll set on the channel if we're inheriting. If
+ // aConsiderCurrentDocument is true, we try to use the current document if
+ // at all possible. If that fails, we fall back on the parent document.
+ // If that fails too, we force creation of a content viewer and use the
+ // resulting principal. If aConsiderCurrentDocument is false, we just look
+ // at the parent.
+ // If aConsiderPartitionedPrincipal is true, we consider the partitioned
+ // principal instead of the node principal.
+ nsIPrincipal* GetInheritedPrincipal(
+ bool aConsiderCurrentDocument,
+ bool aConsiderPartitionedPrincipal = false);
+
+ /**
+ * Helper function that caches a URI and a transition for saving later.
+ *
+ * @param aChannel
+ * Channel that will have these properties saved
+ * @param aURI
+ * The URI to save for later
+ * @param aChannelRedirectFlags
+ * The nsIChannelEventSink redirect flags to save for later
+ */
+ static void SaveLastVisit(nsIChannel* aChannel, nsIURI* aURI,
+ uint32_t aChannelRedirectFlags);
+
+ /**
+ * Helper function for adding a URI visit using IHistory.
+ *
+ * The IHistory API maintains chains of visits, tracking both HTTP referrers
+ * and redirects for a user session. VisitURI requires the current URI and
+ * the previous URI in the chain.
+ *
+ * Visits can be saved either during a redirect or when the request has
+ * reached its final destination. The previous URI in the visit may be
+ * from another redirect.
+ *
+ * @pre aURI is not null.
+ *
+ * @param aURI
+ * The URI that was just visited
+ * @param aPreviousURI
+ * The previous URI of this visit
+ * @param aChannelRedirectFlags
+ * For redirects, the redirect flags from nsIChannelEventSink
+ * (0 otherwise)
+ * @param aResponseStatus
+ * For HTTP channels, the response code (0 otherwise).
+ */
+ void AddURIVisit(nsIURI* aURI, nsIURI* aPreviousURI,
+ uint32_t aChannelRedirectFlags,
+ uint32_t aResponseStatus = 0);
+
+ /**
+ * Internal helper funtion
+ */
+ static void InternalAddURIVisit(
+ nsIURI* aURI, nsIURI* aPreviousURI, uint32_t aChannelRedirectFlags,
+ uint32_t aResponseStatus, mozilla::dom::BrowsingContext* aBrowsingContext,
+ nsIWidget* aWidget, uint32_t aLoadType);
+
+ static already_AddRefed<nsIURIFixupInfo> KeywordToURI(
+ const nsACString& aKeyword, bool aIsPrivateContext);
+
+ // Sets the current document's current state object to the given SHEntry's
+ // state object. The current state object is eventually given to the page
+ // in the PopState event.
+ void SetDocCurrentStateObj(nsISHEntry* aShEntry,
+ mozilla::dom::SessionHistoryInfo* aInfo);
+
+ // Returns true if would have called FireOnLocationChange,
+ // but did not because aFireOnLocationChange was false on entry.
+ // In this case it is the caller's responsibility to ensure
+ // FireOnLocationChange is called.
+ // In all other cases false is returned.
+ bool SetCurrentURI(nsIURI* aURI, nsIRequest* aRequest,
+ bool aFireOnLocationChange, bool aIsInitialAboutBlank,
+ uint32_t aLocationFlags);
+
+ // The following methods deal with saving and restoring content viewers
+ // in session history.
+
+ // mContentViewer points to the current content viewer associated with
+ // this docshell. When loading a new document, the content viewer is
+ // either destroyed or stored into a session history entry. To make sure
+ // that destruction happens in a controlled fashion, a given content viewer
+ // is always owned in exactly one of these ways:
+ // 1) The content viewer is active and owned by a docshell's
+ // mContentViewer.
+ // 2) The content viewer is still being displayed while we begin loading
+ // a new document. The content viewer is owned by the _new_
+ // content viewer's mPreviousViewer, and has a pointer to the
+ // nsISHEntry where it will eventually be stored. The content viewer
+ // has been close()d by the docshell, which detaches the document from
+ // the window object.
+ // 3) The content viewer is cached in session history. The nsISHEntry
+ // has the only owning reference to the content viewer. The viewer
+ // has released its nsISHEntry pointer to prevent circular ownership.
+ //
+ // When restoring a content viewer from session history, open() is called
+ // to reattach the document to the window object. The content viewer is
+ // then placed into mContentViewer and removed from the history entry.
+ // (mContentViewer is put into session history as described above, if
+ // applicable).
+
+ // Determines whether we can safely cache the current mContentViewer in
+ // session history. This checks a number of factors such as cache policy,
+ // pending requests, and unload handlers.
+ // |aLoadType| should be the load type that will replace the current
+ // presentation. |aNewRequest| should be the request for the document to
+ // be loaded in place of the current document, or null if such a request
+ // has not been created yet. |aNewDocument| should be the document that will
+ // replace the current document.
+ bool CanSavePresentation(uint32_t aLoadType, nsIRequest* aNewRequest,
+ mozilla::dom::Document* aNewDocument,
+ bool aReportBFCacheComboTelemetry);
+
+ static void ReportBFCacheComboTelemetry(uint32_t aCombo);
+
+ // Captures the state of the supporting elements of the presentation
+ // (the "window" object, docshell tree, meta-refresh loads, and security
+ // state) and stores them on |mOSHE|.
+ nsresult CaptureState();
+
+ // Begin the toplevel restore process for |aSHEntry|.
+ // This simulates a channel open, and defers the real work until
+ // RestoreFromHistory is called from a PLEvent.
+ nsresult RestorePresentation(nsISHEntry* aSHEntry, bool* aRestoring);
+
+ // Call BeginRestore(nullptr, false) for each child of this shell.
+ nsresult BeginRestoreChildren();
+
+ // Method to get our current position and size without flushing
+ void DoGetPositionAndSize(int32_t* aX, int32_t* aY, int32_t* aWidth,
+ int32_t* aHeight);
+
+ // Call this when a URI load is handed to us (via OnLinkClick or
+ // InternalLoad). This makes sure that we're not inside unload, or that if
+ // we are it's still OK to load this URI.
+ bool IsOKToLoadURI(nsIURI* aURI);
+
+ // helpers for executing commands
+ nsresult GetControllerForCommand(const char* aCommand,
+ nsIController** aResult);
+
+ // Possibly create a ClientSource object to represent an initial about:blank
+ // window that has not been allocated yet. Normally we try not to create
+ // this about:blank window until something calls GetDocument(). We still need
+ // the ClientSource to exist for this conceptual window, though.
+ //
+ // The ClientSource is created with the given principal if specified. If
+ // the principal is not provided we will attempt to inherit it when we
+ // are sure it will match what the real about:blank window principal
+ // would have been. There are some corner cases where we cannot easily
+ // determine the correct principal and will not create the ClientSource.
+ // In these cases the initial about:blank will appear to not exist until
+ // its real document and window are created.
+ void MaybeCreateInitialClientSource(nsIPrincipal* aPrincipal = nullptr);
+
+ // Determine if a service worker is allowed to control a window in this
+ // docshell with the given URL. If there are any reasons it should not,
+ // this will return false. If true is returned then the window *may* be
+ // controlled. The caller must still consult either the parent controller
+ // or the ServiceWorkerManager to determine if a service worker should
+ // actually control the window.
+ bool ServiceWorkerAllowedToControlWindow(nsIPrincipal* aPrincipal,
+ nsIURI* aURI);
+
+ // Return the ClientInfo for the initial about:blank window, if it exists
+ // or we have speculatively created a ClientSource in
+ // MaybeCreateInitialClientSource(). This can return a ClientInfo object
+ // even if GetExtantDoc() returns nullptr.
+ mozilla::Maybe<mozilla::dom::ClientInfo> GetInitialClientInfo() const;
+
+ /**
+ * Initializes mTiming if it isn't yet.
+ * After calling this, mTiming is non-null. This method returns true if the
+ * initialization of the Timing can be reset (basically this is true if a new
+ * Timing object is created).
+ * In case the loading is aborted, MaybeResetInitTiming() can be called
+ * passing the return value of MaybeInitTiming(): if it's possible to reset
+ * the Timing, this method will do it.
+ */
+ [[nodiscard]] bool MaybeInitTiming();
+ void MaybeResetInitTiming(bool aReset);
+
+ // Convenience method for getting our parent docshell. Can return null
+ already_AddRefed<nsDocShell> GetInProcessParentDocshell();
+
+ // Internal implementation of nsIDocShell::FirePageHideNotification.
+ // If aSkipCheckingDynEntries is true, it will not try to remove dynamic
+ // subframe entries. This is to avoid redundant RemoveDynEntries calls in all
+ // children docshells.
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void FirePageHideNotificationInternal(
+ bool aIsUnload, bool aSkipCheckingDynEntries);
+
+ void ThawFreezeNonRecursive(bool aThaw);
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void FirePageHideShowNonRecursive(bool aShow);
+
+ nsresult Dispatch(mozilla::TaskCategory aCategory,
+ already_AddRefed<nsIRunnable>&& aRunnable);
+
+ void ReattachEditorToWindow(nsISHEntry* aSHEntry);
+ void ClearFrameHistory(nsISHEntry* aEntry);
+ // Determine if this type of load should update history.
+ static bool ShouldUpdateGlobalHistory(uint32_t aLoadType);
+ void UpdateGlobalHistoryTitle(nsIURI* aURI);
+ bool IsSubframe() { return mBrowsingContext->IsSubframe(); }
+ bool CanSetOriginAttributes();
+ bool ShouldBlockLoadingForBackButton();
+ static bool ShouldDiscardLayoutState(nsIHttpChannel* aChannel);
+ bool HasUnloadedParent();
+ bool JustStartedNetworkLoad();
+ bool NavigationBlockedByPrinting(bool aDisplayErrorDialog = true);
+ bool IsNavigationAllowed(bool aDisplayPrintErrorDialog = true,
+ bool aCheckIfUnloadFired = true);
+ nsIScrollableFrame* GetRootScrollFrame();
+ nsIChannel* GetCurrentDocChannel();
+ nsresult EnsureScriptEnvironment();
+ nsresult EnsureEditorData();
+ nsresult EnsureTransferableHookData();
+ nsresult EnsureFind();
+ nsresult EnsureCommandHandler();
+ nsresult RefreshURIFromQueue();
+ void RefreshURIToQueue();
+ nsresult Embed(nsIContentViewer* aContentViewer,
+ mozilla::dom::WindowGlobalChild* aWindowActor,
+ bool aIsTransientAboutBlank, bool aPersist,
+ nsIRequest* aRequest, nsIURI* aPreviousURI);
+ nsPresContext* GetEldestPresContext();
+ nsresult CheckLoadingPermissions();
+ nsresult LoadHistoryEntry(nsISHEntry* aEntry, uint32_t aLoadType,
+ bool aUserActivation);
+ nsresult LoadHistoryEntry(
+ const mozilla::dom::LoadingSessionHistoryInfo& aEntry, uint32_t aLoadType,
+ bool aUserActivation);
+ nsresult LoadHistoryEntry(nsDocShellLoadState* aLoadState, uint32_t aLoadType,
+ bool aLoadingCurrentEntry);
+ nsresult GetHttpChannel(nsIChannel* aChannel, nsIHttpChannel** aReturn);
+ nsresult ConfirmRepost(bool* aRepost);
+ nsresult GetPromptAndStringBundle(nsIPrompt** aPrompt,
+ nsIStringBundle** aStringBundle);
+ nsresult SetCurScrollPosEx(int32_t aCurHorizontalPos,
+ int32_t aCurVerticalPos);
+ nsPoint GetCurScrollPos();
+
+ already_AddRefed<mozilla::dom::ChildSHistory> GetRootSessionHistory();
+
+ bool CSSErrorReportingEnabled() const { return mCSSErrorReportingEnabled; }
+
+ // Handles retrieval of subframe session history for nsDocShell::LoadURI. If a
+ // load is requested in a subframe of the current DocShell, the subframe
+ // loadType may need to reflect the loadType of the parent document, or in
+ // some cases (like reloads), the history load may need to be cancelled. See
+ // function comments for in-depth logic descriptions.
+ // Returns true if the method itself deals with the load.
+ bool MaybeHandleSubframeHistory(nsDocShellLoadState* aLoadState,
+ bool aContinueHandlingSubframeHistory);
+
+ // If we are passed a named target during InternalLoad, this method handles
+ // moving the load to the browsing context the target name resolves to.
+ nsresult PerformRetargeting(nsDocShellLoadState* aLoadState);
+
+ // Returns one of nsIContentPolicy::TYPE_DOCUMENT,
+ // nsIContentPolicy::TYPE_INTERNAL_IFRAME, or
+ // nsIContentPolicy::TYPE_INTERNAL_FRAME depending on who is responsible for
+ // this docshell.
+ nsContentPolicyType DetermineContentType();
+
+ // If this is an iframe, and the embedder is OOP, then notifes the
+ // embedder that loading has finished and we shouldn't be blocking
+ // load of the embedder. Only called when we fail to load, as we wait
+ // for the load event of our Document before notifying success.
+ //
+ // If aFireFrameErrorEvent is true, then fires an error event at the
+ // embedder element, for both in-process and OOP embedders.
+ void UnblockEmbedderLoadEventForFailure(bool aFireFrameErrorEvent = false);
+
+ struct SameDocumentNavigationState {
+ nsAutoCString mCurrentHash;
+ nsAutoCString mNewHash;
+ bool mCurrentURIHasRef = false;
+ bool mNewURIHasRef = false;
+ bool mSameExceptHashes = false;
+ bool mSecureUpgradeURI = false;
+ bool mHistoryNavBetweenSameDoc = false;
+ };
+
+ // Check to see if we're loading a prior history entry or doing a fragment
+ // navigation in the same document.
+ // NOTE: In case we are doing a fragment navigation, and HTTPS-Only/ -First
+ // mode is enabled and upgraded the underlying document, we update the URI of
+ // aLoadState from HTTP to HTTPS (if neccessary).
+ bool IsSameDocumentNavigation(nsDocShellLoadState* aLoadState,
+ SameDocumentNavigationState& aState);
+
+ // ... If so, handle the scrolling or other action required instead of
+ // continuing with new document navigation.
+ MOZ_CAN_RUN_SCRIPT
+ nsresult HandleSameDocumentNavigation(nsDocShellLoadState* aLoadState,
+ SameDocumentNavigationState& aState);
+
+ uint32_t GetSameDocumentNavigationFlags(nsIURI* aNewURI);
+
+ // Called when the Private Browsing state of a nsDocShell changes.
+ void NotifyPrivateBrowsingChanged();
+
+ // Internal helpers for BrowsingContext to pass update values to nsIDocShell's
+ // LoadGroup.
+ void SetLoadGroupDefaultLoadFlags(nsLoadFlags aLoadFlags);
+
+ void SetTitleOnHistoryEntry(bool aUpdateEntryInSessionHistory);
+
+ void SetScrollRestorationIsManualOnHistoryEntry(nsISHEntry* aSHEntry,
+ bool aIsManual);
+
+ void SetCacheKeyOnHistoryEntry(nsISHEntry* aSHEntry, uint32_t aCacheKey);
+
+ // If the LoadState's URI is a javascript: URI, checks that the triggering
+ // principal subsumes the principal of the current document, and returns
+ // NS_ERROR_DOM_BAD_CROSS_ORIGIN_URI if it does not.
+ nsresult CheckDisallowedJavascriptLoad(nsDocShellLoadState* aLoadState);
+
+ nsresult LoadURI(nsDocShellLoadState* aLoadState, bool aSetNavigating,
+ bool aContinueHandlingSubframeHistory);
+
+ // Sets the active entry to the current loading entry. aPersist is used in the
+ // case a new session history entry is added to the session history.
+ // aExpired is true if the relevant nsIChannel has its cache token expired.
+ // aCacheKey is the channel's cache key.
+ // aPreviousURI should be the URI that was previously loaded into the
+ // nsDocshell
+ void MoveLoadingToActiveEntry(bool aPersist, bool aExpired,
+ uint32_t aCacheKey, nsIURI* aPreviousURI);
+
+ void ActivenessMaybeChanged();
+
+ /**
+ * Returns true if `noopener` will be force-enabled by any attempt to create
+ * a popup window, even if rel="opener" is requested.
+ */
+ bool NoopenerForceEnabled();
+
+ bool ShouldOpenInBlankTarget(const nsAString& aOriginalTarget,
+ nsIURI* aLinkURI, nsIContent* aContent,
+ bool aIsUserTriggered);
+
+ void RecordSingleChannelId(bool aStartRequest, nsIRequest* aRequest);
+
+ void SetChannelToDisconnectOnPageHide(uint64_t aChannelId) {
+ MOZ_ASSERT(mChannelToDisconnectOnPageHide == 0);
+ mChannelToDisconnectOnPageHide = aChannelId;
+ }
+ void MaybeDisconnectChildListenersOnPageHide();
+
+ /**
+ * Helper for addState and document.open that does just the
+ * history-manipulation guts.
+ *
+ * Arguments the spec defines:
+ *
+ * @param aDocument the document we're manipulating. This will get the new
+ * URI.
+ * @param aNewURI the new URI.
+ * @param aData The serialized state data. May be null.
+ * @param aTitle The new title. May be empty.
+ * @param aReplace whether this should replace the exising SHEntry.
+ *
+ * Arguments we need internally because deriving them from the
+ * others is a bit complicated:
+ *
+ * @param aCurrentURI the current URI we're working with. Might be null.
+ * @param aEqualURIs whether the two URIs involved are equal.
+ */
+ nsresult UpdateURLAndHistory(mozilla::dom::Document* aDocument,
+ nsIURI* aNewURI,
+ nsIStructuredCloneContainer* aData,
+ const nsAString& aTitle, bool aReplace,
+ nsIURI* aCurrentURI, bool aEqualURIs);
+
+ private: // data members
+ nsString mTitle;
+ nsCString mOriginalUriString;
+ nsTObserverArray<nsWeakPtr> mPrivacyObservers;
+ nsTObserverArray<nsWeakPtr> mReflowObservers;
+ nsTObserverArray<nsWeakPtr> mScrollObservers;
+ mozilla::UniquePtr<mozilla::dom::ClientSource> mInitialClientSource;
+ nsCOMPtr<nsINetworkInterceptController> mInterceptController;
+ RefPtr<nsDOMNavigationTiming> mTiming;
+ RefPtr<nsDSURIContentListener> mContentListener;
+ RefPtr<nsGlobalWindowOuter> mScriptGlobal;
+ nsCOMPtr<nsIPrincipal> mParentCharsetPrincipal;
+ // The following 3 lists contain either nsITimer or nsRefreshTimer objects.
+ // URIs to refresh are collected to mRefreshURIList.
+ nsCOMPtr<nsIMutableArray> mRefreshURIList;
+ // mSavedRefreshURIList is used to move the entries from mRefreshURIList to
+ // mOSHE.
+ nsCOMPtr<nsIMutableArray> mSavedRefreshURIList;
+ // BFCache-in-parent implementation caches the entries in
+ // mBFCachedRefreshURIList.
+ nsCOMPtr<nsIMutableArray> mBFCachedRefreshURIList;
+ uint64_t mContentWindowID;
+ nsCOMPtr<nsIContentViewer> mContentViewer;
+ nsCOMPtr<nsIWidget> mParentWidget;
+ RefPtr<mozilla::dom::ChildSHistory> mSessionHistory;
+ nsCOMPtr<nsIWebBrowserFind> mFind;
+ RefPtr<nsCommandManager> mCommandManager;
+ RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext;
+
+ // Weak reference to our BrowserChild actor.
+ nsWeakPtr mBrowserChild;
+
+ // Dimensions of the docshell
+ nsIntRect mBounds;
+
+ /**
+ * Content-Type Hint of the most-recently initiated load. Used for
+ * session history entries.
+ */
+ nsCString mContentTypeHint;
+
+ // An observed docshell wrapper is created when recording markers is enabled.
+ mozilla::UniquePtr<mozilla::ObservedDocShell> mObserved;
+
+ // mCurrentURI should be marked immutable on set if possible.
+ nsCOMPtr<nsIURI> mCurrentURI;
+ nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
+
+#ifdef DEBUG
+ // We're counting the number of |nsDocShells| to help find leaks
+ static unsigned long gNumberOfDocShells;
+
+ nsCOMPtr<nsIURI> mLastOpenedURI;
+#endif
+
+ // Reference to the SHEntry for this docshell until the page is destroyed.
+ // Somebody give me better name
+ // Only used when SHIP is disabled.
+ nsCOMPtr<nsISHEntry> mOSHE;
+
+ // Reference to the SHEntry for this docshell until the page is loaded
+ // Somebody give me better name.
+ // If mLSHE is non-null, non-pushState subframe loads don't create separate
+ // root history entries. That is, frames loaded during the parent page
+ // load don't generate history entries the way frame navigation after the
+ // parent has loaded does. (This isn't the only purpose of mLSHE.)
+ // Only used when SHIP is disabled.
+ nsCOMPtr<nsISHEntry> mLSHE;
+
+ // These are only set when fission.sessionHistoryInParent is set.
+ mozilla::UniquePtr<mozilla::dom::SessionHistoryInfo> mActiveEntry;
+ bool mActiveEntryIsLoadingFromSessionHistory = false;
+ // mLoadingEntry is set when we're about to start loading. Whenever
+ // setting mLoadingEntry, be sure to also set
+ // mNeedToReportActiveAfterLoadingBecomesActive.
+ mozilla::UniquePtr<mozilla::dom::LoadingSessionHistoryInfo> mLoadingEntry;
+
+ // Holds a weak pointer to a RestorePresentationEvent object if any that
+ // holds a weak pointer back to us. We use this pointer to possibly revoke
+ // the event whenever necessary.
+ nsRevocableEventPtr<RestorePresentationEvent> mRestorePresentationEvent;
+
+ // Editor data, if this document is designMode or contentEditable.
+ mozilla::UniquePtr<nsDocShellEditorData> mEditorData;
+
+ // The URI we're currently loading. This is only relevant during the
+ // firing of a pagehide/unload. The caller of FirePageHideNotification()
+ // is responsible for setting it and unsetting it. It may be null if the
+ // pagehide/unload is happening for some reason other than just loading a
+ // new URI.
+ nsCOMPtr<nsIURI> mLoadingURI;
+
+ // Set in LoadErrorPage from the method argument and used later
+ // in CreateContentViewer. We have to delay an shistory entry creation
+ // for which these objects are needed.
+ nsCOMPtr<nsIURI> mFailedURI;
+ nsCOMPtr<nsIChannel> mFailedChannel;
+
+ mozilla::UniquePtr<mozilla::gfx::Matrix5x4> mColorMatrix;
+
+ const mozilla::Encoding* mParentCharset;
+
+ // WEAK REFERENCES BELOW HERE.
+ // Note these are intentionally not addrefd. Doing so will create a cycle.
+ // For that reasons don't use nsCOMPtr.
+
+ nsIDocShellTreeOwner* mTreeOwner; // Weak Reference
+
+ RefPtr<mozilla::dom::EventTarget> mChromeEventHandler;
+
+ mozilla::ScrollbarPreference mScrollbarPref; // persistent across doc loads
+
+ eCharsetReloadState mCharsetReloadState;
+
+ int32_t mParentCharsetSource;
+ mozilla::CSSIntSize mFrameMargins;
+
+ // This can either be a content docshell or a chrome docshell.
+ const int32_t mItemType;
+
+ // Index into the nsISHEntry array, indicating the previous and current
+ // entry at the time that this DocShell begins to load. Consequently
+ // root docshell's indices can differ from child docshells'.
+ int32_t mPreviousEntryIndex;
+ int32_t mLoadedEntryIndex;
+
+ BusyFlags mBusyFlags;
+ AppType mAppType;
+ uint32_t mLoadType;
+ uint32_t mFailedLoadType;
+
+ // A depth count of how many times NotifyRunToCompletionStart
+ // has been called without a matching NotifyRunToCompletionStop.
+ uint32_t mJSRunToCompletionDepth;
+
+ // Whether or not handling of the <meta name="viewport"> tag is overridden.
+ // Possible values are defined as constants in nsIDocShell.idl.
+ MetaViewportOverride mMetaViewportOverride;
+
+ // See WindowGlobalParent::mSingleChannelId.
+ mozilla::Maybe<uint64_t> mSingleChannelId;
+ uint32_t mRequestForBlockingFromBFCacheCount = 0;
+
+ uint64_t mChannelToDisconnectOnPageHide;
+
+ uint32_t mPendingReloadCount = 0;
+
+ // The following two fields cannot be declared as bit fields
+ // because of uses with AutoRestore.
+ bool mCreatingDocument; // (should be) debugging only
+#ifdef DEBUG
+ bool mInEnsureScriptEnv;
+ uint64_t mDocShellID = 0;
+#endif
+
+ bool mInitialized : 1;
+ bool mAllowSubframes : 1;
+ bool mAllowMetaRedirects : 1;
+ bool mAllowImages : 1;
+ bool mAllowMedia : 1;
+ bool mAllowDNSPrefetch : 1;
+ bool mAllowWindowControl : 1;
+ bool mCSSErrorReportingEnabled : 1;
+ bool mAllowAuth : 1;
+ bool mAllowKeywordFixup : 1;
+ bool mDisableMetaRefreshWhenInactive : 1;
+ bool mIsAppTab : 1;
+ bool mWindowDraggingAllowed : 1;
+ bool mInFrameSwap : 1;
+
+ // This boolean is set to true right before we fire pagehide and generally
+ // unset when we embed a new content viewer. While it's true no navigation
+ // is allowed in this docshell.
+ bool mFiredUnloadEvent : 1;
+
+ // this flag is for bug #21358. a docshell may load many urls
+ // which don't result in new documents being created (i.e. a new
+ // content viewer) we want to make sure we don't call a on load
+ // event more than once for a given content viewer.
+ bool mEODForCurrentDocument : 1;
+ bool mURIResultedInDocument : 1;
+
+ bool mIsBeingDestroyed : 1;
+
+ bool mIsExecutingOnLoadHandler : 1;
+
+ // Indicates to CreateContentViewer() that it is safe to cache the old
+ // presentation of the page, and to SetupNewViewer() that the old viewer
+ // should be passed a SHEntry to save itself into.
+ // Only used with SHIP disabled.
+ bool mSavingOldViewer : 1;
+
+ bool mInvisible : 1;
+ bool mHasLoadedNonBlankURI : 1;
+
+ // This flag means that mTiming has been initialized but nulled out.
+ // We will check the innerWin's timing before creating a new one
+ // in MaybeInitTiming()
+ bool mBlankTiming : 1;
+
+ // This flag indicates when the title is valid for the current URI.
+ bool mTitleValidForCurrentURI : 1;
+
+ // If mWillChangeProcess is set to true, then when the docshell is destroyed,
+ // we prepare the browsing context to change process.
+ bool mWillChangeProcess : 1;
+
+ // This flag indicates whether or not the DocShell is currently executing an
+ // nsIWebNavigation navigation method.
+ bool mIsNavigating : 1;
+
+ // Whether we have a pending encoding autodetection request from the
+ // menu for all encodings.
+ bool mForcedAutodetection : 1;
+
+ /*
+ * Set to true if we're checking session history (in the parent process) for
+ * a possible history load. Used only with iframes.
+ */
+ bool mCheckingSessionHistory : 1;
+
+ // Whether mBrowsingContext->SetActiveSessionHistoryEntry() needs to be called
+ // when the loading entry becomes the active entry. This is used for the
+ // initial about:blank-replacing about:blank in order to make the history
+ // length WPTs pass.
+ bool mNeedToReportActiveAfterLoadingBecomesActive : 1;
+};
+
+inline nsISupports* ToSupports(nsDocShell* aDocShell) {
+ return static_cast<nsIDocumentLoader*>(aDocShell);
+}
+
+#endif /* nsDocShell_h__ */
diff --git a/docshell/base/nsDocShellEditorData.cpp b/docshell/base/nsDocShellEditorData.cpp
new file mode 100644
index 0000000000..6fe132a977
--- /dev/null
+++ b/docshell/base/nsDocShellEditorData.cpp
@@ -0,0 +1,139 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDocShellEditorData.h"
+
+#include "mozilla/dom/Document.h"
+#include "mozilla/HTMLEditor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsPIDOMWindow.h"
+#include "nsEditingSession.h"
+#include "nsIDocShell.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsDocShellEditorData::nsDocShellEditorData(nsIDocShell* aOwningDocShell)
+ : mDocShell(aOwningDocShell),
+ mDetachedEditingState(Document::EditingState::eOff),
+ mMakeEditable(false),
+ mIsDetached(false),
+ mDetachedMakeEditable(false) {
+ NS_ASSERTION(mDocShell, "Where is my docShell?");
+}
+
+nsDocShellEditorData::~nsDocShellEditorData() { TearDownEditor(); }
+
+void nsDocShellEditorData::TearDownEditor() {
+ if (mHTMLEditor) {
+ RefPtr<HTMLEditor> htmlEditor = std::move(mHTMLEditor);
+ htmlEditor->PreDestroy();
+ }
+ mEditingSession = nullptr;
+ mIsDetached = false;
+}
+
+nsresult nsDocShellEditorData::MakeEditable(bool aInWaitForUriLoad) {
+ if (mMakeEditable) {
+ return NS_OK;
+ }
+
+ // if we are already editable, and are getting turned off,
+ // nuke the editor.
+ if (mHTMLEditor) {
+ NS_WARNING("Destroying existing editor on frame");
+
+ RefPtr<HTMLEditor> htmlEditor = std::move(mHTMLEditor);
+ htmlEditor->PreDestroy();
+ }
+
+ if (aInWaitForUriLoad) {
+ mMakeEditable = true;
+ }
+ return NS_OK;
+}
+
+bool nsDocShellEditorData::GetEditable() {
+ return mMakeEditable || (mHTMLEditor != nullptr);
+}
+
+nsEditingSession* nsDocShellEditorData::GetEditingSession() {
+ EnsureEditingSession();
+
+ return mEditingSession.get();
+}
+
+nsresult nsDocShellEditorData::SetHTMLEditor(HTMLEditor* aHTMLEditor) {
+ // destroy any editor that we have. Checks for equality are
+ // necessary to ensure that assigment into the nsCOMPtr does
+ // not temporarily reduce the refCount of the editor to zero
+ if (mHTMLEditor == aHTMLEditor) {
+ return NS_OK;
+ }
+
+ if (mHTMLEditor) {
+ RefPtr<HTMLEditor> htmlEditor = std::move(mHTMLEditor);
+ htmlEditor->PreDestroy();
+ MOZ_ASSERT(!mHTMLEditor,
+ "Nested call of nsDocShellEditorData::SetHTMLEditor() detected");
+ }
+
+ mHTMLEditor = aHTMLEditor; // owning addref
+ if (!mHTMLEditor) {
+ mMakeEditable = false;
+ }
+
+ return NS_OK;
+}
+
+// This creates the editing session on the content docShell that owns 'this'.
+void nsDocShellEditorData::EnsureEditingSession() {
+ NS_ASSERTION(mDocShell, "Should have docShell here");
+ NS_ASSERTION(!mIsDetached, "This will stomp editing session!");
+
+ if (!mEditingSession) {
+ mEditingSession = new nsEditingSession();
+ }
+}
+
+nsresult nsDocShellEditorData::DetachFromWindow() {
+ NS_ASSERTION(mEditingSession,
+ "Can't detach when we don't have a session to detach!");
+
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow =
+ mDocShell ? mDocShell->GetWindow() : nullptr;
+ nsresult rv = mEditingSession->DetachFromWindow(domWindow);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mIsDetached = true;
+ mDetachedMakeEditable = mMakeEditable;
+ mMakeEditable = false;
+
+ nsCOMPtr<dom::Document> doc = domWindow->GetDoc();
+ mDetachedEditingState = doc->GetEditingState();
+
+ mDocShell = nullptr;
+
+ return NS_OK;
+}
+
+nsresult nsDocShellEditorData::ReattachToWindow(nsIDocShell* aDocShell) {
+ mDocShell = aDocShell;
+
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow =
+ mDocShell ? mDocShell->GetWindow() : nullptr;
+ nsresult rv = mEditingSession->ReattachToWindow(domWindow);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mIsDetached = false;
+ mMakeEditable = mDetachedMakeEditable;
+
+ RefPtr<dom::Document> doc = domWindow->GetDoc();
+ doc->SetEditingState(mDetachedEditingState);
+
+ return NS_OK;
+}
diff --git a/docshell/base/nsDocShellEditorData.h b/docshell/base/nsDocShellEditorData.h
new file mode 100644
index 0000000000..27f840675b
--- /dev/null
+++ b/docshell/base/nsDocShellEditorData.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nsDocShellEditorData_h__
+#define nsDocShellEditorData_h__
+
+#ifndef nsCOMPtr_h___
+# include "nsCOMPtr.h"
+#endif
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/Document.h"
+
+class nsIDocShell;
+class nsEditingSession;
+
+namespace mozilla {
+class HTMLEditor;
+}
+
+class nsDocShellEditorData {
+ public:
+ explicit nsDocShellEditorData(nsIDocShell* aOwningDocShell);
+ ~nsDocShellEditorData();
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult MakeEditable(bool aWaitForUriLoad);
+ bool GetEditable();
+ nsEditingSession* GetEditingSession();
+ mozilla::HTMLEditor* GetHTMLEditor() const { return mHTMLEditor; }
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult
+ SetHTMLEditor(mozilla::HTMLEditor* aHTMLEditor);
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void TearDownEditor();
+ nsresult DetachFromWindow();
+ nsresult ReattachToWindow(nsIDocShell* aDocShell);
+ bool WaitingForLoad() const { return mMakeEditable; }
+
+ protected:
+ void EnsureEditingSession();
+
+ // The doc shell that owns us. Weak ref, since it always outlives us.
+ nsIDocShell* mDocShell;
+
+ // Only present for the content root docShell. Session is owned here.
+ RefPtr<nsEditingSession> mEditingSession;
+
+ // If this frame is editable, store HTML editor here. It's owned here.
+ RefPtr<mozilla::HTMLEditor> mHTMLEditor;
+
+ // Backup for the corresponding HTMLDocument's editing state while
+ // the editor is detached.
+ mozilla::dom::Document::EditingState mDetachedEditingState;
+
+ // Indicates whether to make an editor after a url load.
+ bool mMakeEditable;
+
+ // Denotes if the editor is detached from its window. The editor is detached
+ // while it's stored in the session history bfcache.
+ bool mIsDetached;
+
+ // Backup for mMakeEditable while the editor is detached.
+ bool mDetachedMakeEditable;
+};
+
+#endif // nsDocShellEditorData_h__
diff --git a/docshell/base/nsDocShellEnumerator.cpp b/docshell/base/nsDocShellEnumerator.cpp
new file mode 100644
index 0000000000..5ad0ad35e6
--- /dev/null
+++ b/docshell/base/nsDocShellEnumerator.cpp
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDocShellEnumerator.h"
+
+#include "nsDocShell.h"
+
+using namespace mozilla;
+
+nsDocShellEnumerator::nsDocShellEnumerator(
+ nsDocShellEnumerator::EnumerationDirection aDirection,
+ int32_t aDocShellType, nsDocShell& aRootItem)
+ : mRootItem(&aRootItem),
+ mDocShellType(aDocShellType),
+ mDirection(aDirection) {}
+
+nsresult nsDocShellEnumerator::BuildDocShellArray(
+ nsTArray<RefPtr<nsIDocShell>>& aItemArray) {
+ MOZ_ASSERT(mRootItem);
+
+ aItemArray.Clear();
+
+ if (mDirection == EnumerationDirection::Forwards) {
+ return BuildArrayRecursiveForwards(mRootItem, aItemArray);
+ }
+ MOZ_ASSERT(mDirection == EnumerationDirection::Backwards);
+ return BuildArrayRecursiveBackwards(mRootItem, aItemArray);
+}
+
+nsresult nsDocShellEnumerator::BuildArrayRecursiveForwards(
+ nsDocShell* aItem, nsTArray<RefPtr<nsIDocShell>>& aItemArray) {
+ nsresult rv;
+
+ // add this item to the array
+ if (mDocShellType == nsIDocShellTreeItem::typeAll ||
+ aItem->ItemType() == mDocShellType) {
+ if (!aItemArray.AppendElement(aItem, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ int32_t numChildren = aItem->ChildCount();
+
+ for (int32_t i = 0; i < numChildren; ++i) {
+ RefPtr<nsDocShell> curChild = aItem->GetInProcessChildAt(i);
+ MOZ_ASSERT(curChild);
+
+ rv = BuildArrayRecursiveForwards(curChild, aItemArray);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsDocShellEnumerator::BuildArrayRecursiveBackwards(
+ nsDocShell* aItem, nsTArray<RefPtr<nsIDocShell>>& aItemArray) {
+ nsresult rv;
+
+ uint32_t numChildren = aItem->ChildCount();
+
+ for (int32_t i = numChildren - 1; i >= 0; --i) {
+ RefPtr<nsDocShell> curChild = aItem->GetInProcessChildAt(i);
+ MOZ_ASSERT(curChild);
+
+ rv = BuildArrayRecursiveBackwards(curChild, aItemArray);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ // add this item to the array
+ if (mDocShellType == nsIDocShellTreeItem::typeAll ||
+ aItem->ItemType() == mDocShellType) {
+ if (!aItemArray.AppendElement(aItem, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ return NS_OK;
+}
diff --git a/docshell/base/nsDocShellEnumerator.h b/docshell/base/nsDocShellEnumerator.h
new file mode 100644
index 0000000000..668ddee7e9
--- /dev/null
+++ b/docshell/base/nsDocShellEnumerator.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDocShellEnumerator_h___
+#define nsDocShellEnumerator_h___
+
+#include "nsTArray.h"
+
+class nsDocShell;
+class nsIDocShell;
+
+class MOZ_STACK_CLASS nsDocShellEnumerator {
+ public:
+ enum class EnumerationDirection : uint8_t { Forwards, Backwards };
+
+ nsDocShellEnumerator(EnumerationDirection aDirection, int32_t aDocShellType,
+ nsDocShell& aRootItem);
+
+ public:
+ nsresult BuildDocShellArray(nsTArray<RefPtr<nsIDocShell>>& aItemArray);
+
+ private:
+ nsresult BuildArrayRecursiveForwards(
+ nsDocShell* aItem, nsTArray<RefPtr<nsIDocShell>>& aItemArray);
+ nsresult BuildArrayRecursiveBackwards(
+ nsDocShell* aItem, nsTArray<RefPtr<nsIDocShell>>& aItemArray);
+
+ private:
+ const RefPtr<nsDocShell> mRootItem;
+
+ const int32_t mDocShellType; // only want shells of this type
+
+ const EnumerationDirection mDirection;
+};
+
+#endif // nsDocShellEnumerator_h___
diff --git a/docshell/base/nsDocShellLoadState.cpp b/docshell/base/nsDocShellLoadState.cpp
new file mode 100644
index 0000000000..f12e146fbd
--- /dev/null
+++ b/docshell/base/nsDocShellLoadState.cpp
@@ -0,0 +1,1266 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDocShellLoadState.h"
+#include "nsIDocShell.h"
+#include "nsDocShell.h"
+#include "nsISHEntry.h"
+#include "nsIURIFixup.h"
+#include "nsIWebNavigation.h"
+#include "nsIChannel.h"
+#include "nsIURLQueryStringStripper.h"
+#include "nsNetUtil.h"
+#include "nsQueryObject.h"
+#include "ReferrerInfo.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Components.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/LoadURIOptionsBinding.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_fission.h"
+#include "mozilla/Telemetry.h"
+
+#include "mozilla/OriginAttributes.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/StaticPtr.h"
+
+#include "mozilla/dom/PContent.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// Global reference to the URI fixup service.
+static mozilla::StaticRefPtr<nsIURIFixup> sURIFixup;
+
+nsDocShellLoadState::nsDocShellLoadState(nsIURI* aURI)
+ : nsDocShellLoadState(aURI, nsContentUtils::GenerateLoadIdentifier()) {}
+
+nsDocShellLoadState::nsDocShellLoadState(
+ const DocShellLoadStateInit& aLoadState, mozilla::ipc::IProtocol* aActor,
+ bool* aReadSuccess)
+ : mNotifiedBeforeUnloadListeners(false),
+ mLoadIdentifier(aLoadState.LoadIdentifier()) {
+ // If we return early, we failed to read in the data.
+ *aReadSuccess = false;
+ if (!aLoadState.URI()) {
+ MOZ_ASSERT_UNREACHABLE("Cannot create a LoadState with a null URI!");
+ return;
+ }
+
+ mResultPrincipalURI = aLoadState.ResultPrincipalURI();
+ mResultPrincipalURIIsSome = aLoadState.ResultPrincipalURIIsSome();
+ mKeepResultPrincipalURIIfSet = aLoadState.KeepResultPrincipalURIIfSet();
+ mLoadReplace = aLoadState.LoadReplace();
+ mInheritPrincipal = aLoadState.InheritPrincipal();
+ mPrincipalIsExplicit = aLoadState.PrincipalIsExplicit();
+ mForceAllowDataURI = aLoadState.ForceAllowDataURI();
+ mIsExemptFromHTTPSOnlyMode = aLoadState.IsExemptFromHTTPSOnlyMode();
+ mOriginalFrameSrc = aLoadState.OriginalFrameSrc();
+ mIsFormSubmission = aLoadState.IsFormSubmission();
+ mLoadType = aLoadState.LoadType();
+ mTarget = aLoadState.Target();
+ mTargetBrowsingContext = aLoadState.TargetBrowsingContext();
+ mLoadFlags = aLoadState.LoadFlags();
+ mInternalLoadFlags = aLoadState.InternalLoadFlags();
+ mFirstParty = aLoadState.FirstParty();
+ mHasValidUserGestureActivation = aLoadState.HasValidUserGestureActivation();
+ mAllowFocusMove = aLoadState.AllowFocusMove();
+ mTypeHint = aLoadState.TypeHint();
+ mFileName = aLoadState.FileName();
+ mIsFromProcessingFrameAttributes =
+ aLoadState.IsFromProcessingFrameAttributes();
+ mReferrerInfo = aLoadState.ReferrerInfo();
+ mURI = aLoadState.URI();
+ mOriginalURI = aLoadState.OriginalURI();
+ mSourceBrowsingContext = aLoadState.SourceBrowsingContext();
+ mBaseURI = aLoadState.BaseURI();
+ mTriggeringPrincipal = aLoadState.TriggeringPrincipal();
+ mPrincipalToInherit = aLoadState.PrincipalToInherit();
+ mPartitionedPrincipalToInherit = aLoadState.PartitionedPrincipalToInherit();
+ mTriggeringSandboxFlags = aLoadState.TriggeringSandboxFlags();
+ mTriggeringRemoteType = aLoadState.TriggeringRemoteType();
+ mCsp = aLoadState.Csp();
+ mOriginalURIString = aLoadState.OriginalURIString();
+ mCancelContentJSEpoch = aLoadState.CancelContentJSEpoch();
+ mPostDataStream = aLoadState.PostDataStream();
+ mHeadersStream = aLoadState.HeadersStream();
+ mSrcdocData = aLoadState.SrcdocData();
+ mChannelInitialized = aLoadState.ChannelInitialized();
+ mIsMetaRefresh = aLoadState.IsMetaRefresh();
+ if (aLoadState.loadingSessionHistoryInfo().isSome()) {
+ mLoadingSessionHistoryInfo = MakeUnique<LoadingSessionHistoryInfo>(
+ aLoadState.loadingSessionHistoryInfo().ref());
+ }
+ mUnstrippedURI = aLoadState.UnstrippedURI();
+ mRemoteTypeOverride = aLoadState.RemoteTypeOverride();
+
+ // We know this was created remotely, as we just received it over IPC.
+ mWasCreatedRemotely = true;
+
+ // If we're in the parent process, potentially validate against a LoadState
+ // which we sent to the source content process.
+ if (XRE_IsParentProcess()) {
+ mozilla::ipc::IToplevelProtocol* top = aActor->ToplevelProtocol();
+ if (!top ||
+ top->GetProtocolId() != mozilla::ipc::ProtocolId::PContentMsgStart ||
+ top->GetSide() != mozilla::ipc::ParentSide) {
+ aActor->FatalError("nsDocShellLoadState must be received over PContent");
+ return;
+ }
+ ContentParent* cp = static_cast<ContentParent*>(top);
+
+ // If this load was sent down to the content process as a navigation
+ // request, ensure it still matches the one we sent down.
+ if (RefPtr<nsDocShellLoadState> originalState =
+ cp->TakePendingLoadStateForId(mLoadIdentifier)) {
+ if (const char* mismatch = ValidateWithOriginalState(originalState)) {
+ aActor->FatalError(
+ nsPrintfCString(
+ "nsDocShellLoadState %s changed while in content process",
+ mismatch)
+ .get());
+ return;
+ }
+ } else if (mTriggeringRemoteType != cp->GetRemoteType()) {
+ // If we don't have a previous load to compare to, the content process
+ // must be the triggering process.
+ aActor->FatalError(
+ "nsDocShellLoadState with invalid triggering remote type");
+ return;
+ }
+ }
+
+ // We successfully read in the data - return a success value.
+ *aReadSuccess = true;
+}
+
+nsDocShellLoadState::nsDocShellLoadState(const nsDocShellLoadState& aOther)
+ : mReferrerInfo(aOther.mReferrerInfo),
+ mURI(aOther.mURI),
+ mOriginalURI(aOther.mOriginalURI),
+ mResultPrincipalURI(aOther.mResultPrincipalURI),
+ mResultPrincipalURIIsSome(aOther.mResultPrincipalURIIsSome),
+ mTriggeringPrincipal(aOther.mTriggeringPrincipal),
+ mTriggeringSandboxFlags(aOther.mTriggeringSandboxFlags),
+ mCsp(aOther.mCsp),
+ mKeepResultPrincipalURIIfSet(aOther.mKeepResultPrincipalURIIfSet),
+ mLoadReplace(aOther.mLoadReplace),
+ mInheritPrincipal(aOther.mInheritPrincipal),
+ mPrincipalIsExplicit(aOther.mPrincipalIsExplicit),
+ mNotifiedBeforeUnloadListeners(aOther.mNotifiedBeforeUnloadListeners),
+ mPrincipalToInherit(aOther.mPrincipalToInherit),
+ mPartitionedPrincipalToInherit(aOther.mPartitionedPrincipalToInherit),
+ mForceAllowDataURI(aOther.mForceAllowDataURI),
+ mIsExemptFromHTTPSOnlyMode(aOther.mIsExemptFromHTTPSOnlyMode),
+ mOriginalFrameSrc(aOther.mOriginalFrameSrc),
+ mIsFormSubmission(aOther.mIsFormSubmission),
+ mLoadType(aOther.mLoadType),
+ mSHEntry(aOther.mSHEntry),
+ mTarget(aOther.mTarget),
+ mTargetBrowsingContext(aOther.mTargetBrowsingContext),
+ mPostDataStream(aOther.mPostDataStream),
+ mHeadersStream(aOther.mHeadersStream),
+ mSrcdocData(aOther.mSrcdocData),
+ mSourceBrowsingContext(aOther.mSourceBrowsingContext),
+ mBaseURI(aOther.mBaseURI),
+ mLoadFlags(aOther.mLoadFlags),
+ mInternalLoadFlags(aOther.mInternalLoadFlags),
+ mFirstParty(aOther.mFirstParty),
+ mHasValidUserGestureActivation(aOther.mHasValidUserGestureActivation),
+ mAllowFocusMove(aOther.mAllowFocusMove),
+ mTypeHint(aOther.mTypeHint),
+ mFileName(aOther.mFileName),
+ mIsFromProcessingFrameAttributes(aOther.mIsFromProcessingFrameAttributes),
+ mPendingRedirectedChannel(aOther.mPendingRedirectedChannel),
+ mOriginalURIString(aOther.mOriginalURIString),
+ mCancelContentJSEpoch(aOther.mCancelContentJSEpoch),
+ mLoadIdentifier(aOther.mLoadIdentifier),
+ mChannelInitialized(aOther.mChannelInitialized),
+ mIsMetaRefresh(aOther.mIsMetaRefresh),
+ mWasCreatedRemotely(aOther.mWasCreatedRemotely),
+ mUnstrippedURI(aOther.mUnstrippedURI),
+ mRemoteTypeOverride(aOther.mRemoteTypeOverride),
+ mTriggeringRemoteType(aOther.mTriggeringRemoteType) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ XRE_IsParentProcess(),
+ "Cloning a nsDocShellLoadState with the same load identifier is only "
+ "allowed in the parent process, as it could break triggering remote type "
+ "tracking in content.");
+ if (aOther.mLoadingSessionHistoryInfo) {
+ mLoadingSessionHistoryInfo = MakeUnique<LoadingSessionHistoryInfo>(
+ *aOther.mLoadingSessionHistoryInfo);
+ }
+}
+
+nsDocShellLoadState::nsDocShellLoadState(nsIURI* aURI, uint64_t aLoadIdentifier)
+ : mURI(aURI),
+ mResultPrincipalURIIsSome(false),
+ mTriggeringSandboxFlags(0),
+ mKeepResultPrincipalURIIfSet(false),
+ mLoadReplace(false),
+ mInheritPrincipal(false),
+ mPrincipalIsExplicit(false),
+ mNotifiedBeforeUnloadListeners(false),
+ mForceAllowDataURI(false),
+ mIsExemptFromHTTPSOnlyMode(false),
+ mOriginalFrameSrc(false),
+ mIsFormSubmission(false),
+ mLoadType(LOAD_NORMAL),
+ mTarget(),
+ mSrcdocData(VoidString()),
+ mLoadFlags(0),
+ mInternalLoadFlags(0),
+ mFirstParty(false),
+ mHasValidUserGestureActivation(false),
+ mAllowFocusMove(false),
+ mTypeHint(VoidCString()),
+ mFileName(VoidString()),
+ mIsFromProcessingFrameAttributes(false),
+ mLoadIdentifier(aLoadIdentifier),
+ mChannelInitialized(false),
+ mIsMetaRefresh(false),
+ mWasCreatedRemotely(false),
+ mTriggeringRemoteType(XRE_IsContentProcess()
+ ? ContentChild::GetSingleton()->GetRemoteType()
+ : NOT_REMOTE_TYPE) {
+ MOZ_ASSERT(aURI, "Cannot create a LoadState with a null URI!");
+}
+
+nsDocShellLoadState::~nsDocShellLoadState() {
+ if (mWasCreatedRemotely && XRE_IsContentProcess()) {
+ ContentChild::GetSingleton()->SendCleanupPendingLoadState(mLoadIdentifier);
+ }
+}
+
+nsresult nsDocShellLoadState::CreateFromPendingChannel(
+ nsIChannel* aPendingChannel, uint64_t aLoadIdentifier,
+ uint64_t aRegistrarId, nsDocShellLoadState** aResult) {
+ // Create the nsDocShellLoadState object with default state pulled from the
+ // passed-in channel.
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aPendingChannel->GetURI(getter_AddRefs(uri));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ RefPtr<nsDocShellLoadState> loadState =
+ new nsDocShellLoadState(uri, aLoadIdentifier);
+ loadState->mPendingRedirectedChannel = aPendingChannel;
+ loadState->mChannelRegistrarId = aRegistrarId;
+
+ // Pull relevant state from the channel, and store it on the
+ // nsDocShellLoadState.
+ nsCOMPtr<nsIURI> originalUri;
+ rv = aPendingChannel->GetOriginalURI(getter_AddRefs(originalUri));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ loadState->SetOriginalURI(originalUri);
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aPendingChannel->LoadInfo();
+ loadState->SetTriggeringPrincipal(loadInfo->TriggeringPrincipal());
+
+ // Return the newly created loadState.
+ loadState.forget(aResult);
+ return NS_OK;
+}
+
+static uint32_t WebNavigationFlagsToFixupFlags(nsIURI* aURI,
+ const nsACString& aURIString,
+ uint32_t aNavigationFlags) {
+ if (aURI) {
+ aNavigationFlags &= ~nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ }
+ uint32_t fixupFlags = nsIURIFixup::FIXUP_FLAG_NONE;
+ if (aNavigationFlags & nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) {
+ fixupFlags |= nsIURIFixup::FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
+ }
+ if (aNavigationFlags & nsIWebNavigation::LOAD_FLAGS_FIXUP_SCHEME_TYPOS) {
+ fixupFlags |= nsIURIFixup::FIXUP_FLAG_FIX_SCHEME_TYPOS;
+ }
+ return fixupFlags;
+};
+
+nsresult nsDocShellLoadState::CreateFromLoadURIOptions(
+ BrowsingContext* aBrowsingContext, const nsAString& aURI,
+ const LoadURIOptions& aLoadURIOptions, nsDocShellLoadState** aResult) {
+ uint32_t loadFlags = aLoadURIOptions.mLoadFlags;
+
+ NS_ASSERTION(
+ (loadFlags & nsDocShell::INTERNAL_LOAD_FLAGS_LOADURI_SETUP_FLAGS) == 0,
+ "Unexpected flags");
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_OK;
+
+ NS_ConvertUTF16toUTF8 uriString(aURI);
+ // Cleanup the empty spaces that might be on each end.
+ uriString.Trim(" ");
+ // Eliminate embedded newlines, which single-line text fields now allow:
+ uriString.StripCRLF();
+ NS_ENSURE_TRUE(!uriString.IsEmpty(), NS_ERROR_FAILURE);
+
+ // Just create a URI and see what happens...
+ rv = NS_NewURI(getter_AddRefs(uri), uriString);
+ bool fixup = true;
+ if (NS_SUCCEEDED(rv) && uri &&
+ (uri->SchemeIs("about") || uri->SchemeIs("chrome"))) {
+ // Avoid third party fixup as a performance optimization.
+ loadFlags &= ~nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ fixup = false;
+ } else if (!sURIFixup && !XRE_IsContentProcess()) {
+ nsCOMPtr<nsIURIFixup> uriFixup = components::URIFixup::Service();
+ if (uriFixup) {
+ sURIFixup = uriFixup;
+ ClearOnShutdown(&sURIFixup);
+ } else {
+ fixup = false;
+ }
+ }
+
+ nsAutoString searchProvider, keyword;
+ RefPtr<nsIInputStream> fixupStream;
+ if (fixup) {
+ uint32_t fixupFlags =
+ WebNavigationFlagsToFixupFlags(uri, uriString, loadFlags);
+
+ // If we don't allow keyword lookups for this URL string, make sure to
+ // update loadFlags to indicate this as well.
+ if (!(fixupFlags & nsIURIFixup::FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP)) {
+ loadFlags &= ~nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ }
+ // Ensure URIFixup will use the right search engine in Private Browsing.
+ if (aBrowsingContext->UsePrivateBrowsing()) {
+ fixupFlags |= nsIURIFixup::FIXUP_FLAG_PRIVATE_CONTEXT;
+ }
+
+ if (!XRE_IsContentProcess()) {
+ nsCOMPtr<nsIURIFixupInfo> fixupInfo;
+ sURIFixup->GetFixupURIInfo(uriString, fixupFlags,
+ getter_AddRefs(fixupInfo));
+ if (fixupInfo) {
+ // We could fix the uri, clear NS_ERROR_MALFORMED_URI.
+ rv = NS_OK;
+ fixupInfo->GetPreferredURI(getter_AddRefs(uri));
+ fixupInfo->SetConsumer(aBrowsingContext);
+ fixupInfo->GetKeywordProviderName(searchProvider);
+ fixupInfo->GetKeywordAsSent(keyword);
+ // GetFixupURIInfo only returns a post data stream if it succeeded
+ // and changed the URI, in which case we should override the
+ // passed-in post data by passing this as an override arg to
+ // our internal method.
+ fixupInfo->GetPostData(getter_AddRefs(fixupStream));
+
+ if (fixupInfo &&
+ loadFlags & nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) {
+ nsCOMPtr<nsIObserverService> serv = services::GetObserverService();
+ if (serv) {
+ serv->NotifyObservers(fixupInfo, "keyword-uri-fixup",
+ PromiseFlatString(aURI).get());
+ }
+ }
+ nsDocShell::MaybeNotifyKeywordSearchLoading(searchProvider, keyword);
+ }
+ }
+ }
+
+ if (rv == NS_ERROR_MALFORMED_URI) {
+ MOZ_ASSERT(!uri);
+ return rv;
+ }
+
+ if (NS_FAILED(rv) || !uri) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<nsDocShellLoadState> loadState;
+ rv = CreateFromLoadURIOptions(
+ aBrowsingContext, uri, aLoadURIOptions, loadFlags,
+ fixupStream ? fixupStream : aLoadURIOptions.mPostData,
+ getter_AddRefs(loadState));
+ NS_ENSURE_SUCCESS(rv, rv);
+ loadState->SetOriginalURIString(uriString);
+ loadState.forget(aResult);
+ return NS_OK;
+}
+
+nsresult nsDocShellLoadState::CreateFromLoadURIOptions(
+ BrowsingContext* aBrowsingContext, nsIURI* aURI,
+ const LoadURIOptions& aLoadURIOptions, nsDocShellLoadState** aResult) {
+ return CreateFromLoadURIOptions(aBrowsingContext, aURI, aLoadURIOptions,
+ aLoadURIOptions.mLoadFlags,
+ aLoadURIOptions.mPostData, aResult);
+}
+
+nsresult nsDocShellLoadState::CreateFromLoadURIOptions(
+ BrowsingContext* aBrowsingContext, nsIURI* aURI,
+ const LoadURIOptions& aLoadURIOptions, uint32_t aLoadFlagsOverride,
+ nsIInputStream* aPostDataOverride, nsDocShellLoadState** aResult) {
+ nsresult rv = NS_OK;
+ uint32_t loadFlags = aLoadFlagsOverride;
+ RefPtr<nsIInputStream> postData = aPostDataOverride;
+ uint64_t available;
+ if (postData) {
+ rv = postData->Available(&available);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (available == 0) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ if (aLoadURIOptions.mHeaders) {
+ rv = aLoadURIOptions.mHeaders->Available(&available);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (available == 0) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ bool forceAllowDataURI =
+ loadFlags & nsIWebNavigation::LOAD_FLAGS_FORCE_ALLOW_DATA_URI;
+
+ // Don't pass certain flags that aren't needed and end up confusing
+ // ConvertLoadTypeToDocShellInfoLoadType. We do need to ensure that they are
+ // passed to LoadURI though, since it uses them.
+ uint32_t extraFlags = (loadFlags & EXTRA_LOAD_FLAGS);
+ loadFlags &= ~EXTRA_LOAD_FLAGS;
+
+ RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(aURI);
+ loadState->SetReferrerInfo(aLoadURIOptions.mReferrerInfo);
+
+ loadState->SetLoadType(MAKE_LOAD_TYPE(LOAD_NORMAL, loadFlags));
+
+ loadState->SetLoadFlags(extraFlags);
+ loadState->SetFirstParty(true);
+ loadState->SetHasValidUserGestureActivation(
+ aLoadURIOptions.mHasValidUserGestureActivation);
+ loadState->SetTriggeringSandboxFlags(aLoadURIOptions.mTriggeringSandboxFlags);
+ loadState->SetPostDataStream(postData);
+ loadState->SetHeadersStream(aLoadURIOptions.mHeaders);
+ loadState->SetBaseURI(aLoadURIOptions.mBaseURI);
+ loadState->SetTriggeringPrincipal(aLoadURIOptions.mTriggeringPrincipal);
+ loadState->SetCsp(aLoadURIOptions.mCsp);
+ loadState->SetForceAllowDataURI(forceAllowDataURI);
+ if (aLoadURIOptions.mCancelContentJSEpoch) {
+ loadState->SetCancelContentJSEpoch(aLoadURIOptions.mCancelContentJSEpoch);
+ }
+
+ if (aLoadURIOptions.mTriggeringRemoteType.WasPassed()) {
+ if (XRE_IsParentProcess()) {
+ loadState->SetTriggeringRemoteType(
+ aLoadURIOptions.mTriggeringRemoteType.Value());
+ } else if (ContentChild::GetSingleton()->GetRemoteType() !=
+ aLoadURIOptions.mTriggeringRemoteType.Value()) {
+ NS_WARNING("Invalid TriggeringRemoteType from LoadURIOptions in content");
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ if (aLoadURIOptions.mRemoteTypeOverride.WasPassed()) {
+ loadState->SetRemoteTypeOverride(
+ aLoadURIOptions.mRemoteTypeOverride.Value());
+ }
+
+ loadState.forget(aResult);
+ return NS_OK;
+}
+
+nsIReferrerInfo* nsDocShellLoadState::GetReferrerInfo() const {
+ return mReferrerInfo;
+}
+
+void nsDocShellLoadState::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
+ mReferrerInfo = aReferrerInfo;
+}
+
+nsIURI* nsDocShellLoadState::URI() const { return mURI; }
+
+void nsDocShellLoadState::SetURI(nsIURI* aURI) { mURI = aURI; }
+
+nsIURI* nsDocShellLoadState::OriginalURI() const { return mOriginalURI; }
+
+void nsDocShellLoadState::SetOriginalURI(nsIURI* aOriginalURI) {
+ mOriginalURI = aOriginalURI;
+}
+
+nsIURI* nsDocShellLoadState::ResultPrincipalURI() const {
+ return mResultPrincipalURI;
+}
+
+void nsDocShellLoadState::SetResultPrincipalURI(nsIURI* aResultPrincipalURI) {
+ mResultPrincipalURI = aResultPrincipalURI;
+}
+
+bool nsDocShellLoadState::ResultPrincipalURIIsSome() const {
+ return mResultPrincipalURIIsSome;
+}
+
+void nsDocShellLoadState::SetResultPrincipalURIIsSome(bool aIsSome) {
+ mResultPrincipalURIIsSome = aIsSome;
+}
+
+bool nsDocShellLoadState::KeepResultPrincipalURIIfSet() const {
+ return mKeepResultPrincipalURIIfSet;
+}
+
+void nsDocShellLoadState::SetKeepResultPrincipalURIIfSet(bool aKeep) {
+ mKeepResultPrincipalURIIfSet = aKeep;
+}
+
+bool nsDocShellLoadState::LoadReplace() const { return mLoadReplace; }
+
+void nsDocShellLoadState::SetLoadReplace(bool aLoadReplace) {
+ mLoadReplace = aLoadReplace;
+}
+
+nsIPrincipal* nsDocShellLoadState::TriggeringPrincipal() const {
+ return mTriggeringPrincipal;
+}
+
+void nsDocShellLoadState::SetTriggeringPrincipal(
+ nsIPrincipal* aTriggeringPrincipal) {
+ mTriggeringPrincipal = aTriggeringPrincipal;
+}
+
+nsIPrincipal* nsDocShellLoadState::PrincipalToInherit() const {
+ return mPrincipalToInherit;
+}
+
+void nsDocShellLoadState::SetPrincipalToInherit(
+ nsIPrincipal* aPrincipalToInherit) {
+ mPrincipalToInherit = aPrincipalToInherit;
+}
+
+nsIPrincipal* nsDocShellLoadState::PartitionedPrincipalToInherit() const {
+ return mPartitionedPrincipalToInherit;
+}
+
+void nsDocShellLoadState::SetPartitionedPrincipalToInherit(
+ nsIPrincipal* aPartitionedPrincipalToInherit) {
+ mPartitionedPrincipalToInherit = aPartitionedPrincipalToInherit;
+}
+
+void nsDocShellLoadState::SetCsp(nsIContentSecurityPolicy* aCsp) {
+ mCsp = aCsp;
+}
+
+nsIContentSecurityPolicy* nsDocShellLoadState::Csp() const { return mCsp; }
+
+void nsDocShellLoadState::SetTriggeringSandboxFlags(uint32_t flags) {
+ mTriggeringSandboxFlags = flags;
+}
+
+uint32_t nsDocShellLoadState::TriggeringSandboxFlags() const {
+ return mTriggeringSandboxFlags;
+}
+
+bool nsDocShellLoadState::InheritPrincipal() const { return mInheritPrincipal; }
+
+void nsDocShellLoadState::SetInheritPrincipal(bool aInheritPrincipal) {
+ mInheritPrincipal = aInheritPrincipal;
+}
+
+bool nsDocShellLoadState::PrincipalIsExplicit() const {
+ return mPrincipalIsExplicit;
+}
+
+void nsDocShellLoadState::SetPrincipalIsExplicit(bool aPrincipalIsExplicit) {
+ mPrincipalIsExplicit = aPrincipalIsExplicit;
+}
+
+bool nsDocShellLoadState::NotifiedBeforeUnloadListeners() const {
+ return mNotifiedBeforeUnloadListeners;
+}
+
+void nsDocShellLoadState::SetNotifiedBeforeUnloadListeners(
+ bool aNotifiedBeforeUnloadListeners) {
+ mNotifiedBeforeUnloadListeners = aNotifiedBeforeUnloadListeners;
+}
+
+bool nsDocShellLoadState::ForceAllowDataURI() const {
+ return mForceAllowDataURI;
+}
+
+void nsDocShellLoadState::SetForceAllowDataURI(bool aForceAllowDataURI) {
+ mForceAllowDataURI = aForceAllowDataURI;
+}
+
+bool nsDocShellLoadState::IsExemptFromHTTPSOnlyMode() const {
+ return mIsExemptFromHTTPSOnlyMode;
+}
+
+void nsDocShellLoadState::SetIsExemptFromHTTPSOnlyMode(
+ bool aIsExemptFromHTTPSOnlyMode) {
+ mIsExemptFromHTTPSOnlyMode = aIsExemptFromHTTPSOnlyMode;
+}
+
+bool nsDocShellLoadState::OriginalFrameSrc() const { return mOriginalFrameSrc; }
+
+void nsDocShellLoadState::SetOriginalFrameSrc(bool aOriginalFrameSrc) {
+ mOriginalFrameSrc = aOriginalFrameSrc;
+}
+
+bool nsDocShellLoadState::IsFormSubmission() const { return mIsFormSubmission; }
+
+void nsDocShellLoadState::SetIsFormSubmission(bool aIsFormSubmission) {
+ mIsFormSubmission = aIsFormSubmission;
+}
+
+uint32_t nsDocShellLoadState::LoadType() const { return mLoadType; }
+
+void nsDocShellLoadState::SetLoadType(uint32_t aLoadType) {
+ mLoadType = aLoadType;
+}
+
+nsISHEntry* nsDocShellLoadState::SHEntry() const { return mSHEntry; }
+
+void nsDocShellLoadState::SetSHEntry(nsISHEntry* aSHEntry) {
+ mSHEntry = aSHEntry;
+ nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(aSHEntry);
+ if (she) {
+ mLoadingSessionHistoryInfo = MakeUnique<LoadingSessionHistoryInfo>(she);
+ } else {
+ mLoadingSessionHistoryInfo = nullptr;
+ }
+}
+
+void nsDocShellLoadState::SetLoadingSessionHistoryInfo(
+ const mozilla::dom::LoadingSessionHistoryInfo& aLoadingInfo) {
+ SetLoadingSessionHistoryInfo(
+ MakeUnique<mozilla::dom::LoadingSessionHistoryInfo>(aLoadingInfo));
+}
+
+void nsDocShellLoadState::SetLoadingSessionHistoryInfo(
+ mozilla::UniquePtr<mozilla::dom::LoadingSessionHistoryInfo> aLoadingInfo) {
+ mLoadingSessionHistoryInfo = std::move(aLoadingInfo);
+}
+
+const mozilla::dom::LoadingSessionHistoryInfo*
+nsDocShellLoadState::GetLoadingSessionHistoryInfo() const {
+ return mLoadingSessionHistoryInfo.get();
+}
+
+void nsDocShellLoadState::SetLoadIsFromSessionHistory(
+ int32_t aOffset, bool aLoadingCurrentEntry) {
+ if (mLoadingSessionHistoryInfo) {
+ mLoadingSessionHistoryInfo->mLoadIsFromSessionHistory = true;
+ mLoadingSessionHistoryInfo->mOffset = aOffset;
+ mLoadingSessionHistoryInfo->mLoadingCurrentEntry = aLoadingCurrentEntry;
+ }
+}
+
+void nsDocShellLoadState::ClearLoadIsFromSessionHistory() {
+ if (mLoadingSessionHistoryInfo) {
+ mLoadingSessionHistoryInfo->mLoadIsFromSessionHistory = false;
+ }
+ mSHEntry = nullptr;
+}
+
+bool nsDocShellLoadState::LoadIsFromSessionHistory() const {
+ return mLoadingSessionHistoryInfo
+ ? mLoadingSessionHistoryInfo->mLoadIsFromSessionHistory
+ : !!mSHEntry;
+}
+
+void nsDocShellLoadState::MaybeStripTrackerQueryStrings(
+ BrowsingContext* aContext) {
+ MOZ_ASSERT(aContext);
+
+ // Return early if the triggering principal doesn't exist. This could happen
+ // when loading a URL by using a browsing context in the Browser Toolbox.
+ if (!TriggeringPrincipal()) {
+ return;
+ }
+
+ // We don't need to strip for sub frames because the query string has been
+ // stripped in the top-level content. Also, we don't apply stripping if it
+ // is triggered by addons.
+ //
+ // Note that we don't need to do the stripping if the channel has been
+ // initialized. This means that this has been loaded speculatively in the
+ // parent process before and the stripping was happening by then.
+ if (GetChannelInitialized() || !aContext->IsTopContent() ||
+ BasePrincipal::Cast(TriggeringPrincipal())->AddonPolicy()) {
+ return;
+ }
+
+ // We don't strip the URI if it's the same-site navigation. Note that we will
+ // consider the system principal triggered load as third-party in case the
+ // user copies and pastes a URL which has tracking query parameters or an
+ // loading from external applications, such as clicking a link in an email
+ // client.
+ bool isThirdPartyURI = false;
+ if (!TriggeringPrincipal()->IsSystemPrincipal() &&
+ (NS_FAILED(
+ TriggeringPrincipal()->IsThirdPartyURI(URI(), &isThirdPartyURI)) ||
+ !isThirdPartyURI)) {
+ return;
+ }
+
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_QUERY_STRIPPING_COUNT::Navigation);
+
+ nsCOMPtr<nsIURI> strippedURI;
+
+ nsresult rv;
+ nsCOMPtr<nsIURLQueryStringStripper> queryStripper =
+ components::URLQueryStringStripper::Service(&rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ uint32_t numStripped;
+
+ queryStripper->Strip(URI(), aContext->UsePrivateBrowsing(),
+ getter_AddRefs(strippedURI), &numStripped);
+ if (numStripped) {
+ if (!mUnstrippedURI) {
+ mUnstrippedURI = URI();
+ }
+ SetURI(strippedURI);
+
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_QUERY_STRIPPING_COUNT::StripForNavigation);
+ Telemetry::Accumulate(Telemetry::QUERY_STRIPPING_PARAM_COUNT, numStripped);
+ }
+
+#ifdef DEBUG
+ // Make sure that unstripped URI is the same as URI() but only the query
+ // string could be different.
+ if (mUnstrippedURI) {
+ nsCOMPtr<nsIURI> uri;
+ Unused << queryStripper->Strip(mUnstrippedURI,
+ aContext->UsePrivateBrowsing(),
+ getter_AddRefs(uri), &numStripped);
+ bool equals = false;
+ Unused << URI()->Equals(uri, &equals);
+ MOZ_ASSERT(equals);
+ }
+#endif
+}
+
+const nsString& nsDocShellLoadState::Target() const { return mTarget; }
+
+void nsDocShellLoadState::SetTarget(const nsAString& aTarget) {
+ mTarget = aTarget;
+}
+
+nsIInputStream* nsDocShellLoadState::PostDataStream() const {
+ return mPostDataStream;
+}
+
+void nsDocShellLoadState::SetPostDataStream(nsIInputStream* aStream) {
+ mPostDataStream = aStream;
+}
+
+nsIInputStream* nsDocShellLoadState::HeadersStream() const {
+ return mHeadersStream;
+}
+
+void nsDocShellLoadState::SetHeadersStream(nsIInputStream* aHeadersStream) {
+ mHeadersStream = aHeadersStream;
+}
+
+const nsString& nsDocShellLoadState::SrcdocData() const { return mSrcdocData; }
+
+void nsDocShellLoadState::SetSrcdocData(const nsAString& aSrcdocData) {
+ mSrcdocData = aSrcdocData;
+}
+
+void nsDocShellLoadState::SetSourceBrowsingContext(
+ BrowsingContext* aSourceBrowsingContext) {
+ mSourceBrowsingContext = aSourceBrowsingContext;
+}
+
+void nsDocShellLoadState::SetTargetBrowsingContext(
+ BrowsingContext* aTargetBrowsingContext) {
+ mTargetBrowsingContext = aTargetBrowsingContext;
+}
+
+nsIURI* nsDocShellLoadState::BaseURI() const { return mBaseURI; }
+
+void nsDocShellLoadState::SetBaseURI(nsIURI* aBaseURI) { mBaseURI = aBaseURI; }
+
+void nsDocShellLoadState::GetMaybeResultPrincipalURI(
+ mozilla::Maybe<nsCOMPtr<nsIURI>>& aRPURI) const {
+ bool isSome = ResultPrincipalURIIsSome();
+ aRPURI.reset();
+
+ if (!isSome) {
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri = ResultPrincipalURI();
+ aRPURI.emplace(std::move(uri));
+}
+
+void nsDocShellLoadState::SetMaybeResultPrincipalURI(
+ mozilla::Maybe<nsCOMPtr<nsIURI>> const& aRPURI) {
+ SetResultPrincipalURI(aRPURI.refOr(nullptr));
+ SetResultPrincipalURIIsSome(aRPURI.isSome());
+}
+
+uint32_t nsDocShellLoadState::LoadFlags() const { return mLoadFlags; }
+
+void nsDocShellLoadState::SetLoadFlags(uint32_t aLoadFlags) {
+ mLoadFlags = aLoadFlags;
+}
+
+void nsDocShellLoadState::SetLoadFlag(uint32_t aFlag) { mLoadFlags |= aFlag; }
+
+void nsDocShellLoadState::UnsetLoadFlag(uint32_t aFlag) {
+ mLoadFlags &= ~aFlag;
+}
+
+bool nsDocShellLoadState::HasLoadFlags(uint32_t aFlags) {
+ return (mLoadFlags & aFlags) == aFlags;
+}
+
+uint32_t nsDocShellLoadState::InternalLoadFlags() const {
+ return mInternalLoadFlags;
+}
+
+void nsDocShellLoadState::SetInternalLoadFlags(uint32_t aLoadFlags) {
+ mInternalLoadFlags = aLoadFlags;
+}
+
+void nsDocShellLoadState::SetInternalLoadFlag(uint32_t aFlag) {
+ mInternalLoadFlags |= aFlag;
+}
+
+void nsDocShellLoadState::UnsetInternalLoadFlag(uint32_t aFlag) {
+ mInternalLoadFlags &= ~aFlag;
+}
+
+bool nsDocShellLoadState::HasInternalLoadFlags(uint32_t aFlags) {
+ return (mInternalLoadFlags & aFlags) == aFlags;
+}
+
+bool nsDocShellLoadState::FirstParty() const { return mFirstParty; }
+
+void nsDocShellLoadState::SetFirstParty(bool aFirstParty) {
+ mFirstParty = aFirstParty;
+}
+
+bool nsDocShellLoadState::HasValidUserGestureActivation() const {
+ return mHasValidUserGestureActivation;
+}
+
+void nsDocShellLoadState::SetHasValidUserGestureActivation(
+ bool aHasValidUserGestureActivation) {
+ mHasValidUserGestureActivation = aHasValidUserGestureActivation;
+}
+
+const nsCString& nsDocShellLoadState::TypeHint() const { return mTypeHint; }
+
+void nsDocShellLoadState::SetTypeHint(const nsCString& aTypeHint) {
+ mTypeHint = aTypeHint;
+}
+
+const nsString& nsDocShellLoadState::FileName() const { return mFileName; }
+
+void nsDocShellLoadState::SetFileName(const nsAString& aFileName) {
+ MOZ_DIAGNOSTIC_ASSERT(aFileName.FindChar(char16_t(0)) == kNotFound,
+ "The filename should never contain null characters");
+ mFileName = aFileName;
+}
+
+const nsCString& nsDocShellLoadState::GetEffectiveTriggeringRemoteType() const {
+ // Consider non-errorpage loads from session history as being triggred by the
+ // parent process, as we'll validate them against the history entry.
+ //
+ // NOTE: Keep this check in-sync with the session-history validation check in
+ // `DocumentLoadListener::Open`!
+ if (LoadIsFromSessionHistory() && LoadType() != LOAD_ERROR_PAGE) {
+ return NOT_REMOTE_TYPE;
+ }
+ return mTriggeringRemoteType;
+}
+
+void nsDocShellLoadState::SetTriggeringRemoteType(
+ const nsACString& aTriggeringRemoteType) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess(), "only settable in parent");
+ mTriggeringRemoteType = aTriggeringRemoteType;
+}
+
+nsresult nsDocShellLoadState::SetupInheritingPrincipal(
+ BrowsingContext::Type aType,
+ const mozilla::OriginAttributes& aOriginAttributes) {
+ // We need a principalToInherit.
+ //
+ // If principalIsExplicit is not set there are 4 possibilities:
+ // (1) If the system principal or an expanded principal was passed
+ // in and we're a typeContent docshell, inherit the principal
+ // from the current document instead.
+ // (2) In all other cases when the principal passed in is not null,
+ // use that principal.
+ // (3) If the caller has allowed inheriting from the current document,
+ // or if we're being called from system code (eg chrome JS or pure
+ // C++) then inheritPrincipal should be true and InternalLoad will get
+ // a principal from the current document. If none of these things are
+ // true, then
+ // (4) we don't pass a principal into the channel, and a principal will be
+ // created later from the channel's internal data.
+ //
+ // If principalIsExplicit *is* set, there are 4 possibilities
+ // (1) If the system principal or an expanded principal was passed in
+ // and we're a typeContent docshell, return an error.
+ // (2) In all other cases when the principal passed in is not null,
+ // use that principal.
+ // (3) If the caller has allowed inheriting from the current document,
+ // then inheritPrincipal should be true and InternalLoad will get
+ // a principal from the current document. If none of these things are
+ // true, then
+ // (4) we dont' pass a principal into the channel, and a principal will be
+ // created later from the channel's internal data.
+ mPrincipalToInherit = mTriggeringPrincipal;
+ if (mPrincipalToInherit && aType != BrowsingContext::Type::Chrome) {
+ if (mPrincipalToInherit->IsSystemPrincipal()) {
+ if (mPrincipalIsExplicit) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ mPrincipalToInherit = nullptr;
+ mInheritPrincipal = true;
+ } else if (nsContentUtils::IsExpandedPrincipal(mPrincipalToInherit)) {
+ if (mPrincipalIsExplicit) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+ // Don't inherit from the current page. Just do the safe thing
+ // and pretend that we were loaded by a nullprincipal.
+ //
+ // We didn't inherit OriginAttributes here as ExpandedPrincipal doesn't
+ // have origin attributes.
+ mPrincipalToInherit = NullPrincipal::Create(aOriginAttributes);
+ mInheritPrincipal = false;
+ }
+ }
+
+ if (!mPrincipalToInherit && !mInheritPrincipal && !mPrincipalIsExplicit) {
+ // See if there's system or chrome JS code running
+ mInheritPrincipal = nsContentUtils::LegacyIsCallerChromeOrNativeCode();
+ }
+
+ if (mLoadFlags & nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL) {
+ mInheritPrincipal = false;
+ // Create a new null principal URI based on our precursor principal.
+ nsCOMPtr<nsIURI> nullPrincipalURI =
+ NullPrincipal::CreateURI(mPrincipalToInherit);
+ // If mFirstParty is true and the pref 'privacy.firstparty.isolate' is
+ // enabled, we will set firstPartyDomain on the origin attributes.
+ OriginAttributes attrs(aOriginAttributes);
+ if (mFirstParty) {
+ attrs.SetFirstPartyDomain(true, nullPrincipalURI);
+ }
+ mPrincipalToInherit = NullPrincipal::Create(attrs, nullPrincipalURI);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsDocShellLoadState::SetupTriggeringPrincipal(
+ const mozilla::OriginAttributes& aOriginAttributes) {
+ // If the triggeringPrincipal is not set, we first try to create a principal
+ // from the referrer, since the referrer URI reflects the web origin that
+ // triggered the load. If there is no referrer URI, we fall back to using the
+ // SystemPrincipal. It's safe to assume that no provided triggeringPrincipal
+ // and no referrer simulate a load that was triggered by the system. It's
+ // important to note that this block of code needs to appear *after* the block
+ // where we munge the principalToInherit, because otherwise we would never
+ // enter code blocks checking if the principalToInherit is null and we will
+ // end up with a wrong inheritPrincipal flag.
+ if (!mTriggeringPrincipal) {
+ if (mReferrerInfo) {
+ nsCOMPtr<nsIURI> referrer = mReferrerInfo->GetOriginalReferrer();
+ mTriggeringPrincipal =
+ BasePrincipal::CreateContentPrincipal(referrer, aOriginAttributes);
+
+ if (!mTriggeringPrincipal) {
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ mTriggeringPrincipal = nsContentUtils::GetSystemPrincipal();
+ }
+ }
+ return NS_OK;
+}
+
+void nsDocShellLoadState::CalculateLoadURIFlags() {
+ if (mInheritPrincipal) {
+ MOZ_ASSERT(
+ !mPrincipalToInherit || !mPrincipalToInherit->IsSystemPrincipal(),
+ "Should not inherit SystemPrincipal");
+ mInternalLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL;
+ }
+
+ if (mReferrerInfo && !mReferrerInfo->GetSendReferrer()) {
+ mInternalLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER;
+ }
+ if (mLoadFlags & nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) {
+ mInternalLoadFlags |=
+ nsDocShell::INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ }
+
+ if (mLoadFlags & nsIWebNavigation::LOAD_FLAGS_FIRST_LOAD) {
+ mInternalLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_FIRST_LOAD;
+ }
+
+ if (mLoadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CLASSIFIER) {
+ mInternalLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER;
+ }
+
+ if (mLoadFlags & nsIWebNavigation::LOAD_FLAGS_FORCE_ALLOW_COOKIES) {
+ mInternalLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES;
+ }
+
+ if (mLoadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE) {
+ mInternalLoadFlags |=
+ nsDocShell::INTERNAL_LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE;
+ }
+
+ if (!mSrcdocData.IsVoid()) {
+ mInternalLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_IS_SRCDOC;
+ }
+
+ if (mForceAllowDataURI) {
+ mInternalLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_FORCE_ALLOW_DATA_URI;
+ }
+
+ if (mOriginalFrameSrc) {
+ mInternalLoadFlags |= nsDocShell::INTERNAL_LOAD_FLAGS_ORIGINAL_FRAME_SRC;
+ }
+}
+
+nsLoadFlags nsDocShellLoadState::CalculateChannelLoadFlags(
+ BrowsingContext* aBrowsingContext, Maybe<bool> aUriModified,
+ Maybe<bool> aIsXFOError) {
+ MOZ_ASSERT(aBrowsingContext);
+
+ nsLoadFlags loadFlags = aBrowsingContext->GetDefaultLoadFlags();
+
+ if (FirstParty()) {
+ // tag first party URL loads
+ loadFlags |= nsIChannel::LOAD_INITIAL_DOCUMENT_URI;
+ }
+
+ const uint32_t loadType = LoadType();
+
+ // These values aren't available for loads initiated in the Parent process.
+ MOZ_ASSERT_IF(loadType == LOAD_HISTORY, aUriModified.isSome());
+ MOZ_ASSERT_IF(loadType == LOAD_ERROR_PAGE, aIsXFOError.isSome());
+
+ if (loadType == LOAD_ERROR_PAGE) {
+ // Error pages are LOAD_BACKGROUND, unless it's an
+ // XFO error for which we want an error page to load
+ // but additionally want the onload() event to fire.
+ if (!*aIsXFOError) {
+ loadFlags |= nsIChannel::LOAD_BACKGROUND;
+ }
+ }
+
+ // Mark the channel as being a document URI and allow content sniffing...
+ loadFlags |=
+ nsIChannel::LOAD_DOCUMENT_URI | nsIChannel::LOAD_CALL_CONTENT_SNIFFERS;
+
+ if (nsDocShell::SandboxFlagsImplyCookies(
+ aBrowsingContext->GetSandboxFlags())) {
+ loadFlags |= nsIRequest::LOAD_DOCUMENT_NEEDS_COOKIE;
+ }
+
+ // Load attributes depend on load type...
+ switch (loadType) {
+ case LOAD_HISTORY: {
+ // Only send VALIDATE_NEVER if mLSHE's URI was never changed via
+ // push/replaceState (bug 669671).
+ if (!*aUriModified) {
+ loadFlags |= nsIRequest::VALIDATE_NEVER;
+ }
+ break;
+ }
+
+ case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE:
+ case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE:
+ loadFlags |=
+ nsIRequest::LOAD_BYPASS_CACHE | nsIRequest::LOAD_FRESH_CONNECTION;
+ [[fallthrough]];
+
+ case LOAD_REFRESH:
+ loadFlags |= nsIRequest::VALIDATE_ALWAYS;
+ break;
+
+ case LOAD_NORMAL_BYPASS_CACHE:
+ case LOAD_NORMAL_BYPASS_PROXY:
+ case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE:
+ case LOAD_RELOAD_BYPASS_CACHE:
+ case LOAD_RELOAD_BYPASS_PROXY:
+ case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
+ case LOAD_REPLACE_BYPASS_CACHE:
+ loadFlags |=
+ nsIRequest::LOAD_BYPASS_CACHE | nsIRequest::LOAD_FRESH_CONNECTION;
+ break;
+
+ case LOAD_RELOAD_NORMAL:
+ if (!StaticPrefs::
+ browser_soft_reload_only_force_validate_top_level_document()) {
+ loadFlags |= nsIRequest::VALIDATE_ALWAYS;
+ break;
+ }
+ [[fallthrough]];
+ case LOAD_NORMAL:
+ case LOAD_LINK:
+ // Set cache checking flags
+ switch (StaticPrefs::browser_cache_check_doc_frequency()) {
+ case 0:
+ loadFlags |= nsIRequest::VALIDATE_ONCE_PER_SESSION;
+ break;
+ case 1:
+ loadFlags |= nsIRequest::VALIDATE_ALWAYS;
+ break;
+ case 2:
+ loadFlags |= nsIRequest::VALIDATE_NEVER;
+ break;
+ }
+ break;
+ }
+
+ if (HasInternalLoadFlags(nsDocShell::INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER)) {
+ loadFlags |= nsIChannel::LOAD_BYPASS_URL_CLASSIFIER;
+ }
+
+ // If the user pressed shift-reload, then do not allow ServiceWorker
+ // interception to occur. See step 12.1 of the SW HandleFetch algorithm.
+ if (IsForceReloadType(loadType)) {
+ loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
+ }
+
+ return loadFlags;
+}
+
+const char* nsDocShellLoadState::ValidateWithOriginalState(
+ nsDocShellLoadState* aOriginalState) {
+ MOZ_ASSERT(mLoadIdentifier == aOriginalState->mLoadIdentifier);
+
+ // Check that `aOriginalState` is sufficiently similar to this state that
+ // they're performing the same load.
+ auto uriEq = [](nsIURI* a, nsIURI* b) -> bool {
+ bool eq = false;
+ return a == b || (a && b && NS_SUCCEEDED(a->Equals(b, &eq)) && eq);
+ };
+ if (!uriEq(mURI, aOriginalState->mURI)) {
+ return "URI";
+ }
+ if (!uriEq(mUnstrippedURI, aOriginalState->mUnstrippedURI)) {
+ return "UnstrippedURI";
+ }
+ if (!uriEq(mOriginalURI, aOriginalState->mOriginalURI)) {
+ return "OriginalURI";
+ }
+ if (!uriEq(mBaseURI, aOriginalState->mBaseURI)) {
+ return "BaseURI";
+ }
+
+ if (!mTriggeringPrincipal->Equals(aOriginalState->mTriggeringPrincipal)) {
+ return "TriggeringPrincipal";
+ }
+ if (mTriggeringSandboxFlags != aOriginalState->mTriggeringSandboxFlags) {
+ return "TriggeringSandboxFlags";
+ }
+ if (mTriggeringRemoteType != aOriginalState->mTriggeringRemoteType) {
+ return "TriggeringRemoteType";
+ }
+
+ if (mOriginalURIString != aOriginalState->mOriginalURIString) {
+ return "OriginalURIString";
+ }
+
+ if (mRemoteTypeOverride != aOriginalState->mRemoteTypeOverride) {
+ return "RemoteTypeOverride";
+ }
+
+ if (mSourceBrowsingContext.ContextId() !=
+ aOriginalState->mSourceBrowsingContext.ContextId()) {
+ return "SourceBrowsingContext";
+ }
+
+ // FIXME: Consider calculating less information in the target process so that
+ // we can validate more properties more easily.
+ // FIXME: Identify what other flags will not change when sent through a
+ // content process.
+
+ return nullptr;
+}
+
+DocShellLoadStateInit nsDocShellLoadState::Serialize(
+ mozilla::ipc::IProtocol* aActor) {
+ MOZ_ASSERT(aActor);
+ DocShellLoadStateInit loadState;
+ loadState.ResultPrincipalURI() = mResultPrincipalURI;
+ loadState.ResultPrincipalURIIsSome() = mResultPrincipalURIIsSome;
+ loadState.KeepResultPrincipalURIIfSet() = mKeepResultPrincipalURIIfSet;
+ loadState.LoadReplace() = mLoadReplace;
+ loadState.InheritPrincipal() = mInheritPrincipal;
+ loadState.PrincipalIsExplicit() = mPrincipalIsExplicit;
+ loadState.ForceAllowDataURI() = mForceAllowDataURI;
+ loadState.IsExemptFromHTTPSOnlyMode() = mIsExemptFromHTTPSOnlyMode;
+ loadState.OriginalFrameSrc() = mOriginalFrameSrc;
+ loadState.IsFormSubmission() = mIsFormSubmission;
+ loadState.LoadType() = mLoadType;
+ loadState.Target() = mTarget;
+ loadState.TargetBrowsingContext() = mTargetBrowsingContext;
+ loadState.LoadFlags() = mLoadFlags;
+ loadState.InternalLoadFlags() = mInternalLoadFlags;
+ loadState.FirstParty() = mFirstParty;
+ loadState.HasValidUserGestureActivation() = mHasValidUserGestureActivation;
+ loadState.AllowFocusMove() = mAllowFocusMove;
+ loadState.TypeHint() = mTypeHint;
+ loadState.FileName() = mFileName;
+ loadState.IsFromProcessingFrameAttributes() =
+ mIsFromProcessingFrameAttributes;
+ loadState.URI() = mURI;
+ loadState.OriginalURI() = mOriginalURI;
+ loadState.SourceBrowsingContext() = mSourceBrowsingContext;
+ loadState.BaseURI() = mBaseURI;
+ loadState.TriggeringPrincipal() = mTriggeringPrincipal;
+ loadState.PrincipalToInherit() = mPrincipalToInherit;
+ loadState.PartitionedPrincipalToInherit() = mPartitionedPrincipalToInherit;
+ loadState.TriggeringSandboxFlags() = mTriggeringSandboxFlags;
+ loadState.TriggeringRemoteType() = mTriggeringRemoteType;
+ loadState.Csp() = mCsp;
+ loadState.OriginalURIString() = mOriginalURIString;
+ loadState.CancelContentJSEpoch() = mCancelContentJSEpoch;
+ loadState.ReferrerInfo() = mReferrerInfo;
+ loadState.PostDataStream() = mPostDataStream;
+ loadState.HeadersStream() = mHeadersStream;
+ loadState.SrcdocData() = mSrcdocData;
+ loadState.ResultPrincipalURI() = mResultPrincipalURI;
+ loadState.LoadIdentifier() = mLoadIdentifier;
+ loadState.ChannelInitialized() = mChannelInitialized;
+ loadState.IsMetaRefresh() = mIsMetaRefresh;
+ if (mLoadingSessionHistoryInfo) {
+ loadState.loadingSessionHistoryInfo().emplace(*mLoadingSessionHistoryInfo);
+ }
+ loadState.UnstrippedURI() = mUnstrippedURI;
+ loadState.RemoteTypeOverride() = mRemoteTypeOverride;
+
+ if (XRE_IsParentProcess()) {
+ mozilla::ipc::IToplevelProtocol* top = aActor->ToplevelProtocol();
+ MOZ_RELEASE_ASSERT(top &&
+ top->GetProtocolId() ==
+ mozilla::ipc::ProtocolId::PContentMsgStart &&
+ top->GetSide() == mozilla::ipc::ParentSide,
+ "nsDocShellLoadState must be sent over PContent");
+ ContentParent* cp = static_cast<ContentParent*>(top);
+ cp->StorePendingLoadState(this);
+ }
+
+ return loadState;
+}
+
+nsIURI* nsDocShellLoadState::GetUnstrippedURI() const { return mUnstrippedURI; }
+
+void nsDocShellLoadState::SetUnstrippedURI(nsIURI* aUnstrippedURI) {
+ mUnstrippedURI = aUnstrippedURI;
+}
diff --git a/docshell/base/nsDocShellLoadState.h b/docshell/base/nsDocShellLoadState.h
new file mode 100644
index 0000000000..f74e4f24fb
--- /dev/null
+++ b/docshell/base/nsDocShellLoadState.h
@@ -0,0 +1,573 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDocShellLoadState_h__
+#define nsDocShellLoadState_h__
+
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/SessionHistoryEntry.h"
+
+// Helper Classes
+#include "mozilla/Maybe.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsDocShellLoadTypes.h"
+#include "nsTArrayForwardDeclare.h"
+
+class nsIContentSecurityPolicy;
+class nsIInputStream;
+class nsISHEntry;
+class nsIURI;
+class nsIDocShell;
+class nsIChannel;
+class nsIReferrerInfo;
+namespace mozilla {
+class OriginAttributes;
+template <typename, class>
+class UniquePtr;
+namespace dom {
+class DocShellLoadStateInit;
+} // namespace dom
+} // namespace mozilla
+
+/**
+ * nsDocShellLoadState contains setup information used in a nsIDocShell::loadURI
+ * call.
+ */
+class nsDocShellLoadState final {
+ using BrowsingContext = mozilla::dom::BrowsingContext;
+ template <typename T>
+ using MaybeDiscarded = mozilla::dom::MaybeDiscarded<T>;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING(nsDocShellLoadState);
+
+ explicit nsDocShellLoadState(nsIURI* aURI);
+ explicit nsDocShellLoadState(
+ const mozilla::dom::DocShellLoadStateInit& aLoadState,
+ mozilla::ipc::IProtocol* aActor, bool* aReadSuccess);
+ explicit nsDocShellLoadState(const nsDocShellLoadState& aOther);
+ nsDocShellLoadState(nsIURI* aURI, uint64_t aLoadIdentifier);
+
+ static nsresult CreateFromPendingChannel(nsIChannel* aPendingChannel,
+ uint64_t aLoadIdentifier,
+ uint64_t aRegistarId,
+ nsDocShellLoadState** aResult);
+
+ static nsresult CreateFromLoadURIOptions(
+ BrowsingContext* aBrowsingContext, const nsAString& aURI,
+ const mozilla::dom::LoadURIOptions& aLoadURIOptions,
+ nsDocShellLoadState** aResult);
+ static nsresult CreateFromLoadURIOptions(
+ BrowsingContext* aBrowsingContext, nsIURI* aURI,
+ const mozilla::dom::LoadURIOptions& aLoadURIOptions,
+ nsDocShellLoadState** aResult);
+
+ // Getters and Setters
+
+ nsIReferrerInfo* GetReferrerInfo() const;
+
+ void SetReferrerInfo(nsIReferrerInfo* aReferrerInfo);
+
+ nsIURI* URI() const;
+
+ void SetURI(nsIURI* aURI);
+
+ nsIURI* OriginalURI() const;
+
+ void SetOriginalURI(nsIURI* aOriginalURI);
+
+ nsIURI* ResultPrincipalURI() const;
+
+ void SetResultPrincipalURI(nsIURI* aResultPrincipalURI);
+
+ bool ResultPrincipalURIIsSome() const;
+
+ void SetResultPrincipalURIIsSome(bool aIsSome);
+
+ bool KeepResultPrincipalURIIfSet() const;
+
+ void SetKeepResultPrincipalURIIfSet(bool aKeep);
+
+ nsIPrincipal* PrincipalToInherit() const;
+
+ void SetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit);
+
+ nsIPrincipal* PartitionedPrincipalToInherit() const;
+
+ void SetPartitionedPrincipalToInherit(
+ nsIPrincipal* aPartitionedPrincipalToInherit);
+
+ bool LoadReplace() const;
+
+ void SetLoadReplace(bool aLoadReplace);
+
+ nsIPrincipal* TriggeringPrincipal() const;
+
+ void SetTriggeringPrincipal(nsIPrincipal* aTriggeringPrincipal);
+
+ uint32_t TriggeringSandboxFlags() const;
+
+ void SetTriggeringSandboxFlags(uint32_t aTriggeringSandboxFlags);
+
+ nsIContentSecurityPolicy* Csp() const;
+
+ void SetCsp(nsIContentSecurityPolicy* aCsp);
+
+ bool InheritPrincipal() const;
+
+ void SetInheritPrincipal(bool aInheritPrincipal);
+
+ bool PrincipalIsExplicit() const;
+
+ void SetPrincipalIsExplicit(bool aPrincipalIsExplicit);
+
+ // If true, "beforeunload" event listeners were notified by the creater of the
+ // LoadState and given the chance to abort the navigation, and should not be
+ // notified again.
+ bool NotifiedBeforeUnloadListeners() const;
+
+ void SetNotifiedBeforeUnloadListeners(bool aNotifiedBeforeUnloadListeners);
+
+ bool ForceAllowDataURI() const;
+
+ void SetForceAllowDataURI(bool aForceAllowDataURI);
+
+ bool IsExemptFromHTTPSOnlyMode() const;
+
+ void SetIsExemptFromHTTPSOnlyMode(bool aIsExemptFromHTTPSOnlyMode);
+
+ bool OriginalFrameSrc() const;
+
+ void SetOriginalFrameSrc(bool aOriginalFrameSrc);
+
+ bool IsFormSubmission() const;
+
+ void SetIsFormSubmission(bool aIsFormSubmission);
+
+ uint32_t LoadType() const;
+
+ void SetLoadType(uint32_t aLoadType);
+
+ nsISHEntry* SHEntry() const;
+
+ void SetSHEntry(nsISHEntry* aSHEntry);
+
+ const mozilla::dom::LoadingSessionHistoryInfo* GetLoadingSessionHistoryInfo()
+ const;
+
+ // Copies aLoadingInfo and stores the copy in this nsDocShellLoadState.
+ void SetLoadingSessionHistoryInfo(
+ const mozilla::dom::LoadingSessionHistoryInfo& aLoadingInfo);
+
+ // Stores aLoadingInfo in this nsDocShellLoadState.
+ void SetLoadingSessionHistoryInfo(
+ mozilla::UniquePtr<mozilla::dom::LoadingSessionHistoryInfo> aLoadingInfo);
+
+ bool LoadIsFromSessionHistory() const;
+
+ const nsString& Target() const;
+
+ void SetTarget(const nsAString& aTarget);
+
+ nsIInputStream* PostDataStream() const;
+
+ void SetPostDataStream(nsIInputStream* aStream);
+
+ nsIInputStream* HeadersStream() const;
+
+ void SetHeadersStream(nsIInputStream* aHeadersStream);
+
+ bool IsSrcdocLoad() const;
+
+ const nsString& SrcdocData() const;
+
+ void SetSrcdocData(const nsAString& aSrcdocData);
+
+ const MaybeDiscarded<BrowsingContext>& SourceBrowsingContext() const {
+ return mSourceBrowsingContext;
+ }
+
+ void SetSourceBrowsingContext(BrowsingContext*);
+
+ void SetAllowFocusMove(bool aAllow) { mAllowFocusMove = aAllow; }
+
+ bool AllowFocusMove() const { return mAllowFocusMove; }
+
+ const MaybeDiscarded<BrowsingContext>& TargetBrowsingContext() const {
+ return mTargetBrowsingContext;
+ }
+
+ void SetTargetBrowsingContext(BrowsingContext* aTargetBrowsingContext);
+
+ nsIURI* BaseURI() const;
+
+ void SetBaseURI(nsIURI* aBaseURI);
+
+ // Helper function allowing convenient work with mozilla::Maybe in C++, hiding
+ // resultPrincipalURI and resultPrincipalURIIsSome attributes from the
+ // consumer.
+ void GetMaybeResultPrincipalURI(
+ mozilla::Maybe<nsCOMPtr<nsIURI>>& aRPURI) const;
+
+ void SetMaybeResultPrincipalURI(
+ mozilla::Maybe<nsCOMPtr<nsIURI>> const& aRPURI);
+
+ uint32_t LoadFlags() const;
+
+ void SetLoadFlags(uint32_t aFlags);
+
+ void SetLoadFlag(uint32_t aFlag);
+
+ void UnsetLoadFlag(uint32_t aFlag);
+
+ bool HasLoadFlags(uint32_t aFlag);
+
+ uint32_t InternalLoadFlags() const;
+
+ void SetInternalLoadFlags(uint32_t aFlags);
+
+ void SetInternalLoadFlag(uint32_t aFlag);
+
+ void UnsetInternalLoadFlag(uint32_t aFlag);
+
+ bool HasInternalLoadFlags(uint32_t aFlag);
+
+ bool FirstParty() const;
+
+ void SetFirstParty(bool aFirstParty);
+
+ bool HasValidUserGestureActivation() const;
+
+ void SetHasValidUserGestureActivation(bool HasValidUserGestureActivation);
+
+ const nsCString& TypeHint() const;
+
+ void SetTypeHint(const nsCString& aTypeHint);
+
+ const nsString& FileName() const;
+
+ void SetFileName(const nsAString& aFileName);
+
+ nsIURI* GetUnstrippedURI() const;
+
+ void SetUnstrippedURI(nsIURI* aUnstrippedURI);
+
+ // Give the type of DocShell we're loading into (chrome/content/etc) and
+ // origin attributes for the URI we're loading, figure out if we should
+ // inherit our principal from the document the load was requested from, or
+ // else if the principal should be set up later in the process (after loads).
+ // See comments in function for more info on principal selection algorithm
+ nsresult SetupInheritingPrincipal(
+ mozilla::dom::BrowsingContext::Type aType,
+ const mozilla::OriginAttributes& aOriginAttributes);
+
+ // If no triggering principal exists at the moment, create one using referrer
+ // information and origin attributes.
+ nsresult SetupTriggeringPrincipal(
+ const mozilla::OriginAttributes& aOriginAttributes);
+
+ void SetIsFromProcessingFrameAttributes() {
+ mIsFromProcessingFrameAttributes = true;
+ }
+ bool GetIsFromProcessingFrameAttributes() const {
+ return mIsFromProcessingFrameAttributes;
+ }
+
+ nsIChannel* GetPendingRedirectedChannel() {
+ return mPendingRedirectedChannel;
+ }
+
+ uint64_t GetPendingRedirectChannelRegistrarId() const {
+ return mChannelRegistrarId;
+ }
+
+ void SetOriginalURIString(const nsCString& aOriginalURI) {
+ mOriginalURIString.emplace(aOriginalURI);
+ }
+ const mozilla::Maybe<nsCString>& GetOriginalURIString() const {
+ return mOriginalURIString;
+ }
+
+ void SetCancelContentJSEpoch(int32_t aCancelEpoch) {
+ mCancelContentJSEpoch.emplace(aCancelEpoch);
+ }
+ const mozilla::Maybe<int32_t>& GetCancelContentJSEpoch() const {
+ return mCancelContentJSEpoch;
+ }
+
+ uint64_t GetLoadIdentifier() const { return mLoadIdentifier; }
+
+ void SetChannelInitialized(bool aInitilized) {
+ mChannelInitialized = aInitilized;
+ }
+
+ bool GetChannelInitialized() const { return mChannelInitialized; }
+
+ void SetIsMetaRefresh(bool aMetaRefresh) { mIsMetaRefresh = aMetaRefresh; }
+
+ bool IsMetaRefresh() const { return mIsMetaRefresh; }
+
+ const mozilla::Maybe<nsCString>& GetRemoteTypeOverride() const {
+ return mRemoteTypeOverride;
+ }
+
+ void SetRemoteTypeOverride(const nsCString& aRemoteTypeOverride) {
+ mRemoteTypeOverride = mozilla::Some(aRemoteTypeOverride);
+ }
+
+ // Determine the remote type of the process which should be considered
+ // responsible for this load for the purposes of security checks.
+ //
+ // This will generally be the process which created the nsDocShellLoadState
+ // originally, however non-errorpage history loads are always considered to be
+ // triggered by the parent process, as we can validate them against the
+ // history entry.
+ const nsCString& GetEffectiveTriggeringRemoteType() const;
+
+ void SetTriggeringRemoteType(const nsACString& aTriggeringRemoteType);
+
+ // When loading a document through nsDocShell::LoadURI(), a special set of
+ // flags needs to be set based on other values in nsDocShellLoadState. This
+ // function calculates those flags, before the LoadState is passed to
+ // nsDocShell::InternalLoad.
+ void CalculateLoadURIFlags();
+
+ // Compute the load flags to be used by creating channel. aUriModified and
+ // aIsXFOError are expected to be Nothing when called from Parent process.
+ nsLoadFlags CalculateChannelLoadFlags(
+ mozilla::dom::BrowsingContext* aBrowsingContext,
+ mozilla::Maybe<bool> aUriModified, mozilla::Maybe<bool> aIsXFOError);
+
+ mozilla::dom::DocShellLoadStateInit Serialize(
+ mozilla::ipc::IProtocol* aActor);
+
+ void SetLoadIsFromSessionHistory(int32_t aOffset, bool aLoadingCurrentEntry);
+ void ClearLoadIsFromSessionHistory();
+
+ void MaybeStripTrackerQueryStrings(mozilla::dom::BrowsingContext* aContext);
+
+ protected:
+ // Destructor can't be defaulted or inlined, as header doesn't have all type
+ // includes it needs to do so.
+ ~nsDocShellLoadState();
+
+ // Given the original `nsDocShellLoadState` which was sent to a content
+ // process, validate that they corespond to the same load.
+ // Returns a static (telemetry-safe) string naming what did not match, or
+ // nullptr if it succeeds.
+ const char* ValidateWithOriginalState(nsDocShellLoadState* aOriginalState);
+
+ static nsresult CreateFromLoadURIOptions(
+ BrowsingContext* aBrowsingContext, nsIURI* aURI,
+ const mozilla::dom::LoadURIOptions& aLoadURIOptions,
+ uint32_t aLoadFlagsOverride, nsIInputStream* aPostDataOverride,
+ nsDocShellLoadState** aResult);
+
+ // This is the referrer for the load.
+ nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
+
+ // The URI we are navigating to. Will not be null once set.
+ nsCOMPtr<nsIURI> mURI;
+
+ // The URI to set as the originalURI on the channel that does the load. If
+ // null, aURI will be set as the originalURI.
+ nsCOMPtr<nsIURI> mOriginalURI;
+
+ // The URI to be set to loadInfo.resultPrincipalURI
+ // - When Nothing, there will be no change
+ // - When Some, the principal URI will overwrite even
+ // with a null value.
+ //
+ // Valid only if mResultPrincipalURIIsSome is true (has the same meaning as
+ // isSome() on mozilla::Maybe.)
+ nsCOMPtr<nsIURI> mResultPrincipalURI;
+ bool mResultPrincipalURIIsSome;
+
+ // The principal of the load, that is, the entity responsible for causing the
+ // load to occur. In most cases the referrer and the triggeringPrincipal's URI
+ // will be identical.
+ //
+ // Please note that this is the principal that is used for security checks. If
+ // the argument aURI is provided by the web, then please do not pass a
+ // SystemPrincipal as the triggeringPrincipal.
+ nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
+
+ // The SandboxFlags of the load, that are, the SandboxFlags of the entity
+ // responsible for causing the load to occur. Most likely this are the
+ // SandboxFlags of the document that started the load.
+ uint32_t mTriggeringSandboxFlags;
+
+ // The CSP of the load, that is, the CSP of the entity responsible for causing
+ // the load to occur. Most likely this is the CSP of the document that started
+ // the load. In case the entity starting the load did not use a CSP, then mCsp
+ // can be null. Please note that this is also the CSP that will be applied to
+ // the load in case the load encounters a server side redirect.
+ nsCOMPtr<nsIContentSecurityPolicy> mCsp;
+
+ // If a refresh is caused by http-equiv="refresh" we want to set
+ // aResultPrincipalURI, but we do not want to overwrite the channel's
+ // ResultPrincipalURI, if it has already been set on the channel by a protocol
+ // handler.
+ bool mKeepResultPrincipalURIIfSet;
+
+ // If set LOAD_REPLACE flag will be set on the channel. If aOriginalURI is
+ // null, this argument is ignored.
+ bool mLoadReplace;
+
+ // If this attribute is true and no triggeringPrincipal is specified,
+ // copy the principal from the referring document.
+ bool mInheritPrincipal;
+
+ // If this attribute is true only ever use the principal specified
+ // by the triggeringPrincipal and inheritPrincipal attributes.
+ // If there are security reasons for why this is unsafe, such
+ // as trying to use a systemprincipal as the triggeringPrincipal
+ // for a content docshell the load fails.
+ bool mPrincipalIsExplicit;
+
+ bool mNotifiedBeforeUnloadListeners;
+
+ // Principal we're inheriting. If null, this means the principal should be
+ // inherited from the current document. If set to NullPrincipal, the channel
+ // will fill in principal information later in the load. See internal comments
+ // of SetupInheritingPrincipal for more info.
+ //
+ // When passed to InternalLoad, If this argument is null then
+ // principalToInherit is computed differently. See nsDocShell::InternalLoad
+ // for more comments.
+
+ nsCOMPtr<nsIPrincipal> mPrincipalToInherit;
+
+ nsCOMPtr<nsIPrincipal> mPartitionedPrincipalToInherit;
+
+ // If this attribute is true, then a top-level navigation
+ // to a data URI will be allowed.
+ bool mForceAllowDataURI;
+
+ // If this attribute is true, then the top-level navigaion
+ // will be exempt from HTTPS-Only-Mode upgrades.
+ bool mIsExemptFromHTTPSOnlyMode;
+
+ // If this attribute is true, this load corresponds to a frame
+ // element loading its original src (or srcdoc) attribute.
+ bool mOriginalFrameSrc;
+
+ // If this attribute is true, then the load was initiated by a
+ // form submission. This is important to know for the CSP directive
+ // navigate-to.
+ bool mIsFormSubmission;
+
+ // Contains a load type as specified by the nsDocShellLoadTypes::load*
+ // constants
+ uint32_t mLoadType;
+
+ // Active Session History entry (if loading from SH)
+ nsCOMPtr<nsISHEntry> mSHEntry;
+
+ // Loading session history info for the load
+ mozilla::UniquePtr<mozilla::dom::LoadingSessionHistoryInfo>
+ mLoadingSessionHistoryInfo;
+
+ // Target for load, like _content, _blank etc.
+ nsString mTarget;
+
+ // When set, this is the Target Browsing Context for the navigation
+ // after retargeting.
+ MaybeDiscarded<BrowsingContext> mTargetBrowsingContext;
+
+ // Post data stream (if POSTing)
+ nsCOMPtr<nsIInputStream> mPostDataStream;
+
+ // Additional Headers
+ nsCOMPtr<nsIInputStream> mHeadersStream;
+
+ // When set, the load will be interpreted as a srcdoc load, where contents of
+ // this string will be loaded instead of the URI. Setting srcdocData sets
+ // isSrcdocLoad to true
+ nsString mSrcdocData;
+
+ // When set, this is the Source Browsing Context for the navigation.
+ MaybeDiscarded<BrowsingContext> mSourceBrowsingContext;
+
+ // Used for srcdoc loads to give view-source knowledge of the load's base URI
+ // as this information isn't embedded in the load's URI.
+ nsCOMPtr<nsIURI> mBaseURI;
+
+ // Set of Load Flags, taken from nsDocShellLoadTypes.h and nsIWebNavigation
+ uint32_t mLoadFlags;
+
+ // Set of internal load flags
+ uint32_t mInternalLoadFlags;
+
+ // Is this a First Party Load?
+ bool mFirstParty;
+
+ // Is this load triggered by a user gesture?
+ bool mHasValidUserGestureActivation;
+
+ // Whether this load can steal the focus from the source browsing context.
+ bool mAllowFocusMove;
+
+ // A hint as to the content-type of the resulting data. If no hint, IsVoid()
+ // should return true.
+ nsCString mTypeHint;
+
+ // Non-void when the link should be downloaded as the given filename.
+ // mFileName being non-void but empty means that no filename hint was
+ // specified, but link should still trigger a download. If not a download,
+ // mFileName.IsVoid() should return true.
+ nsString mFileName;
+
+ // This will be true if this load is triggered by attribute changes.
+ // See nsILoadInfo.isFromProcessingFrameAttributes
+ bool mIsFromProcessingFrameAttributes;
+
+ // If set, a pending cross-process redirected channel should be used to
+ // perform the load. The channel will be stored in this value.
+ nsCOMPtr<nsIChannel> mPendingRedirectedChannel;
+
+ // An optional string representation of mURI, before any
+ // fixups were applied, so that we can send it to a search
+ // engine service if needed.
+ mozilla::Maybe<nsCString> mOriginalURIString;
+
+ // An optional value to pass to nsIDocShell::setCancelJSEpoch
+ // when initiating the load.
+ mozilla::Maybe<int32_t> mCancelContentJSEpoch;
+
+ // If mPendingRedirectChannel is set, then this is the identifier
+ // that the parent-process equivalent channel has been registered
+ // with using RedirectChannelRegistrar.
+ uint64_t mChannelRegistrarId;
+
+ // An identifier to make it possible to examine if two loads are
+ // equal, and which browsing context they belong to (see
+ // BrowsingContext::{Get, Set}CurrentLoadIdentifier)
+ const uint64_t mLoadIdentifier;
+
+ // Optional value to indicate that a channel has been
+ // pre-initialized in the parent process.
+ bool mChannelInitialized;
+
+ // True if the load was triggered by a meta refresh.
+ bool mIsMetaRefresh;
+
+ // True if the nsDocShellLoadState was received over IPC.
+ bool mWasCreatedRemotely = false;
+
+ // The original URI before query stripping happened. If it's present, it shows
+ // the query stripping happened. Otherwise, it will be a nullptr.
+ nsCOMPtr<nsIURI> mUnstrippedURI;
+
+ // If set, the remote type which the load should be completed within.
+ mozilla::Maybe<nsCString> mRemoteTypeOverride;
+
+ // Remote type of the process which originally requested the load.
+ nsCString mTriggeringRemoteType;
+};
+
+#endif /* nsDocShellLoadState_h__ */
diff --git a/docshell/base/nsDocShellLoadTypes.h b/docshell/base/nsDocShellLoadTypes.h
new file mode 100644
index 0000000000..1de19e81eb
--- /dev/null
+++ b/docshell/base/nsDocShellLoadTypes.h
@@ -0,0 +1,205 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDocShellLoadTypes_h_
+#define nsDocShellLoadTypes_h_
+
+#ifdef MOZILLA_INTERNAL_API
+
+# include "nsDOMNavigationTiming.h"
+# include "nsIDocShell.h"
+# include "nsIWebNavigation.h"
+
+/**
+ * Load flag for error pages. This uses one of the reserved flag
+ * values from nsIWebNavigation.
+ */
+# define LOAD_FLAGS_ERROR_PAGE 0x0001U
+
+# define MAKE_LOAD_TYPE(type, flags) ((type) | ((flags) << 16))
+# define LOAD_TYPE_HAS_FLAGS(type, flags) ((type) & ((flags) << 16))
+
+/**
+ * These are flags that confuse ConvertLoadTypeToDocShellLoadInfo and should
+ * not be passed to MAKE_LOAD_TYPE. In particular this includes all flags
+ * above 0xffff (e.g. LOAD_FLAGS_BYPASS_CLASSIFIER), since MAKE_LOAD_TYPE would
+ * just shift them out anyway.
+ */
+# define EXTRA_LOAD_FLAGS \
+ (nsIWebNavigation::LOAD_FLAGS_FROM_EXTERNAL | \
+ nsIWebNavigation::LOAD_FLAGS_FIRST_LOAD | \
+ nsIWebNavigation::LOAD_FLAGS_ALLOW_POPUPS | 0xffff0000)
+
+/* load types are legal combinations of load commands and flags
+ *
+ * NOTE:
+ * Remember to update the IsValidLoadType function below if you change this
+ * enum to ensure bad flag combinations will be rejected.
+ */
+enum LoadType : uint32_t {
+ LOAD_NORMAL = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_NONE),
+ LOAD_NORMAL_REPLACE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY),
+ LOAD_HISTORY = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_HISTORY,
+ nsIWebNavigation::LOAD_FLAGS_NONE),
+ LOAD_NORMAL_BYPASS_CACHE = MAKE_LOAD_TYPE(
+ nsIDocShell::LOAD_CMD_NORMAL, nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE),
+ LOAD_NORMAL_BYPASS_PROXY = MAKE_LOAD_TYPE(
+ nsIDocShell::LOAD_CMD_NORMAL, nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY),
+ LOAD_NORMAL_BYPASS_PROXY_AND_CACHE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE |
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY),
+ LOAD_RELOAD_NORMAL = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_RELOAD,
+ nsIWebNavigation::LOAD_FLAGS_NONE),
+ LOAD_RELOAD_BYPASS_CACHE = MAKE_LOAD_TYPE(
+ nsIDocShell::LOAD_CMD_RELOAD, nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE),
+ LOAD_RELOAD_BYPASS_PROXY = MAKE_LOAD_TYPE(
+ nsIDocShell::LOAD_CMD_RELOAD, nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY),
+ LOAD_RELOAD_BYPASS_PROXY_AND_CACHE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_RELOAD,
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE |
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY),
+ LOAD_LINK = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_IS_LINK),
+ LOAD_REFRESH = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_IS_REFRESH),
+ LOAD_REFRESH_REPLACE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_IS_REFRESH |
+ nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY),
+ LOAD_RELOAD_CHARSET_CHANGE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_RELOAD,
+ nsIWebNavigation::LOAD_FLAGS_CHARSET_CHANGE),
+ LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_RELOAD,
+ nsIWebNavigation::LOAD_FLAGS_CHARSET_CHANGE |
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE |
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY),
+ LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_RELOAD,
+ nsIWebNavigation::LOAD_FLAGS_CHARSET_CHANGE |
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE),
+ LOAD_BYPASS_HISTORY =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_HISTORY),
+ LOAD_STOP_CONTENT = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_STOP_CONTENT),
+ LOAD_STOP_CONTENT_AND_REPLACE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_STOP_CONTENT |
+ nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY),
+ LOAD_PUSHSTATE = MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_PUSHSTATE,
+ nsIWebNavigation::LOAD_FLAGS_NONE),
+ LOAD_REPLACE_BYPASS_CACHE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL,
+ nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY |
+ nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE),
+ /**
+ * Load type for an error page. These loads are never triggered by users of
+ * Docshell. Instead, Docshell triggers the load itself when a
+ * consumer-triggered load failed.
+ */
+ LOAD_ERROR_PAGE =
+ MAKE_LOAD_TYPE(nsIDocShell::LOAD_CMD_NORMAL, LOAD_FLAGS_ERROR_PAGE)
+
+ // NOTE: Adding a new value? Remember to update IsValidLoadType!
+};
+
+static inline bool IsForceReloadType(uint32_t aLoadType) {
+ switch (aLoadType) {
+ case LOAD_RELOAD_BYPASS_CACHE:
+ case LOAD_RELOAD_BYPASS_PROXY:
+ case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
+ return true;
+ }
+ return false;
+}
+
+static inline bool IsValidLoadType(uint32_t aLoadType) {
+ switch (aLoadType) {
+ case LOAD_NORMAL:
+ case LOAD_NORMAL_REPLACE:
+ case LOAD_NORMAL_BYPASS_CACHE:
+ case LOAD_NORMAL_BYPASS_PROXY:
+ case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE:
+ case LOAD_HISTORY:
+ case LOAD_RELOAD_NORMAL:
+ case LOAD_RELOAD_BYPASS_CACHE:
+ case LOAD_RELOAD_BYPASS_PROXY:
+ case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
+ case LOAD_LINK:
+ case LOAD_REFRESH:
+ case LOAD_REFRESH_REPLACE:
+ case LOAD_RELOAD_CHARSET_CHANGE:
+ case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE:
+ case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE:
+ case LOAD_BYPASS_HISTORY:
+ case LOAD_STOP_CONTENT:
+ case LOAD_STOP_CONTENT_AND_REPLACE:
+ case LOAD_PUSHSTATE:
+ case LOAD_REPLACE_BYPASS_CACHE:
+ case LOAD_ERROR_PAGE:
+ return true;
+ }
+ return false;
+}
+
+inline nsDOMNavigationTiming::Type ConvertLoadTypeToNavigationType(
+ uint32_t aLoadType) {
+ // Not initialized, assume it's normal load.
+ if (aLoadType == 0) {
+ aLoadType = LOAD_NORMAL;
+ }
+
+ auto result = nsDOMNavigationTiming::TYPE_RESERVED;
+ switch (aLoadType) {
+ case LOAD_NORMAL:
+ case LOAD_NORMAL_BYPASS_CACHE:
+ case LOAD_NORMAL_BYPASS_PROXY:
+ case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE:
+ case LOAD_NORMAL_REPLACE:
+ case LOAD_LINK:
+ case LOAD_STOP_CONTENT:
+ // FIXME: It isn't clear that LOAD_REFRESH_REPLACE should have a different
+ // navigation type than LOAD_REFRESH. Those loads historically used the
+ // LOAD_NORMAL_REPLACE type, and therefore wound up with TYPE_NAVIGATE by
+ // default.
+ case LOAD_REFRESH_REPLACE:
+ case LOAD_REPLACE_BYPASS_CACHE:
+ result = nsDOMNavigationTiming::TYPE_NAVIGATE;
+ break;
+ case LOAD_HISTORY:
+ result = nsDOMNavigationTiming::TYPE_BACK_FORWARD;
+ break;
+ case LOAD_RELOAD_NORMAL:
+ case LOAD_RELOAD_CHARSET_CHANGE:
+ case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE:
+ case LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE:
+ case LOAD_RELOAD_BYPASS_CACHE:
+ case LOAD_RELOAD_BYPASS_PROXY:
+ case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
+ result = nsDOMNavigationTiming::TYPE_RELOAD;
+ break;
+ case LOAD_STOP_CONTENT_AND_REPLACE:
+ case LOAD_REFRESH:
+ case LOAD_BYPASS_HISTORY:
+ case LOAD_ERROR_PAGE:
+ case LOAD_PUSHSTATE:
+ result = nsDOMNavigationTiming::TYPE_RESERVED;
+ break;
+ default:
+ result = nsDOMNavigationTiming::TYPE_RESERVED;
+ break;
+ }
+
+ return result;
+}
+
+#endif // MOZILLA_INTERNAL_API
+#endif
diff --git a/docshell/base/nsDocShellTelemetryUtils.cpp b/docshell/base/nsDocShellTelemetryUtils.cpp
new file mode 100644
index 0000000000..cd78e3bce5
--- /dev/null
+++ b/docshell/base/nsDocShellTelemetryUtils.cpp
@@ -0,0 +1,206 @@
+/* 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 "nsDocShellTelemetryUtils.h"
+
+namespace {
+
+using ErrorLabel = mozilla::Telemetry::LABELS_PAGE_LOAD_ERROR;
+
+struct LoadErrorTelemetryResult {
+ nsresult mValue;
+ ErrorLabel mLabel;
+};
+
+static const LoadErrorTelemetryResult sResult[] = {
+ {
+ NS_ERROR_UNKNOWN_PROTOCOL,
+ ErrorLabel::UNKNOWN_PROTOCOL,
+ },
+ {
+ NS_ERROR_FILE_NOT_FOUND,
+ ErrorLabel::FILE_NOT_FOUND,
+ },
+ {
+ NS_ERROR_FILE_ACCESS_DENIED,
+ ErrorLabel::FILE_ACCESS_DENIED,
+ },
+ {
+ NS_ERROR_UNKNOWN_HOST,
+ ErrorLabel::UNKNOWN_HOST,
+ },
+ {
+ NS_ERROR_CONNECTION_REFUSED,
+ ErrorLabel::CONNECTION_REFUSED,
+ },
+ {
+ NS_ERROR_PROXY_BAD_GATEWAY,
+ ErrorLabel::PROXY_BAD_GATEWAY,
+ },
+ {
+ NS_ERROR_NET_INTERRUPT,
+ ErrorLabel::NET_INTERRUPT,
+ },
+ {
+ NS_ERROR_NET_TIMEOUT,
+ ErrorLabel::NET_TIMEOUT,
+ },
+ {
+ NS_ERROR_PROXY_GATEWAY_TIMEOUT,
+ ErrorLabel::P_GATEWAY_TIMEOUT,
+ },
+ {
+ NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION,
+ ErrorLabel::CSP_FRAME_ANCEST,
+ },
+ {
+ NS_ERROR_CSP_FORM_ACTION_VIOLATION,
+ ErrorLabel::CSP_FORM_ACTION,
+ },
+ {
+ NS_ERROR_CSP_NAVIGATE_TO_VIOLATION,
+ ErrorLabel::CSP_NAVIGATE_TO,
+ },
+ {
+ NS_ERROR_XFO_VIOLATION,
+ ErrorLabel::XFO_VIOLATION,
+ },
+ {
+ NS_ERROR_PHISHING_URI,
+ ErrorLabel::PHISHING_URI,
+ },
+ {
+ NS_ERROR_MALWARE_URI,
+ ErrorLabel::MALWARE_URI,
+ },
+ {
+ NS_ERROR_UNWANTED_URI,
+ ErrorLabel::UNWANTED_URI,
+ },
+ {
+ NS_ERROR_HARMFUL_URI,
+ ErrorLabel::HARMFUL_URI,
+ },
+ {
+ NS_ERROR_CONTENT_CRASHED,
+ ErrorLabel::CONTENT_CRASHED,
+ },
+ {
+ NS_ERROR_FRAME_CRASHED,
+ ErrorLabel::FRAME_CRASHED,
+ },
+ {
+ NS_ERROR_BUILDID_MISMATCH,
+ ErrorLabel::BUILDID_MISMATCH,
+ },
+ {
+ NS_ERROR_NET_RESET,
+ ErrorLabel::NET_RESET,
+ },
+ {
+ NS_ERROR_MALFORMED_URI,
+ ErrorLabel::MALFORMED_URI,
+ },
+ {
+ NS_ERROR_REDIRECT_LOOP,
+ ErrorLabel::REDIRECT_LOOP,
+ },
+ {
+ NS_ERROR_UNKNOWN_SOCKET_TYPE,
+ ErrorLabel::UNKNOWN_SOCKET,
+ },
+ {
+ NS_ERROR_DOCUMENT_NOT_CACHED,
+ ErrorLabel::DOCUMENT_N_CACHED,
+ },
+ {
+ NS_ERROR_OFFLINE,
+ ErrorLabel::OFFLINE,
+ },
+ {
+ NS_ERROR_DOCUMENT_IS_PRINTMODE,
+ ErrorLabel::DOC_PRINTMODE,
+ },
+ {
+ NS_ERROR_PORT_ACCESS_NOT_ALLOWED,
+ ErrorLabel::PORT_ACCESS,
+ },
+ {
+ NS_ERROR_UNKNOWN_PROXY_HOST,
+ ErrorLabel::UNKNOWN_PROXY_HOST,
+ },
+ {
+ NS_ERROR_PROXY_CONNECTION_REFUSED,
+ ErrorLabel::PROXY_CONNECTION,
+ },
+ {
+ NS_ERROR_PROXY_FORBIDDEN,
+ ErrorLabel::PROXY_FORBIDDEN,
+ },
+ {
+ NS_ERROR_PROXY_NOT_IMPLEMENTED,
+ ErrorLabel::P_NOT_IMPLEMENTED,
+ },
+ {
+ NS_ERROR_PROXY_AUTHENTICATION_FAILED,
+ ErrorLabel::PROXY_AUTH,
+ },
+ {
+ NS_ERROR_PROXY_TOO_MANY_REQUESTS,
+ ErrorLabel::PROXY_TOO_MANY,
+ },
+ {
+ NS_ERROR_INVALID_CONTENT_ENCODING,
+ ErrorLabel::CONTENT_ENCODING,
+ },
+ {
+ NS_ERROR_UNSAFE_CONTENT_TYPE,
+ ErrorLabel::UNSAFE_CONTENT,
+ },
+ {
+ NS_ERROR_CORRUPTED_CONTENT,
+ ErrorLabel::CORRUPTED_CONTENT,
+ },
+ {
+ NS_ERROR_INTERCEPTION_FAILED,
+ ErrorLabel::INTERCEPTION_FAIL,
+ },
+ {
+ NS_ERROR_NET_INADEQUATE_SECURITY,
+ ErrorLabel::INADEQUATE_SEC,
+ },
+ {
+ NS_ERROR_BLOCKED_BY_POLICY,
+ ErrorLabel::BLOCKED_BY_POLICY,
+ },
+ {
+ NS_ERROR_NET_HTTP2_SENT_GOAWAY,
+ ErrorLabel::HTTP2_SENT_GOAWAY,
+ },
+ {
+ NS_ERROR_NET_HTTP3_PROTOCOL_ERROR,
+ ErrorLabel::HTTP3_PROTOCOL,
+ },
+ {
+ NS_BINDING_FAILED,
+ ErrorLabel::BINDING_FAILED,
+ },
+};
+} // anonymous namespace
+
+namespace mozilla {
+namespace dom {
+mozilla::Telemetry::LABELS_PAGE_LOAD_ERROR LoadErrorToTelemetryLabel(
+ nsresult aRv) {
+ MOZ_ASSERT(aRv != NS_OK);
+
+ for (const auto& p : sResult) {
+ if (p.mValue == aRv) {
+ return p.mLabel;
+ }
+ }
+ return ErrorLabel::otherError;
+}
+} // namespace dom
+} // namespace mozilla
diff --git a/docshell/base/nsDocShellTelemetryUtils.h b/docshell/base/nsDocShellTelemetryUtils.h
new file mode 100644
index 0000000000..4e0097caec
--- /dev/null
+++ b/docshell/base/nsDocShellTelemetryUtils.h
@@ -0,0 +1,22 @@
+//* -*- Mode: C++; tab-width: 8; 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 nsDocShellTelemetryUtils_h__
+#define nsDocShellTelemetryUtils_h__
+
+#include "mozilla/Telemetry.h"
+
+namespace mozilla {
+namespace dom {
+/**
+ * Convert page load errors to telemetry labels
+ * Only select nsresults are converted, otherwise this function
+ * will return "errorOther", view the list of errors at
+ * docshell/base/nsDocShellTelemetryUtils.cpp.
+ */
+Telemetry::LABELS_PAGE_LOAD_ERROR LoadErrorToTelemetryLabel(nsresult aRv);
+} // namespace dom
+} // namespace mozilla
+#endif // nsDocShellTelemetryUtils_h__
diff --git a/docshell/base/nsDocShellTreeOwner.cpp b/docshell/base/nsDocShellTreeOwner.cpp
new file mode 100644
index 0000000000..b49dfb6343
--- /dev/null
+++ b/docshell/base/nsDocShellTreeOwner.cpp
@@ -0,0 +1,1340 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Local Includes
+#include "nsDocShellTreeOwner.h"
+#include "nsWebBrowser.h"
+
+// Helper Classes
+#include "nsContentUtils.h"
+#include "nsSize.h"
+#include "mozilla/ReflowInput.h"
+#include "mozilla/ScopeExit.h"
+#include "nsComponentManagerUtils.h"
+#include "nsString.h"
+#include "nsAtom.h"
+#include "nsReadableUtils.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/LookAndFeel.h"
+
+// Interfaces needed to be included
+#include "nsPresContext.h"
+#include "nsITooltipListener.h"
+#include "nsINode.h"
+#include "Link.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/MouseEvent.h"
+#include "mozilla/dom/SVGTitleElement.h"
+#include "nsIFormControl.h"
+#include "nsIWebNavigation.h"
+#include "nsPIDOMWindow.h"
+#include "nsPIWindowRoot.h"
+#include "nsIWindowWatcher.h"
+#include "nsPIWindowWatcher.h"
+#include "nsIPrompt.h"
+#include "nsIRemoteTab.h"
+#include "nsIBrowserChild.h"
+#include "nsRect.h"
+#include "nsIWebBrowserChromeFocus.h"
+#include "nsIContent.h"
+#include "nsServiceManagerUtils.h"
+#include "nsViewManager.h"
+#include "nsView.h"
+#include "nsXULTooltipListener.h"
+#include "nsIConstraintValidation.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/dom/DragEvent.h"
+#include "mozilla/dom/Event.h" // for Event
+#include "mozilla/dom/File.h" // for input type=file
+#include "mozilla/dom/FileList.h" // for input type=file
+#include "mozilla/dom/LoadURIOptionsBinding.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/TextEvents.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// A helper routine that navigates the tricky path from a |nsWebBrowser| to
+// a |EventTarget| via the window root and chrome event handler.
+static nsresult GetDOMEventTarget(nsWebBrowser* aInBrowser,
+ EventTarget** aTarget) {
+ if (!aInBrowser) {
+ return NS_ERROR_INVALID_POINTER;
+ }
+
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ aInBrowser->GetContentDOMWindow(getter_AddRefs(domWindow));
+ if (!domWindow) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto* outerWindow = nsPIDOMWindowOuter::From(domWindow);
+ nsPIDOMWindowOuter* rootWindow = outerWindow->GetPrivateRoot();
+ NS_ENSURE_TRUE(rootWindow, NS_ERROR_FAILURE);
+ nsCOMPtr<EventTarget> target = rootWindow->GetChromeEventHandler();
+ NS_ENSURE_TRUE(target, NS_ERROR_FAILURE);
+ target.forget(aTarget);
+
+ return NS_OK;
+}
+
+nsDocShellTreeOwner::nsDocShellTreeOwner()
+ : mWebBrowser(nullptr),
+ mTreeOwner(nullptr),
+ mPrimaryContentShell(nullptr),
+ mWebBrowserChrome(nullptr),
+ mOwnerWin(nullptr),
+ mOwnerRequestor(nullptr) {}
+
+nsDocShellTreeOwner::~nsDocShellTreeOwner() { RemoveChromeListeners(); }
+
+NS_IMPL_ADDREF(nsDocShellTreeOwner)
+NS_IMPL_RELEASE(nsDocShellTreeOwner)
+
+NS_INTERFACE_MAP_BEGIN(nsDocShellTreeOwner)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDocShellTreeOwner)
+ NS_INTERFACE_MAP_ENTRY(nsIDocShellTreeOwner)
+ NS_INTERFACE_MAP_ENTRY(nsIBaseWindow)
+ NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+ NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener)
+ NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+// The class that listens to the chrome events and tells the embedding chrome to
+// show tooltips, as appropriate. Handles registering itself with the DOM with
+// AddChromeListeners() and removing itself with RemoveChromeListeners().
+class ChromeTooltipListener final : public nsIDOMEventListener {
+ protected:
+ virtual ~ChromeTooltipListener();
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ ChromeTooltipListener(nsWebBrowser* aInBrowser,
+ nsIWebBrowserChrome* aInChrome);
+
+ NS_DECL_NSIDOMEVENTLISTENER
+ NS_IMETHOD MouseMove(mozilla::dom::Event* aMouseEvent);
+
+ // Add/remove the relevant listeners, based on what interfaces the embedding
+ // chrome implements.
+ NS_IMETHOD AddChromeListeners();
+ NS_IMETHOD RemoveChromeListeners();
+
+ NS_IMETHOD HideTooltip();
+
+ bool WebProgressShowedTooltip(nsIWebProgress* aWebProgress);
+
+ private:
+ // pixel tolerance for mousemove event
+ static constexpr CSSIntCoord kTooltipMouseMoveTolerance = 7;
+
+ NS_IMETHOD AddTooltipListener();
+ NS_IMETHOD RemoveTooltipListener();
+
+ NS_IMETHOD ShowTooltip(int32_t aInXCoords, int32_t aInYCoords,
+ const nsAString& aInTipText,
+ const nsAString& aDirText);
+ nsITooltipTextProvider* GetTooltipTextProvider();
+
+ nsWebBrowser* mWebBrowser;
+ nsCOMPtr<mozilla::dom::EventTarget> mEventTarget;
+ nsCOMPtr<nsITooltipTextProvider> mTooltipTextProvider;
+
+ // This must be a strong ref in order to make sure we can hide the tooltip if
+ // the window goes away while we're displaying one. If we don't hold a strong
+ // ref, the chrome might have been disposed of before we get a chance to tell
+ // it, and no one would ever tell us of that fact.
+ nsCOMPtr<nsIWebBrowserChrome> mWebBrowserChrome;
+
+ bool mTooltipListenerInstalled;
+
+ nsCOMPtr<nsITimer> mTooltipTimer;
+ static void sTooltipCallback(nsITimer* aTimer, void* aListener);
+
+ // Mouse coordinates for last mousemove event we saw
+ CSSIntPoint mMouseClientPoint;
+
+ // Mouse coordinates for tooltip event
+ LayoutDeviceIntPoint mMouseScreenPoint;
+
+ bool mShowingTooltip;
+
+ bool mTooltipShownOnce;
+
+ // The string of text that we last displayed.
+ nsString mLastShownTooltipText;
+
+ nsWeakPtr mLastDocshell;
+
+ // The node hovered over that fired the timer. This may turn into the node
+ // that triggered the tooltip, but only if the timer ever gets around to
+ // firing. This is a strong reference, because the tooltip content can be
+ // destroyed while we're waiting for the tooltip to pop up, and we need to
+ // detect that. It's set only when the tooltip timer is created and launched.
+ // The timer must either fire or be cancelled (or possibly released?), and we
+ // release this reference in each of those cases. So we don't leak.
+ nsCOMPtr<nsINode> mPossibleTooltipNode;
+};
+
+//*****************************************************************************
+// nsDocShellTreeOwner::nsIInterfaceRequestor
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetInterface(const nsIID& aIID, void** aSink) {
+ NS_ENSURE_ARG_POINTER(aSink);
+
+ if (NS_SUCCEEDED(QueryInterface(aIID, aSink))) {
+ return NS_OK;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsIWebBrowserChromeFocus))) {
+ if (mWebBrowserChromeWeak != nullptr) {
+ return mWebBrowserChromeWeak->QueryReferent(aIID, aSink);
+ }
+ return mOwnerWin->QueryInterface(aIID, aSink);
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsIPrompt))) {
+ nsCOMPtr<nsIPrompt> prompt;
+ EnsurePrompter();
+ prompt = mPrompter;
+ if (prompt) {
+ prompt.forget(aSink);
+ return NS_OK;
+ }
+ return NS_NOINTERFACE;
+ }
+
+ if (aIID.Equals(NS_GET_IID(nsIAuthPrompt))) {
+ nsCOMPtr<nsIAuthPrompt> prompt;
+ EnsureAuthPrompter();
+ prompt = mAuthPrompter;
+ if (prompt) {
+ prompt.forget(aSink);
+ return NS_OK;
+ }
+ return NS_NOINTERFACE;
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> req = GetOwnerRequestor();
+ if (req) {
+ return req->GetInterface(aIID, aSink);
+ }
+
+ return NS_NOINTERFACE;
+}
+
+//*****************************************************************************
+// nsDocShellTreeOwner::nsIDocShellTreeOwner
+//*****************************************************************************
+
+void nsDocShellTreeOwner::EnsurePrompter() {
+ if (mPrompter) {
+ return;
+ }
+
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ if (wwatch && mWebBrowser) {
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ mWebBrowser->GetContentDOMWindow(getter_AddRefs(domWindow));
+ if (domWindow) {
+ wwatch->GetNewPrompter(domWindow, getter_AddRefs(mPrompter));
+ }
+ }
+}
+
+void nsDocShellTreeOwner::EnsureAuthPrompter() {
+ if (mAuthPrompter) {
+ return;
+ }
+
+ nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ if (wwatch && mWebBrowser) {
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ mWebBrowser->GetContentDOMWindow(getter_AddRefs(domWindow));
+ if (domWindow) {
+ wwatch->GetNewAuthPrompter(domWindow, getter_AddRefs(mAuthPrompter));
+ }
+ }
+}
+
+void nsDocShellTreeOwner::AddToWatcher() {
+ if (mWebBrowser) {
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ mWebBrowser->GetContentDOMWindow(getter_AddRefs(domWindow));
+ if (domWindow) {
+ nsCOMPtr<nsPIWindowWatcher> wwatch(
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ if (wwatch) {
+ nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = GetWebBrowserChrome();
+ if (webBrowserChrome) {
+ wwatch->AddWindow(domWindow, webBrowserChrome);
+ }
+ }
+ }
+ }
+}
+
+void nsDocShellTreeOwner::RemoveFromWatcher() {
+ if (mWebBrowser) {
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ mWebBrowser->GetContentDOMWindow(getter_AddRefs(domWindow));
+ if (domWindow) {
+ nsCOMPtr<nsPIWindowWatcher> wwatch(
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ if (wwatch) {
+ wwatch->RemoveWindow(domWindow);
+ }
+ }
+ }
+}
+
+void nsDocShellTreeOwner::EnsureContentTreeOwner() {
+ if (mContentTreeOwner) {
+ return;
+ }
+
+ mContentTreeOwner = new nsDocShellTreeOwner();
+ nsCOMPtr<nsIWebBrowserChrome> browserChrome = GetWebBrowserChrome();
+ if (browserChrome) {
+ mContentTreeOwner->SetWebBrowserChrome(browserChrome);
+ }
+
+ if (mWebBrowser) {
+ mContentTreeOwner->WebBrowser(mWebBrowser);
+ }
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::ContentShellAdded(nsIDocShellTreeItem* aContentShell,
+ bool aPrimary) {
+ if (mTreeOwner) return mTreeOwner->ContentShellAdded(aContentShell, aPrimary);
+
+ EnsureContentTreeOwner();
+ aContentShell->SetTreeOwner(mContentTreeOwner);
+
+ if (aPrimary) {
+ mPrimaryContentShell = aContentShell;
+ mPrimaryRemoteTab = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::ContentShellRemoved(nsIDocShellTreeItem* aContentShell) {
+ if (mTreeOwner) {
+ return mTreeOwner->ContentShellRemoved(aContentShell);
+ }
+
+ if (mPrimaryContentShell == aContentShell) {
+ mPrimaryContentShell = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetPrimaryContentShell(nsIDocShellTreeItem** aShell) {
+ NS_ENSURE_ARG_POINTER(aShell);
+
+ if (mTreeOwner) {
+ return mTreeOwner->GetPrimaryContentShell(aShell);
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> shell;
+ if (!mPrimaryRemoteTab) {
+ shell =
+ mPrimaryContentShell ? mPrimaryContentShell : mWebBrowser->mDocShell;
+ }
+ shell.forget(aShell);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::RemoteTabAdded(nsIRemoteTab* aTab, bool aPrimary) {
+ if (mTreeOwner) {
+ return mTreeOwner->RemoteTabAdded(aTab, aPrimary);
+ }
+
+ if (aPrimary) {
+ mPrimaryRemoteTab = aTab;
+ mPrimaryContentShell = nullptr;
+ } else if (mPrimaryRemoteTab == aTab) {
+ mPrimaryRemoteTab = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::RemoteTabRemoved(nsIRemoteTab* aTab) {
+ if (mTreeOwner) {
+ return mTreeOwner->RemoteTabRemoved(aTab);
+ }
+
+ if (aTab == mPrimaryRemoteTab) {
+ mPrimaryRemoteTab = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetPrimaryRemoteTab(nsIRemoteTab** aTab) {
+ if (mTreeOwner) {
+ return mTreeOwner->GetPrimaryRemoteTab(aTab);
+ }
+
+ nsCOMPtr<nsIRemoteTab> tab = mPrimaryRemoteTab;
+ tab.forget(aTab);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetPrimaryContentBrowsingContext(
+ mozilla::dom::BrowsingContext** aBc) {
+ if (mTreeOwner) {
+ return mTreeOwner->GetPrimaryContentBrowsingContext(aBc);
+ }
+ if (mPrimaryRemoteTab) {
+ return mPrimaryRemoteTab->GetBrowsingContext(aBc);
+ }
+ if (mPrimaryContentShell) {
+ return mPrimaryContentShell->GetBrowsingContextXPCOM(aBc);
+ }
+ if (mWebBrowser->mDocShell) {
+ return mWebBrowser->mDocShell->GetBrowsingContextXPCOM(aBc);
+ }
+ *aBc = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetPrimaryContentSize(int32_t* aWidth, int32_t* aHeight) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetPrimaryContentSize(int32_t aWidth, int32_t aHeight) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetRootShellSize(int32_t* aWidth, int32_t* aHeight) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetRootShellSize(int32_t aWidth, int32_t aHeight) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SizeShellTo(nsIDocShellTreeItem* aShellItem, int32_t aCX,
+ int32_t aCY) {
+ nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = GetWebBrowserChrome();
+
+ NS_ENSURE_STATE(mTreeOwner || webBrowserChrome);
+
+ if (nsCOMPtr<nsIDocShellTreeOwner> treeOwner = mTreeOwner) {
+ return treeOwner->SizeShellTo(aShellItem, aCX, aCY);
+ }
+
+ if (aShellItem == mWebBrowser->mDocShell) {
+ nsCOMPtr<nsIBrowserChild> browserChild =
+ do_QueryInterface(webBrowserChrome);
+ if (browserChild) {
+ // The XUL window to resize is in the parent process, but there we
+ // won't be able to get the size of aShellItem. We can ask the parent
+ // process to change our size instead.
+ nsCOMPtr<nsIBaseWindow> shellAsWin(do_QueryInterface(aShellItem));
+ NS_ENSURE_TRUE(shellAsWin, NS_ERROR_FAILURE);
+
+ LayoutDeviceIntSize shellSize;
+ shellAsWin->GetSize(&shellSize.width, &shellSize.height);
+ LayoutDeviceIntSize deltaSize = LayoutDeviceIntSize(aCX, aCY) - shellSize;
+
+ LayoutDeviceIntSize currentSize;
+ GetSize(&currentSize.width, &currentSize.height);
+
+ LayoutDeviceIntSize newSize = currentSize + deltaSize;
+ return SetSize(newSize.width, newSize.height, true);
+ }
+ // XXX: this is weird, but we used to call a method here
+ // (webBrowserChrome->SizeBrowserTo()) whose implementations all failed
+ // like this, so...
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("This is unimplemented, API should be cleaned up");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetPersistence(bool aPersistPosition, bool aPersistSize,
+ bool aPersistSizeMode) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetPersistence(bool* aPersistPosition, bool* aPersistSize,
+ bool* aPersistSizeMode) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetTabCount(uint32_t* aResult) {
+ if (mTreeOwner) {
+ return mTreeOwner->GetTabCount(aResult);
+ }
+
+ *aResult = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetHasPrimaryContent(bool* aResult) {
+ *aResult = mPrimaryRemoteTab || mPrimaryContentShell;
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsDocShellTreeOwner::nsIBaseWindow
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::InitWindow(nativeWindow aParentNativeWindow,
+ nsIWidget* aParentWidget, int32_t aX,
+ int32_t aY, int32_t aCX, int32_t aCY) {
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::Destroy() {
+ nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = GetWebBrowserChrome();
+ if (webBrowserChrome) {
+ // XXX: this is weird, but we used to call a method here
+ // (webBrowserChrome->DestroyBrowserWindow()) whose implementations all
+ // failed like this, so...
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ return NS_ERROR_NULL_POINTER;
+}
+
+double nsDocShellTreeOwner::GetWidgetCSSToDeviceScale() {
+ return mWebBrowser ? mWebBrowser->GetWidgetCSSToDeviceScale() : 1.0;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetDevicePixelsPerDesktopPixel(double* aScale) {
+ if (mWebBrowser) {
+ return mWebBrowser->GetDevicePixelsPerDesktopPixel(aScale);
+ }
+
+ *aScale = 1.0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetPositionDesktopPix(int32_t aX, int32_t aY) {
+ if (mWebBrowser) {
+ nsresult rv = mWebBrowser->SetPositionDesktopPix(aX, aY);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ double scale = 1.0;
+ GetDevicePixelsPerDesktopPixel(&scale);
+ return SetPosition(NSToIntRound(aX * scale), NSToIntRound(aY * scale));
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetPosition(int32_t aX, int32_t aY) {
+ return SetDimensions(
+ {DimensionKind::Outer, Some(aX), Some(aY), Nothing(), Nothing()});
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetPosition(int32_t* aX, int32_t* aY) {
+ return GetDimensions(DimensionKind::Outer, aX, aY, nullptr, nullptr);
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetSize(int32_t aCX, int32_t aCY, bool aRepaint) {
+ return SetDimensions(
+ {DimensionKind::Outer, Nothing(), Nothing(), Some(aCX), Some(aCY)});
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetSize(int32_t* aCX, int32_t* aCY) {
+ return GetDimensions(DimensionKind::Outer, nullptr, nullptr, aCX, aCY);
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetPositionAndSize(int32_t aX, int32_t aY, int32_t aCX,
+ int32_t aCY, uint32_t aFlags) {
+ return SetDimensions(
+ {DimensionKind::Outer, Some(aX), Some(aY), Some(aCX), Some(aCY)});
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetPositionAndSize(int32_t* aX, int32_t* aY, int32_t* aCX,
+ int32_t* aCY) {
+ return GetDimensions(DimensionKind::Outer, aX, aY, aCX, aCY);
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetDimensions(DimensionRequest&& aRequest) {
+ nsCOMPtr<nsIBaseWindow> ownerWin = GetOwnerWin();
+ if (ownerWin) {
+ return ownerWin->SetDimensions(std::move(aRequest));
+ }
+
+ nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = GetWebBrowserChrome();
+ NS_ENSURE_STATE(webBrowserChrome);
+ return webBrowserChrome->SetDimensions(std::move(aRequest));
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetDimensions(DimensionKind aDimensionKind, int32_t* aX,
+ int32_t* aY, int32_t* aCX, int32_t* aCY) {
+ nsCOMPtr<nsIBaseWindow> ownerWin = GetOwnerWin();
+ if (ownerWin) {
+ return ownerWin->GetDimensions(aDimensionKind, aX, aY, aCX, aCY);
+ }
+
+ nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = GetWebBrowserChrome();
+ NS_ENSURE_STATE(webBrowserChrome);
+ return webBrowserChrome->GetDimensions(aDimensionKind, aX, aY, aCX, aCY);
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::Repaint(bool aForce) { return NS_ERROR_NULL_POINTER; }
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetParentWidget(nsIWidget** aParentWidget) {
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetParentWidget(nsIWidget* aParentWidget) {
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetParentNativeWindow(nativeWindow* aParentNativeWindow) {
+ nsCOMPtr<nsIBaseWindow> ownerWin = GetOwnerWin();
+ if (ownerWin) {
+ return ownerWin->GetParentNativeWindow(aParentNativeWindow);
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetParentNativeWindow(nativeWindow aParentNativeWindow) {
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetNativeHandle(nsAString& aNativeHandle) {
+ // the nativeHandle should be accessed from nsIAppWindow
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetVisibility(bool* aVisibility) {
+ nsCOMPtr<nsIBaseWindow> ownerWin = GetOwnerWin();
+ if (ownerWin) {
+ return ownerWin->GetVisibility(aVisibility);
+ }
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetVisibility(bool aVisibility) {
+ nsCOMPtr<nsIBaseWindow> ownerWin = GetOwnerWin();
+ if (ownerWin) {
+ return ownerWin->SetVisibility(aVisibility);
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetEnabled(bool* aEnabled) {
+ NS_ENSURE_ARG_POINTER(aEnabled);
+ *aEnabled = true;
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetEnabled(bool aEnabled) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetMainWidget(nsIWidget** aMainWidget) {
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::GetTitle(nsAString& aTitle) {
+ nsCOMPtr<nsIBaseWindow> ownerWin = GetOwnerWin();
+ if (ownerWin) {
+ return ownerWin->GetTitle(aTitle);
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetTitle(const nsAString& aTitle) {
+ nsCOMPtr<nsIBaseWindow> ownerWin = GetOwnerWin();
+ if (ownerWin) {
+ return ownerWin->SetTitle(aTitle);
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+//*****************************************************************************
+// nsDocShellTreeOwner::nsIWebProgressListener
+//*****************************************************************************
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::OnProgressChange(nsIWebProgress* aProgress,
+ nsIRequest* aRequest,
+ int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress) {
+ // In the absence of DOM document creation event, this method is the
+ // most convenient place to install the mouse listener on the
+ // DOM document.
+ return AddChromeListeners();
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::OnStateChange(nsIWebProgress* aProgress,
+ nsIRequest* aRequest,
+ uint32_t aProgressStateFlags,
+ nsresult aStatus) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::OnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, nsIURI* aURI,
+ uint32_t aFlags) {
+ if (mChromeTooltipListener && aWebProgress &&
+ !(aFlags & nsIWebProgressListener::LOCATION_CHANGE_SAME_DOCUMENT) &&
+ mChromeTooltipListener->WebProgressShowedTooltip(aWebProgress)) {
+ mChromeTooltipListener->HideTooltip();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, nsresult aStatus,
+ const char16_t* aMessage) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::OnSecurityChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, uint32_t aState) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aEvent) {
+ return NS_OK;
+}
+
+//*****************************************************************************
+// nsDocShellTreeOwner: Accessors
+//*****************************************************************************
+
+void nsDocShellTreeOwner::WebBrowser(nsWebBrowser* aWebBrowser) {
+ if (!aWebBrowser) {
+ RemoveChromeListeners();
+ }
+ if (aWebBrowser != mWebBrowser) {
+ mPrompter = nullptr;
+ mAuthPrompter = nullptr;
+ }
+
+ mWebBrowser = aWebBrowser;
+
+ if (mContentTreeOwner) {
+ mContentTreeOwner->WebBrowser(aWebBrowser);
+ if (!aWebBrowser) {
+ mContentTreeOwner = nullptr;
+ }
+ }
+}
+
+nsWebBrowser* nsDocShellTreeOwner::WebBrowser() { return mWebBrowser; }
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetTreeOwner(nsIDocShellTreeOwner* aTreeOwner) {
+ if (aTreeOwner) {
+ nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome(do_GetInterface(aTreeOwner));
+ NS_ENSURE_TRUE(webBrowserChrome, NS_ERROR_INVALID_ARG);
+ NS_ENSURE_SUCCESS(SetWebBrowserChrome(webBrowserChrome),
+ NS_ERROR_INVALID_ARG);
+ mTreeOwner = aTreeOwner;
+ } else {
+ mTreeOwner = nullptr;
+ nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = GetWebBrowserChrome();
+ if (!webBrowserChrome) {
+ NS_ENSURE_SUCCESS(SetWebBrowserChrome(nullptr), NS_ERROR_FAILURE);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::SetWebBrowserChrome(
+ nsIWebBrowserChrome* aWebBrowserChrome) {
+ if (!aWebBrowserChrome) {
+ mWebBrowserChrome = nullptr;
+ mOwnerWin = nullptr;
+ mOwnerRequestor = nullptr;
+ mWebBrowserChromeWeak = nullptr;
+ } else {
+ nsCOMPtr<nsISupportsWeakReference> supportsweak =
+ do_QueryInterface(aWebBrowserChrome);
+ if (supportsweak) {
+ supportsweak->GetWeakReference(getter_AddRefs(mWebBrowserChromeWeak));
+ } else {
+ nsCOMPtr<nsIBaseWindow> ownerWin(do_QueryInterface(aWebBrowserChrome));
+ nsCOMPtr<nsIInterfaceRequestor> requestor(
+ do_QueryInterface(aWebBrowserChrome));
+
+ // it's ok for ownerWin or requestor to be null.
+ mWebBrowserChrome = aWebBrowserChrome;
+ mOwnerWin = ownerWin;
+ mOwnerRequestor = requestor;
+ }
+ }
+
+ if (mContentTreeOwner) {
+ mContentTreeOwner->SetWebBrowserChrome(aWebBrowserChrome);
+ }
+
+ return NS_OK;
+}
+
+// Hook up things to the chrome like context menus and tooltips, if the chrome
+// has implemented the right interfaces.
+NS_IMETHODIMP
+nsDocShellTreeOwner::AddChromeListeners() {
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome = GetWebBrowserChrome();
+ if (!webBrowserChrome) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // install tooltips
+ if (!mChromeTooltipListener) {
+ nsCOMPtr<nsITooltipListener> tooltipListener(
+ do_QueryInterface(webBrowserChrome));
+ if (tooltipListener) {
+ mChromeTooltipListener =
+ new ChromeTooltipListener(mWebBrowser, webBrowserChrome);
+ rv = mChromeTooltipListener->AddChromeListeners();
+ }
+ }
+
+ nsCOMPtr<EventTarget> target;
+ GetDOMEventTarget(mWebBrowser, getter_AddRefs(target));
+
+ // register dragover and drop event listeners with the listener manager
+ MOZ_ASSERT(target, "how does this happen? (see bug 1659758)");
+ if (target) {
+ if (EventListenerManager* elmP = target->GetOrCreateListenerManager()) {
+ elmP->AddEventListenerByType(this, u"dragover"_ns,
+ TrustedEventsAtSystemGroupBubble());
+ elmP->AddEventListenerByType(this, u"drop"_ns,
+ TrustedEventsAtSystemGroupBubble());
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::RemoveChromeListeners() {
+ if (mChromeTooltipListener) {
+ mChromeTooltipListener->RemoveChromeListeners();
+ mChromeTooltipListener = nullptr;
+ }
+
+ nsCOMPtr<EventTarget> piTarget;
+ GetDOMEventTarget(mWebBrowser, getter_AddRefs(piTarget));
+ if (!piTarget) {
+ return NS_OK;
+ }
+
+ EventListenerManager* elmP = piTarget->GetOrCreateListenerManager();
+ if (elmP) {
+ elmP->RemoveEventListenerByType(this, u"dragover"_ns,
+ TrustedEventsAtSystemGroupBubble());
+ elmP->RemoveEventListenerByType(this, u"drop"_ns,
+ TrustedEventsAtSystemGroupBubble());
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocShellTreeOwner::HandleEvent(Event* aEvent) {
+ DragEvent* dragEvent = aEvent ? aEvent->AsDragEvent() : nullptr;
+ if (NS_WARN_IF(!dragEvent)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (dragEvent->DefaultPrevented()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDroppedLinkHandler> handler =
+ do_GetService("@mozilla.org/content/dropped-link-handler;1");
+ if (!handler) {
+ return NS_OK;
+ }
+
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+ if (eventType.EqualsLiteral("dragover")) {
+ bool canDropLink = false;
+ handler->CanDropLink(dragEvent, false, &canDropLink);
+ if (canDropLink) {
+ aEvent->PreventDefault();
+ }
+ } else if (eventType.EqualsLiteral("drop")) {
+ nsCOMPtr<nsIWebNavigation> webnav =
+ static_cast<nsIWebNavigation*>(mWebBrowser);
+
+ // The page might have cancelled the dragover event itself, so check to
+ // make sure that the link can be dropped first.
+ bool canDropLink = false;
+ handler->CanDropLink(dragEvent, false, &canDropLink);
+ if (!canDropLink) {
+ return NS_OK;
+ }
+
+ nsTArray<RefPtr<nsIDroppedLinkItem>> links;
+ if (webnav && NS_SUCCEEDED(handler->DropLinks(dragEvent, true, links))) {
+ if (links.Length() >= 1) {
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal;
+ handler->GetTriggeringPrincipal(dragEvent,
+ getter_AddRefs(triggeringPrincipal));
+ if (triggeringPrincipal) {
+ nsCOMPtr<nsIWebBrowserChrome> webBrowserChrome =
+ GetWebBrowserChrome();
+ if (webBrowserChrome) {
+ nsCOMPtr<nsIBrowserChild> browserChild =
+ do_QueryInterface(webBrowserChrome);
+ if (browserChild) {
+ nsresult rv = browserChild->RemoteDropLinks(links);
+ return rv;
+ }
+ }
+ nsAutoString url;
+ if (NS_SUCCEEDED(links[0]->GetUrl(url))) {
+ if (!url.IsEmpty()) {
+#ifndef ANDROID
+ MOZ_ASSERT(triggeringPrincipal,
+ "nsDocShellTreeOwner::HandleEvent: Need a valid "
+ "triggeringPrincipal");
+#endif
+ LoadURIOptions loadURIOptions;
+ loadURIOptions.mTriggeringPrincipal = triggeringPrincipal;
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ handler->GetCsp(dragEvent, getter_AddRefs(csp));
+ loadURIOptions.mCsp = csp;
+ webnav->FixupAndLoadURIString(url, loadURIOptions);
+ }
+ }
+ }
+ }
+ } else {
+ aEvent->StopPropagation();
+ aEvent->PreventDefault();
+ }
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<nsIWebBrowserChrome>
+nsDocShellTreeOwner::GetWebBrowserChrome() {
+ nsCOMPtr<nsIWebBrowserChrome> chrome;
+ if (mWebBrowserChromeWeak) {
+ chrome = do_QueryReferent(mWebBrowserChromeWeak);
+ } else if (mWebBrowserChrome) {
+ chrome = mWebBrowserChrome;
+ }
+ return chrome.forget();
+}
+
+already_AddRefed<nsIBaseWindow> nsDocShellTreeOwner::GetOwnerWin() {
+ nsCOMPtr<nsIBaseWindow> win;
+ if (mWebBrowserChromeWeak) {
+ win = do_QueryReferent(mWebBrowserChromeWeak);
+ } else if (mOwnerWin) {
+ win = mOwnerWin;
+ }
+ return win.forget();
+}
+
+already_AddRefed<nsIInterfaceRequestor>
+nsDocShellTreeOwner::GetOwnerRequestor() {
+ nsCOMPtr<nsIInterfaceRequestor> req;
+ if (mWebBrowserChromeWeak) {
+ req = do_QueryReferent(mWebBrowserChromeWeak);
+ } else if (mOwnerRequestor) {
+ req = mOwnerRequestor;
+ }
+ return req.forget();
+}
+
+NS_IMPL_ISUPPORTS(ChromeTooltipListener, nsIDOMEventListener)
+
+ChromeTooltipListener::ChromeTooltipListener(nsWebBrowser* aInBrowser,
+ nsIWebBrowserChrome* aInChrome)
+ : mWebBrowser(aInBrowser),
+ mWebBrowserChrome(aInChrome),
+ mTooltipListenerInstalled(false),
+ mShowingTooltip(false),
+ mTooltipShownOnce(false) {}
+
+ChromeTooltipListener::~ChromeTooltipListener() {}
+
+nsITooltipTextProvider* ChromeTooltipListener::GetTooltipTextProvider() {
+ if (!mTooltipTextProvider) {
+ mTooltipTextProvider = do_GetService(NS_TOOLTIPTEXTPROVIDER_CONTRACTID);
+ }
+
+ if (!mTooltipTextProvider) {
+ mTooltipTextProvider =
+ do_GetService(NS_DEFAULTTOOLTIPTEXTPROVIDER_CONTRACTID);
+ }
+
+ return mTooltipTextProvider;
+}
+
+// Hook up things to the chrome like context menus and tooltips, if the chrome
+// has implemented the right interfaces.
+NS_IMETHODIMP
+ChromeTooltipListener::AddChromeListeners() {
+ if (!mEventTarget) {
+ GetDOMEventTarget(mWebBrowser, getter_AddRefs(mEventTarget));
+ }
+
+ // Register the appropriate events for tooltips, but only if
+ // the embedding chrome cares.
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsITooltipListener> tooltipListener(
+ do_QueryInterface(mWebBrowserChrome));
+ if (tooltipListener && !mTooltipListenerInstalled) {
+ rv = AddTooltipListener();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ return rv;
+}
+
+// Subscribe to the events that will allow us to track tooltips. We need "mouse"
+// for mouseExit, "mouse motion" for mouseMove, and "key" for keyDown. As we
+// add the listeners, keep track of how many succeed so we can clean up
+// correctly in Release().
+NS_IMETHODIMP
+ChromeTooltipListener::AddTooltipListener() {
+ if (mEventTarget) {
+ MOZ_TRY(mEventTarget->AddSystemEventListener(u"keydown"_ns, this, false,
+ false));
+ MOZ_TRY(mEventTarget->AddSystemEventListener(u"mousedown"_ns, this, false,
+ false));
+ MOZ_TRY(mEventTarget->AddSystemEventListener(u"mouseout"_ns, this, false,
+ false));
+ MOZ_TRY(mEventTarget->AddSystemEventListener(u"mousemove"_ns, this, false,
+ false));
+
+ mTooltipListenerInstalled = true;
+ }
+
+ return NS_OK;
+}
+
+// Unsubscribe from the various things we've hooked up to the window root.
+NS_IMETHODIMP
+ChromeTooltipListener::RemoveChromeListeners() {
+ HideTooltip();
+
+ if (mTooltipListenerInstalled) {
+ RemoveTooltipListener();
+ }
+
+ mEventTarget = nullptr;
+
+ // it really doesn't matter if these fail...
+ return NS_OK;
+}
+
+// Unsubscribe from all the various tooltip events that we were listening to.
+NS_IMETHODIMP
+ChromeTooltipListener::RemoveTooltipListener() {
+ if (mEventTarget) {
+ mEventTarget->RemoveSystemEventListener(u"keydown"_ns, this, false);
+ mEventTarget->RemoveSystemEventListener(u"mousedown"_ns, this, false);
+ mEventTarget->RemoveSystemEventListener(u"mouseout"_ns, this, false);
+ mEventTarget->RemoveSystemEventListener(u"mousemove"_ns, this, false);
+ mTooltipListenerInstalled = false;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ChromeTooltipListener::HandleEvent(Event* aEvent) {
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+
+ if (eventType.EqualsLiteral("mousedown")) {
+ return HideTooltip();
+ } else if (eventType.EqualsLiteral("keydown")) {
+ WidgetKeyboardEvent* keyEvent = aEvent->WidgetEventPtr()->AsKeyboardEvent();
+ if (nsXULTooltipListener::KeyEventHidesTooltip(*keyEvent)) {
+ return HideTooltip();
+ }
+ return NS_OK;
+ } else if (eventType.EqualsLiteral("mouseout")) {
+ // Reset flag so that tooltip will display on the next MouseMove
+ mTooltipShownOnce = false;
+ return HideTooltip();
+ } else if (eventType.EqualsLiteral("mousemove")) {
+ return MouseMove(aEvent);
+ }
+
+ NS_ERROR("Unexpected event type");
+ return NS_OK;
+}
+
+// If we're a tooltip, fire off a timer to see if a tooltip should be shown. If
+// the timer fires, we cache the node in |mPossibleTooltipNode|.
+nsresult ChromeTooltipListener::MouseMove(Event* aMouseEvent) {
+ if (!nsXULTooltipListener::ShowTooltips()) {
+ return NS_OK;
+ }
+
+ MouseEvent* mouseEvent = aMouseEvent->AsMouseEvent();
+ if (!mouseEvent) {
+ return NS_OK;
+ }
+
+ // stash the coordinates of the event so that we can still get back to it from
+ // within the timer callback. On win32, we'll get a MouseMove event even when
+ // a popup goes away -- even when the mouse doesn't change position! To get
+ // around this, we make sure the mouse has really moved before proceeding.
+ CSSIntPoint newMouseClientPoint = mouseEvent->ClientPoint();
+ if (mMouseClientPoint == newMouseClientPoint) {
+ return NS_OK;
+ }
+
+ // Filter out minor mouse movements.
+ if (mShowingTooltip &&
+ (abs(mMouseClientPoint.x - newMouseClientPoint.x) <=
+ kTooltipMouseMoveTolerance) &&
+ (abs(mMouseClientPoint.y - newMouseClientPoint.y) <=
+ kTooltipMouseMoveTolerance)) {
+ return NS_OK;
+ }
+
+ mMouseClientPoint = newMouseClientPoint;
+ mMouseScreenPoint = mouseEvent->ScreenPointLayoutDevicePix();
+
+ if (mTooltipTimer) {
+ mTooltipTimer->Cancel();
+ mTooltipTimer = nullptr;
+ }
+
+ if (!mShowingTooltip) {
+ nsIEventTarget* target = nullptr;
+ if (nsCOMPtr<EventTarget> eventTarget = aMouseEvent->GetComposedTarget()) {
+ mPossibleTooltipNode = nsINode::FromEventTarget(eventTarget);
+ nsCOMPtr<nsIGlobalObject> global(eventTarget->GetOwnerGlobal());
+ if (global) {
+ target = global->EventTargetFor(TaskCategory::UI);
+ }
+ }
+
+ if (mPossibleTooltipNode) {
+ nsresult rv = NS_NewTimerWithFuncCallback(
+ getter_AddRefs(mTooltipTimer), sTooltipCallback, this,
+ LookAndFeel::GetInt(LookAndFeel::IntID::TooltipDelay, 500),
+ nsITimer::TYPE_ONE_SHOT, "ChromeTooltipListener::MouseMove", target);
+ if (NS_FAILED(rv)) {
+ mPossibleTooltipNode = nullptr;
+ NS_WARNING("Could not create a timer for tooltip tracking");
+ }
+ }
+ } else {
+ mTooltipShownOnce = true;
+ return HideTooltip();
+ }
+
+ return NS_OK;
+}
+
+// Tell the registered chrome that they should show the tooltip.
+NS_IMETHODIMP
+ChromeTooltipListener::ShowTooltip(int32_t aInXCoords, int32_t aInYCoords,
+ const nsAString& aInTipText,
+ const nsAString& aTipDir) {
+ nsresult rv = NS_OK;
+
+ // do the work to call the client
+ nsCOMPtr<nsITooltipListener> tooltipListener(
+ do_QueryInterface(mWebBrowserChrome));
+ if (tooltipListener) {
+ rv = tooltipListener->OnShowTooltip(aInXCoords, aInYCoords, aInTipText,
+ aTipDir);
+ if (NS_SUCCEEDED(rv)) {
+ mShowingTooltip = true;
+ }
+ }
+
+ return rv;
+}
+
+// Tell the registered chrome that they should rollup the tooltip
+// NOTE: This routine is safe to call even if the popup is already closed.
+NS_IMETHODIMP
+ChromeTooltipListener::HideTooltip() {
+ nsresult rv = NS_OK;
+
+ // shut down the relevant timers
+ if (mTooltipTimer) {
+ mTooltipTimer->Cancel();
+ mTooltipTimer = nullptr;
+ // release tooltip target
+ mPossibleTooltipNode = nullptr;
+ mLastDocshell = nullptr;
+ }
+
+ // if we're showing the tip, tell the chrome to hide it
+ if (mShowingTooltip) {
+ nsCOMPtr<nsITooltipListener> tooltipListener(
+ do_QueryInterface(mWebBrowserChrome));
+ if (tooltipListener) {
+ rv = tooltipListener->OnHideTooltip();
+ if (NS_SUCCEEDED(rv)) {
+ mShowingTooltip = false;
+ }
+ }
+ }
+
+ return rv;
+}
+
+bool ChromeTooltipListener::WebProgressShowedTooltip(
+ nsIWebProgress* aWebProgress) {
+ nsCOMPtr<nsIDocShell> docshell = do_QueryInterface(aWebProgress);
+ nsCOMPtr<nsIDocShell> lastUsed = do_QueryReferent(mLastDocshell);
+ while (lastUsed) {
+ if (lastUsed == docshell) {
+ return true;
+ }
+ // We can't use the docshell hierarchy here, because when the parent
+ // docshell is navigated, the child docshell is disconnected (ie its
+ // references to the parent are nulled out) despite it still being
+ // alive here. So we use the document hierarchy instead:
+ Document* document = lastUsed->GetDocument();
+ if (document) {
+ document = document->GetInProcessParentDocument();
+ }
+ if (!document) {
+ break;
+ }
+ lastUsed = document->GetDocShell();
+ }
+ return false;
+}
+
+// A timer callback, fired when the mouse has hovered inside of a frame for the
+// appropriate amount of time. Getting to this point means that we should show
+// the tooltip, but only after we determine there is an appropriate TITLE
+// element.
+//
+// This relies on certain things being cached into the |aChromeTooltipListener|
+// object passed to us by the timer:
+// -- the x/y coordinates of the mouse (mMouseClientY, mMouseClientX)
+// -- the dom node the user hovered over (mPossibleTooltipNode)
+void ChromeTooltipListener::sTooltipCallback(nsITimer* aTimer,
+ void* aChromeTooltipListener) {
+ auto* self = static_cast<ChromeTooltipListener*>(aChromeTooltipListener);
+ if (!self || !self->mPossibleTooltipNode) {
+ return;
+ }
+ // release tooltip target once done, no matter what we do here.
+ auto cleanup = MakeScopeExit([&] { self->mPossibleTooltipNode = nullptr; });
+ if (!self->mPossibleTooltipNode->IsInComposedDoc()) {
+ return;
+ }
+ // Check that the document or its ancestors haven't been replaced.
+ {
+ Document* doc = self->mPossibleTooltipNode->OwnerDoc();
+ while (doc) {
+ if (!doc->IsCurrentActiveDocument()) {
+ return;
+ }
+ doc = doc->GetInProcessParentDocument();
+ }
+ }
+
+ nsCOMPtr<nsIDocShell> docShell =
+ do_GetInterface(static_cast<nsIWebBrowser*>(self->mWebBrowser));
+ if (!docShell || !docShell->GetBrowsingContext()->IsActive()) {
+ return;
+ }
+
+ // if there is text associated with the node, show the tip and fire
+ // off a timer to auto-hide it.
+ nsITooltipTextProvider* tooltipProvider = self->GetTooltipTextProvider();
+ if (!tooltipProvider) {
+ return;
+ }
+ nsString tooltipText;
+ nsString directionText;
+ bool textFound = false;
+ tooltipProvider->GetNodeText(self->mPossibleTooltipNode,
+ getter_Copies(tooltipText),
+ getter_Copies(directionText), &textFound);
+
+ if (textFound && (!self->mTooltipShownOnce ||
+ tooltipText != self->mLastShownTooltipText)) {
+ // ShowTooltip expects screen-relative position.
+ self->ShowTooltip(self->mMouseScreenPoint.x, self->mMouseScreenPoint.y,
+ tooltipText, directionText);
+ self->mLastShownTooltipText = std::move(tooltipText);
+ self->mLastDocshell = do_GetWeakReference(
+ self->mPossibleTooltipNode->OwnerDoc()->GetDocShell());
+ }
+}
diff --git a/docshell/base/nsDocShellTreeOwner.h b/docshell/base/nsDocShellTreeOwner.h
new file mode 100644
index 0000000000..0627357606
--- /dev/null
+++ b/docshell/base/nsDocShellTreeOwner.h
@@ -0,0 +1,111 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDocShellTreeOwner_h__
+#define nsDocShellTreeOwner_h__
+
+// Helper Classes
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+// Interfaces Needed
+#include "nsIBaseWindow.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIWebBrowserChrome.h"
+#include "nsIDOMEventListener.h"
+#include "nsIWebProgressListener.h"
+#include "nsWeakReference.h"
+#include "nsITimer.h"
+#include "nsIPrompt.h"
+#include "nsIAuthPrompt.h"
+#include "nsITooltipTextProvider.h"
+#include "nsCTooltipTextProvider.h"
+
+namespace mozilla {
+namespace dom {
+class Event;
+class EventTarget;
+} // namespace dom
+} // namespace mozilla
+
+class nsIDocShellTreeItem;
+class nsWebBrowser;
+class ChromeTooltipListener;
+
+class nsDocShellTreeOwner final : public nsIDocShellTreeOwner,
+ public nsIBaseWindow,
+ public nsIInterfaceRequestor,
+ public nsIWebProgressListener,
+ public nsIDOMEventListener,
+ public nsSupportsWeakReference {
+ friend class nsWebBrowser;
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIBASEWINDOW
+ NS_DECL_NSIDOCSHELLTREEOWNER
+ NS_DECL_NSIDOMEVENTLISTENER
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSIWEBPROGRESSLISTENER
+
+ protected:
+ nsDocShellTreeOwner();
+ virtual ~nsDocShellTreeOwner();
+
+ void WebBrowser(nsWebBrowser* aWebBrowser);
+
+ nsWebBrowser* WebBrowser();
+ NS_IMETHOD SetTreeOwner(nsIDocShellTreeOwner* aTreeOwner);
+ NS_IMETHOD SetWebBrowserChrome(nsIWebBrowserChrome* aWebBrowserChrome);
+
+ NS_IMETHOD AddChromeListeners();
+ NS_IMETHOD RemoveChromeListeners();
+
+ void EnsurePrompter();
+ void EnsureAuthPrompter();
+
+ void AddToWatcher();
+ void RemoveFromWatcher();
+
+ void EnsureContentTreeOwner();
+
+ // These helper functions return the correct instances of the requested
+ // interfaces. If the object passed to SetWebBrowserChrome() implements
+ // nsISupportsWeakReference, then these functions call QueryReferent on
+ // that object. Otherwise, they return an addrefed pointer. If the
+ // WebBrowserChrome object doesn't exist, they return nullptr.
+ already_AddRefed<nsIWebBrowserChrome> GetWebBrowserChrome();
+ already_AddRefed<nsIBaseWindow> GetOwnerWin();
+ already_AddRefed<nsIInterfaceRequestor> GetOwnerRequestor();
+
+ protected:
+ // Weak References
+ nsWebBrowser* mWebBrowser;
+ nsIDocShellTreeOwner* mTreeOwner;
+ nsIDocShellTreeItem* mPrimaryContentShell;
+
+ nsIWebBrowserChrome* mWebBrowserChrome;
+ nsIBaseWindow* mOwnerWin;
+ nsIInterfaceRequestor* mOwnerRequestor;
+
+ nsWeakPtr mWebBrowserChromeWeak; // nsIWebBrowserChrome
+
+ // the objects that listen for chrome events like context menus and tooltips.
+ // They are separate objects to avoid circular references between |this|
+ // and the DOM.
+ RefPtr<ChromeTooltipListener> mChromeTooltipListener;
+
+ RefPtr<nsDocShellTreeOwner> mContentTreeOwner;
+
+ nsCOMPtr<nsIPrompt> mPrompter;
+ nsCOMPtr<nsIAuthPrompt> mAuthPrompter;
+ nsCOMPtr<nsIRemoteTab> mPrimaryRemoteTab;
+};
+
+#endif /* nsDocShellTreeOwner_h__ */
diff --git a/docshell/base/nsIContentViewer.idl b/docshell/base/nsIContentViewer.idl
new file mode 100644
index 0000000000..4f9dab1c35
--- /dev/null
+++ b/docshell/base/nsIContentViewer.idl
@@ -0,0 +1,318 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIDocShell;
+interface nsISHEntry;
+interface nsIPrintSettings;
+webidl Document;
+webidl Node;
+
+%{ C++
+#include "mozilla/Maybe.h"
+#include "nsTArray.h"
+#include "nsRect.h"
+#include "Units.h"
+
+class nsIWidget;
+class nsPresContext;
+class nsView;
+class nsDOMNavigationTiming;
+namespace mozilla {
+class Encoding;
+class PresShell;
+namespace dom {
+class WindowGlobalChild;
+} // namespace dom
+namespace layout {
+class RemotePrintJobChild;
+} // namespace layout
+} // namespace mozilla
+%}
+
+[ptr] native nsIWidgetPtr(nsIWidget);
+[ref] native nsIntRectRef(nsIntRect);
+[ptr] native nsPresContextPtr(nsPresContext);
+[ptr] native nsViewPtr(nsView);
+[ptr] native nsDOMNavigationTimingPtr(nsDOMNavigationTiming);
+[ptr] native Encoding(const mozilla::Encoding);
+[ptr] native PresShellPtr(mozilla::PresShell);
+[ptr] native RemotePrintJobChildPtr(mozilla::layout::RemotePrintJobChild);
+[ptr] native WindowGlobalChildPtr(mozilla::dom::WindowGlobalChild);
+
+[scriptable, builtinclass, uuid(2da17016-7851-4a45-a7a8-00b360e01595)]
+interface nsIContentViewer : nsISupports
+{
+ [noscript] void init(in nsIWidgetPtr aParentWidget,
+ [const] in nsIntRectRef aBounds,
+ in WindowGlobalChildPtr aWindowActor);
+
+ attribute nsIDocShell container;
+
+ [noscript,notxpcom,nostdcall] void loadStart(in Document aDoc);
+ [can_run_script] void loadComplete(in nsresult aStatus);
+ [notxpcom,nostdcall] readonly attribute boolean loadCompleted;
+
+ [notxpcom,nostdcall] readonly attribute boolean isStopped;
+
+ /**
+ * aAction is passed to PermitUnload to indicate what action to take
+ * if a beforeunload handler wants to prompt the user.
+ *
+ * ePrompt: Prompt and return the user's choice (default).
+ * eDontPromptAndDontUnload: Don't prompt and return false (unload not permitted)
+ * if the document (or its children) asks us to prompt.
+ * eDontPromptAndUnload: Don't prompt and return true (unload permitted) no matter what.
+ *
+ * NOTE: Keep this in sync with PermitUnloadAction in WindowGlobalActors.webidl.
+ */
+ cenum PermitUnloadAction : 8 {
+ ePrompt = 0,
+ eDontPromptAndDontUnload = 1,
+ eDontPromptAndUnload = 2
+ };
+
+ /**
+ * The result of dispatching a "beforeunload" event. If `eAllowNavigation`,
+ * no "beforeunload" listener requested to prevent the navigation, or its
+ * request was ignored. If `eRequestBlockNavigation`, a listener did request
+ * to block the navigation, and the user should be prompted.
+ */
+ cenum PermitUnloadResult : 8 {
+ eAllowNavigation = 0,
+ eRequestBlockNavigation = 1,
+ };
+
+ /**
+ * Overload PermitUnload method for C++ consumers with no aPermitUnloadFlags
+ * argument.
+ */
+ %{C++
+ nsresult PermitUnload(bool* canUnload) {
+ return PermitUnload(ePrompt, canUnload);
+ }
+ %}
+
+ /**
+ * Checks if the document wants to prevent unloading by firing beforeunload on
+ * the document.
+ * The result is returned.
+ */
+ boolean permitUnload([optional] in nsIContentViewer_PermitUnloadAction aAction);
+
+ /**
+ * Exposes whether we're blocked in a call to permitUnload.
+ */
+ readonly attribute boolean inPermitUnload;
+
+ /**
+ * Dispatches the "beforeunload" event and returns the result, as documented
+ * in the `PermitUnloadResult` enum.
+ */
+ [noscript,nostdcall,notxpcom] nsIContentViewer_PermitUnloadResult dispatchBeforeUnload();
+
+ /**
+ * Exposes whether we're in the process of firing the beforeunload event.
+ * In this case, the corresponding docshell will not allow navigation.
+ */
+ readonly attribute boolean beforeUnloadFiring;
+
+ [can_run_script] void pageHide(in boolean isUnload);
+
+ /**
+ * All users of a content viewer are responsible for calling both
+ * close() and destroy(), in that order.
+ *
+ * close() should be called when the load of a new page for the next
+ * content viewer begins, and destroy() should be called when the next
+ * content viewer replaces this one.
+ *
+ * |historyEntry| sets the session history entry for the content viewer. If
+ * this is null, then Destroy() will be called on the document by close().
+ * If it is non-null, the document will not be destroyed, and the following
+ * actions will happen when destroy() is called (*):
+ * - Sanitize() will be called on the viewer's document
+ * - The content viewer will set the contentViewer property on the
+ * history entry, and release its reference (ownership reversal).
+ * - hide() will be called, and no further destruction will happen.
+ *
+ * (*) unless the document is currently being printed, in which case
+ * it will never be saved in session history.
+ *
+ */
+ void close(in nsISHEntry historyEntry);
+ void destroy();
+
+ void stop();
+
+ /**
+ * Returns the same thing as getDocument(), but for use from script
+ * only. C++ consumers should use getDocument().
+ */
+ readonly attribute Document DOMDocument;
+
+ /**
+ * Returns DOMDocument without addrefing.
+ */
+ [noscript,notxpcom,nostdcall] Document getDocument();
+
+ /**
+ * Allows setting the document.
+ */
+ [noscript,nostdcall] void setDocument(in Document aDocument);
+
+ [noscript] void getBounds(in nsIntRectRef aBounds);
+ [noscript] void setBounds([const] in nsIntRectRef aBounds);
+ /**
+ * The 'aFlags' argument to setBoundsWithFlags is a set of these bits.
+ */
+ const unsigned long eDelayResize = 1;
+ [noscript] void setBoundsWithFlags([const] in nsIntRectRef aBounds,
+ in unsigned long aFlags);
+
+ /**
+ * The previous content viewer, which has been |close|d but not
+ * |destroy|ed.
+ */
+ [notxpcom,nostdcall] attribute nsIContentViewer previousViewer;
+
+ void move(in long aX, in long aY);
+
+ void show();
+ void hide();
+
+ attribute boolean sticky;
+
+ /**
+ * Attach the content viewer to its DOM window and docshell.
+ * @param aState A state object that might be useful in attaching the DOM
+ * window.
+ * @param aSHEntry The history entry that the content viewer was stored in.
+ * The entry must have the docshells for all of the child
+ * documents stored in its child shell list.
+ */
+ void open(in nsISupports aState, in nsISHEntry aSHEntry);
+
+ /**
+ * Clears the current history entry. This is used if we need to clear out
+ * the saved presentation state.
+ */
+ void clearHistoryEntry();
+
+ /**
+ * Change the layout to view the document with page layout (like print preview), but
+ * dynamic and editable (like Galley layout).
+ */
+ void setPageModeForTesting(in boolean aPageMode,
+ in nsIPrintSettings aPrintSettings);
+
+ /**
+ * Sets the print settings for print / print-previewing a subdocument.
+ */
+ [can_run_script] void setPrintSettingsForSubdocument(in nsIPrintSettings aPrintSettings,
+ in RemotePrintJobChildPtr aRemotePrintJob);
+
+ /**
+ * Get the history entry that this viewer will save itself into when
+ * destroyed. Can return null
+ */
+ readonly attribute nsISHEntry historyEntry;
+
+ /**
+ * Indicates when we're in a state where content shouldn't be allowed to
+ * trigger a tab-modal prompt (as opposed to a window-modal prompt) because
+ * we're part way through some operation (eg beforeunload) that shouldn't be
+ * rentrant if the user closes the tab while the prompt is showing.
+ * See bug 613800.
+ */
+ readonly attribute boolean isTabModalPromptAllowed;
+
+ /**
+ * Returns whether this content viewer is in a hidden state.
+ *
+ * @note Only Gecko internal code should set the attribute!
+ */
+ attribute boolean isHidden;
+
+ // presShell can be null.
+ [notxpcom,nostdcall] readonly attribute PresShellPtr presShell;
+ // presContext can be null.
+ [notxpcom,nostdcall] readonly attribute nsPresContextPtr presContext;
+ // aDocument must not be null.
+ [noscript] void setDocumentInternal(in Document aDocument,
+ in boolean aForceReuseInnerWindow);
+ /**
+ * Find the view to use as the container view for MakeWindow. Returns
+ * null if this will be the root of a view manager hierarchy. In that
+ * case, if mParentWidget is null then this document should not even
+ * be displayed.
+ */
+ [noscript,notxpcom,nostdcall] nsViewPtr findContainerView();
+ /**
+ * Set collector for navigation timing data (load, unload events).
+ */
+ [noscript,notxpcom,nostdcall] void setNavigationTiming(in nsDOMNavigationTimingPtr aTiming);
+
+ /**
+ * The actual full zoom in effect, as modified by the device context.
+ * For a requested full zoom, the device context may choose a slightly
+ * different effectiveFullZoom to accomodate integer rounding of app units
+ * per dev pixel. This property returns the actual zoom amount in use,
+ * though it may not be good user experience to report that a requested zoom
+ * of 90% is actually 89.1%, for example. This value is provided primarily to
+ * support media queries of dppx values, because those queries are matched
+ * against the actual native device pixel ratio and the actual full zoom.
+ *
+ * You should only need this for testing.
+ */
+ readonly attribute float deviceFullZoomForTest;
+
+ /**
+ * Disable entire author style level (including HTML presentation hints),
+ * for this viewer but not any child viewers.
+ */
+ attribute boolean authorStyleDisabled;
+
+ /**
+ * Returns the preferred width and height of the content, constrained to the
+ * given maximum values. If either maxWidth or maxHeight is less than or
+ * equal to zero, that dimension is not constrained.
+ *
+ * If a pref width is provided, it is max'd with the min-content size.
+ *
+ * All input and output values are in CSS pixels.
+ */
+ void getContentSize(in long maxWidth,
+ in long maxHeight,
+ in long prefWidth,
+ out long width,
+ out long height);
+
+%{C++
+ mozilla::Maybe<mozilla::CSSIntSize> GetContentSize(int32_t aMaxWidth = 0, int32_t aMaxHeight = 0, int32_t aPrefWidth = 0) {
+ int32_t w = 0;
+ int32_t h = 0;
+ if (NS_SUCCEEDED(GetContentSize(aMaxWidth, aMaxHeight, aPrefWidth, &w, &h))) {
+ return mozilla::Some(mozilla::CSSIntSize(w, h));
+ }
+ return mozilla::Nothing();
+ }
+%}
+
+ [noscript, notxpcom] Encoding getReloadEncodingAndSource(out int32_t aSource);
+ [noscript, notxpcom] void setReloadEncodingAndSource(in Encoding aEncoding, in int32_t aSource);
+ [noscript, notxpcom] void forgetReloadEncoding();
+};
+
+%{C++
+namespace mozilla {
+namespace dom {
+
+using XPCOMPermitUnloadAction = nsIContentViewer::PermitUnloadAction;
+using PermitUnloadResult = nsIContentViewer::PermitUnloadResult;
+
+} // namespace dom
+} // namespace mozilla
+%}
diff --git a/docshell/base/nsIContentViewerEdit.idl b/docshell/base/nsIContentViewerEdit.idl
new file mode 100644
index 0000000000..551222bbc2
--- /dev/null
+++ b/docshell/base/nsIContentViewerEdit.idl
@@ -0,0 +1,36 @@
+/* -*- Mode: IDL; tab-width: 4; 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/. */
+
+#include "nsISupports.idl"
+
+webidl Node;
+
+[scriptable, uuid(35BE2D7E-F29B-48EC-BF7E-80A30A724DE3)]
+interface nsIContentViewerEdit : nsISupports
+{
+ void clearSelection();
+ void selectAll();
+
+ void copySelection();
+ readonly attribute boolean copyable;
+
+ void copyLinkLocation();
+ readonly attribute boolean inLink;
+
+ const long COPY_IMAGE_TEXT = 0x0001;
+ const long COPY_IMAGE_HTML = 0x0002;
+ const long COPY_IMAGE_DATA = 0x0004;
+ const long COPY_IMAGE_ALL = -1;
+ void copyImage(in long aCopyFlags);
+ readonly attribute boolean inImage;
+
+ AString getContents(in string aMimeType, in boolean aSelectionOnly);
+ readonly attribute boolean canGetContents;
+
+ // Set the node that will be the subject of the editing commands above.
+ // Usually this will be the node that was context-clicked.
+ void setCommandNode(in Node aNode);
+};
diff --git a/docshell/base/nsIDocShell.idl b/docshell/base/nsIDocShell.idl
new file mode 100644
index 0000000000..68f32e968c
--- /dev/null
+++ b/docshell/base/nsIDocShell.idl
@@ -0,0 +1,796 @@
+/* -*- Mode: IDL; tab-width: 4; 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/. */
+
+#include "domstubs.idl"
+#include "nsIDocShellTreeItem.idl"
+#include "nsIRequest.idl"
+
+%{ C++
+#include "js/TypeDecls.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+#include "nsIURI.h"
+class nsCommandManager;
+class nsPresContext;
+class nsDocShellLoadState;
+namespace mozilla {
+class Encoding;
+class HTMLEditor;
+class PresShell;
+namespace dom {
+class BrowsingContext;
+class ClientSource;
+} // namespace dom
+}
+%}
+
+/**
+ * The nsIDocShell interface.
+ */
+
+[ptr] native nsPresContext(nsPresContext);
+[ptr] native nsCommandManager(nsCommandManager);
+[ptr] native PresShell(mozilla::PresShell);
+[ref] native MaybeURI(mozilla::Maybe<nsCOMPtr<nsIURI>>);
+[ref] native Encoding(const mozilla::Encoding*);
+ native UniqueClientSource(mozilla::UniquePtr<mozilla::dom::ClientSource>);
+
+interface nsIURI;
+interface nsIChannel;
+interface nsIContentViewer;
+interface nsIContentSecurityPolicy;
+interface nsIEditor;
+interface nsIEditingSession;
+interface nsIInputStream;
+interface nsIRequest;
+interface nsISHEntry;
+interface nsILayoutHistoryState;
+interface nsISecureBrowserUI;
+interface nsIScriptGlobalObject;
+interface nsIStructuredCloneContainer;
+interface nsIDOMStorage;
+interface nsIPrincipal;
+interface nsIPrivacyTransitionObserver;
+interface nsIReflowObserver;
+interface nsIScrollObserver;
+interface nsIRemoteTab;
+interface nsIBrowserChild;
+interface nsICommandParams;
+interface nsILoadURIDelegate;
+native BrowserChildRef(already_AddRefed<nsIBrowserChild>);
+native nsDocShellLoadStatePtr(nsDocShellLoadState*);
+
+webidl BrowsingContext;
+webidl ContentFrameMessageManager;
+webidl EventTarget;
+webidl Document;
+
+[scriptable, builtinclass, uuid(049234fe-da10-478b-bc5d-bc6f9a1ba63d)]
+interface nsIDocShell : nsIDocShellTreeItem
+{
+ void setCancelContentJSEpoch(in long aEpoch);
+
+ /**
+ * Loads a given URI. This will give priority to loading the requested URI
+ * in the object implementing this interface. If it can't be loaded here
+ * however, the URL dispatcher will go through its normal process of content
+ * loading.
+ *
+ * @param aLoadState This is the extended load info for this load.
+ * @param aSetNavigating If we should set isNavigating to true while initiating
+ * the load.
+ */
+ [noscript]void loadURI(in nsDocShellLoadStatePtr aLoadState, in boolean aSetNavigating);
+
+ /**
+ * Do either a history.pushState() or history.replaceState() operation,
+ * depending on the value of aReplace.
+ */
+ [implicit_jscontext]
+ void addState(in jsval aData, in AString aTitle,
+ in AString aURL, in boolean aReplace);
+
+ /**
+ * Reset state to a new content model within the current document and the document
+ * viewer. Called by the document before initiating an out of band document.write().
+ */
+ void prepareForNewContentModel();
+
+ /**
+ * Helper for the session store to change the URI associated with the
+ * document.
+ */
+ void setCurrentURIForSessionStore(in nsIURI aURI);
+
+ /**
+ * Notify the associated content viewer and all child docshells that they are
+ * about to be hidden. If |isUnload| is true, then the document is being
+ * unloaded and all dynamic subframe history entries are removed as well.
+ *
+ * @param isUnload
+ * True to fire the unload event in addition to the pagehide event,
+ * and remove all dynamic subframe history entries.
+ */
+ [noscript] void firePageHideNotification(in boolean isUnload);
+
+ /**
+ * Presentation context for the currently loaded document. This may be null.
+ */
+ [notxpcom,nostdcall] readonly attribute nsPresContext presContext;
+
+ /**
+ * Presentation shell for the currently loaded document. This may be null.
+ */
+ [notxpcom,nostdcall] readonly attribute PresShell presShell;
+
+ /**
+ * Presentation shell for the oldest document, if this docshell is
+ * currently transitioning between documents.
+ */
+ [notxpcom,nostdcall] readonly attribute PresShell eldestPresShell;
+
+ /**
+ * Content Viewer that is currently loaded for this DocShell. This may
+ * change as the underlying content changes.
+ */
+ [infallible] readonly attribute nsIContentViewer contentViewer;
+
+ /**
+ * Get the id of the outer window that is or will be in this docshell.
+ */
+ [infallible] readonly attribute unsigned long long outerWindowID;
+
+ /**
+ * This attribute allows chrome to tie in to handle DOM events that may
+ * be of interest to chrome.
+ */
+ attribute EventTarget chromeEventHandler;
+
+ /**
+ * This allows chrome to set a custom User agent on a specific docshell
+ */
+ attribute AString customUserAgent;
+
+ /**
+ * Whether CSS error reporting is enabled.
+ */
+ attribute boolean cssErrorReportingEnabled;
+
+ /**
+ * Whether to allow plugin execution
+ */
+ attribute boolean allowPlugins;
+
+ /**
+ * Attribute stating if refresh based redirects can be allowed
+ */
+ attribute boolean allowMetaRedirects;
+
+ /**
+ * Attribute stating if it should allow subframes (framesets/iframes) or not
+ */
+ attribute boolean allowSubframes;
+
+ /**
+ * Attribute stating whether or not images should be loaded.
+ */
+ attribute boolean allowImages;
+
+ /**
+ * Attribute stating whether or not media (audio/video) should be loaded.
+ */
+ [infallible] attribute boolean allowMedia;
+
+ /**
+ * Attribute that determines whether DNS prefetch is allowed for this subtree
+ * of the docshell tree. Defaults to true. Setting this will make it take
+ * effect starting with the next document loaded in the docshell.
+ */
+ attribute boolean allowDNSPrefetch;
+
+ /**
+ * Attribute that determines whether window control (move/resize) is allowed.
+ */
+ attribute boolean allowWindowControl;
+
+ /**
+ * True if the docshell allows its content to be handled by a content listener
+ * other than the docshell itself, including the external helper app service,
+ * and false otherwise. Defaults to true.
+ */
+ [infallible] attribute boolean allowContentRetargeting;
+
+ /**
+ * True if new child docshells should allow content retargeting.
+ * Setting allowContentRetargeting also overwrites this value.
+ */
+ [infallible] attribute boolean allowContentRetargetingOnChildren;
+
+ /**
+ * Get an array of this docShell and its children.
+ *
+ * @param aItemType - Only include docShells of this type, or if typeAll,
+ * include all child shells.
+ * Uses types from nsIDocShellTreeItem.
+ * @param aDirection - Whether to enumerate forwards or backwards.
+ */
+
+ cenum DocShellEnumeratorDirection : 8 {
+ ENUMERATE_FORWARDS = 0,
+ ENUMERATE_BACKWARDS = 1
+ };
+
+ Array<nsIDocShell> getAllDocShellsInSubtree(in long aItemType,
+ in nsIDocShell_DocShellEnumeratorDirection aDirection);
+
+ /**
+ * The type of application that created this window.
+ *
+ * DO NOT DELETE, see bug 176166. For firefox, this value will always be
+ * UNKNOWN. However, it is used heavily in Thunderbird/comm-central and we
+ * don't really have a great replacement at the moment, so we'll just leave it
+ * here.
+ */
+ cenum AppType : 8 {
+ APP_TYPE_UNKNOWN = 0,
+ APP_TYPE_MAIL = 1,
+ APP_TYPE_EDITOR = 2
+ };
+
+ [infallible] attribute nsIDocShell_AppType appType;
+
+ /**
+ * certain docshells (like the message pane)
+ * should not throw up auth dialogs
+ * because it can act as a password trojan
+ */
+ attribute boolean allowAuth;
+
+ /**
+ * Set/Get the document scale factor. When setting this attribute, a
+ * NS_ERROR_NOT_IMPLEMENTED error may be returned by implementations
+ * not supporting zoom. Implementations not supporting zoom should return
+ * 1.0 all the time for the Get operation. 1.0 by the way is the default
+ * of zoom. This means 100% of normal scaling or in other words normal size
+ * no zoom.
+ */
+ attribute float zoom;
+
+ /*
+ * Tells the docshell to offer focus to its tree owner.
+ * This is currently only necessary for embedding chrome.
+ * If forDocumentNavigation is true, then document navigation should be
+ * performed, where only the root of documents are selected. Otherwise, the
+ * next element in the parent should be returned. Returns true if focus was
+ * successfully taken by the tree owner.
+ */
+ bool tabToTreeOwner(in boolean forward, in boolean forDocumentNavigation);
+
+ /**
+ * Current busy state for DocShell
+ */
+ cenum BusyFlags : 8 {
+ BUSY_FLAGS_NONE = 0,
+ BUSY_FLAGS_BUSY = 1,
+ BUSY_FLAGS_BEFORE_PAGE_LOAD = 2,
+ BUSY_FLAGS_PAGE_LOADING = 4,
+ };
+
+ [infallible] readonly attribute nsIDocShell_BusyFlags busyFlags;
+
+ /**
+ * Load commands for the document
+ */
+ cenum LoadCommand : 8 {
+ LOAD_CMD_NORMAL = 0x1, // Normal load
+ LOAD_CMD_RELOAD = 0x2, // Reload
+ LOAD_CMD_HISTORY = 0x4, // Load from history
+ LOAD_CMD_PUSHSTATE = 0x8, // History.pushState()
+ };
+
+ /*
+ * Attribute to access the loadtype for the document. LoadType Enum is
+ * defined in nsDocShellLoadTypes.h
+ */
+ [infallible] attribute unsigned long loadType;
+
+ /*
+ * Default load flags (as defined in nsIRequest) that will be set on all
+ * requests made by this docShell and propagated to all child docShells and
+ * to nsILoadGroup::defaultLoadFlags for the docShell's loadGroup.
+ * Default is no flags. Once set, only future requests initiated by the
+ * docShell are affected, so in general, these flags should be set before
+ * the docShell loads any content.
+ */
+ attribute nsLoadFlags defaultLoadFlags;
+
+ /*
+ * returns true if the docshell is being destroyed, false otherwise
+ */
+ boolean isBeingDestroyed();
+
+ /*
+ * Returns true if the docshell is currently executing the onLoad Handler
+ */
+ readonly attribute boolean isExecutingOnLoadHandler;
+
+ attribute nsILayoutHistoryState layoutHistoryState;
+
+ /**
+ * Object used to delegate URI loading to an upper context.
+ * Currently only set for GeckoView to allow handling of load requests
+ * at the application level.
+ */
+ readonly attribute nsILoadURIDelegate loadURIDelegate;
+
+ /**
+ * Cancel the XPCOM timers for each meta-refresh URI in this docshell,
+ * and this docshell's children, recursively. The meta-refresh timers can be
+ * restarted using resumeRefreshURIs(). If the timers are already suspended,
+ * this has no effect.
+ */
+ void suspendRefreshURIs();
+
+ /**
+ * Restart the XPCOM timers for each meta-refresh URI in this docshell,
+ * and this docshell's children, recursively. If the timers are already
+ * running, this has no effect.
+ */
+ void resumeRefreshURIs();
+
+ /**
+ * Begin firing WebProgressListener notifications for restoring a page
+ * presentation. |viewer| is the content viewer whose document we are
+ * starting to load. If null, it defaults to the docshell's current content
+ * viewer, creating one if necessary. |top| should be true for the toplevel
+ * docshell that is being restored; it will be set to false when this method
+ * is called for child docshells. This method will post an event to
+ * complete the simulated load after returning to the event loop.
+ */
+ void beginRestore(in nsIContentViewer viewer, in boolean top);
+
+ /**
+ * Finish firing WebProgressListener notifications and DOM events for
+ * restoring a page presentation. This should only be called via
+ * beginRestore().
+ */
+ void finishRestore();
+
+ void clearCachedUserAgent();
+
+ void clearCachedPlatform();
+
+ /* Track whether we're currently restoring a document presentation. */
+ readonly attribute boolean restoringDocument;
+
+ /* attribute to access whether error pages are enabled */
+ attribute boolean useErrorPages;
+
+ /**
+ * Display a load error in a frame while keeping that frame's currentURI
+ * pointing correctly to the page where the error ocurred, rather than to
+ * the error document page. You must provide either the aURI or aURL parameter.
+ *
+ * @param aError The error code to be displayed
+ * @param aURI nsIURI of the page where the error happened
+ * @param aURL wstring of the page where the error happened
+ * @param aFailedChannel The channel related to this error
+ *
+ * Returns whether or not we displayed an error page (note: will always
+ * return false if in-content error pages are disabled!)
+ */
+ boolean displayLoadError(in nsresult aError,
+ in nsIURI aURI,
+ in wstring aURL,
+ [optional] in nsIChannel aFailedChannel);
+
+ /**
+ * The channel that failed to load and resulted in an error page.
+ * May be null. Relevant only to error pages.
+ */
+ readonly attribute nsIChannel failedChannel;
+
+ /**
+ * Keeps track of the previous nsISHEntry index and the current
+ * nsISHEntry index at the time that the doc shell begins to load.
+ * Used for ContentViewer eviction.
+ */
+ readonly attribute long previousEntryIndex;
+ readonly attribute long loadedEntryIndex;
+
+ /**
+ * Notification that entries have been removed from the beginning of a
+ * nsSHistory which has this as its rootDocShell.
+ *
+ * @param numEntries - The number of entries removed
+ */
+ void historyPurged(in long numEntries);
+
+ /**
+ * Gets the channel for the currently loaded document, if any.
+ * For a new document load, this will be the channel of the previous document
+ * until after OnLocationChange fires.
+ */
+ readonly attribute nsIChannel currentDocumentChannel;
+
+ /**
+ * Find out whether the docshell is currently in the middle of a page
+ * transition. This is set just before the pagehide/unload events fire.
+ */
+ [infallible] readonly attribute boolean isInUnload;
+
+ /**
+ * Disconnects this docshell's editor from its window, and stores the
+ * editor data in the open document's session history entry. This
+ * should be called only during page transitions.
+ */
+ [noscript, notxpcom] void DetachEditorFromWindow();
+
+ /**
+ * Propagated to the print preview document viewer. Must only be called on
+ * a document viewer that has been initialized for print preview.
+ */
+ void exitPrintPreview();
+
+ /**
+ * The ID of the docshell in the session history.
+ */
+ readonly attribute nsIDRef historyID;
+
+ /**
+ * Helper method for accessing this value from C++
+ */
+ [noscript, notxpcom] nsIDRef HistoryID();
+
+ /**
+ * Create a new about:blank document and content viewer.
+ * @param aPrincipal the principal to use for the new document.
+ * @param aPartitionedPrincipal the partitioned principal to use for the new
+ * document.
+ * @param aCsp the CSP to use for the new document.
+ */
+ void createAboutBlankContentViewer(in nsIPrincipal aPrincipal,
+ in nsIPrincipal aPartitionedPrincipal,
+ [optional] in nsIContentSecurityPolicy aCSP);
+
+ /**
+ * Upon getting, returns the canonical encoding label of the document
+ * currently loaded into this docshell.
+ */
+ readonly attribute ACString charset;
+
+ void forceEncodingDetection();
+
+ /**
+ * In a child docshell, this is the charset of the parent docshell
+ */
+ [noscript, notxpcom, nostdcall] void setParentCharset(
+ in Encoding parentCharset,
+ in int32_t parentCharsetSource,
+ in nsIPrincipal parentCharsetPrincipal);
+ [noscript, notxpcom, nostdcall] void getParentCharset(
+ out Encoding parentCharset,
+ out int32_t parentCharsetSource,
+ out nsIPrincipal parentCharsetPrincipal);
+
+ /**
+ * Whether the docShell records profile timeline markers at the moment
+ */
+ [infallible] attribute boolean recordProfileTimelineMarkers;
+
+ /**
+ * Return a DOMHighResTimeStamp representing the number of
+ * milliseconds from an arbitrary point in time. The reference
+ * point is shared by all DocShells and is also used by timestamps
+ * on markers.
+ */
+ DOMHighResTimeStamp now();
+
+ /**
+ * Returns and flushes the profile timeline markers gathered by the docShell
+ */
+ [implicit_jscontext]
+ jsval popProfileTimelineMarkers();
+
+ /**
+ * Add an observer to the list of parties to be notified when this docshell's
+ * private browsing status is changed. |obs| must support weak references.
+ */
+ void addWeakPrivacyTransitionObserver(in nsIPrivacyTransitionObserver obs);
+
+ /**
+ * Add an observer to the list of parties to be notified when reflows are
+ * occurring. |obs| must support weak references.
+ */
+ void addWeakReflowObserver(in nsIReflowObserver obs);
+
+ /**
+ * Remove an observer from the list of parties to be notified about reflows.
+ */
+ void removeWeakReflowObserver(in nsIReflowObserver obs);
+
+ /**
+ * Notify all attached observers that a reflow has just occurred.
+ *
+ * @param interruptible if true, the reflow was interruptible.
+ * @param start timestamp when reflow started, in milliseconds since
+ * navigationStart (accurate to 1/1000 of a ms)
+ * @param end timestamp when reflow ended, in milliseconds since
+ * navigationStart (accurate to 1/1000 of a ms)
+ */
+ [noscript] void notifyReflowObservers(in bool interruptible,
+ in DOMHighResTimeStamp start,
+ in DOMHighResTimeStamp end);
+
+ /**
+ * Add an observer to the list of parties to be notified when scroll position
+ * of some elements is changed.
+ */
+ [noscript] void addWeakScrollObserver(in nsIScrollObserver obs);
+
+ /**
+ * Add an observer to the list of parties to be notified when scroll position
+ * of some elements is changed.
+ */
+ [noscript] void removeWeakScrollObserver(in nsIScrollObserver obs);
+
+ /**
+ * Notify all attached observers that the scroll position of some element
+ * has changed.
+ */
+ [noscript] void notifyScrollObservers();
+
+ /**
+ * Returns true if this docshell is the top level content docshell.
+ */
+ [infallible] readonly attribute boolean isTopLevelContentDocShell;
+
+ /**
+ * Like nsIDocShellTreeItem::GetSameTypeParent, except this ignores <iframe
+ * mozbrowser> boundaries. Which no longer exist.
+ *
+ * @deprecated: Use `BrowsingContext::GetParent()` in the future.
+ */
+ nsIDocShell getSameTypeInProcessParentIgnoreBrowserBoundaries();
+
+ /**
+ * True iff asynchronous panning and zooming is enabled for this
+ * docshell.
+ */
+ readonly attribute bool asyncPanZoomEnabled;
+
+ /**
+ * Are plugins allowed in the current document loaded in this docshell ?
+ * (if there is one). This depends on whether plugins are allowed by this
+ * docshell itself or if the document is sandboxed and hence plugins should
+ * not be allowed.
+ */
+ [noscript, notxpcom] bool pluginsAllowedInCurrentDoc();
+
+ /**
+ * Indicates whether the UI may enable the character encoding menu. The UI
+ * must disable the menu when this property is false.
+ */
+ [infallible] readonly attribute boolean mayEnableCharacterEncodingMenu;
+
+ attribute nsIEditor editor;
+ readonly attribute boolean editable; /* this docShell is editable */
+ readonly attribute boolean hasEditingSession; /* this docShell has an editing session */
+
+ /**
+ * Make this docShell editable, setting a flag that causes
+ * an editor to get created, either immediately, or after
+ * a url has been loaded.
+ * @param inWaitForUriLoad true to wait for a URI before
+ * creating the editor.
+ */
+ void makeEditable(in boolean inWaitForUriLoad);
+
+ /**
+ * Returns false for mLSHE, true for mOSHE
+ */
+ boolean getCurrentSHEntry(out nsISHEntry aEntry);
+
+ /**
+ * Cherry picked parts of nsIController.
+ * They are here, because we want to call these functions
+ * from JS.
+ */
+ boolean isCommandEnabled(in string command);
+ [can_run_script]
+ void doCommand(in string command);
+ [can_run_script]
+ void doCommandWithParams(in string command, in nsICommandParams aParams);
+
+ /**
+ * Invisible DocShell are dummy construct to simulate DOM windows
+ * without any actual visual representation. They have to be marked
+ * at construction time, to avoid any painting activity.
+ */
+ [noscript, notxpcom] bool IsInvisible();
+ [noscript, notxpcom] void SetInvisible(in bool aIsInvisibleDocshell);
+
+/**
+ * Get the script global for the document in this docshell.
+*/
+ [noscript,notxpcom,nostdcall] nsIScriptGlobalObject GetScriptGlobalObject();
+
+ [noscript,notxpcom,nostdcall] Document getExtantDocument();
+
+ /**
+ * Notify DocShell when the browser is about to start executing JS, and after
+ * that execution has stopped. This only occurs when the Timeline devtool
+ * is collecting information.
+ */
+ [noscript,notxpcom,nostdcall] void notifyJSRunToCompletionStart(in string aReason,
+ in AString functionName,
+ in AString fileName,
+ in unsigned long lineNumber,
+ in jsval asyncStack,
+ in string asyncCause);
+ [noscript,notxpcom,nostdcall] void notifyJSRunToCompletionStop();
+
+ /**
+ * This attribute determines whether a document which is not about:blank has
+ * already be loaded by this docShell.
+ */
+ [infallible] readonly attribute boolean hasLoadedNonBlankURI;
+
+ /**
+ * Allow usage of -moz-window-dragging:drag for content docshells.
+ * True for top level chrome docshells. Throws if set to false with
+ * top level chrome docshell.
+ */
+ attribute boolean windowDraggingAllowed;
+
+ /**
+ * Sets/gets the current scroll restoration mode.
+ * @see https://html.spec.whatwg.org/#dom-history-scroll-restoration
+ */
+ attribute boolean currentScrollRestorationIsManual;
+
+ /**
+ * Setter and getter for the origin attributes living on this docshell.
+ */
+ [implicit_jscontext]
+ jsval getOriginAttributes();
+
+ [implicit_jscontext]
+ void setOriginAttributes(in jsval aAttrs);
+
+ /**
+ * The editing session for this docshell.
+ */
+ readonly attribute nsIEditingSession editingSession;
+
+ /**
+ * The browser child for this docshell.
+ */
+ [binaryname(ScriptableBrowserChild)] readonly attribute nsIBrowserChild browserChild;
+ [noscript,notxpcom,nostdcall] BrowserChildRef GetBrowserChild();
+
+ [noscript,nostdcall,notxpcom] nsCommandManager GetCommandManager();
+
+ cenum MetaViewportOverride: 8 {
+ /**
+ * Override platform/pref default behaviour and force-disable support for
+ * <meta name="viewport">.
+ */
+ META_VIEWPORT_OVERRIDE_DISABLED = 0,
+ /**
+ * Override platform/pref default behaviour and force-enable support for
+ * <meta name="viewport">.
+ */
+ META_VIEWPORT_OVERRIDE_ENABLED = 1,
+ /**
+ * Don't override the platform/pref default behaviour for support for
+ * <meta name="viewport">.
+ */
+ META_VIEWPORT_OVERRIDE_NONE = 2,
+ };
+
+ /**
+ * This allows chrome to override the default choice of whether the
+ * <meta name="viewport"> tag is respected in a specific docshell.
+ * Possible values are listed above.
+ */
+ [infallible, setter_can_run_script] attribute nsIDocShell_MetaViewportOverride metaViewportOverride;
+
+ /**
+ * Attribute that determines whether tracking protection is enabled.
+ */
+ attribute boolean useTrackingProtection;
+
+ /**
+ * Fire a dummy location change event asynchronously.
+ */
+ [noscript] void dispatchLocationChangeEvent();
+
+
+ /**
+ * Start delayed autoplay media which are in the current document.
+ */
+ [noscript] void startDelayedAutoplayMediaComponents();
+
+ /**
+ * Take ownership of the ClientSource representing an initial about:blank
+ * document that was never needed. As an optimization we avoid creating
+ * this document if no code calls GetDocument(), but we still need a
+ * ClientSource object to represent the about:blank window. This may return
+ * nullptr; for example if the docshell has created a real window and document
+ * already.
+ */
+ [noscript, nostdcall, notxpcom]
+ UniqueClientSource TakeInitialClientSource();
+
+ void setColorMatrix(in Array<float> aMatrix);
+
+ /**
+ * Returns true if the current load is a forced reload,
+ * e.g. started by holding shift whilst triggering reload.
+ */
+ readonly attribute bool isForceReloading;
+
+ Array<float> getColorMatrix();
+
+%{C++
+ /**
+ * These methods call nsDocShell::GetHTMLEditorInternal() and
+ * nsDocShell::SetHTMLEditorInternal() with static_cast.
+ */
+ mozilla::HTMLEditor* GetHTMLEditor();
+ nsresult SetHTMLEditor(mozilla::HTMLEditor* aHTMLEditor);
+%}
+
+ /**
+ * The message manager for this docshell. This does not throw, but
+ * can return null if the docshell has no message manager.
+ */
+ [infallible] readonly attribute ContentFrameMessageManager messageManager;
+
+ /**
+ * This returns a Promise which resolves to a boolean. True when the
+ * document has Tracking Content that has been blocked from loading, false
+ * otherwise.
+ */
+ Promise getHasTrackingContentBlocked();
+
+ /**
+ * Return whether this docshell is "attempting to navigate" in the
+ * sense that's relevant to document.open.
+ */
+ [notxpcom, nostdcall] readonly attribute boolean isAttemptingToNavigate;
+
+ /*
+ * Whether or not this docshell is executing a nsIWebNavigation navigation
+ * method.
+ *
+ * This will be true when the following methods are executing:
+ * nsIWebNavigation.binaryLoadURI
+ * nsIWebNavigation.goBack
+ * nsIWebNavigation.goForward
+ * nsIWebNavigation.gotoIndex
+ * nsIWebNavigation.loadURI
+ */
+ [infallible] readonly attribute boolean isNavigating;
+
+ /**
+ * @see nsISHEntry synchronizeLayoutHistoryState().
+ */
+ void synchronizeLayoutHistoryState();
+
+ /**
+ * This attempts to save any applicable layout history state (like
+ * scroll position) in the nsISHEntry. This is normally done
+ * automatically when transitioning from page to page in the
+ * same process. We expose this function to support transitioning
+ * from page to page across processes as a workaround for bug 1630234
+ * until session history state is moved into the parent process.
+ */
+ void persistLayoutHistoryState();
+};
diff --git a/docshell/base/nsIDocShellTreeItem.idl b/docshell/base/nsIDocShellTreeItem.idl
new file mode 100644
index 0000000000..a80373832f
--- /dev/null
+++ b/docshell/base/nsIDocShellTreeItem.idl
@@ -0,0 +1,171 @@
+/* -*- Mode: IDL; tab-width: 4; 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/. */
+
+#include "nsISupports.idl"
+
+interface mozIDOMWindowProxy;
+interface nsIDocShellTreeOwner;
+interface nsPIDOMWindowOuter;
+
+webidl Document;
+webidl BrowsingContext;
+
+/**
+ * The nsIDocShellTreeItem supplies the methods that are required of any item
+ * that wishes to be able to live within the docshell tree either as a middle
+ * node or a leaf.
+ */
+
+[scriptable, builtinclass, uuid(9b7c586f-9214-480c-a2c4-49b526fff1a6)]
+interface nsIDocShellTreeItem : nsISupports
+{
+ /*
+ name of the DocShellTreeItem
+ */
+ attribute AString name;
+
+ /**
+ * Compares the provided name against the item's name and
+ * returns the appropriate result.
+ *
+ * @return <CODE>PR_TRUE</CODE> if names match;
+ * <CODE>PR_FALSE</CODE> otherwise.
+ */
+ boolean nameEquals(in AString name);
+
+ /*
+ Definitions for the item types.
+ */
+ const long typeChrome=0; // typeChrome must equal 0
+ const long typeContent=1; // typeContent must equal 1
+ const long typeContentWrapper=2; // typeContentWrapper must equal 2
+ const long typeChromeWrapper=3; // typeChromeWrapper must equal 3
+
+ const long typeAll=0x7FFFFFFF;
+
+ /*
+ The type this item is.
+ */
+ readonly attribute long itemType;
+ [noscript,notxpcom,nostdcall] long ItemType();
+
+ /*
+ Parent DocShell.
+
+ @deprecated: Use `BrowsingContext::GetParent()` instead.
+ (NOTE: `BrowsingContext::GetParent()` will not cross isolation boundaries)
+ */
+ [binaryname(InProcessParent)]
+ readonly attribute nsIDocShellTreeItem parent;
+
+ /*
+ This getter returns the same thing parent does however if the parent
+ is of a different itemType, or if the parent is an <iframe mozbrowser>.
+ It will instead return nullptr. This call is a convience function for
+ Ithose wishing to not cross the boundaries at which item types change.
+
+ @deprecated: Use `BrowsingContext::GetParent()` instead.
+ */
+ [binaryname(InProcessSameTypeParent)]
+ readonly attribute nsIDocShellTreeItem sameTypeParent;
+
+ /*
+ Returns the root DocShellTreeItem. This is a convience equivalent to
+ getting the parent and its parent until there isn't a parent.
+
+ @deprecated: Use `BrowsingContext::Top()` instead.
+ (NOTE: `BrowsingContext::Top()` will not cross isolation boundaries)
+ */
+ [binaryname(InProcessRootTreeItem)]
+ readonly attribute nsIDocShellTreeItem rootTreeItem;
+
+ /*
+ Returns the root DocShellTreeItem of the same type. This is a convience
+ equivalent to getting the parent of the same type and its parent until
+ there isn't a parent.
+
+ @deprecated: Use `BrowsingContext::Top()` instead.
+ */
+ [binaryname(InProcessSameTypeRootTreeItem)]
+ readonly attribute nsIDocShellTreeItem sameTypeRootTreeItem;
+
+ /*
+ The owner of the DocShell Tree. This interface will be called upon when
+ the docshell has things it needs to tell to the owner of the docshell.
+ Note that docShell tree ownership does not cross tree types. Meaning
+ setting ownership on a chrome tree does not set ownership on the content
+ sub-trees. A given tree's boundaries are identified by the type changes.
+ Trees of different types may be connected, but should not be traversed
+ for things such as ownership.
+
+ Note implementers of this interface should NOT effect the lifetime of the
+ parent DocShell by holding this reference as it creates a cycle. Owners
+ when releasing this interface should set the treeOwner to nullptr.
+ Implementers of this interface are guaranteed that when treeOwner is
+ set that the poitner is valid without having to addref.
+
+ Further note however when others try to get the interface it should be
+ addref'd before handing it to them.
+ */
+ readonly attribute nsIDocShellTreeOwner treeOwner;
+ [noscript] void setTreeOwner(in nsIDocShellTreeOwner treeOwner);
+
+ /*
+ The current number of DocShells which are immediate children of the
+ this object.
+
+
+ @deprecated: Prefer using `BrowsingContext::Children()`, as this count will
+ not include out-of-process iframes.
+ */
+ [binaryname(InProcessChildCount), infallible]
+ readonly attribute long childCount;
+
+ /*
+ Add a new child DocShellTreeItem. Adds to the end of the list.
+ Note that this does NOT take a reference to the child. The child stays
+ alive only as long as it's referenced from outside the docshell tree.
+
+ @throws NS_ERROR_ILLEGAL_VALUE if child corresponds to the same
+ object as this treenode or an ancestor of this treenode
+ @throws NS_ERROR_UNEXPECTED if this node is a leaf in the tree.
+ */
+ [noscript] void addChild(in nsIDocShellTreeItem child);
+
+ /*
+ Removes a child DocShellTreeItem.
+
+ @throws NS_ERROR_UNEXPECTED if this node is a leaf in the tree.
+ */
+ [noscript] void removeChild(in nsIDocShellTreeItem child);
+
+ /**
+ * Return the child at the index requested. This is 0-based.
+ *
+ * @deprecated: Prefer using `BrowsingContext::Children()`, as this will not
+ * include out-of-process iframes.
+ *
+ * @throws NS_ERROR_UNEXPECTED if the index is out of range
+ */
+ [binaryname(GetInProcessChildAt)]
+ nsIDocShellTreeItem getChildAt(in long index);
+
+ /**
+ * BrowsingContext associated with the DocShell.
+ */
+ [binaryname(BrowsingContextXPCOM)]
+ readonly attribute BrowsingContext browsingContext;
+
+ [noscript,notxpcom,nostdcall] BrowsingContext getBrowsingContext();
+
+ /**
+ * Returns the DOM outer window for the content viewer.
+ */
+ readonly attribute mozIDOMWindowProxy domWindow;
+
+ [noscript,nostdcall,notxpcom] Document getDocument();
+ [noscript,nostdcall,notxpcom] nsPIDOMWindowOuter getWindow();
+};
diff --git a/docshell/base/nsIDocShellTreeOwner.idl b/docshell/base/nsIDocShellTreeOwner.idl
new file mode 100644
index 0000000000..db6faa59c9
--- /dev/null
+++ b/docshell/base/nsIDocShellTreeOwner.idl
@@ -0,0 +1,113 @@
+/* -*- Mode: IDL; tab-width: 4; 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/. */
+
+#include "nsISupports.idl"
+
+/**
+ * The nsIDocShellTreeOwner
+ */
+
+interface nsIDocShellTreeItem;
+interface nsIRemoteTab;
+webidl BrowsingContext;
+
+[scriptable, uuid(0e3dc4b1-4cea-4a37-af71-79f0afd07574)]
+interface nsIDocShellTreeOwner : nsISupports
+{
+ /**
+ * Called when a content shell is added to the docshell tree. This is
+ * _only_ called for "root" content shells (that is, ones whose parent is a
+ * chrome shell).
+ *
+ * @param aContentShell the shell being added.
+ * @param aPrimary whether the shell is primary.
+ */
+ void contentShellAdded(in nsIDocShellTreeItem aContentShell,
+ in boolean aPrimary);
+
+ /**
+ * Called when a content shell is removed from the docshell tree. This is
+ * _only_ called for "root" content shells (that is, ones whose parent is a
+ * chrome shell). Note that if aContentShell was never added,
+ * contentShellRemoved should just do nothing.
+ *
+ * @param aContentShell the shell being removed.
+ */
+ void contentShellRemoved(in nsIDocShellTreeItem aContentShell);
+
+ /*
+ Returns the Primary Content Shell
+ */
+ readonly attribute nsIDocShellTreeItem primaryContentShell;
+
+ void remoteTabAdded(in nsIRemoteTab aTab, in boolean aPrimary);
+ void remoteTabRemoved(in nsIRemoteTab aTab);
+
+ /*
+ In multiprocess case we may not have primaryContentShell but
+ primaryRemoteTab.
+ */
+ readonly attribute nsIRemoteTab primaryRemoteTab;
+
+ /*
+ Get the BrowsingContext associated with either the primary content shell or
+ primary remote tab, depending on which is available.
+ */
+ readonly attribute BrowsingContext primaryContentBrowsingContext;
+
+ /*
+ Tells the tree owner to size its window or parent window in such a way
+ that the shell passed along will be the size specified.
+ */
+ [can_run_script]
+ void sizeShellTo(in nsIDocShellTreeItem shell, in long cx, in long cy);
+
+ /*
+ Gets the size of the primary content area in device pixels. This should work
+ for both in-process and out-of-process content areas.
+ */
+ void getPrimaryContentSize(out long width, out long height);
+ /*
+ Sets the size of the primary content area in device pixels. This should work
+ for both in-process and out-of-process content areas.
+ */
+ void setPrimaryContentSize(in long width, in long height);
+
+ /*
+ Gets the size of the root docshell in device pixels.
+ */
+ void getRootShellSize(out long width, out long height);
+ /*
+ Sets the size of the root docshell in device pixels.
+ */
+ void setRootShellSize(in long width, in long height);
+
+ /*
+ Sets the persistence of different attributes of the window.
+ */
+ void setPersistence(in boolean aPersistPosition,
+ in boolean aPersistSize,
+ in boolean aPersistSizeMode);
+
+ /*
+ Gets the current persistence states of the window.
+ */
+ void getPersistence(out boolean aPersistPosition,
+ out boolean aPersistSize,
+ out boolean aPersistSizeMode);
+
+ /*
+ Gets the number of tabs currently open in our window, assuming
+ this tree owner has such a concept.
+ */
+ readonly attribute unsigned long tabCount;
+
+ /*
+ Returns true if there is a primary content shell or a primary
+ remote tab.
+ */
+ readonly attribute bool hasPrimaryContent;
+};
diff --git a/docshell/base/nsIDocumentLoaderFactory.idl b/docshell/base/nsIDocumentLoaderFactory.idl
new file mode 100644
index 0000000000..cd08bc0f3f
--- /dev/null
+++ b/docshell/base/nsIDocumentLoaderFactory.idl
@@ -0,0 +1,39 @@
+/* -*- Mode: IDL; tab-width: 4; 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/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIContentViewer;
+interface nsIStreamListener;
+interface nsIDocShell;
+interface nsILoadGroup;
+interface nsIPrincipal;
+
+webidl Document;
+
+/**
+ * To get a component that implements nsIDocumentLoaderFactory
+ * for a given mimetype, use nsICategoryManager to find an entry
+ * with the mimetype as its name in the category "Gecko-Content-Viewers".
+ * The value of the entry is the contractid of the component.
+ * The component is a service, so use GetService, not CreateInstance to get it.
+ */
+
+[scriptable, uuid(e795239e-9d3c-47c4-b063-9e600fb3b287)]
+interface nsIDocumentLoaderFactory : nsISupports {
+ nsIContentViewer createInstance(in string aCommand,
+ in nsIChannel aChannel,
+ in nsILoadGroup aLoadGroup,
+ in ACString aContentType,
+ in nsIDocShell aContainer,
+ in nsISupports aExtraInfo,
+ out nsIStreamListener aDocListenerResult);
+
+ nsIContentViewer createInstanceForDocument(in nsISupports aContainer,
+ in Document aDocument,
+ in string aCommand);
+};
diff --git a/docshell/base/nsILoadContext.idl b/docshell/base/nsILoadContext.idl
new file mode 100644
index 0000000000..af71b96b34
--- /dev/null
+++ b/docshell/base/nsILoadContext.idl
@@ -0,0 +1,148 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: ft=cpp tw=78 sw=2 et ts=2 sts=2 cin
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface mozIDOMWindowProxy;
+
+webidl Element;
+
+[ref] native OriginAttributes(mozilla::OriginAttributes);
+
+%{C++
+#ifdef MOZILLA_INTERNAL_API
+namespace mozilla {
+class OriginAttributes;
+}
+#endif
+%}
+
+/**
+ * An nsILoadContext represents the context of a load. This interface
+ * can be queried for various information about where the load is
+ * happening.
+ */
+[builtinclass, scriptable, uuid(2813a7a3-d084-4d00-acd0-f76620315c02)]
+interface nsILoadContext : nsISupports
+{
+ /**
+ * associatedWindow is the window with which the load is associated, if any.
+ * Note that the load may be triggered by a document which is different from
+ * the document in associatedWindow, and in fact the source of the load need
+ * not be same-origin with the document in associatedWindow. This attribute
+ * may be null if there is no associated window.
+ */
+ readonly attribute mozIDOMWindowProxy associatedWindow;
+
+ /**
+ * topWindow is the top window which is of same type as associatedWindow.
+ * This is equivalent to associatedWindow.top, but is provided here as a
+ * convenience. All the same caveats as associatedWindow of apply, of
+ * course. This attribute may be null if there is no associated window.
+ */
+ readonly attribute mozIDOMWindowProxy topWindow;
+
+ /**
+ * topFrameElement is the <iframe>, <frame>, or <browser> element which
+ * contains the topWindow with which the load is associated.
+ *
+ * Note that we may have a topFrameElement even when we don't have an
+ * associatedWindow, if the topFrameElement's content lives out of process.
+ * topFrameElement is available in single-process and multiprocess contexts.
+ * Note that topFrameElement may be in chrome even when the nsILoadContext is
+ * associated with content.
+ */
+ readonly attribute Element topFrameElement;
+
+ /**
+ * True if the load context is content (as opposed to chrome). This is
+ * determined based on the type of window the load is performed in, NOT based
+ * on any URIs that might be around.
+ */
+ readonly attribute boolean isContent;
+
+ /*
+ * Attribute that determines if private browsing should be used. May not be
+ * changed after a document has been loaded in this context.
+ */
+ attribute boolean usePrivateBrowsing;
+
+ /**
+ * Attribute that determines if remote (out-of-process) tabs should be used.
+ */
+ readonly attribute boolean useRemoteTabs;
+
+ /**
+ * Determines if out-of-process iframes should be used.
+ */
+ readonly attribute boolean useRemoteSubframes;
+
+ /*
+ * Attribute that determines if tracking protection should be used. May not be
+ * changed after a document has been loaded in this context.
+ */
+ attribute boolean useTrackingProtection;
+
+%{C++
+ /**
+ * De-XPCOMed getter to make call-sites cleaner.
+ */
+ bool UsePrivateBrowsing()
+ {
+ bool usingPB = false;
+ GetUsePrivateBrowsing(&usingPB);
+ return usingPB;
+ }
+
+ bool UseRemoteTabs()
+ {
+ bool usingRT = false;
+ GetUseRemoteTabs(&usingRT);
+ return usingRT;
+ }
+
+ bool UseRemoteSubframes()
+ {
+ bool usingRSF = false;
+ GetUseRemoteSubframes(&usingRSF);
+ return usingRSF;
+ }
+
+ bool UseTrackingProtection()
+ {
+ bool usingTP = false;
+ GetUseTrackingProtection(&usingTP);
+ return usingTP;
+ }
+%}
+
+ /**
+ * Set the private browsing state of the load context, meant to be used internally.
+ */
+ [noscript] void SetPrivateBrowsing(in boolean aInPrivateBrowsing);
+
+ /**
+ * Set the remote tabs state of the load context, meant to be used internally.
+ */
+ [noscript] void SetRemoteTabs(in boolean aUseRemoteTabs);
+
+ /**
+ * Set the remote subframes bit of this load context. Exclusively meant to be used internally.
+ */
+ [noscript] void SetRemoteSubframes(in boolean aUseRemoteSubframes);
+
+ /**
+ * A dictionary of the non-default origin attributes associated with this
+ * nsILoadContext.
+ */
+ [binaryname(ScriptableOriginAttributes), implicit_jscontext]
+ readonly attribute jsval originAttributes;
+
+ /**
+ * The C++ getter for origin attributes.
+ */
+ [noscript, notxpcom] void GetOriginAttributes(out OriginAttributes aAttrs);
+};
diff --git a/docshell/base/nsILoadURIDelegate.idl b/docshell/base/nsILoadURIDelegate.idl
new file mode 100644
index 0000000000..eb5d4cbaf5
--- /dev/null
+++ b/docshell/base/nsILoadURIDelegate.idl
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIPrincipal;
+
+/**
+ * The nsILoadURIDelegate interface.
+ * Used for delegating URI loads to GeckoView's application, e.g., Custom Tabs
+ * or Progressive Web Apps.
+ */
+[scriptable, uuid(78e42d37-a34c-4d96-b901-25385669aba4)]
+interface nsILoadURIDelegate : nsISupports
+{
+ /**
+ * Delegates page load error handling. This may be called for either top-level
+ * loads or subframes.
+ *
+ * @param aURI The URI that failed to load.
+ * @param aError The error code.
+ * @param aErrorModule The error module code.
+
+ * Returns an error page URL to load, or null to show the default error page.
+ * No error page is shown at all if an error is thrown.
+ */
+ nsIURI
+ handleLoadError(in nsIURI aURI, in nsresult aError, in short aErrorModule);
+};
diff --git a/docshell/base/nsIPrivacyTransitionObserver.idl b/docshell/base/nsIPrivacyTransitionObserver.idl
new file mode 100644
index 0000000000..c85d468d33
--- /dev/null
+++ b/docshell/base/nsIPrivacyTransitionObserver.idl
@@ -0,0 +1,11 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, function, uuid(b4b1449d-0ef0-47f5-b62e-adc57fd49702)]
+interface nsIPrivacyTransitionObserver : nsISupports
+{
+ void privateModeChanged(in bool enabled);
+};
diff --git a/docshell/base/nsIReflowObserver.idl b/docshell/base/nsIReflowObserver.idl
new file mode 100644
index 0000000000..fb602e2603
--- /dev/null
+++ b/docshell/base/nsIReflowObserver.idl
@@ -0,0 +1,31 @@
+/* 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 "domstubs.idl"
+
+[scriptable, uuid(832e692c-c4a6-11e2-8fd1-dce678957a39)]
+interface nsIReflowObserver : nsISupports
+{
+ /**
+ * Called when an uninterruptible reflow has occurred.
+ *
+ * @param start timestamp when reflow ended, in milliseconds since
+ * navigationStart (accurate to 1/1000 of a ms)
+ * @param end timestamp when reflow ended, in milliseconds since
+ * navigationStart (accurate to 1/1000 of a ms)
+ */
+ void reflow(in DOMHighResTimeStamp start,
+ in DOMHighResTimeStamp end);
+
+ /**
+ * Called when an interruptible reflow has occurred.
+ *
+ * @param start timestamp when reflow ended, in milliseconds since
+ * navigationStart (accurate to 1/1000 of a ms)
+ * @param end timestamp when reflow ended, in milliseconds since
+ * navigationStart (accurate to 1/1000 of a ms)
+ */
+ void reflowInterruptible(in DOMHighResTimeStamp start,
+ in DOMHighResTimeStamp end);
+};
diff --git a/docshell/base/nsIRefreshURI.idl b/docshell/base/nsIRefreshURI.idl
new file mode 100644
index 0000000000..a4a578a344
--- /dev/null
+++ b/docshell/base/nsIRefreshURI.idl
@@ -0,0 +1,52 @@
+/* -*- Mode: IDL; tab-width: 4; 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/. */
+
+#include "nsISupports.idl"
+
+interface nsIChannel;
+interface nsIPrincipal;
+interface nsIURI;
+
+[scriptable, uuid(a5e61a3c-51bd-45be-ac0c-e87b71860656)]
+interface nsIRefreshURI : nsISupports {
+ /**
+ * Load a uri after waiting for aMillis milliseconds (as a result of a
+ * meta refresh). If the docshell is busy loading a page currently, the
+ * refresh request will be queued and executed when the current load
+ * finishes.
+ *
+ * @param aUri The uri to refresh.
+ * @param aPrincipal The triggeringPrincipal for the refresh load
+ * May be null, in which case the principal of current document will be
+ * applied.
+ * @param aMillis The number of milliseconds to wait.
+ */
+ void refreshURI(in nsIURI aURI, in nsIPrincipal aPrincipal,
+ in unsigned long aMillis);
+
+ /**
+ * Loads a URI immediately as if it were a meta refresh.
+ *
+ * @param aURI The URI to refresh.
+ * @param aPrincipal The triggeringPrincipal for the refresh load
+ * May be null, in which case the principal of current document will be
+ * applied.
+ * @param aMillis The number of milliseconds by which this refresh would
+ * be delayed if it were not being forced.
+ */
+ void forceRefreshURI(in nsIURI aURI, in nsIPrincipal aPrincipal,
+ in unsigned long aMillis);
+
+ /**
+ * Cancels all timer loads.
+ */
+ void cancelRefreshURITimers();
+
+ /**
+ * True when there are pending refreshes, false otherwise.
+ */
+ readonly attribute boolean refreshPending;
+};
diff --git a/docshell/base/nsIScrollObserver.h b/docshell/base/nsIScrollObserver.h
new file mode 100644
index 0000000000..9ff89002f0
--- /dev/null
+++ b/docshell/base/nsIScrollObserver.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsIScrollObserver_h___
+#define nsIScrollObserver_h___
+
+#include "nsISupports.h"
+#include "Units.h"
+
+// Must be kept in sync with xpcom/rust/xpcom/src/interfaces/nonidl.rs
+#define NS_ISCROLLOBSERVER_IID \
+ { \
+ 0xaa5026eb, 0x2f88, 0x4026, { \
+ 0xa4, 0x6b, 0xf4, 0x59, 0x6b, 0x4e, 0xdf, 0x00 \
+ } \
+ }
+
+class nsIScrollObserver : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISCROLLOBSERVER_IID)
+
+ /**
+ * Called when the scroll position of some element has changed.
+ */
+ virtual void ScrollPositionChanged() = 0;
+
+ /**
+ * Called when an async panning/zooming transform has started being applied
+ * and passed the scroll offset
+ */
+ MOZ_CAN_RUN_SCRIPT virtual void AsyncPanZoomStarted(){};
+
+ /**
+ * Called when an async panning/zooming transform is no longer applied
+ * and passed the scroll offset
+ */
+ MOZ_CAN_RUN_SCRIPT virtual void AsyncPanZoomStopped(){};
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIScrollObserver, NS_ISCROLLOBSERVER_IID)
+
+#endif /* nsIScrollObserver_h___ */
diff --git a/docshell/base/nsITooltipListener.idl b/docshell/base/nsITooltipListener.idl
new file mode 100644
index 0000000000..0498aca57d
--- /dev/null
+++ b/docshell/base/nsITooltipListener.idl
@@ -0,0 +1,44 @@
+/* -*- Mode: IDL; tab-width: 4; 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/. */
+
+#include "nsISupports.idl"
+
+/**
+ * An optional interface for embedding clients wishing to receive
+ * notifications for when a tooltip should be displayed or removed.
+ * The embedder implements this interface on the web browser chrome
+ * object associated with the window that notifications are required
+ * for.
+ *
+ * @see nsITooltipTextProvider
+ */
+[scriptable, uuid(44b78386-1dd2-11b2-9ad2-e4eee2ca1916)]
+interface nsITooltipListener : nsISupports
+{
+ /**
+ * Called when a tooltip should be displayed.
+ *
+ * @param aXCoords The tooltip left edge X coordinate.
+ * @param aYCoords The tooltip top edge Y coordinate.
+ * @param aTipText The text to display in the tooltip, typically obtained
+ * from the TITLE attribute of the node (or containing parent)
+ * over which the pointer has been positioned.
+ * @param aTipDir The direction (ltr or rtl) in which to display the text
+ *
+ * @note
+ * Coordinates are specified in device pixels, relative to the top-left
+ * corner of the browser area.
+ *
+ * @return <code>NS_OK</code> if the tooltip was displayed.
+ */
+ void onShowTooltip(in long aXCoords, in long aYCoords, in AString aTipText,
+ in AString aTipDir);
+
+ /**
+ * Called when the tooltip should be hidden, either because the pointer
+ * has moved or the tooltip has timed out.
+ */
+ void onHideTooltip();
+};
diff --git a/docshell/base/nsITooltipTextProvider.idl b/docshell/base/nsITooltipTextProvider.idl
new file mode 100644
index 0000000000..3afaddbe2a
--- /dev/null
+++ b/docshell/base/nsITooltipTextProvider.idl
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 4; 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/. */
+
+#include "nsISupports.idl"
+
+webidl Node;
+
+/**
+ * An interface implemented by a tooltip text provider service. This
+ * service is called to discover what tooltip text is associated
+ * with the node that the pointer is positioned over.
+ *
+ * Embedders may implement and register their own tooltip text provider
+ * service if they wish to provide different tooltip text.
+ *
+ * The default service returns the text stored in the TITLE
+ * attribute of the node or a containing parent.
+ *
+ * @note
+ * The tooltip text provider service is registered with the contract
+ * defined in NS_TOOLTIPTEXTPROVIDER_CONTRACTID.
+ *
+ * @see nsITooltipListener
+ * @see nsIComponentManager
+ * @see Node
+ */
+[scriptable, uuid(b128a1e6-44f3-4331-8fbe-5af360ff21ee)]
+interface nsITooltipTextProvider : nsISupports
+{
+ /**
+ * Called to obtain the tooltip text for a node.
+ *
+ * @arg aNode The node to obtain the text from.
+ * @arg aText The tooltip text.
+ * @arg aDirection The text direction (ltr or rtl) to use
+ *
+ * @return <CODE>PR_TRUE</CODE> if tooltip text is associated
+ * with the node and was returned in the aText argument;
+ * <CODE>PR_FALSE</CODE> otherwise.
+ */
+ boolean getNodeText(in Node aNode, out wstring aText, out wstring aDirection);
+};
diff --git a/docshell/base/nsIURIFixup.idl b/docshell/base/nsIURIFixup.idl
new file mode 100644
index 0000000000..3ddbd6c29e
--- /dev/null
+++ b/docshell/base/nsIURIFixup.idl
@@ -0,0 +1,205 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIInputStream;
+interface nsIDNSListener;
+webidl BrowsingContext;
+
+/**
+ * Interface indicating what we found/corrected when fixing up a URI
+ */
+[scriptable, uuid(4819f183-b532-4932-ac09-b309cd853be7)]
+interface nsIURIFixupInfo : nsISupports
+{
+ /**
+ * Consumer that asked for fixed up URI.
+ */
+ attribute BrowsingContext consumer;
+
+ /**
+ * Our best guess as to what URI the consumer will want. Might
+ * be null if we couldn't salvage anything (for instance, because
+ * the input was invalid as a URI and FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP
+ * was not passed)
+ */
+ attribute nsIURI preferredURI;
+
+ /**
+ * The fixed-up original input, *never* using a keyword search.
+ * (might be null if the original input was not recoverable as
+ * a URL, e.g. "foo bar"!)
+ */
+ attribute nsIURI fixedURI;
+
+ /**
+ * The name of the keyword search provider used to provide a keyword search;
+ * empty string if no keyword search was done.
+ */
+ attribute AString keywordProviderName;
+
+ /**
+ * The keyword as used for the search (post trimming etc.)
+ * empty string if no keyword search was done.
+ */
+ attribute AString keywordAsSent;
+
+ /**
+ * Whether we changed the protocol instead of using one from the input as-is.
+ */
+ attribute boolean fixupChangedProtocol;
+
+ /**
+ * Whether we created an alternative URI. We might have added a prefix and/or
+ * suffix, the contents of which are controlled by the
+ * browser.fixup.alternate.prefix and .suffix prefs, with the defaults being
+ * "www." and ".com", respectively.
+ */
+ attribute boolean fixupCreatedAlternateURI;
+
+ /**
+ * The original input
+ */
+ attribute AUTF8String originalInput;
+
+ /**
+ * The POST data to submit with the returned URI (see nsISearchSubmission).
+ */
+ attribute nsIInputStream postData;
+};
+
+
+/**
+ * Interface implemented by objects capable of fixing up strings into URIs
+ */
+[scriptable, uuid(1da7e9d4-620b-4949-849a-1cd6077b1b2d)]
+interface nsIURIFixup : nsISupports
+{
+ /** No fixup flags. */
+ const unsigned long FIXUP_FLAG_NONE = 0;
+
+ /**
+ * Allow the fixup to use a keyword lookup service to complete the URI.
+ * The fixup object implementer should honour this flag and only perform
+ * any lengthy keyword (or search) operation if it is set.
+ */
+ const unsigned long FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP = 1;
+
+ /**
+ * Tell the fixup to make an alternate URI from the input URI, for example
+ * to turn foo into www.foo.com.
+ */
+ const unsigned long FIXUP_FLAGS_MAKE_ALTERNATE_URI = 2;
+
+ /*
+ * Set when the fixup happens in a private context, the used search engine
+ * may differ in this case. Not all consumers care about this, because they
+ * may not want the url to be transformed in a search.
+ */
+ const unsigned long FIXUP_FLAG_PRIVATE_CONTEXT = 4;
+
+ /*
+ * Fix common scheme typos.
+ */
+ const unsigned long FIXUP_FLAG_FIX_SCHEME_TYPOS = 8;
+
+ /*
+ * Force the fixup to make an alternate URI from the input URI regardless
+ * of the browser.fixup.alternate.enabled preference.
+ */
+ const unsigned long FIXUP_FLAG_FORCE_ALTERNATE_URI = 16;
+
+ /**
+ * Tries to converts the specified string into a URI, first attempting
+ * to correct any errors in the syntax or other vagaries.
+ * It returns information about what it corrected
+ * (e.g. whether we could rescue the URI or "just" generated a keyword
+ * search URI instead).
+ *
+ * @param aURIText Candidate URI.
+ * @param aFixupFlags Flags that govern ways the URI may be fixed up.
+ * Defaults to FIXUP_FLAG_NONE.
+ */
+ nsIURIFixupInfo getFixupURIInfo(in AUTF8String aURIText,
+ [optional] in unsigned long aFixupFlags);
+
+ /**
+ * Convert load flags from nsIWebNavigation to URI fixup flags for use in
+ * getFixupURIInfo.
+ *
+ * @param aURIText Candidate URI; used for determining whether to
+ * allow keyword lookups.
+ * @param aDocShellFlags Load flags from nsIDocShell to convert.
+ */
+ unsigned long webNavigationFlagsToFixupFlags(
+ in AUTF8String aURIText, in unsigned long aDocShellFlags);
+
+ /**
+ * Converts the specified keyword string into a URI. Note that it's the
+ * caller's responsibility to check whether keywords are enabled and
+ * whether aKeyword is a sensible keyword.
+ *
+ * @param aKeyword The keyword string to convert into a URI
+ * @param aIsPrivateContext Whether this is invoked from a private context.
+ */
+ nsIURIFixupInfo keywordToURI(in AUTF8String aKeyword,
+ [optional] in boolean aIsPrivateContext);
+
+ /**
+ * Given a uri-like string with a protocol, attempt to fix and convert it
+ * into an instance of nsIURIFixupInfo.
+ *
+ * Differently from getFixupURIInfo, this assumes the input string is an
+ * http/https uri, and can add a prefix and/or suffix to its hostname.
+ *
+ * The scheme will be changed to the scheme defined in
+ * "browser.fixup.alternate.protocol", which is by default, https.
+ *
+ * If the prefix and suffix of the host are missing, it will add them to
+ * the host using the preferences "browser.fixup.alternate.prefix" and
+ * "browser.fixup.alternate.suffix" as references.
+ *
+ * If a hostname suffix is present, but the URI doesn't contain a prefix,
+ * it will add the prefix via "browser.fixup.alternate.prefix"
+ *
+ * @param aUriString The URI to fixup and convert.
+ * @returns nsIURIFixupInfo
+ * A nsIURIFixupInfo object with the property fixedURI
+ * which contains the modified URI.
+ * @throws NS_ERROR_FAILURE
+ * If aUriString is undefined, or the scheme is not
+ * http/https.
+ */
+ nsIURIFixupInfo forceHttpFixup(in AUTF8String aUriString);
+
+ /**
+ * With the host associated with the URI, use nsIDNSService to determine
+ * if an IP address can be found for this host. This method will ignore checking
+ * hosts that are IP addresses. If the host does not contain any periods, depending
+ * on the browser.urlbar.dnsResolveFullyQualifiedNames preference value, a period
+ * may be appended in order to make it a fully qualified domain name.
+ *
+ * @param aURI The URI to parse and pass into the DNS lookup.
+ * @param aListener The listener when the result from the lookup is available.
+ * @param aOriginAttributes The originAttributes to pass the DNS lookup.
+ * @throws NS_ERROR_FAILURE if aURI does not have a displayHost or asciiHost.
+ */
+ void checkHost(in nsIURI aURI,
+ in nsIDNSListener aListener,
+ [optional] in jsval aOriginAttributes);
+
+ /**
+ * Returns true if the specified domain is known and false otherwise.
+ * A known domain is relevant when we have a single word and can't be
+ * sure whether to treat the word as a host name or should instead be
+ * treated as a search term.
+ *
+ * @param aDomain A domain name to query.
+ */
+ bool isDomainKnown(in AUTF8String aDomain);
+};
diff --git a/docshell/base/nsIWebNavigation.idl b/docshell/base/nsIWebNavigation.idl
new file mode 100644
index 0000000000..1800e7312e
--- /dev/null
+++ b/docshell/base/nsIWebNavigation.idl
@@ -0,0 +1,415 @@
+/* -*- Mode: IDL; tab-width: 4; 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/. */
+
+#include "nsISupports.idl"
+
+interface nsIInputStream;
+interface nsISHistory;
+interface nsIURI;
+interface nsIPrincipal;
+interface nsIChildSHistory;
+webidl Document;
+
+%{ C++
+#include "mozilla/dom/ChildSHistory.h"
+namespace mozilla {
+namespace dom {
+struct LoadURIOptions;
+} // namespace dom
+} // namespace mozilla
+%}
+
+[ref] native LoadURIOptionsRef(const mozilla::dom::LoadURIOptions);
+
+/**
+ * The nsIWebNavigation interface defines an interface for navigating the web.
+ * It provides methods and attributes to direct an object to navigate to a new
+ * location, stop or restart an in process load, or determine where the object
+ * has previously gone.
+ *
+ * Even though this is builtinclass, most of the interface is also implemented
+ * in RemoteWebNavigation, so if this interface changes, the implementation
+ * there may also need to change.
+ */
+[scriptable, builtinclass, uuid(3ade79d4-8cb9-4952-b18d-4f9b63ca0d31)]
+interface nsIWebNavigation : nsISupports
+{
+ /**
+ * Indicates if the object can go back. If true this indicates that
+ * there is back session history available for navigation.
+ */
+ readonly attribute boolean canGoBack;
+
+ /**
+ * Indicates if the object can go forward. If true this indicates that
+ * there is forward session history available for navigation
+ */
+ readonly attribute boolean canGoForward;
+
+ /**
+ * Tells the object to navigate to the previous session history item. When a
+ * page is loaded from session history, all content is loaded from the cache
+ * (if available) and page state (such as form values and scroll position) is
+ * restored.
+ *
+ * @param {boolean} aRequireUserInteraction
+ * Tells goBack to skip history items that did not record any user
+ * interaction on their corresponding document while they were active.
+ * This means in case of multiple entries mapping to the same document,
+ * each entry has to have been flagged with user interaction separately.
+ * If no items have user interaction, the function will fall back
+ * to the first session history entry.
+ *
+ * @param {boolean} aUserActivation
+ * Tells goBack that the call was triggered by a user action (e.g.:
+ * The user clicked the back button).
+ *
+ * @throw NS_ERROR_UNEXPECTED
+ * Indicates that the call was unexpected at this time, which implies
+ * that canGoBack is false.
+ */
+ void goBack([optional] in boolean aRequireUserInteraction, [optional] in boolean aUserActivation);
+
+ /**
+ * Tells the object to navigate to the next session history item. When a
+ * page is loaded from session history, all content is loaded from the cache
+ * (if available) and page state (such as form values and scroll position) is
+ * restored.
+ *
+ * @param {boolean} aRequireUserInteraction
+ * Tells goForward to skip history items that did not record any user
+ * interaction on their corresponding document while they were active.
+ * This means in case of multiple entries mapping to the same document,
+ * each entry has to have been flagged with user interaction separately.
+ * If no items have user interaction, the function will fall back
+ * to the latest session history entry.
+ *
+ * @param {boolean} aUserActivation
+ * Tells goForward that the call was triggered by a user action (e.g.:
+ * The user clicked the forward button).
+ *
+ * @throw NS_ERROR_UNEXPECTED
+ * Indicates that the call was unexpected at this time, which implies
+ * that canGoForward is false.
+ */
+ void goForward([optional] in boolean aRequireUserInteraction, [optional] in boolean aUserActivation);
+
+ /**
+ * Tells the object to navigate to the session history item at a given index.
+ *
+ * @param {boolean} aUserActivation
+ * Tells goForward that the call was triggered by a user action (e.g.:
+ * The user clicked the forward button).
+ *
+ * @throw NS_ERROR_UNEXPECTED
+ * Indicates that the call was unexpected at this time, which implies
+ * that session history entry at the given index does not exist.
+ */
+ void gotoIndex(in long index, [optional] in boolean aUserActivation);
+
+ /****************************************************************************
+ * The following flags may be bitwise combined to form the load flags
+ * parameter passed to either the loadURI or reload method. Some of these
+ * flags are only applicable to loadURI.
+ */
+
+ /**
+ * This flags defines the range of bits that may be specified. Flags
+ * outside this range may be used, but may not be passed to Reload().
+ */
+ const unsigned long LOAD_FLAGS_MASK = 0xffff;
+
+ /**
+ * This is the default value for the load flags parameter.
+ */
+ const unsigned long LOAD_FLAGS_NONE = 0x0000;
+
+ /**
+ * Flags 0x1, 0x2, 0x4, 0x8 are reserved for internal use by
+ * nsIWebNavigation implementations for now.
+ */
+
+ /**
+ * This flag specifies that the load should have the semantics of an HTML
+ * Meta-refresh tag (i.e., that the cache should be bypassed). This flag
+ * is only applicable to loadURI.
+ * XXX the meaning of this flag is poorly defined.
+ * XXX no one uses this, so we should probably deprecate and remove it.
+ */
+ const unsigned long LOAD_FLAGS_IS_REFRESH = 0x0010;
+
+ /**
+ * This flag specifies that the load should have the semantics of a link
+ * click. This flag is only applicable to loadURI.
+ * XXX the meaning of this flag is poorly defined.
+ */
+ const unsigned long LOAD_FLAGS_IS_LINK = 0x0020;
+
+ /**
+ * This flag specifies that history should not be updated. This flag is only
+ * applicable to loadURI.
+ */
+ const unsigned long LOAD_FLAGS_BYPASS_HISTORY = 0x0040;
+
+ /**
+ * This flag specifies that any existing history entry should be replaced.
+ * This flag is only applicable to loadURI.
+ */
+ const unsigned long LOAD_FLAGS_REPLACE_HISTORY = 0x0080;
+
+ /**
+ * This flag specifies that the local web cache should be bypassed, but an
+ * intermediate proxy cache could still be used to satisfy the load.
+ */
+ const unsigned long LOAD_FLAGS_BYPASS_CACHE = 0x0100;
+
+ /**
+ * This flag specifies that any intermediate proxy caches should be bypassed
+ * (i.e., that the content should be loaded from the origin server).
+ */
+ const unsigned long LOAD_FLAGS_BYPASS_PROXY = 0x0200;
+
+ /**
+ * This flag specifies that a reload was triggered as a result of detecting
+ * an incorrect character encoding while parsing a previously loaded
+ * document.
+ */
+ const unsigned long LOAD_FLAGS_CHARSET_CHANGE = 0x0400;
+
+ /**
+ * If this flag is set, Stop() will be called before the load starts
+ * and will stop both content and network activity (the default is to
+ * only stop network activity). Effectively, this passes the
+ * STOP_CONTENT flag to Stop(), in addition to the STOP_NETWORK flag.
+ */
+ const unsigned long LOAD_FLAGS_STOP_CONTENT = 0x0800;
+
+ /**
+ * A hint this load was prompted by an external program: take care!
+ */
+ const unsigned long LOAD_FLAGS_FROM_EXTERNAL = 0x1000;
+
+ /**
+ * This flag specifies that this is the first load in this object.
+ * Set with care, since setting incorrectly can cause us to assume that
+ * nothing was actually loaded in this object if the load ends up being
+ * handled by an external application. This flag must not be passed to
+ * Reload.
+ */
+ const unsigned long LOAD_FLAGS_FIRST_LOAD = 0x4000;
+
+ /**
+ * This flag specifies that the load should not be subject to popup
+ * blocking checks. This flag must not be passed to Reload.
+ */
+ const unsigned long LOAD_FLAGS_ALLOW_POPUPS = 0x8000;
+
+ /**
+ * This flag specifies that the URI classifier should not be checked for
+ * this load. This flag must not be passed to Reload.
+ */
+ const unsigned long LOAD_FLAGS_BYPASS_CLASSIFIER = 0x10000;
+
+ /**
+ * Force relevant cookies to be sent with this load even if normally they
+ * wouldn't be.
+ */
+ const unsigned long LOAD_FLAGS_FORCE_ALLOW_COOKIES = 0x20000;
+
+ /**
+ * Prevent the owner principal from being inherited for this load.
+ */
+ const unsigned long LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL = 0x40000;
+
+ /**
+ * Overwrite the returned error code with a specific result code
+ * when an error page is displayed.
+ */
+ const unsigned long LOAD_FLAGS_ERROR_LOAD_CHANGES_RV = 0x80000;
+
+ /**
+ * This flag specifies that the URI may be submitted to a third-party
+ * server for correction. This should only be applied to non-sensitive
+ * URIs entered by users. This flag must not be passed to Reload.
+ */
+ const unsigned long LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP = 0x100000;
+
+ /**
+ * This flag specifies that common scheme typos should be corrected.
+ */
+ const unsigned long LOAD_FLAGS_FIXUP_SCHEME_TYPOS = 0x200000;
+
+ /**
+ * Allows a top-level data: navigation to occur. E.g. view-image
+ * is an explicit user action which should be allowed.
+ */
+ const unsigned long LOAD_FLAGS_FORCE_ALLOW_DATA_URI = 0x400000;
+
+ /**
+ * This load is the result of an HTTP redirect.
+ */
+ const unsigned long LOAD_FLAGS_IS_REDIRECT = 0x800000;
+
+ /**
+ * These flags force TRR modes 1 or 3 for the load.
+ */
+ const unsigned long LOAD_FLAGS_DISABLE_TRR = 0x1000000;
+ const unsigned long LOAD_FLAGS_FORCE_TRR = 0x2000000;
+
+ /**
+ * This load should bypass the LoadURIDelegate.loadUri.
+ */
+ const unsigned long LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE = 0x4000000;
+
+ /**
+ * This load has a user activation. (e.g: reload button was clicked)
+ */
+ const unsigned long LOAD_FLAGS_USER_ACTIVATION = 0x8000000;
+
+ /**
+ * Loads a given URI. This will give priority to loading the requested URI
+ * in the object implementing this interface. If it can't be loaded here
+ * however, the URI dispatcher will go through its normal process of content
+ * loading.
+ *
+ * @param aURI
+ * The URI to load.
+ * @param aLoadURIOptions
+ * A JSObject defined in LoadURIOptions.webidl holding info like e.g.
+ * the triggeringPrincipal, the referrer info.
+ */
+ [implicit_jscontext, binaryname(LoadURIFromScript)]
+ void loadURI(in nsIURI aURI,
+ in jsval aLoadURIOptions);
+
+ /**
+ * Parse / fix up a URI out of the string and load it.
+ * This will give priority to loading the requested URI
+ * in the object implementing this interface. If it can't be loaded here
+ * however, the URI dispatcher will go through its normal process of content
+ * loading.
+ *
+ * @param aURIString
+ * The URI string to load. For HTTP and FTP URLs and possibly others,
+ * characters above U+007F will be converted to UTF-8 and then URL-
+ * escaped per the rules of RFC 2396.
+ * This method may use nsIURIFixup to try to fix up typos etc. in the
+ * input string based on the load flag arguments in aLoadURIOptions.
+ * It can even convert the input to a search results page using the
+ * default search service.
+ * If you have an nsIURI anyway, prefer calling `loadURI`, above.
+ * @param aLoadURIOptions
+ * A JSObject defined in LoadURIOptions.webidl holding info like e.g.
+ * the triggeringPrincipal, the referrer info.
+ */
+ [implicit_jscontext, binaryname(FixupAndLoadURIStringFromScript)]
+ void fixupAndLoadURIString(in AString aURIString,
+ in jsval aLoadURIOptions);
+
+ /**
+ * A C++ friendly version of loadURI
+ */
+ [nostdcall, binaryname(LoadURI)]
+ void binaryLoadURI(in nsIURI aURI,
+ in LoadURIOptionsRef aLoadURIOptions);
+
+ /**
+ * A C++ friendly version of fixupAndLoadURIString
+ */
+ [nostdcall, binaryname(FixupAndLoadURIString)]
+ void binaryFixupAndLoadURIString(in AString aURIString,
+ in LoadURIOptionsRef aLoadURIOptions);
+
+ /**
+ * Tells the Object to reload the current page. There may be cases where the
+ * user will be asked to confirm the reload (for example, when it is
+ * determined that the request is non-idempotent).
+ *
+ * @param aReloadFlags
+ * Flags modifying load behaviour. This parameter is a bitwise
+ * combination of the Load Flags defined above. (Undefined bits are
+ * reserved for future use.) Generally you will pass LOAD_FLAGS_NONE
+ * for this parameter.
+ *
+ * @throw NS_BINDING_ABORTED
+ * Indicating that the user canceled the reload.
+ */
+ void reload(in unsigned long aReloadFlags);
+
+ /****************************************************************************
+ * The following flags may be passed as the stop flags parameter to the stop
+ * method defined on this interface.
+ */
+
+ /**
+ * This flag specifies that all network activity should be stopped. This
+ * includes both active network loads and pending META-refreshes.
+ */
+ const unsigned long STOP_NETWORK = 0x01;
+
+ /**
+ * This flag specifies that all content activity should be stopped. This
+ * includes animated images, plugins and pending Javascript timeouts.
+ */
+ const unsigned long STOP_CONTENT = 0x02;
+
+ /**
+ * This flag specifies that all activity should be stopped.
+ */
+ const unsigned long STOP_ALL = 0x03;
+
+ /**
+ * Stops a load of a URI.
+ *
+ * @param aStopFlags
+ * This parameter is one of the stop flags defined above.
+ */
+ void stop(in unsigned long aStopFlags);
+
+ /**
+ * Retrieves the current DOM document for the frame, or lazily creates a
+ * blank document if there is none. This attribute never returns null except
+ * for unexpected error situations.
+ */
+ readonly attribute Document document;
+
+ /**
+ * The currently loaded URI or null.
+ */
+ readonly attribute nsIURI currentURI;
+
+ /**
+ * The session history object used by this web navigation instance. This
+ * object will be a mozilla::dom::ChildSHistory object, but is returned as
+ * nsISupports so it can be called from JS code.
+ */
+ [binaryname(SessionHistoryXPCOM)]
+ readonly attribute nsISupports sessionHistory;
+
+ %{ C++
+ /**
+ * Get the session history object used by this nsIWebNavigation instance.
+ * Use this method instead of the XPCOM method when getting the
+ * SessionHistory from C++ code.
+ */
+ already_AddRefed<mozilla::dom::ChildSHistory>
+ GetSessionHistory()
+ {
+ nsCOMPtr<nsISupports> history;
+ GetSessionHistoryXPCOM(getter_AddRefs(history));
+ return history.forget()
+ .downcast<mozilla::dom::ChildSHistory>();
+ }
+ %}
+
+ /**
+ * Resume a load which has been redirected from another process.
+ *
+ * A negative |aHistoryIndex| value corresponds to a non-history load being
+ * resumed.
+ */
+ void resumeRedirectedLoad(in unsigned long long aLoadIdentifier,
+ in long aHistoryIndex);
+};
diff --git a/docshell/base/nsIWebNavigationInfo.idl b/docshell/base/nsIWebNavigationInfo.idl
new file mode 100644
index 0000000000..0c3d07a8ed
--- /dev/null
+++ b/docshell/base/nsIWebNavigationInfo.idl
@@ -0,0 +1,55 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * The nsIWebNavigationInfo interface exposes a way to get information
+ * on the capabilities of Gecko webnavigation objects.
+ */
+[scriptable, uuid(62a93afb-93a1-465c-84c8-0432264229de)]
+interface nsIWebNavigationInfo : nsISupports
+{
+ /**
+ * Returned by isTypeSupported to indicate lack of support for a type.
+ * @note this is guaranteed not to change, so that boolean tests can be done
+ * on the return value if isTypeSupported to detect whether a type is
+ * supported at all.
+ */
+ const unsigned long UNSUPPORTED = 0;
+
+ /**
+ * Returned by isTypeSupported to indicate that a type is supported as an
+ * image.
+ */
+ const unsigned long IMAGE = 1;
+
+ /**
+ * Returned by isTypeSupported to indicate that a type is a special NPAPI
+ * plugin that render as a transparent region (we do not support NPAPI
+ * plugins).
+ */
+ const unsigned long FALLBACK = 2;
+
+ /**
+ * @note Other return types may be added here in the future as they become
+ * relevant.
+ */
+
+ /**
+ * Returned by isTypeSupported to indicate that a type is supported via some
+ * other means.
+ */
+ const unsigned long OTHER = 1 << 15;
+
+ /**
+ * Query whether aType is supported.
+ * @param aType the MIME type in question.
+ * @return an enum value indicating whether and how aType is supported.
+ * @note This method may rescan plugins to ensure that they're properly
+ * registered for the types they support.
+ */
+ unsigned long isTypeSupported(in ACString aType);
+};
diff --git a/docshell/base/nsIWebPageDescriptor.idl b/docshell/base/nsIWebPageDescriptor.idl
new file mode 100644
index 0000000000..866cb54e6b
--- /dev/null
+++ b/docshell/base/nsIWebPageDescriptor.idl
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "nsISupports.idl"
+interface nsIDocShell;
+
+/**
+ * The nsIWebPageDescriptor interface allows content being displayed in one
+ * window to be loaded into another window without refetching it from the
+ * network.
+ */
+
+[scriptable, uuid(6f30b676-3710-4c2c-80b1-0395fb26516e)]
+interface nsIWebPageDescriptor : nsISupports
+{
+ /**
+ * Tells the object to load the page that otherDocShell is currently loading,
+ * or has loaded already, as view source, with the url being `aURL`.
+ *
+ * @throws NS_ERROR_FAILURE - NS_ERROR_INVALID_POINTER
+ */
+ void loadPageAsViewSource(in nsIDocShell otherDocShell, in AString aURL);
+
+
+ /**
+ * Retrieves the page descriptor for the curent document.
+ * @note, currentDescriptor is currently always an nsISHEntry object or null.
+ */
+ readonly attribute nsISupports currentDescriptor;
+};
diff --git a/docshell/base/nsPingListener.cpp b/docshell/base/nsPingListener.cpp
new file mode 100644
index 0000000000..23ac220489
--- /dev/null
+++ b/docshell/base/nsPingListener.cpp
@@ -0,0 +1,345 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPingListener.h"
+
+#include "mozilla/Encoding.h"
+#include "mozilla/Preferences.h"
+
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/dom/Document.h"
+
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIInputStream.h"
+#include "nsIProtocolHandler.h"
+#include "nsIUploadChannel2.h"
+
+#include "nsComponentManagerUtils.h"
+#include "nsNetUtil.h"
+#include "nsStreamUtils.h"
+#include "nsStringStream.h"
+#include "nsWhitespaceTokenizer.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_ISUPPORTS(nsPingListener, nsIStreamListener, nsIRequestObserver)
+
+//*****************************************************************************
+// <a ping> support
+//*****************************************************************************
+
+#define PREF_PINGS_ENABLED "browser.send_pings"
+#define PREF_PINGS_MAX_PER_LINK "browser.send_pings.max_per_link"
+#define PREF_PINGS_REQUIRE_SAME_HOST "browser.send_pings.require_same_host"
+
+// Check prefs to see if pings are enabled and if so what restrictions might
+// be applied.
+//
+// @param maxPerLink
+// This parameter returns the number of pings that are allowed per link click
+//
+// @param requireSameHost
+// This parameter returns true if pings are restricted to the same host as
+// the document in which the click occurs. If the same host restriction is
+// imposed, then we still allow for pings to cross over to different
+// protocols and ports for flexibility and because it is not possible to send
+// a ping via FTP.
+//
+// @returns
+// true if pings are enabled and false otherwise.
+//
+static bool PingsEnabled(int32_t* aMaxPerLink, bool* aRequireSameHost) {
+ bool allow = Preferences::GetBool(PREF_PINGS_ENABLED, false);
+
+ *aMaxPerLink = 1;
+ *aRequireSameHost = true;
+
+ if (allow) {
+ Preferences::GetInt(PREF_PINGS_MAX_PER_LINK, aMaxPerLink);
+ Preferences::GetBool(PREF_PINGS_REQUIRE_SAME_HOST, aRequireSameHost);
+ }
+
+ return allow;
+}
+
+// We wait this many milliseconds before killing the ping channel...
+#define PING_TIMEOUT 10000
+
+static void OnPingTimeout(nsITimer* aTimer, void* aClosure) {
+ nsILoadGroup* loadGroup = static_cast<nsILoadGroup*>(aClosure);
+ if (loadGroup) {
+ loadGroup->Cancel(NS_ERROR_ABORT);
+ }
+}
+
+struct MOZ_STACK_CLASS SendPingInfo {
+ int32_t numPings;
+ int32_t maxPings;
+ bool requireSameHost;
+ nsIURI* target;
+ nsIReferrerInfo* referrerInfo;
+ nsIDocShell* docShell;
+};
+
+static void SendPing(void* aClosure, nsIContent* aContent, nsIURI* aURI,
+ nsIIOService* aIOService) {
+ SendPingInfo* info = static_cast<SendPingInfo*>(aClosure);
+ if (info->maxPings > -1 && info->numPings >= info->maxPings) {
+ return;
+ }
+
+ Document* doc = aContent->OwnerDoc();
+
+ nsCOMPtr<nsIChannel> chan;
+ NS_NewChannel(getter_AddRefs(chan), aURI, doc,
+ info->requireSameHost
+ ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED
+ : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_PING,
+ nullptr, // PerformanceStorage
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ nsIRequest::LOAD_NORMAL, // aLoadFlags,
+ aIOService);
+
+ if (!chan) {
+ return;
+ }
+
+ // Don't bother caching the result of this URI load, but do not exempt
+ // it from Safe Browsing.
+ chan->SetLoadFlags(nsIRequest::INHIBIT_CACHING);
+
+ nsCOMPtr<nsIHttpChannel> httpChan = do_QueryInterface(chan);
+ if (!httpChan) {
+ return;
+ }
+
+ // This is needed in order for 3rd-party cookie blocking to work.
+ nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(httpChan);
+ nsresult rv;
+ if (httpInternal) {
+ rv = httpInternal->SetDocumentURI(doc->GetDocumentURI());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ rv = httpChan->SetRequestMethod("POST"_ns);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Remove extraneous request headers (to reduce request size)
+ rv = httpChan->SetRequestHeader("accept"_ns, ""_ns, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = httpChan->SetRequestHeader("accept-language"_ns, ""_ns, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = httpChan->SetRequestHeader("accept-encoding"_ns, ""_ns, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Always send a Ping-To header.
+ nsAutoCString pingTo;
+ if (NS_SUCCEEDED(info->target->GetSpec(pingTo))) {
+ rv = httpChan->SetRequestHeader("Ping-To"_ns, pingTo, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ nsCOMPtr<nsIScriptSecurityManager> sm =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
+
+ if (sm && info->referrerInfo) {
+ nsCOMPtr<nsIURI> referrer = info->referrerInfo->GetOriginalReferrer();
+ bool referrerIsSecure = false;
+ uint32_t flags = nsIProtocolHandler::URI_IS_POTENTIALLY_TRUSTWORTHY;
+ if (referrer) {
+ rv = NS_URIChainHasFlags(referrer, flags, &referrerIsSecure);
+ }
+
+ // Default to sending less data if NS_URIChainHasFlags() fails.
+ referrerIsSecure = NS_FAILED(rv) || referrerIsSecure;
+
+ bool isPrivateWin = false;
+ if (doc) {
+ isPrivateWin =
+ doc->NodePrincipal()->OriginAttributesRef().mPrivateBrowsingId > 0;
+ }
+
+ bool sameOrigin = NS_SUCCEEDED(
+ sm->CheckSameOriginURI(referrer, aURI, false, isPrivateWin));
+
+ // If both the address of the document containing the hyperlink being
+ // audited and "ping URL" have the same origin or the document containing
+ // the hyperlink being audited was not retrieved over an encrypted
+ // connection, send a Ping-From header.
+ if (sameOrigin || !referrerIsSecure) {
+ nsAutoCString pingFrom;
+ if (NS_SUCCEEDED(referrer->GetSpec(pingFrom))) {
+ rv = httpChan->SetRequestHeader("Ping-From"_ns, pingFrom, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ // If the document containing the hyperlink being audited was not retrieved
+ // over an encrypted connection and its address does not have the same
+ // origin as "ping URL", send a referrer.
+ if (!sameOrigin && !referrerIsSecure && info->referrerInfo) {
+ rv = httpChan->SetReferrerInfo(info->referrerInfo);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ nsCOMPtr<nsIUploadChannel2> uploadChan = do_QueryInterface(httpChan);
+ if (!uploadChan) {
+ return;
+ }
+
+ constexpr auto uploadData = "PING"_ns;
+
+ nsCOMPtr<nsIInputStream> uploadStream;
+ rv = NS_NewCStringInputStream(getter_AddRefs(uploadStream), uploadData);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ uploadChan->ExplicitSetUploadStream(uploadStream, "text/ping"_ns,
+ uploadData.Length(), "POST"_ns, false);
+
+ // The channel needs to have a loadgroup associated with it, so that we can
+ // cancel the channel and any redirected channels it may create.
+ nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
+ if (!loadGroup) {
+ return;
+ }
+ nsCOMPtr<nsIInterfaceRequestor> callbacks = do_QueryInterface(info->docShell);
+ loadGroup->SetNotificationCallbacks(callbacks);
+ chan->SetLoadGroup(loadGroup);
+
+ RefPtr<nsPingListener> pingListener = new nsPingListener();
+ chan->AsyncOpen(pingListener);
+
+ // Even if AsyncOpen failed, we still count this as a successful ping. It's
+ // possible that AsyncOpen may have failed after triggering some background
+ // process that may have written something to the network.
+ info->numPings++;
+
+ // Prevent ping requests from stalling and never being garbage collected...
+ if (NS_FAILED(pingListener->StartTimeout(doc->GetDocGroup()))) {
+ // If we failed to setup the timer, then we should just cancel the channel
+ // because we won't be able to ensure that it goes away in a timely manner.
+ chan->Cancel(NS_ERROR_ABORT);
+ return;
+ }
+ // if the channel openend successfully, then make the pingListener hold
+ // a strong reference to the loadgroup which is released in ::OnStopRequest
+ pingListener->SetLoadGroup(loadGroup);
+}
+
+typedef void (*ForEachPingCallback)(void* closure, nsIContent* content,
+ nsIURI* uri, nsIIOService* ios);
+
+static void ForEachPing(nsIContent* aContent, ForEachPingCallback aCallback,
+ void* aClosure) {
+ // NOTE: Using nsIDOMHTMLAnchorElement::GetPing isn't really worth it here
+ // since we'd still need to parse the resulting string. Instead, we
+ // just parse the raw attribute. It might be nice if the content node
+ // implemented an interface that exposed an enumeration of nsIURIs.
+
+ // Make sure we are dealing with either an <A> or <AREA> element in the HTML
+ // or XHTML namespace.
+ if (!aContent->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area)) {
+ return;
+ }
+
+ nsAutoString value;
+ aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::ping, value);
+ if (value.IsEmpty()) {
+ return;
+ }
+
+ nsCOMPtr<nsIIOService> ios = do_GetIOService();
+ if (!ios) {
+ return;
+ }
+
+ Document* doc = aContent->OwnerDoc();
+ nsAutoCString charset;
+ doc->GetDocumentCharacterSet()->Name(charset);
+
+ nsWhitespaceTokenizer tokenizer(value);
+
+ while (tokenizer.hasMoreTokens()) {
+ nsCOMPtr<nsIURI> uri;
+ NS_NewURI(getter_AddRefs(uri), tokenizer.nextToken(), charset.get(),
+ aContent->GetBaseURI());
+ // if we can't generate a valid URI, then there is nothing to do
+ if (!uri) {
+ continue;
+ }
+ // Explicitly not allow loading data: URIs
+ if (!net::SchemeIsData(uri)) {
+ aCallback(aClosure, aContent, uri, ios);
+ }
+ }
+}
+
+// Spec: http://whatwg.org/specs/web-apps/current-work/#ping
+/*static*/ void nsPingListener::DispatchPings(nsIDocShell* aDocShell,
+ nsIContent* aContent,
+ nsIURI* aTarget,
+ nsIReferrerInfo* aReferrerInfo) {
+ SendPingInfo info;
+
+ if (!PingsEnabled(&info.maxPings, &info.requireSameHost)) {
+ return;
+ }
+ if (info.maxPings == 0) {
+ return;
+ }
+
+ info.numPings = 0;
+ info.target = aTarget;
+ info.referrerInfo = aReferrerInfo;
+ info.docShell = aDocShell;
+
+ ForEachPing(aContent, SendPing, &info);
+}
+
+nsPingListener::~nsPingListener() {
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+}
+
+nsresult nsPingListener::StartTimeout(DocGroup* aDocGroup) {
+ NS_ENSURE_ARG(aDocGroup);
+
+ return NS_NewTimerWithFuncCallback(
+ getter_AddRefs(mTimer), OnPingTimeout, mLoadGroup, PING_TIMEOUT,
+ nsITimer::TYPE_ONE_SHOT, "nsPingListener::StartTimeout",
+ aDocGroup->EventTargetFor(TaskCategory::Network));
+}
+
+NS_IMETHODIMP
+nsPingListener::OnStartRequest(nsIRequest* aRequest) { return NS_OK; }
+
+NS_IMETHODIMP
+nsPingListener::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aStream,
+ uint64_t aOffset, uint32_t aCount) {
+ uint32_t result;
+ return aStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &result);
+}
+
+NS_IMETHODIMP
+nsPingListener::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
+ mLoadGroup = nullptr;
+
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+
+ return NS_OK;
+}
diff --git a/docshell/base/nsPingListener.h b/docshell/base/nsPingListener.h
new file mode 100644
index 0000000000..7cf6ff98b5
--- /dev/null
+++ b/docshell/base/nsPingListener.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPingListener_h__
+#define nsPingListener_h__
+
+#include "nsIStreamListener.h"
+#include "nsIReferrerInfo.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+namespace dom {
+class DocGroup;
+}
+} // namespace mozilla
+
+class nsIContent;
+class nsIDocShell;
+class nsILoadGroup;
+class nsITimer;
+class nsIURI;
+
+class nsPingListener final : public nsIStreamListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ nsPingListener() {}
+
+ void SetLoadGroup(nsILoadGroup* aLoadGroup) { mLoadGroup = aLoadGroup; }
+
+ nsresult StartTimeout(mozilla::dom::DocGroup* aDocGroup);
+
+ static void DispatchPings(nsIDocShell* aDocShell, nsIContent* aContent,
+ nsIURI* aTarget, nsIReferrerInfo* aReferrerInfo);
+
+ private:
+ ~nsPingListener();
+
+ nsCOMPtr<nsILoadGroup> mLoadGroup;
+ nsCOMPtr<nsITimer> mTimer;
+};
+
+#endif /* nsPingListener_h__ */
diff --git a/docshell/base/nsRefreshTimer.cpp b/docshell/base/nsRefreshTimer.cpp
new file mode 100644
index 0000000000..867e3ba1cb
--- /dev/null
+++ b/docshell/base/nsRefreshTimer.cpp
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsRefreshTimer.h"
+
+#include "nsIURI.h"
+#include "nsIPrincipal.h"
+
+#include "nsDocShell.h"
+
+NS_IMPL_ADDREF(nsRefreshTimer)
+NS_IMPL_RELEASE(nsRefreshTimer)
+
+NS_INTERFACE_MAP_BEGIN(nsRefreshTimer)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimerCallback)
+ NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+ NS_INTERFACE_MAP_ENTRY(nsINamed)
+NS_INTERFACE_MAP_END
+
+nsRefreshTimer::nsRefreshTimer(nsDocShell* aDocShell, nsIURI* aURI,
+ nsIPrincipal* aPrincipal, int32_t aDelay)
+ : mDocShell(aDocShell),
+ mURI(aURI),
+ mPrincipal(aPrincipal),
+ mDelay(aDelay) {}
+
+nsRefreshTimer::~nsRefreshTimer() {}
+
+NS_IMETHODIMP
+nsRefreshTimer::Notify(nsITimer* aTimer) {
+ NS_ASSERTION(mDocShell, "DocShell is somehow null");
+
+ if (mDocShell && aTimer) {
+ // Get the delay count to determine load type
+ uint32_t delay = 0;
+ aTimer->GetDelay(&delay);
+ mDocShell->ForceRefreshURIFromTimer(mURI, mPrincipal, delay, aTimer);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsRefreshTimer::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsRefreshTimer");
+ return NS_OK;
+}
diff --git a/docshell/base/nsRefreshTimer.h b/docshell/base/nsRefreshTimer.h
new file mode 100644
index 0000000000..423f8807eb
--- /dev/null
+++ b/docshell/base/nsRefreshTimer.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsRefreshTimer_h__
+#define nsRefreshTimer_h__
+
+#include "nsINamed.h"
+#include "nsITimer.h"
+
+#include "nsCOMPtr.h"
+
+class nsDocShell;
+class nsIURI;
+class nsIPrincipal;
+
+class nsRefreshTimer : public nsITimerCallback, public nsINamed {
+ public:
+ nsRefreshTimer(nsDocShell* aDocShell, nsIURI* aURI, nsIPrincipal* aPrincipal,
+ int32_t aDelay);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ int32_t GetDelay() { return mDelay; }
+
+ RefPtr<nsDocShell> mDocShell;
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ int32_t mDelay;
+
+ private:
+ virtual ~nsRefreshTimer();
+};
+
+#endif /* nsRefreshTimer_h__ */
diff --git a/docshell/base/nsWebNavigationInfo.cpp b/docshell/base/nsWebNavigationInfo.cpp
new file mode 100644
index 0000000000..0339499697
--- /dev/null
+++ b/docshell/base/nsWebNavigationInfo.cpp
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWebNavigationInfo.h"
+
+#include "nsServiceManagerUtils.h"
+#include "nsIDocumentLoaderFactory.h"
+#include "nsContentUtils.h"
+#include "imgLoader.h"
+
+NS_IMPL_ISUPPORTS(nsWebNavigationInfo, nsIWebNavigationInfo)
+
+NS_IMETHODIMP
+nsWebNavigationInfo::IsTypeSupported(const nsACString& aType,
+ uint32_t* aIsTypeSupported) {
+ MOZ_ASSERT(aIsTypeSupported, "null out param?");
+
+ *aIsTypeSupported = IsTypeSupported(aType);
+ return NS_OK;
+}
+
+uint32_t nsWebNavigationInfo::IsTypeSupported(const nsACString& aType) {
+ // We want to claim that the type for PDF documents is unsupported,
+ // so that the internal PDF viewer's stream converted will get used.
+ if (aType.LowerCaseEqualsLiteral("application/pdf") &&
+ nsContentUtils::IsPDFJSEnabled()) {
+ return nsIWebNavigationInfo::UNSUPPORTED;
+ }
+
+ const nsCString& flatType = PromiseFlatCString(aType);
+ return IsTypeSupportedInternal(flatType);
+}
+
+uint32_t nsWebNavigationInfo::IsTypeSupportedInternal(const nsCString& aType) {
+ nsContentUtils::ContentViewerType vtype = nsContentUtils::TYPE_UNSUPPORTED;
+
+ nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
+ nsContentUtils::FindInternalContentViewer(aType, &vtype);
+
+ switch (vtype) {
+ case nsContentUtils::TYPE_UNSUPPORTED:
+ return nsIWebNavigationInfo::UNSUPPORTED;
+
+ case nsContentUtils::TYPE_FALLBACK:
+ return nsIWebNavigationInfo::FALLBACK;
+
+ case nsContentUtils::TYPE_UNKNOWN:
+ return nsIWebNavigationInfo::OTHER;
+
+ case nsContentUtils::TYPE_CONTENT:
+ // XXXbz we only need this because images register for the same
+ // contractid as documents, so we can't tell them apart based on
+ // contractid.
+ if (imgLoader::SupportImageWithMimeType(aType)) {
+ return nsIWebNavigationInfo::IMAGE;
+ }
+ return nsIWebNavigationInfo::OTHER;
+ }
+
+ return nsIWebNavigationInfo::UNSUPPORTED;
+}
diff --git a/docshell/base/nsWebNavigationInfo.h b/docshell/base/nsWebNavigationInfo.h
new file mode 100644
index 0000000000..97b07c825e
--- /dev/null
+++ b/docshell/base/nsWebNavigationInfo.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsWebNavigationInfo_h__
+#define nsWebNavigationInfo_h__
+
+#include "nsIWebNavigationInfo.h"
+#include "nsCOMPtr.h"
+#include "nsICategoryManager.h"
+#include "nsStringFwd.h"
+#include "mozilla/Attributes.h"
+
+class nsWebNavigationInfo final : public nsIWebNavigationInfo {
+ public:
+ nsWebNavigationInfo() {}
+
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIWEBNAVIGATIONINFO
+
+ static uint32_t IsTypeSupported(const nsACString& aType);
+
+ private:
+ ~nsWebNavigationInfo() {}
+
+ // Check whether aType is supported, and returns an nsIWebNavigationInfo
+ // constant.
+ static uint32_t IsTypeSupportedInternal(const nsCString& aType);
+};
+
+#endif // nsWebNavigationInfo_h__
diff --git a/docshell/base/timeline/AbstractTimelineMarker.cpp b/docshell/base/timeline/AbstractTimelineMarker.cpp
new file mode 100644
index 0000000000..471742089c
--- /dev/null
+++ b/docshell/base/timeline/AbstractTimelineMarker.cpp
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AbstractTimelineMarker.h"
+
+#include "mozilla/TimeStamp.h"
+#include "MainThreadUtils.h"
+#include "nsAppRunner.h"
+
+namespace mozilla {
+
+AbstractTimelineMarker::AbstractTimelineMarker(const char* aName,
+ MarkerTracingType aTracingType)
+ : mName(aName),
+ mTracingType(aTracingType),
+ mProcessType(XRE_GetProcessType()),
+ mIsOffMainThread(!NS_IsMainThread()) {
+ MOZ_COUNT_CTOR(AbstractTimelineMarker);
+ SetCurrentTime();
+}
+
+AbstractTimelineMarker::AbstractTimelineMarker(const char* aName,
+ const TimeStamp& aTime,
+ MarkerTracingType aTracingType)
+ : mName(aName),
+ mTracingType(aTracingType),
+ mProcessType(XRE_GetProcessType()),
+ mIsOffMainThread(!NS_IsMainThread()) {
+ MOZ_COUNT_CTOR(AbstractTimelineMarker);
+ SetCustomTime(aTime);
+}
+
+UniquePtr<AbstractTimelineMarker> AbstractTimelineMarker::Clone() {
+ MOZ_ASSERT(false, "Clone method not yet implemented on this marker type.");
+ return nullptr;
+}
+
+bool AbstractTimelineMarker::Equals(const AbstractTimelineMarker& aOther) {
+ // Check whether two markers should be considered the same, for the purpose
+ // of pairing start and end markers. Normally this definition suffices.
+ return strcmp(mName, aOther.mName) == 0;
+}
+
+AbstractTimelineMarker::~AbstractTimelineMarker() {
+ MOZ_COUNT_DTOR(AbstractTimelineMarker);
+}
+
+void AbstractTimelineMarker::SetCurrentTime() {
+ TimeStamp now = TimeStamp::Now();
+ SetCustomTime(now);
+}
+
+void AbstractTimelineMarker::SetCustomTime(const TimeStamp& aTime) {
+ mTime = (aTime - TimeStamp::ProcessCreation()).ToMilliseconds();
+}
+
+void AbstractTimelineMarker::SetCustomTime(DOMHighResTimeStamp aTime) {
+ mTime = aTime;
+}
+
+void AbstractTimelineMarker::SetProcessType(GeckoProcessType aProcessType) {
+ mProcessType = aProcessType;
+}
+
+void AbstractTimelineMarker::SetOffMainThread(bool aIsOffMainThread) {
+ mIsOffMainThread = aIsOffMainThread;
+}
+
+} // namespace mozilla
diff --git a/docshell/base/timeline/AbstractTimelineMarker.h b/docshell/base/timeline/AbstractTimelineMarker.h
new file mode 100644
index 0000000000..8581ba53f8
--- /dev/null
+++ b/docshell/base/timeline/AbstractTimelineMarker.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_AbstractTimelineMarker_h_
+#define mozilla_AbstractTimelineMarker_h_
+
+#include "TimelineMarkerEnums.h" // for MarkerTracingType
+#include "nsDOMNavigationTiming.h" // for DOMHighResTimeStamp
+#include "nsXULAppAPI.h" // for GeckoProcessType
+#include "mozilla/UniquePtr.h"
+
+struct JSContext;
+class JSObject;
+
+namespace mozilla {
+class TimeStamp;
+
+namespace dom {
+struct ProfileTimelineMarker;
+}
+
+class AbstractTimelineMarker {
+ private:
+ AbstractTimelineMarker() = delete;
+ AbstractTimelineMarker(const AbstractTimelineMarker& aOther) = delete;
+ void operator=(const AbstractTimelineMarker& aOther) = delete;
+
+ public:
+ AbstractTimelineMarker(const char* aName, MarkerTracingType aTracingType);
+
+ AbstractTimelineMarker(const char* aName, const TimeStamp& aTime,
+ MarkerTracingType aTracingType);
+
+ virtual ~AbstractTimelineMarker();
+
+ virtual UniquePtr<AbstractTimelineMarker> Clone();
+ virtual bool Equals(const AbstractTimelineMarker& aOther);
+
+ virtual void AddDetails(JSContext* aCx,
+ dom::ProfileTimelineMarker& aMarker) = 0;
+ virtual JSObject* GetStack() = 0;
+
+ const char* GetName() const { return mName; }
+ DOMHighResTimeStamp GetTime() const { return mTime; }
+ MarkerTracingType GetTracingType() const { return mTracingType; }
+
+ uint8_t GetProcessType() const { return mProcessType; };
+ bool IsOffMainThread() const { return mIsOffMainThread; };
+
+ private:
+ const char* mName;
+ DOMHighResTimeStamp mTime;
+ MarkerTracingType mTracingType;
+
+ uint8_t mProcessType; // @see `enum GeckoProcessType`.
+ bool mIsOffMainThread;
+
+ protected:
+ void SetCurrentTime();
+ void SetCustomTime(const TimeStamp& aTime);
+ void SetCustomTime(DOMHighResTimeStamp aTime);
+ void SetProcessType(GeckoProcessType aProcessType);
+ void SetOffMainThread(bool aIsOffMainThread);
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_AbstractTimelineMarker_h_ */
diff --git a/docshell/base/timeline/AutoGlobalTimelineMarker.cpp b/docshell/base/timeline/AutoGlobalTimelineMarker.cpp
new file mode 100644
index 0000000000..bf5a19cbab
--- /dev/null
+++ b/docshell/base/timeline/AutoGlobalTimelineMarker.cpp
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AutoGlobalTimelineMarker.h"
+
+#include "TimelineConsumers.h"
+#include "MainThreadUtils.h"
+
+namespace mozilla {
+
+AutoGlobalTimelineMarker::AutoGlobalTimelineMarker(
+ const char* aName, MarkerStackRequest aStackRequest /* = STACK */
+ )
+ : mName(aName), mStackRequest(aStackRequest) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (TimelineConsumers::IsEmpty()) {
+ return;
+ }
+
+ TimelineConsumers::AddMarkerForAllObservedDocShells(
+ mName, MarkerTracingType::START, mStackRequest);
+}
+
+AutoGlobalTimelineMarker::~AutoGlobalTimelineMarker() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (TimelineConsumers::IsEmpty()) {
+ return;
+ }
+
+ TimelineConsumers::AddMarkerForAllObservedDocShells(
+ mName, MarkerTracingType::END, mStackRequest);
+}
+
+} // namespace mozilla
diff --git a/docshell/base/timeline/AutoGlobalTimelineMarker.h b/docshell/base/timeline/AutoGlobalTimelineMarker.h
new file mode 100644
index 0000000000..a9bbc92fce
--- /dev/null
+++ b/docshell/base/timeline/AutoGlobalTimelineMarker.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_AutoGlobalTimelineMarker_h_
+#define mozilla_AutoGlobalTimelineMarker_h_
+
+#include "mozilla/Attributes.h"
+#include "TimelineMarkerEnums.h"
+
+namespace mozilla {
+
+// # AutoGlobalTimelineMarker
+//
+// Similar to `AutoTimelineMarker`, but adds its traced marker to all docshells,
+// not a single particular one. This is useful for operations that aren't
+// associated with any one particular doc shell, or when it isn't clear which
+// docshell triggered the operation.
+//
+// Example usage:
+//
+// {
+// AutoGlobalTimelineMarker marker("Cycle Collection");
+// nsCycleCollector* cc = GetCycleCollector();
+// cc->Collect();
+// ...
+// }
+class MOZ_RAII AutoGlobalTimelineMarker {
+ // The name of the marker we are adding.
+ const char* mName;
+ // Whether to capture the JS stack or not.
+ MarkerStackRequest mStackRequest;
+
+ public:
+ explicit AutoGlobalTimelineMarker(
+ const char* aName,
+ MarkerStackRequest aStackRequest = MarkerStackRequest::STACK);
+ ~AutoGlobalTimelineMarker();
+
+ AutoGlobalTimelineMarker(const AutoGlobalTimelineMarker& aOther) = delete;
+ void operator=(const AutoGlobalTimelineMarker& aOther) = delete;
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_AutoGlobalTimelineMarker_h_ */
diff --git a/docshell/base/timeline/AutoRestyleTimelineMarker.cpp b/docshell/base/timeline/AutoRestyleTimelineMarker.cpp
new file mode 100644
index 0000000000..d9992bc33f
--- /dev/null
+++ b/docshell/base/timeline/AutoRestyleTimelineMarker.cpp
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AutoRestyleTimelineMarker.h"
+
+#include "TimelineConsumers.h"
+#include "MainThreadUtils.h"
+#include "nsIDocShell.h"
+#include "RestyleTimelineMarker.h"
+
+namespace mozilla {
+
+AutoRestyleTimelineMarker::AutoRestyleTimelineMarker(nsIDocShell* aDocShell,
+ bool aIsAnimationOnly)
+ : mDocShell(nullptr), mIsAnimationOnly(aIsAnimationOnly) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aDocShell) {
+ return;
+ }
+
+ if (!TimelineConsumers::HasConsumer(aDocShell)) {
+ return;
+ }
+
+ mDocShell = aDocShell;
+ TimelineConsumers::AddMarkerForDocShell(
+ mDocShell, MakeUnique<RestyleTimelineMarker>(mIsAnimationOnly,
+ MarkerTracingType::START));
+}
+
+AutoRestyleTimelineMarker::~AutoRestyleTimelineMarker() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mDocShell) {
+ return;
+ }
+
+ if (!TimelineConsumers::HasConsumer(mDocShell)) {
+ return;
+ }
+
+ TimelineConsumers::AddMarkerForDocShell(
+ mDocShell, MakeUnique<RestyleTimelineMarker>(mIsAnimationOnly,
+ MarkerTracingType::END));
+}
+
+} // namespace mozilla
diff --git a/docshell/base/timeline/AutoRestyleTimelineMarker.h b/docshell/base/timeline/AutoRestyleTimelineMarker.h
new file mode 100644
index 0000000000..1246e1a9bd
--- /dev/null
+++ b/docshell/base/timeline/AutoRestyleTimelineMarker.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_AutoRestyleTimelineMarker_h_
+#define mozilla_AutoRestyleTimelineMarker_h_
+
+#include "mozilla/RefPtr.h"
+
+class nsIDocShell;
+
+namespace mozilla {
+
+class MOZ_RAII AutoRestyleTimelineMarker {
+ RefPtr<nsIDocShell> mDocShell;
+ bool mIsAnimationOnly;
+
+ public:
+ AutoRestyleTimelineMarker(nsIDocShell* aDocShell, bool aIsAnimationOnly);
+ ~AutoRestyleTimelineMarker();
+
+ AutoRestyleTimelineMarker(const AutoRestyleTimelineMarker& aOther) = delete;
+ void operator=(const AutoRestyleTimelineMarker& aOther) = delete;
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_AutoRestyleTimelineMarker_h_ */
diff --git a/docshell/base/timeline/AutoTimelineMarker.cpp b/docshell/base/timeline/AutoTimelineMarker.cpp
new file mode 100644
index 0000000000..a826706d8e
--- /dev/null
+++ b/docshell/base/timeline/AutoTimelineMarker.cpp
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AutoTimelineMarker.h"
+
+#include "nsIDocShell.h"
+#include "TimelineConsumers.h"
+#include "MainThreadUtils.h"
+
+namespace mozilla {
+
+AutoTimelineMarker::AutoTimelineMarker(nsIDocShell* aDocShell,
+ const char* aName)
+ : mName(aName), mDocShell(nullptr) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aDocShell) {
+ return;
+ }
+
+ if (!TimelineConsumers::HasConsumer(aDocShell)) {
+ return;
+ }
+
+ mDocShell = aDocShell;
+ TimelineConsumers::AddMarkerForDocShell(mDocShell, mName,
+ MarkerTracingType::START);
+}
+
+AutoTimelineMarker::~AutoTimelineMarker() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mDocShell) {
+ return;
+ }
+
+ if (!TimelineConsumers::HasConsumer(mDocShell)) {
+ return;
+ }
+
+ TimelineConsumers::AddMarkerForDocShell(mDocShell, mName,
+ MarkerTracingType::END);
+}
+
+} // namespace mozilla
diff --git a/docshell/base/timeline/AutoTimelineMarker.h b/docshell/base/timeline/AutoTimelineMarker.h
new file mode 100644
index 0000000000..a86c741bb0
--- /dev/null
+++ b/docshell/base/timeline/AutoTimelineMarker.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_AutoTimelineMarker_h_
+#define mozilla_AutoTimelineMarker_h_
+
+#include "mozilla/RefPtr.h"
+
+class nsIDocShell;
+
+namespace mozilla {
+
+// # AutoTimelineMarker
+//
+// An RAII class to trace some task in the platform by adding a start and end
+// timeline marker pair. These markers are then rendered in the devtools'
+// performance tool's waterfall graph.
+//
+// Example usage:
+//
+// {
+// AutoTimelineMarker marker(mDocShell, "Parse CSS");
+// nsresult rv = ParseTheCSSFile(mFile);
+// ...
+// }
+class MOZ_RAII AutoTimelineMarker {
+ // The name of the marker we are adding.
+ const char* mName;
+
+ // The docshell that is associated with this marker.
+ RefPtr<nsIDocShell> mDocShell;
+
+ public:
+ AutoTimelineMarker(nsIDocShell* aDocShell, const char* aName);
+ ~AutoTimelineMarker();
+
+ AutoTimelineMarker(const AutoTimelineMarker& aOther) = delete;
+ void operator=(const AutoTimelineMarker& aOther) = delete;
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_AutoTimelineMarker_h_ */
diff --git a/docshell/base/timeline/CompositeTimelineMarker.h b/docshell/base/timeline/CompositeTimelineMarker.h
new file mode 100644
index 0000000000..cd2896fefa
--- /dev/null
+++ b/docshell/base/timeline/CompositeTimelineMarker.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_CompositeTimelineMarker_h_
+#define mozilla_CompositeTimelineMarker_h_
+
+#include "TimelineMarker.h"
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+
+namespace mozilla {
+
+class CompositeTimelineMarker : public TimelineMarker {
+ public:
+ CompositeTimelineMarker(const TimeStamp& aTime,
+ MarkerTracingType aTracingType)
+ : TimelineMarker("Composite", aTime, aTracingType) {
+ // Even though these markers end up being created on the main thread in the
+ // content or chrome processes, they actually trace down code in the
+ // compositor parent process. All the information for creating these markers
+ // is sent along via IPC to an nsView when a composite finishes.
+ // Mark this as 'off the main thread' to style it differently in frontends.
+ SetOffMainThread(true);
+ }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_CompositeTimelineMarker_h_
diff --git a/docshell/base/timeline/ConsoleTimelineMarker.h b/docshell/base/timeline/ConsoleTimelineMarker.h
new file mode 100644
index 0000000000..232aa1a60e
--- /dev/null
+++ b/docshell/base/timeline/ConsoleTimelineMarker.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ConsoleTimelineMarker_h_
+#define mozilla_ConsoleTimelineMarker_h_
+
+#include "TimelineMarker.h"
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+
+namespace mozilla {
+
+class ConsoleTimelineMarker : public TimelineMarker {
+ public:
+ ConsoleTimelineMarker(const nsAString& aCause, MarkerTracingType aTracingType)
+ : TimelineMarker("ConsoleTime", aTracingType), mCause(aCause) {
+ // Stack is captured by default on the "start" marker. Explicitly also
+ // capture stack on the "end" marker.
+ if (aTracingType == MarkerTracingType::END) {
+ CaptureStack();
+ }
+ }
+
+ virtual bool Equals(const AbstractTimelineMarker& aOther) override {
+ if (!TimelineMarker::Equals(aOther)) {
+ return false;
+ }
+ // Console markers must have matching causes as well. It is safe to perform
+ // a static_cast here as the previous equality check ensures that this is
+ // a console marker instance.
+ return mCause == static_cast<const ConsoleTimelineMarker*>(&aOther)->mCause;
+ }
+
+ virtual void AddDetails(JSContext* aCx,
+ dom::ProfileTimelineMarker& aMarker) override {
+ TimelineMarker::AddDetails(aCx, aMarker);
+
+ if (GetTracingType() == MarkerTracingType::START) {
+ aMarker.mCauseName.Construct(mCause);
+ } else {
+ aMarker.mEndStack = GetStack();
+ }
+ }
+
+ private:
+ nsString mCause;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ConsoleTimelineMarker_h_
diff --git a/docshell/base/timeline/DocLoadingTimelineMarker.h b/docshell/base/timeline/DocLoadingTimelineMarker.h
new file mode 100644
index 0000000000..8f6a7db780
--- /dev/null
+++ b/docshell/base/timeline/DocLoadingTimelineMarker.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_DocLoadingTimelineMarker_h_
+#define mozilla_DocLoadingTimelineMarker_h_
+
+#include "TimelineMarker.h"
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+
+namespace mozilla {
+
+class DocLoadingTimelineMarker : public TimelineMarker {
+ public:
+ explicit DocLoadingTimelineMarker(const char* aName)
+ : TimelineMarker(aName, MarkerTracingType::TIMESTAMP),
+ mUnixTime(PR_Now()) {}
+
+ virtual void AddDetails(JSContext* aCx,
+ dom::ProfileTimelineMarker& aMarker) override {
+ TimelineMarker::AddDetails(aCx, aMarker);
+ aMarker.mUnixTime.Construct(mUnixTime);
+ }
+
+ private:
+ // Certain consumers might use Date.now() or similar for tracing time.
+ // However, TimelineMarkers use process creation as an epoch, which provides
+ // more precision. To allow syncing, attach an additional unix timestamp.
+ // Using this instead of `AbstractTimelineMarker::GetTime()'s` timestamp
+ // is strongly discouraged.
+ PRTime mUnixTime;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_DocLoadingTimelineMarker_h_
diff --git a/docshell/base/timeline/EventTimelineMarker.h b/docshell/base/timeline/EventTimelineMarker.h
new file mode 100644
index 0000000000..5e5bc5c4a6
--- /dev/null
+++ b/docshell/base/timeline/EventTimelineMarker.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_EventTimelineMarker_h_
+#define mozilla_EventTimelineMarker_h_
+
+#include "TimelineMarker.h"
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+
+namespace mozilla {
+
+class EventTimelineMarker : public TimelineMarker {
+ public:
+ EventTimelineMarker(const nsAString& aType, uint16_t aPhase,
+ MarkerTracingType aTracingType)
+ : TimelineMarker("DOMEvent", aTracingType),
+ mType(aType),
+ mPhase(aPhase) {}
+
+ virtual void AddDetails(JSContext* aCx,
+ dom::ProfileTimelineMarker& aMarker) override {
+ TimelineMarker::AddDetails(aCx, aMarker);
+
+ if (GetTracingType() == MarkerTracingType::START) {
+ aMarker.mType.Construct(mType);
+ aMarker.mEventPhase.Construct(mPhase);
+ }
+ }
+
+ private:
+ nsString mType;
+ uint16_t mPhase;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_EventTimelineMarker_h_
diff --git a/docshell/base/timeline/JavascriptTimelineMarker.h b/docshell/base/timeline/JavascriptTimelineMarker.h
new file mode 100644
index 0000000000..0e07f3b605
--- /dev/null
+++ b/docshell/base/timeline/JavascriptTimelineMarker.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_JavascriptTimelineMarker_h_
+#define mozilla_JavascriptTimelineMarker_h_
+
+#include "TimelineMarker.h"
+
+#include "mozilla/Maybe.h"
+
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/ToJSValue.h"
+
+namespace mozilla {
+
+class JavascriptTimelineMarker : public TimelineMarker {
+ public:
+ // The caller owns |aAsyncCause| here, so we must copy it into a separate
+ // string for use later on.
+ JavascriptTimelineMarker(const char* aReason, const nsAString& aFunctionName,
+ const nsAString& aFileName, uint32_t aLineNumber,
+ MarkerTracingType aTracingType,
+ JS::Handle<JS::Value> aAsyncStack,
+ const char* aAsyncCause)
+ : TimelineMarker("Javascript", aTracingType,
+ MarkerStackRequest::NO_STACK),
+ mCause(NS_ConvertUTF8toUTF16(aReason)),
+ mFunctionName(aFunctionName),
+ mFileName(aFileName),
+ mLineNumber(aLineNumber),
+ mAsyncCause(aAsyncCause) {
+ JSContext* ctx = nsContentUtils::GetCurrentJSContext();
+ if (ctx) {
+ mAsyncStack.init(ctx, aAsyncStack);
+ }
+ }
+
+ virtual void AddDetails(JSContext* aCx,
+ dom::ProfileTimelineMarker& aMarker) override {
+ TimelineMarker::AddDetails(aCx, aMarker);
+
+ aMarker.mCauseName.Construct(mCause);
+
+ if (!mFunctionName.IsEmpty() || !mFileName.IsEmpty()) {
+ dom::RootedDictionary<dom::ProfileTimelineStackFrame> stackFrame(aCx);
+ stackFrame.mLine.Construct(mLineNumber);
+ stackFrame.mSource.Construct(mFileName);
+ stackFrame.mFunctionDisplayName.Construct(mFunctionName);
+
+ if (mAsyncStack.isObject() && !mAsyncCause.IsEmpty()) {
+ JS::Rooted<JSObject*> asyncStack(aCx, &mAsyncStack.toObject());
+ JS::Rooted<JSObject*> parentFrame(aCx);
+ JS::Rooted<JSString*> asyncCause(
+ aCx, JS_NewUCStringCopyN(aCx, mAsyncCause.BeginReading(),
+ mAsyncCause.Length()));
+ if (!asyncCause) {
+ JS_ClearPendingException(aCx);
+ return;
+ }
+
+ if (JS::IsMaybeWrappedSavedFrame(asyncStack) &&
+ !JS::CopyAsyncStack(aCx, asyncStack, asyncCause, &parentFrame,
+ mozilla::Nothing())) {
+ JS_ClearPendingException(aCx);
+ } else {
+ stackFrame.mAsyncParent = parentFrame;
+ }
+ }
+
+ JS::Rooted<JS::Value> newStack(aCx);
+ if (ToJSValue(aCx, stackFrame, &newStack)) {
+ if (newStack.isObject()) {
+ aMarker.mStack = &newStack.toObject();
+ }
+ } else {
+ JS_ClearPendingException(aCx);
+ }
+ }
+ }
+
+ private:
+ nsString mCause;
+ nsString mFunctionName;
+ nsString mFileName;
+ uint32_t mLineNumber;
+ JS::PersistentRooted<JS::Value> mAsyncStack;
+ NS_ConvertUTF8toUTF16 mAsyncCause;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_JavascriptTimelineMarker_h_
diff --git a/docshell/base/timeline/LayerTimelineMarker.h b/docshell/base/timeline/LayerTimelineMarker.h
new file mode 100644
index 0000000000..7c7336ba52
--- /dev/null
+++ b/docshell/base/timeline/LayerTimelineMarker.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_LayerTimelineMarker_h_
+#define mozilla_LayerTimelineMarker_h_
+
+#include "TimelineMarker.h"
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+#include "mozilla/mozalloc_oom.h"
+#include "nsRegion.h"
+
+namespace mozilla {
+
+class LayerTimelineMarker : public TimelineMarker {
+ public:
+ explicit LayerTimelineMarker(const nsIntRegion& aRegion)
+ : TimelineMarker("Layer", MarkerTracingType::HELPER_EVENT),
+ mRegion(aRegion) {}
+
+ void AddLayerRectangles(
+ dom::Sequence<dom::ProfileTimelineLayerRect>& aRectangles) {
+ for (auto iter = mRegion.RectIter(); !iter.Done(); iter.Next()) {
+ const nsIntRect& iterRect = iter.Get();
+ dom::ProfileTimelineLayerRect rect;
+ rect.mX = iterRect.X();
+ rect.mY = iterRect.Y();
+ rect.mWidth = iterRect.Width();
+ rect.mHeight = iterRect.Height();
+ if (!aRectangles.AppendElement(rect, fallible)) {
+ // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might
+ // involve multiple reallocations) and potentially crashing here,
+ // SetCapacity could be called outside the loop once.
+ mozalloc_handle_oom(0);
+ }
+ }
+ }
+
+ private:
+ nsIntRegion mRegion;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_LayerTimelineMarker_h_
diff --git a/docshell/base/timeline/MarkersStorage.h b/docshell/base/timeline/MarkersStorage.h
new file mode 100644
index 0000000000..3ee7623068
--- /dev/null
+++ b/docshell/base/timeline/MarkersStorage.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_MarkersStorage_h_
+#define mozilla_MarkersStorage_h_
+
+#include "TimelineMarkerEnums.h" // for MarkerReleaseRequest
+#include "MainThreadUtils.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/LinkedList.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+class AbstractTimelineMarker;
+
+namespace dom {
+struct ProfileTimelineMarker;
+}
+
+class MarkersStorage : public LinkedListElement<MarkersStorage> {
+ public:
+ MarkersStorage() { MOZ_ASSERT(NS_IsMainThread()); }
+ virtual ~MarkersStorage() { MOZ_ASSERT(NS_IsMainThread()); }
+
+ MarkersStorage(const MarkersStorage& aOther) = delete;
+ void operator=(const MarkersStorage& aOther) = delete;
+
+ virtual void AddMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) = 0;
+ virtual void AddOTMTMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) = 0;
+ virtual void ClearMarkers() = 0;
+ virtual void PopMarkers(JSContext* aCx,
+ nsTArray<dom::ProfileTimelineMarker>& aStore) = 0;
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_MarkersStorage_h_ */
diff --git a/docshell/base/timeline/MessagePortTimelineMarker.h b/docshell/base/timeline/MessagePortTimelineMarker.h
new file mode 100644
index 0000000000..c6d91fa7eb
--- /dev/null
+++ b/docshell/base/timeline/MessagePortTimelineMarker.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_MessagePortTimelineMarker_h_
+#define mozilla_MessagePortTimelineMarker_h_
+
+#include "TimelineMarker.h"
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+
+namespace mozilla {
+
+class MessagePortTimelineMarker : public TimelineMarker {
+ public:
+ MessagePortTimelineMarker(
+ dom::ProfileTimelineMessagePortOperationType aOperationType,
+ MarkerTracingType aTracingType)
+ : TimelineMarker("MessagePort", aTracingType,
+ MarkerStackRequest::NO_STACK),
+ mOperationType(aOperationType) {}
+
+ virtual UniquePtr<AbstractTimelineMarker> Clone() override {
+ MessagePortTimelineMarker* clone =
+ new MessagePortTimelineMarker(mOperationType, GetTracingType());
+ clone->SetCustomTime(GetTime());
+ return UniquePtr<AbstractTimelineMarker>(clone);
+ }
+
+ virtual void AddDetails(JSContext* aCx,
+ dom::ProfileTimelineMarker& aMarker) override {
+ TimelineMarker::AddDetails(aCx, aMarker);
+
+ if (GetTracingType() == MarkerTracingType::START) {
+ aMarker.mMessagePortOperation.Construct(mOperationType);
+ }
+ }
+
+ private:
+ dom::ProfileTimelineMessagePortOperationType mOperationType;
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_MessagePortTimelineMarker_h_ */
diff --git a/docshell/base/timeline/ObservedDocShell.cpp b/docshell/base/timeline/ObservedDocShell.cpp
new file mode 100644
index 0000000000..e4a2b319f5
--- /dev/null
+++ b/docshell/base/timeline/ObservedDocShell.cpp
@@ -0,0 +1,171 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ObservedDocShell.h"
+
+#include <utility>
+
+#include "AbstractTimelineMarker.h"
+#include "LayerTimelineMarker.h"
+#include "MainThreadUtils.h"
+#include "mozilla/AutoRestore.h"
+#include "nsIDocShell.h"
+
+namespace mozilla {
+
+ObservedDocShell::ObservedDocShell(nsIDocShell* aDocShell)
+ : mDocShell(aDocShell), mLock("ObservedDocShellMutex") {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+void ObservedDocShell::AddMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) {
+ // Only allow main thread markers to go into this list. No need to lock
+ // here since `mTimelineMarkers` will only be accessed or modified on the
+ // main thread only.
+ MOZ_ASSERT(NS_IsMainThread());
+ // Don't accept any markers generated by the process of popping
+ // markers.
+ if (!mPopping) {
+ mTimelineMarkers.AppendElement(std::move(aMarker));
+ }
+}
+
+void ObservedDocShell::AddOTMTMarker(
+ UniquePtr<AbstractTimelineMarker>&& aMarker) {
+ // Only allow off the main thread markers to go into this list. Since most
+ // of our markers come from the main thread, be a little more efficient and
+ // avoid dealing with multithreading scenarios until all the markers are
+ // actually cleared or popped in `ClearMarkers` or `PopMarkers`.
+ MOZ_ASSERT(!NS_IsMainThread());
+ MutexAutoLock lock(mLock); // for `mOffTheMainThreadTimelineMarkers`.
+ mOffTheMainThreadTimelineMarkers.AppendElement(std::move(aMarker));
+}
+
+void ObservedDocShell::ClearMarkers() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MutexAutoLock lock(mLock); // for `mOffTheMainThreadTimelineMarkers`.
+ mTimelineMarkers.Clear();
+ mOffTheMainThreadTimelineMarkers.Clear();
+}
+
+void ObservedDocShell::PopMarkers(
+ JSContext* aCx, nsTArray<dom::ProfileTimelineMarker>& aStore) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MOZ_RELEASE_ASSERT(!mPopping);
+ AutoRestore<bool> resetPopping(mPopping);
+ mPopping = true;
+
+ {
+ MutexAutoLock lock(mLock); // for `mOffTheMainThreadTimelineMarkers`.
+
+ // First, move all of our markers into a single array. We'll chose
+ // the `mTimelineMarkers` store because that's where we expect most of
+ // our markers to be, and we can access it without holding the lock.
+ mTimelineMarkers.AppendElements(
+ std::move(mOffTheMainThreadTimelineMarkers));
+ }
+
+ // If we see an unpaired START, we keep it around for the next call
+ // to ObservedDocShell::PopMarkers. We store the kept START objects here.
+ nsTArray<UniquePtr<AbstractTimelineMarker>> keptStartMarkers;
+
+ for (uint32_t i = 0; i < mTimelineMarkers.Length(); ++i) {
+ UniquePtr<AbstractTimelineMarker>& startPayload =
+ mTimelineMarkers.ElementAt(i);
+
+ // If this is a TIMESTAMP marker, there's no corresponding END,
+ // as it's a single unit of time, not a duration.
+ if (startPayload->GetTracingType() == MarkerTracingType::TIMESTAMP) {
+ dom::ProfileTimelineMarker* marker = aStore.AppendElement();
+ marker->mName = NS_ConvertUTF8toUTF16(startPayload->GetName());
+ marker->mStart = startPayload->GetTime();
+ marker->mEnd = startPayload->GetTime();
+ marker->mStack = startPayload->GetStack();
+ startPayload->AddDetails(aCx, *marker);
+ continue;
+ }
+
+ // Whenever a START marker is found, look for the corresponding END
+ // and build a {name,start,end} JS object.
+ if (startPayload->GetTracingType() == MarkerTracingType::START) {
+ bool hasSeenEnd = false;
+
+ // "Paint" markers are different because painting is handled at root
+ // docshell level. The information that a paint was done is stored at
+ // sub-docshell level, but we can only be sure that a paint did actually
+ // happen in if a "Layer" marker was recorded too.
+ bool startIsPaintType = strcmp(startPayload->GetName(), "Paint") == 0;
+ bool hasSeenLayerType = false;
+
+ // If we are processing a "Paint" marker, we append information from
+ // all the embedded "Layer" markers to this array.
+ dom::Sequence<dom::ProfileTimelineLayerRect> layerRectangles;
+
+ // DOM events can be nested, so we must take care when searching
+ // for the matching end. It doesn't hurt to apply this logic to
+ // all event types.
+ uint32_t markerDepth = 0;
+
+ // The assumption is that the devtools timeline flushes markers frequently
+ // enough for the amount of markers to always be small enough that the
+ // nested for loop isn't going to be a performance problem.
+ for (uint32_t j = i + 1; j < mTimelineMarkers.Length(); ++j) {
+ UniquePtr<AbstractTimelineMarker>& endPayload =
+ mTimelineMarkers.ElementAt(j);
+ bool endIsLayerType = strcmp(endPayload->GetName(), "Layer") == 0;
+
+ // Look for "Layer" markers to stream out "Paint" markers.
+ if (startIsPaintType && endIsLayerType) {
+ AbstractTimelineMarker* raw = endPayload.get();
+ LayerTimelineMarker* layerPayload =
+ static_cast<LayerTimelineMarker*>(raw);
+ layerPayload->AddLayerRectangles(layerRectangles);
+ hasSeenLayerType = true;
+ }
+ if (!startPayload->Equals(*endPayload)) {
+ continue;
+ }
+ if (endPayload->GetTracingType() == MarkerTracingType::START) {
+ ++markerDepth;
+ continue;
+ }
+ if (endPayload->GetTracingType() == MarkerTracingType::END) {
+ if (markerDepth > 0) {
+ --markerDepth;
+ continue;
+ }
+ if (!startIsPaintType || (startIsPaintType && hasSeenLayerType)) {
+ dom::ProfileTimelineMarker* marker = aStore.AppendElement();
+ marker->mName = NS_ConvertUTF8toUTF16(startPayload->GetName());
+ marker->mStart = startPayload->GetTime();
+ marker->mEnd = endPayload->GetTime();
+ marker->mStack = startPayload->GetStack();
+ if (hasSeenLayerType) {
+ marker->mRectangles.Construct(layerRectangles);
+ }
+ startPayload->AddDetails(aCx, *marker);
+ endPayload->AddDetails(aCx, *marker);
+ }
+ hasSeenEnd = true;
+ break;
+ }
+ }
+
+ // If we did not see the corresponding END, keep the START.
+ if (!hasSeenEnd) {
+ keptStartMarkers.AppendElement(
+ std::move(mTimelineMarkers.ElementAt(i)));
+ mTimelineMarkers.RemoveElementAt(i);
+ --i;
+ }
+ }
+ }
+
+ mTimelineMarkers = std::move(keptStartMarkers);
+}
+
+} // namespace mozilla
diff --git a/docshell/base/timeline/ObservedDocShell.h b/docshell/base/timeline/ObservedDocShell.h
new file mode 100644
index 0000000000..9d0c39fcb2
--- /dev/null
+++ b/docshell/base/timeline/ObservedDocShell.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ObservedDocShell_h_
+#define mozilla_ObservedDocShell_h_
+
+#include "MarkersStorage.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/UniquePtr.h"
+#include "nsTArray.h"
+
+class nsIDocShell;
+
+namespace mozilla {
+class AbstractTimelineMarker;
+
+namespace dom {
+struct ProfileTimelineMarker;
+}
+
+// # ObservedDocShell
+//
+// A wrapper around a docshell for which docshell-specific markers are
+// allowed to exist. See TimelineConsumers for register/unregister logic.
+class ObservedDocShell : public MarkersStorage {
+ private:
+ RefPtr<nsIDocShell> mDocShell;
+
+ // Main thread only.
+ nsTArray<UniquePtr<AbstractTimelineMarker>> mTimelineMarkers;
+ bool mPopping = false;
+
+ // Off the main thread only.
+ Mutex mLock;
+ nsTArray<UniquePtr<AbstractTimelineMarker>> mOffTheMainThreadTimelineMarkers
+ MOZ_GUARDED_BY(mLock);
+
+ public:
+ explicit ObservedDocShell(nsIDocShell* aDocShell);
+ nsIDocShell* operator*() const { return mDocShell.get(); }
+
+ void AddMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) override;
+ void AddOTMTMarker(UniquePtr<AbstractTimelineMarker>&& aMarker) override;
+ void ClearMarkers() override;
+ void PopMarkers(JSContext* aCx,
+ nsTArray<dom::ProfileTimelineMarker>& aStore) override;
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_ObservedDocShell_h_ */
diff --git a/docshell/base/timeline/RestyleTimelineMarker.h b/docshell/base/timeline/RestyleTimelineMarker.h
new file mode 100644
index 0000000000..4cce10d94b
--- /dev/null
+++ b/docshell/base/timeline/RestyleTimelineMarker.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_RestyleTimelineMarker_h_
+#define mozilla_RestyleTimelineMarker_h_
+
+#include "TimelineMarker.h"
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+
+namespace mozilla {
+
+class RestyleTimelineMarker : public TimelineMarker {
+ public:
+ RestyleTimelineMarker(bool aIsAnimationOnly, MarkerTracingType aTracingType)
+ : TimelineMarker("Styles", aTracingType) {
+ mIsAnimationOnly = aIsAnimationOnly;
+ }
+
+ virtual void AddDetails(JSContext* aCx,
+ dom::ProfileTimelineMarker& aMarker) override {
+ TimelineMarker::AddDetails(aCx, aMarker);
+
+ if (GetTracingType() == MarkerTracingType::START) {
+ aMarker.mIsAnimationOnly.Construct(mIsAnimationOnly);
+ }
+ }
+
+ private:
+ bool mIsAnimationOnly;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_RestyleTimelineMarker_h_
diff --git a/docshell/base/timeline/TimelineConsumers.cpp b/docshell/base/timeline/TimelineConsumers.cpp
new file mode 100644
index 0000000000..e475b74ee1
--- /dev/null
+++ b/docshell/base/timeline/TimelineConsumers.cpp
@@ -0,0 +1,202 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TimelineConsumers.h"
+
+#include "mozilla/ObservedDocShell.h"
+#include "mozilla/TimelineMarker.h"
+#include "jsapi.h"
+#include "nsAppRunner.h" // for XRE_IsContentProcess, XRE_IsParentProcess
+#include "nsCRT.h"
+#include "nsDocShell.h"
+
+namespace mozilla {
+
+StaticMutex TimelineConsumers::sMutex;
+
+uint32_t TimelineConsumers::sActiveConsumers = 0;
+
+StaticAutoPtr<LinkedList<MarkersStorage>> TimelineConsumers::sMarkersStores;
+
+LinkedList<MarkersStorage>& TimelineConsumers::MarkersStores() {
+ if (!sMarkersStores) {
+ sMarkersStores = new LinkedList<MarkersStorage>;
+ }
+ return *sMarkersStores;
+}
+
+void TimelineConsumers::AddConsumer(nsDocShell* aDocShell) {
+ MOZ_ASSERT(NS_IsMainThread());
+ StaticMutexAutoLock lock(
+ sMutex); // for `sActiveConsumers` and `sMarkersStores`.
+
+ UniquePtr<ObservedDocShell>& observed = aDocShell->mObserved;
+ MOZ_ASSERT(!observed);
+
+ if (sActiveConsumers == 0) {
+ JS::SetProfileTimelineRecordingEnabled(true);
+ }
+ sActiveConsumers++;
+
+ ObservedDocShell* obsDocShell = new ObservedDocShell(aDocShell);
+ MarkersStorage* storage = static_cast<MarkersStorage*>(obsDocShell);
+
+ observed.reset(obsDocShell);
+ MarkersStores().insertFront(storage);
+}
+
+void TimelineConsumers::RemoveConsumer(nsDocShell* aDocShell) {
+ MOZ_ASSERT(NS_IsMainThread());
+ StaticMutexAutoLock lock(
+ sMutex); // for `sActiveConsumers` and `sMarkersStores`.
+
+ UniquePtr<ObservedDocShell>& observed = aDocShell->mObserved;
+ MOZ_ASSERT(observed);
+
+ sActiveConsumers--;
+ if (sActiveConsumers == 0) {
+ JS::SetProfileTimelineRecordingEnabled(false);
+ }
+
+ // Clear all markers from the `mTimelineMarkers` store.
+ observed->ClearMarkers();
+ // Remove self from the `sMarkersStores` store.
+ observed->remove();
+ // Prepare for becoming a consumer later.
+ observed.reset(nullptr);
+}
+
+bool TimelineConsumers::HasConsumer(nsIDocShell* aDocShell) {
+ MOZ_ASSERT(NS_IsMainThread());
+ return aDocShell ? aDocShell->GetRecordProfileTimelineMarkers() : false;
+}
+
+bool TimelineConsumers::IsEmpty() {
+ StaticMutexAutoLock lock(sMutex); // for `sActiveConsumers`.
+ return sActiveConsumers == 0;
+}
+
+void TimelineConsumers::AddMarkerForDocShell(nsDocShell* aDocShell,
+ const char* aName,
+ MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (HasConsumer(aDocShell)) {
+ aDocShell->mObserved->AddMarker(
+ MakeUnique<TimelineMarker>(aName, aTracingType, aStackRequest));
+ }
+}
+
+void TimelineConsumers::AddMarkerForDocShell(nsDocShell* aDocShell,
+ const char* aName,
+ const TimeStamp& aTime,
+ MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (HasConsumer(aDocShell)) {
+ aDocShell->mObserved->AddMarker(
+ MakeUnique<TimelineMarker>(aName, aTime, aTracingType, aStackRequest));
+ }
+}
+
+void TimelineConsumers::AddMarkerForDocShell(
+ nsDocShell* aDocShell, UniquePtr<AbstractTimelineMarker>&& aMarker) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (HasConsumer(aDocShell)) {
+ aDocShell->mObserved->AddMarker(std::move(aMarker));
+ }
+}
+
+void TimelineConsumers::AddMarkerForDocShell(nsIDocShell* aDocShell,
+ const char* aName,
+ MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest) {
+ MOZ_ASSERT(NS_IsMainThread());
+ AddMarkerForDocShell(static_cast<nsDocShell*>(aDocShell), aName, aTracingType,
+ aStackRequest);
+}
+
+void TimelineConsumers::AddMarkerForDocShell(nsIDocShell* aDocShell,
+ const char* aName,
+ const TimeStamp& aTime,
+ MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest) {
+ MOZ_ASSERT(NS_IsMainThread());
+ AddMarkerForDocShell(static_cast<nsDocShell*>(aDocShell), aName, aTime,
+ aTracingType, aStackRequest);
+}
+
+void TimelineConsumers::AddMarkerForDocShell(
+ nsIDocShell* aDocShell, UniquePtr<AbstractTimelineMarker>&& aMarker) {
+ MOZ_ASSERT(NS_IsMainThread());
+ AddMarkerForDocShell(static_cast<nsDocShell*>(aDocShell), std::move(aMarker));
+}
+
+void TimelineConsumers::AddMarkerForAllObservedDocShells(
+ const char* aName, MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest /* = STACK */) {
+ bool isMainThread = NS_IsMainThread();
+ StaticMutexAutoLock lock(sMutex); // for `sMarkersStores`.
+
+ for (MarkersStorage* storage = MarkersStores().getFirst(); storage != nullptr;
+ storage = storage->getNext()) {
+ UniquePtr<AbstractTimelineMarker> marker =
+ MakeUnique<TimelineMarker>(aName, aTracingType, aStackRequest);
+ if (isMainThread) {
+ storage->AddMarker(std::move(marker));
+ } else {
+ storage->AddOTMTMarker(std::move(marker));
+ }
+ }
+}
+
+void TimelineConsumers::AddMarkerForAllObservedDocShells(
+ const char* aName, const TimeStamp& aTime, MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest /* = STACK */) {
+ bool isMainThread = NS_IsMainThread();
+ StaticMutexAutoLock lock(sMutex); // for `sMarkersStores`.
+
+ for (MarkersStorage* storage = MarkersStores().getFirst(); storage != nullptr;
+ storage = storage->getNext()) {
+ UniquePtr<AbstractTimelineMarker> marker =
+ MakeUnique<TimelineMarker>(aName, aTime, aTracingType, aStackRequest);
+ if (isMainThread) {
+ storage->AddMarker(std::move(marker));
+ } else {
+ storage->AddOTMTMarker(std::move(marker));
+ }
+ }
+}
+
+void TimelineConsumers::AddMarkerForAllObservedDocShells(
+ UniquePtr<AbstractTimelineMarker>& aMarker) {
+ bool isMainThread = NS_IsMainThread();
+ StaticMutexAutoLock lock(sMutex); // for `sMarkersStores`.
+
+ for (MarkersStorage* storage = MarkersStores().getFirst(); storage != nullptr;
+ storage = storage->getNext()) {
+ UniquePtr<AbstractTimelineMarker> clone = aMarker->Clone();
+ if (isMainThread) {
+ storage->AddMarker(std::move(clone));
+ } else {
+ storage->AddOTMTMarker(std::move(clone));
+ }
+ }
+}
+
+void TimelineConsumers::PopMarkers(
+ nsDocShell* aDocShell, JSContext* aCx,
+ nsTArray<dom::ProfileTimelineMarker>& aStore) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!aDocShell || !aDocShell->mObserved) {
+ return;
+ }
+
+ aDocShell->mObserved->PopMarkers(aCx, aStore);
+}
+
+} // namespace mozilla
diff --git a/docshell/base/timeline/TimelineConsumers.h b/docshell/base/timeline/TimelineConsumers.h
new file mode 100644
index 0000000000..a199560d8c
--- /dev/null
+++ b/docshell/base/timeline/TimelineConsumers.h
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_TimelineConsumers_h_
+#define mozilla_TimelineConsumers_h_
+
+#include "nsIObserver.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/StaticMutex.h"
+#include "nsTArray.h"
+#include "TimelineMarkerEnums.h" // for MarkerTracingType
+
+class nsDocShell;
+class nsIDocShell;
+struct JSContext;
+
+namespace mozilla {
+class TimeStamp;
+class MarkersStorage;
+class AbstractTimelineMarker;
+
+namespace dom {
+struct ProfileTimelineMarker;
+}
+
+class TimelineConsumers {
+ public:
+ // Methods for registering interested consumers (i.e. "devtools toolboxes").
+ // Each consumer should be directly focused on a particular docshell, but
+ // timeline markers don't necessarily have to be tied to that docshell.
+ // See the public `AddMarker*` methods below.
+ // Main thread only.
+ static void AddConsumer(nsDocShell* aDocShell);
+ static void RemoveConsumer(nsDocShell* aDocShell);
+
+ static bool HasConsumer(nsIDocShell* aDocShell);
+
+ // Checks if there's any existing interested consumer.
+ // May be called from any thread.
+ static bool IsEmpty();
+
+ // Methods for adding markers relevant for particular docshells, or generic
+ // (meaning that they either can't be tied to a particular docshell, or one
+ // wasn't accessible in the part of the codebase where they're instantiated).
+ // These will only add markers if at least one docshell is currently being
+ // observed by a timeline. Markers tied to a particular docshell won't be
+ // created unless that docshell is specifically being currently observed.
+ // See nsIDocShell::recordProfileTimelineMarkers
+
+ // These methods create a basic TimelineMarker from a name and some metadata,
+ // relevant for a specific docshell.
+ // Main thread only.
+ static void AddMarkerForDocShell(
+ nsDocShell* aDocShell, const char* aName, MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest = MarkerStackRequest::STACK);
+ static void AddMarkerForDocShell(
+ nsIDocShell* aDocShell, const char* aName, MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest = MarkerStackRequest::STACK);
+
+ static void AddMarkerForDocShell(
+ nsDocShell* aDocShell, const char* aName, const TimeStamp& aTime,
+ MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest = MarkerStackRequest::STACK);
+ static void AddMarkerForDocShell(
+ nsIDocShell* aDocShell, const char* aName, const TimeStamp& aTime,
+ MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest = MarkerStackRequest::STACK);
+
+ // These methods register and receive ownership of an already created marker,
+ // relevant for a specific docshell.
+ // Main thread only.
+ static void AddMarkerForDocShell(nsDocShell* aDocShell,
+ UniquePtr<AbstractTimelineMarker>&& aMarker);
+ static void AddMarkerForDocShell(nsIDocShell* aDocShell,
+ UniquePtr<AbstractTimelineMarker>&& aMarker);
+
+ // These methods create a basic marker from a name and some metadata,
+ // which doesn't have to be relevant to a specific docshell.
+ // May be called from any thread.
+ static void AddMarkerForAllObservedDocShells(
+ const char* aName, MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest = MarkerStackRequest::STACK);
+ static void AddMarkerForAllObservedDocShells(
+ const char* aName, const TimeStamp& aTime, MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest = MarkerStackRequest::STACK);
+
+ // This method clones and registers an already instantiated marker,
+ // which doesn't have to be relevant to a specific docshell.
+ // May be called from any thread.
+ static void AddMarkerForAllObservedDocShells(
+ UniquePtr<AbstractTimelineMarker>& aMarker);
+
+ static void PopMarkers(nsDocShell* aDocShell, JSContext* aCx,
+ nsTArray<dom::ProfileTimelineMarker>& aStore);
+
+ private:
+ static StaticMutex sMutex;
+
+ static LinkedList<MarkersStorage>& MarkersStores() MOZ_REQUIRES(sMutex);
+
+ static uint32_t sActiveConsumers MOZ_GUARDED_BY(sMutex);
+ static StaticAutoPtr<LinkedList<MarkersStorage>> sMarkersStores
+ MOZ_GUARDED_BY(sMutex);
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_TimelineConsumers_h_ */
diff --git a/docshell/base/timeline/TimelineMarker.cpp b/docshell/base/timeline/TimelineMarker.cpp
new file mode 100644
index 0000000000..9f09ebf0a5
--- /dev/null
+++ b/docshell/base/timeline/TimelineMarker.cpp
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "TimelineMarker.h"
+
+#include "jsapi.h"
+#include "js/Exception.h"
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+#include "nsContentUtils.h"
+
+namespace mozilla {
+
+TimelineMarker::TimelineMarker(const char* aName,
+ MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest)
+ : AbstractTimelineMarker(aName, aTracingType) {
+ CaptureStackIfNecessary(aTracingType, aStackRequest);
+}
+
+TimelineMarker::TimelineMarker(const char* aName, const TimeStamp& aTime,
+ MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest)
+ : AbstractTimelineMarker(aName, aTime, aTracingType) {
+ CaptureStackIfNecessary(aTracingType, aStackRequest);
+}
+
+void TimelineMarker::AddDetails(JSContext* aCx,
+ dom::ProfileTimelineMarker& aMarker) {
+ if (GetTracingType() == MarkerTracingType::START) {
+ aMarker.mProcessType.Construct(GetProcessType());
+ aMarker.mIsOffMainThread.Construct(IsOffMainThread());
+ }
+}
+
+JSObject* TimelineMarker::GetStack() {
+ if (mStackTrace.initialized()) {
+ return mStackTrace;
+ }
+ return nullptr;
+}
+
+void TimelineMarker::CaptureStack() {
+ JSContext* ctx = nsContentUtils::GetCurrentJSContext();
+ if (ctx) {
+ JS::Rooted<JSObject*> stack(ctx);
+ if (JS::CaptureCurrentStack(ctx, &stack)) {
+ mStackTrace.init(ctx, stack.get());
+ } else {
+ JS_ClearPendingException(ctx);
+ }
+ }
+}
+
+void TimelineMarker::CaptureStackIfNecessary(MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest) {
+ if ((aTracingType == MarkerTracingType::START ||
+ aTracingType == MarkerTracingType::TIMESTAMP) &&
+ aStackRequest != MarkerStackRequest::NO_STACK) {
+ CaptureStack();
+ }
+}
+
+} // namespace mozilla
diff --git a/docshell/base/timeline/TimelineMarker.h b/docshell/base/timeline/TimelineMarker.h
new file mode 100644
index 0000000000..b7c5a83ce9
--- /dev/null
+++ b/docshell/base/timeline/TimelineMarker.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_TimelineMarker_h_
+#define mozilla_TimelineMarker_h_
+
+#include "AbstractTimelineMarker.h"
+#include "js/RootingAPI.h"
+
+namespace mozilla {
+
+// Objects of this type can be added to the timeline if there is an interested
+// consumer. The class can also be subclassed to let a given marker creator
+// provide custom details.
+class TimelineMarker : public AbstractTimelineMarker {
+ public:
+ TimelineMarker(const char* aName, MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest = MarkerStackRequest::STACK);
+
+ TimelineMarker(const char* aName, const TimeStamp& aTime,
+ MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest = MarkerStackRequest::STACK);
+
+ virtual void AddDetails(JSContext* aCx,
+ dom::ProfileTimelineMarker& aMarker) override;
+ virtual JSObject* GetStack() override;
+
+ protected:
+ void CaptureStack();
+
+ private:
+ // While normally it is not a good idea to make a persistent root,
+ // in this case changing nsDocShell to participate in cycle
+ // collection was deemed too invasive, and the markers are only held
+ // here temporarily to boot.
+ JS::PersistentRooted<JSObject*> mStackTrace;
+
+ void CaptureStackIfNecessary(MarkerTracingType aTracingType,
+ MarkerStackRequest aStackRequest);
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_TimelineMarker_h_ */
diff --git a/docshell/base/timeline/TimelineMarkerEnums.h b/docshell/base/timeline/TimelineMarkerEnums.h
new file mode 100644
index 0000000000..6c6a39245c
--- /dev/null
+++ b/docshell/base/timeline/TimelineMarkerEnums.h
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_TimelineMarkerEnums_h_
+#define mozilla_TimelineMarkerEnums_h_
+
+namespace mozilla {
+
+enum class MarkerTracingType { START, END, TIMESTAMP, HELPER_EVENT };
+
+enum class MarkerStackRequest { STACK, NO_STACK };
+
+} // namespace mozilla
+
+#endif // mozilla_TimelineMarkerEnums_h_
diff --git a/docshell/base/timeline/TimestampTimelineMarker.h b/docshell/base/timeline/TimestampTimelineMarker.h
new file mode 100644
index 0000000000..6950f85899
--- /dev/null
+++ b/docshell/base/timeline/TimestampTimelineMarker.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_TimestampTimelineMarker_h_
+#define mozilla_TimestampTimelineMarker_h_
+
+#include "TimelineMarker.h"
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+
+namespace mozilla {
+
+class TimestampTimelineMarker : public TimelineMarker {
+ public:
+ explicit TimestampTimelineMarker(const nsAString& aCause)
+ : TimelineMarker("TimeStamp", MarkerTracingType::TIMESTAMP),
+ mCause(aCause) {}
+
+ virtual void AddDetails(JSContext* aCx,
+ dom::ProfileTimelineMarker& aMarker) override {
+ TimelineMarker::AddDetails(aCx, aMarker);
+
+ if (!mCause.IsEmpty()) {
+ aMarker.mCauseName.Construct(mCause);
+ }
+ }
+
+ private:
+ nsString mCause;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_TimestampTimelineMarker_h_
diff --git a/docshell/base/timeline/WorkerTimelineMarker.h b/docshell/base/timeline/WorkerTimelineMarker.h
new file mode 100644
index 0000000000..c0c0517b09
--- /dev/null
+++ b/docshell/base/timeline/WorkerTimelineMarker.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_WorkerTimelineMarker_h_
+#define mozilla_WorkerTimelineMarker_h_
+
+#include "TimelineMarker.h"
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
+
+namespace mozilla {
+
+class WorkerTimelineMarker : public TimelineMarker {
+ public:
+ WorkerTimelineMarker(dom::ProfileTimelineWorkerOperationType aOperationType,
+ MarkerTracingType aTracingType)
+ : TimelineMarker("Worker", aTracingType, MarkerStackRequest::NO_STACK),
+ mOperationType(aOperationType) {}
+
+ virtual UniquePtr<AbstractTimelineMarker> Clone() override {
+ WorkerTimelineMarker* clone =
+ new WorkerTimelineMarker(mOperationType, GetTracingType());
+ clone->SetCustomTime(GetTime());
+ return UniquePtr<AbstractTimelineMarker>(clone);
+ }
+
+ virtual void AddDetails(JSContext* aCx,
+ dom::ProfileTimelineMarker& aMarker) override {
+ TimelineMarker::AddDetails(aCx, aMarker);
+
+ if (GetTracingType() == MarkerTracingType::START) {
+ aMarker.mWorkerOperation.Construct(mOperationType);
+ }
+ }
+
+ private:
+ dom::ProfileTimelineWorkerOperationType mOperationType;
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_WorkerTimelineMarker_h_ */
diff --git a/docshell/base/timeline/moz.build b/docshell/base/timeline/moz.build
new file mode 100644
index 0000000000..d5daf84ae7
--- /dev/null
+++ b/docshell/base/timeline/moz.build
@@ -0,0 +1,44 @@
+# -*- Mode: python; 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("DevTools", "Performance Tools (Profiler/Timeline)")
+
+EXPORTS.mozilla += [
+ "AbstractTimelineMarker.h",
+ "AutoGlobalTimelineMarker.h",
+ "AutoRestyleTimelineMarker.h",
+ "AutoTimelineMarker.h",
+ "CompositeTimelineMarker.h",
+ "ConsoleTimelineMarker.h",
+ "DocLoadingTimelineMarker.h",
+ "EventTimelineMarker.h",
+ "JavascriptTimelineMarker.h",
+ "LayerTimelineMarker.h",
+ "MarkersStorage.h",
+ "MessagePortTimelineMarker.h",
+ "ObservedDocShell.h",
+ "RestyleTimelineMarker.h",
+ "TimelineConsumers.h",
+ "TimelineMarker.h",
+ "TimelineMarkerEnums.h",
+ "TimestampTimelineMarker.h",
+ "WorkerTimelineMarker.h",
+]
+
+UNIFIED_SOURCES += [
+ "AbstractTimelineMarker.cpp",
+ "AutoGlobalTimelineMarker.cpp",
+ "AutoRestyleTimelineMarker.cpp",
+ "AutoTimelineMarker.cpp",
+ "ObservedDocShell.cpp",
+ "TimelineConsumers.cpp",
+ "TimelineMarker.cpp",
+]
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += ["/docshell/base"]
diff --git a/docshell/base/timeline/readme.md b/docshell/base/timeline/readme.md
new file mode 100644
index 0000000000..d2c3d5c215
--- /dev/null
+++ b/docshell/base/timeline/readme.md
@@ -0,0 +1,97 @@
+
+#Timeline
+
+The files in this directory are concerned with providing the backend platform features required for the developer tools interested in tracking down operations done in Gecko. The mechanism we use to define these operations are `markers`.
+
+Examples of traced operations include:
+
+* Style Recalculation
+* Layout
+* Painting
+* JavaScript run-to-completion
+* HTML parsing
+* etc.
+
+The traced operations are displayed in the DevTools Performance tool's timeline.
+
+This is an overview of how everything works and can be extended.
+
+##MarkersStorage
+A `MarkersStorage` is an abstract class defining a place where timeline markers may be held. It defines an interface with pure virtual functions to highlight how this storage can be interacted with:
+
+- `AddMarker`: adding a marker, from the main thread only
+- `AddOTMTMarker`: adding a marker off the main thread only
+- `ClearMarkers`: clearing all accumulated markers (both from the main thread and off it)
+- `PopMarkers`: popping all accumulated markers (both from the main thread and off it).
+
+Note on why we handle on/off the main thread markers separately: since most of our markers will come from the main thread, we can be a little more efficient and avoid dealing with multithreading scenarios until all the markers are actually cleared or popped in `ClearMarkers` or `PopMarkers`. Main thread markers may only be added via `AddMarker`, while off the main thread markers may only be added via `AddOTMTMarker`. Clearing and popping markers will yield until all operations involving off the main thread markers finish. When popping, the markers accumulated off the main thread will be moved over. We expect popping to be fairly infrequent (every few hundred milliseconds, currently we schedule this to happen every 200ms).
+
+##ObservedDocShell
+The only implementation of a MarkersStorage we have right now is an `ObservedDocShell`.
+
+Instances of `ObservedDocShell` accumulate markers that are *mostly* about a particular docshell. At a high level, for example, an `ObservedDocshell` would be created when a timeline tool is opened on a page. It is reasonable to assume that most operations which are interesting for that particular page happen on the main thread. However certain operations may happen outside of it, yet are interesting for its developers, for which markers can be created as well (e.g. web audio stuff, service workers etc.). It is also reasonable to assume that a docshell may sometimes not be easily accessible from certain parts of the platform code, but for which markers still need to be created.
+
+Therefore, the following scenarios arise:
+
+- a). creating a marker on the main thread about a particular docshell
+
+- b). creating a marker on the main thread without pinpointing to an affected docshell (unlikely, but allowed; in this case such a marker would have to be stored in all currently existing `ObservedDocShell` instances)
+
+- c). creating a marker off the main thread about a particular docshell (impossible; docshells can't be referenced outside the main thread, in which case some other type of identification mechanism needs to be put in place).
+
+- d). creating a marker off the main thread without pinpointing to a particular docshell (same path as c. here, such a marker would have to be stored in all currently existing `ObservedDocShell` instances).
+
+An observed docshell (in other words, "a docshell for which a timeline tool was opened") can thus receive both main thread and off the main thread markers.
+
+Cross-process markers are unnecessary at the moment, but tracked in bug 1200120.
+
+##TimelineConsumers
+A `TimelineConsumer` is a singleton that facilitates access to `ObservedDocShell` instances. This is where a docshell can register/unregister itself as being observed via the `AddConsumer` and `RemoveConsumer` methods.
+
+All markers may only be stored via this singleton. Certain helper methods are available:
+
+* Main thread only
+`AddMarkerForDocShell(nsDocShell*, const char*, MarkerTracingType)`
+`AddMarkerForDocShell(nsDocShell*, const char*, const TimeStamp&, MarkerTracingType)`
+`AddMarkerForDocShell(nsDocShell*, UniquePtr<AbstractTimelineMarker>&&)`
+
+* Any thread
+`AddMarkerForAllObservedDocShells(const char*, MarkerTracingType)`
+`AddMarkerForAllObservedDocShells(const char*, const TimeStamp&, MarkerTracingType)`
+`AddMarkerForAllObservedDocShells(UniquePtr<AbstractTimelineMarker>&)`
+
+The "main thread only" methods deal with point a). described above. The "any thread" methods deal with points b). and d).
+
+##AbstractTimelineMarker
+
+All markers inherit from this abstract class, providing a simple thread-safe extendable blueprint.
+
+Markers are readonly after instantiation, and will always be identified by a name, a timestamp and their tracing type (`START`, `END`, `TIMESTAMP`). It *should not* make sense to modify their data after their creation.
+
+There are only two accessible constructors:
+`AbstractTimelineMarker(const char*, MarkerTracingType)`
+`AbstractTimelineMarker(const char*, const TimeStamp&, MarkerTracingType)`
+which create a marker with a name and a tracing type. If unspecified, the corresponding timestamp will be the current instantiation time. Instantiating a marker *much later* after a particular operation is possible, but be careful providing the correct timestamp.
+
+The `AddDetails` virtual method should be implemented by subclasses when creating WebIDL versions of these markers, which will be sent over to a JavaScript frontend.
+
+##TimelineMarker
+A `TimelineMarker` is the main `AbstractTimelineMarker` implementation. They allow attaching a JavaScript stack on `START` and `TIMESTAMP` markers.
+
+These markers will be created when using the `TimelineConsumers` helper methods which take in a string, a tracing type and (optionally) a timestamp. For more complex markers, subclasses are encouraged. See `EventTimelineMarker` or `ConsoleTimelineMarker` for some examples.
+
+##RAII
+
+### mozilla::AutoTimelineMarker
+
+The easiest way to trace Gecko events/tasks with start and end timeline markers is to use the `mozilla::AutoTimelineMarker` RAII class. It automatically adds the start marker on construction, and adds the end marker on destruction. Don't worry too much about potential performance impact! It only actually adds the markers when the given docshell is being observed by a timeline consumer, so essentially nothing will happen if a tool to inspect those markers isn't specifically open.
+
+This class may only be used on the main thread, and pointer to a docshell is necessary. If the docshell is a nullptr, nothing happens and this operation fails silently.
+
+Example: `AutoTimelineMarker marker(aTargetNode->OwnerDoc()->GetDocShell(), "Parse HTML");`
+
+### mozilla::AutoGlobalTimelineMarker`
+
+Similar to the previous RAII class, but doesn't expect a specific docshell, and the marker will be visible in all timeline consumers. This is useful for generic operations that don't involve a particular docshell, or where a docshell isn't accessible. May also only be used on the main thread.
+
+Example: `AutoGlobalTimelineMarker marker("Some global operation");`
diff --git a/docshell/build/components.conf b/docshell/build/components.conf
new file mode 100644
index 0000000000..9817f14f64
--- /dev/null
+++ b/docshell/build/components.conf
@@ -0,0 +1,192 @@
+# -*- Mode: python; 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/.
+
+about_pages = [
+ 'about',
+ 'addons',
+ 'buildconfig',
+ 'certificate',
+ 'checkerboard',
+ 'config',
+ 'crashcontent',
+ 'crashparent',
+ 'crashgpu',
+ 'credits',
+ 'httpsonlyerror',
+ 'license',
+ 'logging',
+ 'logo',
+ 'memory',
+ 'mozilla',
+ 'neterror',
+ 'networking',
+ 'performance',
+ 'plugins',
+ 'processes',
+ 'serviceworkers',
+ 'srcdoc',
+ 'support',
+ 'telemetry',
+ 'translations',
+ 'url-classifier',
+ 'webrtc',
+]
+
+if defined('MOZ_CRASHREPORTER'):
+ about_pages.append('crashes')
+if buildconfig.substs['MOZ_WIDGET_TOOLKIT'] != 'android':
+ about_pages.append('profiles')
+if buildconfig.substs['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ about_pages.append('third-party')
+ about_pages.append('windows-messages')
+if not defined('MOZ_GLEAN_ANDROID'):
+ about_pages.append('glean')
+
+Headers = ['/docshell/build/nsDocShellModule.h']
+
+InitFunc = 'mozilla::InitDocShellModule'
+UnloadFunc = 'mozilla::UnloadDocShellModule'
+
+Classes = [
+ {
+ 'name': 'DocLoader',
+ 'cid': '{057b04d0-0ccf-11d2-beba-00805f8a66dc}',
+ 'contract_ids': ['@mozilla.org/docloaderservice;1'],
+ 'type': 'nsDocLoader',
+ 'headers': ['nsDocLoader.h'],
+ 'init_method': 'Init',
+ },
+ {
+ 'name': 'URIFixup',
+ 'js_name': 'uriFixup',
+ 'cid': '{c6cf88b7-452e-47eb-bdc9-86e3561648ef}',
+ 'contract_ids': ['@mozilla.org/docshell/uri-fixup;1'],
+ 'interfaces': ['nsIURIFixup'],
+ 'esModule': 'resource://gre/modules/URIFixup.sys.mjs',
+ 'singleton': True,
+ 'constructor': 'URIFixup',
+ },
+ {
+ 'cid': '{33d75835-722f-42c0-89cc-44f328e56a86}',
+ 'contract_ids': ['@mozilla.org/docshell/uri-fixup-info;1'],
+ 'esModule': 'resource://gre/modules/URIFixup.sys.mjs',
+ 'constructor': 'URIFixupInfo',
+ },
+ {
+ 'cid': '{56ebedd4-6ccf-48e8-bdae-adc77f044567}',
+ 'contract_ids': [
+ '@mozilla.org/network/protocol/about;1?what=%s' % path
+ for path in about_pages
+ ],
+ 'legacy_constructor': 'nsAboutRedirector::Create',
+ 'headers': ['/docshell/base/nsAboutRedirector.h'],
+ },
+ {
+ 'name': 'ExternalProtocolHandler',
+ 'cid': '{bd6390c8-fbea-11d4-98f6-001083010e9b}',
+ 'contract_ids': ['@mozilla.org/network/protocol;1?name=default'],
+ 'type': 'nsExternalProtocolHandler',
+ 'headers': ['/uriloader/exthandler/nsExternalProtocolHandler.h'],
+ 'protocol_config': {
+ 'scheme': 'default',
+ 'flags': [
+ 'URI_NORELATIVE',
+ 'URI_NOAUTH',
+ 'URI_LOADABLE_BY_ANYONE',
+ 'URI_NON_PERSISTABLE',
+ 'URI_DOES_NOT_RETURN_DATA',
+ ],
+ 'default_port': 0,
+ },
+ 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+ },
+ {
+ 'cid': '{95790842-75a0-430d-98bf-f5ce3788ea6d}',
+ 'contract_ids': ['@mozilla.org/ospermissionrequest;1'],
+ 'type': 'nsOSPermissionRequest',
+ 'headers': ['nsOSPermissionRequest.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'name': 'Prefetch',
+ 'cid': '{6b8bdffc-3394-417d-be83-a81b7c0f63bf}',
+ 'contract_ids': ['@mozilla.org/prefetch-service;1'],
+ 'type': 'nsPrefetchService',
+ 'headers': ['/uriloader/prefetch/nsPrefetchService.h'],
+ 'init_method': 'Init',
+ },
+ {
+ 'cid': '{c4b6fb7c-bfb1-49dc-a65f-035796524b53}',
+ 'contract_ids': ['@mozilla.org/uriloader/handler-service;1'],
+ 'type': 'nsIHandlerService',
+ 'headers': ['ContentHandlerService.h'],
+ 'constructor': 'mozilla::dom::ContentHandlerService::Create',
+ },
+ {
+ 'cid': '{bc0017e3-2438-47be-a567-41db58f17627}',
+ 'contract_ids': ['@mozilla.org/uriloader/local-handler-app;1'],
+ 'type': 'PlatformLocalHandlerApp_t',
+ 'headers': ['/uriloader/exthandler/nsLocalHandlerApp.h'],
+ },
+ {
+ 'name': 'URILoader',
+ 'cid': '{9f6d5d40-90e7-11d3-af80-00a024ffc08c}',
+ 'contract_ids': ['@mozilla.org/uriloader;1'],
+ 'type': 'nsURILoader',
+ 'headers': ['nsURILoader.h'],
+ },
+ {
+ 'cid': '{f30bc0a2-958b-4287-bf62-ce38ba0c811e}',
+ 'contract_ids': ['@mozilla.org/webnavigation-info;1'],
+ 'type': 'nsWebNavigationInfo',
+ 'headers': ['/docshell/base/nsWebNavigationInfo.h'],
+ },
+]
+
+if defined('MOZ_ENABLE_DBUS'):
+ Classes += [
+ {
+ 'name': 'DBusHandlerApp',
+ 'cid': '{6c3c274b-4cbf-4bb5-a635-05ad2cbb6535}',
+ 'contract_ids': ['@mozilla.org/uriloader/dbus-handler-app;1'],
+ 'type': 'nsDBusHandlerApp',
+ 'headers': ['/uriloader/exthandler/nsDBusHandlerApp.h'],
+ },
+ ]
+
+if buildconfig.substs['MOZ_WIDGET_TOOLKIT'] == 'android':
+ Classes += [
+ # Android has its own externel-helper-app-service, so we omit
+ # that here for nsExternalHelperAppService.
+ {
+ 'cid': '{a7f800e0-4306-11d4-98d0-001083010e9b}',
+ 'contract_ids': [
+ '@mozilla.org/mime;1',
+ '@mozilla.org/uriloader/external-protocol-service;1',
+ ],
+ 'type': 'nsExternalHelperAppService',
+ 'constructor': 'nsExternalHelperAppService::GetSingleton',
+ 'headers': ['nsExternalHelperAppService.h'],
+ 'init_method': 'Init',
+ 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+ },
+ ]
+else:
+ Classes += [
+ {
+ 'cid': '{a7f800e0-4306-11d4-98d0-001083010e9b}',
+ 'contract_ids': [
+ '@mozilla.org/mime;1',
+ '@mozilla.org/uriloader/external-helper-app-service;1',
+ '@mozilla.org/uriloader/external-protocol-service;1',
+ ],
+ 'type': 'nsExternalHelperAppService',
+ 'constructor': 'nsExternalHelperAppService::GetSingleton',
+ 'headers': ['nsExternalHelperAppService.h'],
+ 'init_method': 'Init',
+ 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+ },
+ ]
diff --git a/docshell/build/moz.build b/docshell/build/moz.build
new file mode 100644
index 0000000000..d9fd81848e
--- /dev/null
+++ b/docshell/build/moz.build
@@ -0,0 +1,25 @@
+# -*- Mode: python; 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/.
+
+EXPORTS += [
+ "nsDocShellCID.h",
+]
+
+SOURCES += [
+ "nsDocShellModule.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/docshell/shistory",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
diff --git a/docshell/build/nsDocShellCID.h b/docshell/build/nsDocShellCID.h
new file mode 100644
index 0000000000..9a6a90d87a
--- /dev/null
+++ b/docshell/build/nsDocShellCID.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDocShellCID_h__
+#define nsDocShellCID_h__
+
+/**
+ * A contract that can be used to get a service that provides
+ * meta-information about nsIWebNavigation objects' capabilities.
+ * @implements nsIWebNavigationInfo
+ */
+#define NS_WEBNAVIGATION_INFO_CONTRACTID "@mozilla.org/webnavigation-info;1"
+
+/**
+ * Contract ID to obtain the IHistory interface. This is a non-scriptable
+ * interface used to interact with history in an asynchronous manner.
+ */
+#define NS_IHISTORY_CONTRACTID "@mozilla.org/browser/history;1"
+
+/**
+ * An observer service topic that can be listened to to catch creation
+ * of content browsing areas (both toplevel ones and subframes). The
+ * subject of the notification will be the nsIWebNavigation being
+ * created. At this time the additional data wstring is not defined
+ * to be anything in particular.
+ */
+#define NS_WEBNAVIGATION_CREATE "webnavigation-create"
+
+/**
+ * An observer service topic that can be listened to to catch creation
+ * of chrome browsing areas (both toplevel ones and subframes). The
+ * subject of the notification will be the nsIWebNavigation being
+ * created. At this time the additional data wstring is not defined
+ * to be anything in particular.
+ */
+#define NS_CHROME_WEBNAVIGATION_CREATE "chrome-webnavigation-create"
+
+/**
+ * An observer service topic that can be listened to to catch destruction
+ * of content browsing areas (both toplevel ones and subframes). The
+ * subject of the notification will be the nsIWebNavigation being
+ * destroyed. At this time the additional data wstring is not defined
+ * to be anything in particular.
+ */
+#define NS_WEBNAVIGATION_DESTROY "webnavigation-destroy"
+
+/**
+ * An observer service topic that can be listened to to catch destruction
+ * of chrome browsing areas (both toplevel ones and subframes). The
+ * subject of the notification will be the nsIWebNavigation being
+ * destroyed. At this time the additional data wstring is not defined
+ * to be anything in particular.
+ */
+#define NS_CHROME_WEBNAVIGATION_DESTROY "chrome-webnavigation-destroy"
+
+#endif // nsDocShellCID_h__
diff --git a/docshell/build/nsDocShellModule.cpp b/docshell/build/nsDocShellModule.cpp
new file mode 100644
index 0000000000..8602497a57
--- /dev/null
+++ b/docshell/build/nsDocShellModule.cpp
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/BrowsingContextGroup.h"
+
+// session history
+#include "nsSHEntryShared.h"
+#include "nsSHistory.h"
+
+namespace mozilla {
+
+// The one time initialization for this module
+nsresult InitDocShellModule() {
+ mozilla::dom::BrowsingContext::Init();
+
+ return NS_OK;
+}
+
+void UnloadDocShellModule() { nsSHistory::Shutdown(); }
+
+} // namespace mozilla
diff --git a/docshell/build/nsDocShellModule.h b/docshell/build/nsDocShellModule.h
new file mode 100644
index 0000000000..c64f3ad8a9
--- /dev/null
+++ b/docshell/build/nsDocShellModule.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDocShellModule_h
+#define nsDocShellModule_h
+
+#include "nscore.h"
+
+namespace mozilla {
+
+nsresult InitDocShellModule();
+
+void UnloadDocShellModule();
+
+} // namespace mozilla
+
+#endif
diff --git a/docshell/moz.build b/docshell/moz.build
new file mode 100644
index 0000000000..b60e0f66ad
--- /dev/null
+++ b/docshell/moz.build
@@ -0,0 +1,48 @@
+# -*- Mode: python; 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DOM: Navigation")
+
+if CONFIG["MOZ_BUILD_APP"] == "browser":
+ DEFINES["MOZ_BUILD_APP_IS_BROWSER"] = True
+
+DIRS += [
+ "base",
+ "shistory",
+ "build",
+]
+
+XPCSHELL_TESTS_MANIFESTS += [
+ "test/unit/xpcshell.ini",
+ "test/unit_ipc/xpcshell.ini",
+]
+
+MOCHITEST_MANIFESTS += [
+ "test/iframesandbox/mochitest.ini",
+ "test/mochitest/mochitest.ini",
+ "test/navigation/mochitest.ini",
+]
+
+MOCHITEST_CHROME_MANIFESTS += [
+ "test/chrome/chrome.ini",
+]
+
+BROWSER_CHROME_MANIFESTS += [
+ "test/browser/browser.ini",
+ "test/navigation/browser.ini",
+]
+
+TEST_HARNESS_FILES.testing.mochitest.tests.docshell.test.chrome += [
+ "test/chrome/215405_nocache.html",
+ "test/chrome/215405_nocache.html^headers^",
+ "test/chrome/215405_nostore.html",
+ "test/chrome/215405_nostore.html^headers^",
+ "test/chrome/allowContentRetargeting.sjs",
+ "test/chrome/blue.png",
+ "test/chrome/bug89419.sjs",
+ "test/chrome/red.png",
+]
diff --git a/docshell/shistory/ChildSHistory.cpp b/docshell/shistory/ChildSHistory.cpp
new file mode 100644
index 0000000000..87b6a7b3fc
--- /dev/null
+++ b/docshell/shistory/ChildSHistory.cpp
@@ -0,0 +1,294 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ChildSHistory.h"
+#include "mozilla/dom/ChildSHistoryBinding.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentFrameMessageManager.h"
+#include "nsIXULRuntime.h"
+#include "nsComponentManagerUtils.h"
+#include "nsSHEntry.h"
+#include "nsSHistory.h"
+#include "nsDocShell.h"
+#include "nsXULAppAPI.h"
+
+extern mozilla::LazyLogModule gSHLog;
+
+namespace mozilla {
+namespace dom {
+
+ChildSHistory::ChildSHistory(BrowsingContext* aBrowsingContext)
+ : mBrowsingContext(aBrowsingContext) {}
+
+ChildSHistory::~ChildSHistory() {
+ if (mHistory) {
+ static_cast<nsSHistory*>(mHistory.get())->SetBrowsingContext(nullptr);
+ }
+}
+
+void ChildSHistory::SetBrowsingContext(BrowsingContext* aBrowsingContext) {
+ mBrowsingContext = aBrowsingContext;
+}
+
+void ChildSHistory::SetIsInProcess(bool aIsInProcess) {
+ if (!aIsInProcess) {
+ MOZ_ASSERT_IF(mozilla::SessionHistoryInParent(), !mHistory);
+ if (!mozilla::SessionHistoryInParent()) {
+ RemovePendingHistoryNavigations();
+ if (mHistory) {
+ static_cast<nsSHistory*>(mHistory.get())->SetBrowsingContext(nullptr);
+ mHistory = nullptr;
+ }
+ }
+
+ return;
+ }
+
+ if (mHistory || mozilla::SessionHistoryInParent()) {
+ return;
+ }
+
+ mHistory = new nsSHistory(mBrowsingContext);
+}
+
+int32_t ChildSHistory::Count() {
+ if (mozilla::SessionHistoryInParent()) {
+ uint32_t length = mLength;
+ for (uint32_t i = 0; i < mPendingSHistoryChanges.Length(); ++i) {
+ length += mPendingSHistoryChanges[i].mLengthDelta;
+ }
+
+ return length;
+ }
+ return mHistory->GetCount();
+}
+
+int32_t ChildSHistory::Index() {
+ if (mozilla::SessionHistoryInParent()) {
+ uint32_t index = mIndex;
+ for (uint32_t i = 0; i < mPendingSHistoryChanges.Length(); ++i) {
+ index += mPendingSHistoryChanges[i].mIndexDelta;
+ }
+
+ return index;
+ }
+ int32_t index;
+ mHistory->GetIndex(&index);
+ return index;
+}
+
+nsID ChildSHistory::AddPendingHistoryChange() {
+ int32_t indexDelta = 1;
+ int32_t lengthDelta = (Index() + indexDelta) - (Count() - 1);
+ return AddPendingHistoryChange(indexDelta, lengthDelta);
+}
+
+nsID ChildSHistory::AddPendingHistoryChange(int32_t aIndexDelta,
+ int32_t aLengthDelta) {
+ nsID changeID = nsID::GenerateUUID();
+ PendingSHistoryChange change = {changeID, aIndexDelta, aLengthDelta};
+ mPendingSHistoryChanges.AppendElement(change);
+ return changeID;
+}
+
+void ChildSHistory::SetIndexAndLength(uint32_t aIndex, uint32_t aLength,
+ const nsID& aChangeID) {
+ mIndex = aIndex;
+ mLength = aLength;
+ mPendingSHistoryChanges.RemoveElementsBy(
+ [aChangeID](const PendingSHistoryChange& aChange) {
+ return aChange.mChangeID == aChangeID;
+ });
+}
+
+void ChildSHistory::Reload(uint32_t aReloadFlags, ErrorResult& aRv) {
+ if (mozilla::SessionHistoryInParent()) {
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsISHistory> shistory =
+ mBrowsingContext->Canonical()->GetSessionHistory();
+ if (shistory) {
+ aRv = shistory->Reload(aReloadFlags);
+ }
+ } else {
+ ContentChild::GetSingleton()->SendHistoryReload(mBrowsingContext,
+ aReloadFlags);
+ }
+
+ return;
+ }
+ nsCOMPtr<nsISHistory> shistory = mHistory;
+ aRv = shistory->Reload(aReloadFlags);
+}
+
+bool ChildSHistory::CanGo(int32_t aOffset) {
+ CheckedInt<int32_t> index = Index();
+ index += aOffset;
+ if (!index.isValid()) {
+ return false;
+ }
+ return index.value() < Count() && index.value() >= 0;
+}
+
+void ChildSHistory::Go(int32_t aOffset, bool aRequireUserInteraction,
+ bool aUserActivation, ErrorResult& aRv) {
+ CheckedInt<int32_t> index = Index();
+ MOZ_LOG(
+ gSHLog, LogLevel::Debug,
+ ("ChildSHistory::Go(%d), current index = %d", aOffset, index.value()));
+ if (aRequireUserInteraction && aOffset != -1 && aOffset != 1) {
+ NS_ERROR(
+ "aRequireUserInteraction may only be used with an offset of -1 or 1");
+ aRv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ while (true) {
+ index += aOffset;
+ if (!index.isValid()) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ // Check for user interaction if desired, except for the first and last
+ // history entries. We compare with >= to account for the case where
+ // aOffset >= Count().
+ if (!aRequireUserInteraction || index.value() >= Count() - 1 ||
+ index.value() <= 0) {
+ break;
+ }
+ if (mHistory && mHistory->HasUserInteractionAtIndex(index.value())) {
+ break;
+ }
+ }
+
+ GotoIndex(index.value(), aOffset, aRequireUserInteraction, aUserActivation,
+ aRv);
+}
+
+void ChildSHistory::AsyncGo(int32_t aOffset, bool aRequireUserInteraction,
+ bool aUserActivation, CallerType aCallerType,
+ ErrorResult& aRv) {
+ CheckedInt<int32_t> index = Index();
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("ChildSHistory::AsyncGo(%d), current index = %d", aOffset,
+ index.value()));
+ nsresult rv = mBrowsingContext->CheckLocationChangeRateLimit(aCallerType);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(gSHLog, LogLevel::Debug, ("Rejected"));
+ aRv.Throw(rv);
+ return;
+ }
+
+ RefPtr<PendingAsyncHistoryNavigation> asyncNav =
+ new PendingAsyncHistoryNavigation(this, aOffset, aRequireUserInteraction,
+ aUserActivation);
+ mPendingNavigations.insertBack(asyncNav);
+ NS_DispatchToCurrentThread(asyncNav.forget());
+}
+
+void ChildSHistory::GotoIndex(int32_t aIndex, int32_t aOffset,
+ bool aRequireUserInteraction,
+ bool aUserActivation, ErrorResult& aRv) {
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("ChildSHistory::GotoIndex(%d, %d), epoch %" PRIu64, aIndex, aOffset,
+ mHistoryEpoch));
+ if (mozilla::SessionHistoryInParent()) {
+ if (!mPendingEpoch) {
+ mPendingEpoch = true;
+ RefPtr<ChildSHistory> self(this);
+ NS_DispatchToCurrentThread(
+ NS_NewRunnableFunction("UpdateEpochRunnable", [self] {
+ self->mHistoryEpoch++;
+ self->mPendingEpoch = false;
+ }));
+ }
+
+ nsCOMPtr<nsISHistory> shistory = mHistory;
+ RefPtr<BrowsingContext> bc = mBrowsingContext;
+ bc->HistoryGo(
+ aOffset, mHistoryEpoch, aRequireUserInteraction, aUserActivation,
+ [shistory](Maybe<int32_t>&& aRequestedIndex) {
+ // FIXME Should probably only do this for non-fission.
+ if (aRequestedIndex.isSome() && shistory) {
+ shistory->InternalSetRequestedIndex(aRequestedIndex.value());
+ }
+ });
+ } else {
+ nsCOMPtr<nsISHistory> shistory = mHistory;
+ aRv = shistory->GotoIndex(aIndex, aUserActivation);
+ }
+}
+
+void ChildSHistory::RemovePendingHistoryNavigations() {
+ // Per the spec, this generally shouldn't remove all navigations - it
+ // depends if they're in the same document family or not. We don't do
+ // that. Also with SessionHistoryInParent, this can only abort AsyncGo's
+ // that have not yet been sent to the parent - see discussion of point
+ // 2.2 in comments in nsDocShell::UpdateURLAndHistory()
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("ChildSHistory::RemovePendingHistoryNavigations: %zu",
+ mPendingNavigations.length()));
+ mPendingNavigations.clear();
+}
+
+void ChildSHistory::EvictLocalContentViewers() {
+ if (!mozilla::SessionHistoryInParent()) {
+ mHistory->EvictAllContentViewers();
+ }
+}
+
+nsISHistory* ChildSHistory::GetLegacySHistory(ErrorResult& aError) {
+ if (mozilla::SessionHistoryInParent()) {
+ aError.ThrowTypeError(
+ "legacySHistory is not available with session history in the parent.");
+ return nullptr;
+ }
+
+ MOZ_RELEASE_ASSERT(mHistory);
+ return mHistory;
+}
+
+nsISHistory* ChildSHistory::LegacySHistory() {
+ IgnoredErrorResult ignore;
+ nsISHistory* shistory = GetLegacySHistory(ignore);
+ MOZ_RELEASE_ASSERT(shistory);
+ return shistory;
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ChildSHistory)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ChildSHistory)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ChildSHistory)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ChildSHistory)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ChildSHistory)
+ if (tmp->mHistory) {
+ static_cast<nsSHistory*>(tmp->mHistory.get())->SetBrowsingContext(nullptr);
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowsingContext, mHistory)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ChildSHistory)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContext, mHistory)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+JSObject* ChildSHistory::WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return ChildSHistory_Binding::Wrap(cx, this, aGivenProto);
+}
+
+nsISupports* ChildSHistory::GetParentObject() const {
+ return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/docshell/shistory/ChildSHistory.h b/docshell/shistory/ChildSHistory.h
new file mode 100644
index 0000000000..031c91c7da
--- /dev/null
+++ b/docshell/shistory/ChildSHistory.h
@@ -0,0 +1,149 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * ChildSHistory represents a view of session history from a child process. It
+ * exposes getters for some cached history state, and mutators which are
+ * implemented by communicating with the actual history storage.
+ *
+ * NOTE: Currently session history is in transition, meaning that we're still
+ * using the legacy nsSHistory class internally. The API exposed from this class
+ * should be only the API which we expect to expose when this transition is
+ * complete, and special cases will need to call through the LegacySHistory()
+ * getters.
+ */
+
+#ifndef mozilla_dom_ChildSHistory_h
+#define mozilla_dom_ChildSHistory_h
+
+#include "nsCOMPtr.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsWrapperCache.h"
+#include "nsThreadUtils.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/LinkedList.h"
+#include "nsID.h"
+
+class nsISHEntry;
+class nsISHistory;
+
+namespace mozilla::dom {
+
+class BrowsingContext;
+
+class ChildSHistory : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(ChildSHistory)
+ nsISupports* GetParentObject() const;
+ JSObject* WrapObject(JSContext* cx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ explicit ChildSHistory(BrowsingContext* aBrowsingContext);
+
+ void SetBrowsingContext(BrowsingContext* aBrowsingContext);
+
+ // Create or destroy the session history implementation in the child process.
+ // This can be removed once session history is stored exclusively in the
+ // parent process.
+ void SetIsInProcess(bool aIsInProcess);
+ bool IsInProcess() { return !!mHistory; }
+
+ int32_t Count();
+ int32_t Index();
+
+ /**
+ * Reload the current entry in the session history.
+ */
+ void Reload(uint32_t aReloadFlags, ErrorResult& aRv);
+
+ /**
+ * The CanGo and Go methods are called with an offset from the current index.
+ * Positive numbers go forward in history, while negative numbers go
+ * backwards.
+ */
+ bool CanGo(int32_t aOffset);
+ void Go(int32_t aOffset, bool aRequireUserInteraction, bool aUserActivation,
+ ErrorResult& aRv);
+ void AsyncGo(int32_t aOffset, bool aRequireUserInteraction,
+ bool aUserActivation, CallerType aCallerType, ErrorResult& aRv);
+
+ // aIndex is the new index, and aOffset is the offset between new and current.
+ void GotoIndex(int32_t aIndex, int32_t aOffset, bool aRequireUserInteraction,
+ bool aUserActivation, ErrorResult& aRv);
+
+ void RemovePendingHistoryNavigations();
+
+ /**
+ * Evicts all content viewers within the current process.
+ */
+ void EvictLocalContentViewers();
+
+ // GetLegacySHistory and LegacySHistory have been deprecated. Don't
+ // use these, but instead handle the interaction with nsISHistory in
+ // the parent process.
+ nsISHistory* GetLegacySHistory(ErrorResult& aError);
+ nsISHistory* LegacySHistory();
+
+ void SetIndexAndLength(uint32_t aIndex, uint32_t aLength,
+ const nsID& aChangeId);
+ nsID AddPendingHistoryChange();
+ nsID AddPendingHistoryChange(int32_t aIndexDelta, int32_t aLengthDelta);
+
+ private:
+ virtual ~ChildSHistory();
+
+ class PendingAsyncHistoryNavigation
+ : public Runnable,
+ public mozilla::LinkedListElement<PendingAsyncHistoryNavigation> {
+ public:
+ PendingAsyncHistoryNavigation(ChildSHistory* aHistory, int32_t aOffset,
+ bool aRequireUserInteraction,
+ bool aUserActivation)
+ : Runnable("PendingAsyncHistoryNavigation"),
+ mHistory(aHistory),
+ mRequireUserInteraction(aRequireUserInteraction),
+ mUserActivation(aUserActivation),
+ mOffset(aOffset) {}
+
+ NS_IMETHOD Run() override {
+ if (isInList()) {
+ remove();
+ mHistory->Go(mOffset, mRequireUserInteraction, mUserActivation,
+ IgnoreErrors());
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<ChildSHistory> mHistory;
+ bool mRequireUserInteraction;
+ bool mUserActivation;
+ int32_t mOffset;
+ };
+
+ RefPtr<BrowsingContext> mBrowsingContext;
+ nsCOMPtr<nsISHistory> mHistory;
+ // Can be removed once history-in-parent is the only way
+ mozilla::LinkedList<PendingAsyncHistoryNavigation> mPendingNavigations;
+ int32_t mIndex = -1;
+ int32_t mLength = 0;
+
+ struct PendingSHistoryChange {
+ nsID mChangeID;
+ int32_t mIndexDelta;
+ int32_t mLengthDelta;
+ };
+ AutoTArray<PendingSHistoryChange, 2> mPendingSHistoryChanges;
+
+ // Needs to start 1 above default epoch in parent
+ uint64_t mHistoryEpoch = 1;
+ bool mPendingEpoch = false;
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_ChildSHistory_h */
diff --git a/docshell/shistory/SessionHistoryEntry.cpp b/docshell/shistory/SessionHistoryEntry.cpp
new file mode 100644
index 0000000000..67829e630e
--- /dev/null
+++ b/docshell/shistory/SessionHistoryEntry.cpp
@@ -0,0 +1,1828 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SessionHistoryEntry.h"
+#include "ipc/IPCMessageUtilsSpecializations.h"
+#include "nsDocShell.h"
+#include "nsDocShellLoadState.h"
+#include "nsFrameLoader.h"
+#include "nsIFormPOSTActionChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsIUploadChannel2.h"
+#include "nsIXULRuntime.h"
+#include "nsSHEntryShared.h"
+#include "nsSHistory.h"
+#include "nsStreamUtils.h"
+#include "nsStructuredCloneContainer.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/PresState.h"
+#include "mozilla/StaticPrefs_fission.h"
+
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/CSPMessageUtils.h"
+#include "mozilla/dom/DocumentBinding.h"
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/dom/nsCSPContext.h"
+#include "mozilla/dom/PermissionMessageUtils.h"
+#include "mozilla/dom/ReferrerInfoUtils.h"
+#include "mozilla/ipc/IPDLParamTraits.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+
+extern mozilla::LazyLogModule gSHLog;
+
+namespace mozilla {
+namespace dom {
+
+SessionHistoryInfo::SessionHistoryInfo(nsDocShellLoadState* aLoadState,
+ nsIChannel* aChannel)
+ : mURI(aLoadState->URI()),
+ mOriginalURI(aLoadState->OriginalURI()),
+ mResultPrincipalURI(aLoadState->ResultPrincipalURI()),
+ mUnstrippedURI(aLoadState->GetUnstrippedURI()),
+ mLoadType(aLoadState->LoadType()),
+ mSrcdocData(aLoadState->SrcdocData().IsVoid()
+ ? Nothing()
+ : Some(aLoadState->SrcdocData())),
+ mBaseURI(aLoadState->BaseURI()),
+ mLoadReplace(aLoadState->LoadReplace()),
+ mHasUserInteraction(false),
+ mHasUserActivation(aLoadState->HasValidUserGestureActivation()),
+ mSharedState(SharedState::Create(
+ aLoadState->TriggeringPrincipal(), aLoadState->PrincipalToInherit(),
+ aLoadState->PartitionedPrincipalToInherit(), aLoadState->Csp(),
+ /* FIXME Is this correct? */
+ aLoadState->TypeHint())) {
+ // Pull the upload stream off of the channel instead of the load state, as
+ // ownership has already been transferred from the load state to the channel.
+ if (nsCOMPtr<nsIUploadChannel2> postChannel = do_QueryInterface(aChannel)) {
+ int64_t contentLength;
+ MOZ_ALWAYS_SUCCEEDS(postChannel->CloneUploadStream(
+ &contentLength, getter_AddRefs(mPostData)));
+ MOZ_ASSERT_IF(mPostData, NS_InputStreamIsCloneable(mPostData));
+ }
+
+ if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel)) {
+ mReferrerInfo = httpChannel->GetReferrerInfo();
+ }
+
+ MaybeUpdateTitleFromURI();
+}
+
+SessionHistoryInfo::SessionHistoryInfo(
+ const SessionHistoryInfo& aSharedStateFrom, nsIURI* aURI)
+ : mURI(aURI), mSharedState(aSharedStateFrom.mSharedState) {
+ MaybeUpdateTitleFromURI();
+}
+
+SessionHistoryInfo::SessionHistoryInfo(
+ nsIURI* aURI, nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp, const nsACString& aContentType)
+ : mURI(aURI),
+ mSharedState(SharedState::Create(
+ aTriggeringPrincipal, aPrincipalToInherit,
+ aPartitionedPrincipalToInherit, aCsp, aContentType)) {
+ MaybeUpdateTitleFromURI();
+}
+
+SessionHistoryInfo::SessionHistoryInfo(
+ nsIChannel* aChannel, uint32_t aLoadType,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp) {
+ aChannel->GetURI(getter_AddRefs(mURI));
+ mLoadType = aLoadType;
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ aChannel->GetLoadInfo(getter_AddRefs(loadInfo));
+
+ loadInfo->GetResultPrincipalURI(getter_AddRefs(mResultPrincipalURI));
+ loadInfo->GetUnstrippedURI(getter_AddRefs(mUnstrippedURI));
+ loadInfo->GetTriggeringPrincipal(
+ getter_AddRefs(mSharedState.Get()->mTriggeringPrincipal));
+ loadInfo->GetPrincipalToInherit(
+ getter_AddRefs(mSharedState.Get()->mPrincipalToInherit));
+
+ mSharedState.Get()->mPartitionedPrincipalToInherit =
+ aPartitionedPrincipalToInherit;
+ mSharedState.Get()->mCsp = aCsp;
+ aChannel->GetContentType(mSharedState.Get()->mContentType);
+ aChannel->GetOriginalURI(getter_AddRefs(mOriginalURI));
+
+ uint32_t loadFlags;
+ aChannel->GetLoadFlags(&loadFlags);
+ mLoadReplace = !!(loadFlags & nsIChannel::LOAD_REPLACE);
+
+ MaybeUpdateTitleFromURI();
+
+ if (nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel)) {
+ mReferrerInfo = httpChannel->GetReferrerInfo();
+ }
+}
+
+void SessionHistoryInfo::Reset(nsIURI* aURI, const nsID& aDocShellID,
+ bool aDynamicCreation,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp,
+ const nsACString& aContentType) {
+ mURI = aURI;
+ mOriginalURI = nullptr;
+ mResultPrincipalURI = nullptr;
+ mUnstrippedURI = nullptr;
+ mReferrerInfo = nullptr;
+ // Default title is the URL.
+ nsAutoCString spec;
+ if (NS_SUCCEEDED(mURI->GetSpec(spec))) {
+ CopyUTF8toUTF16(spec, mTitle);
+ }
+ mPostData = nullptr;
+ mLoadType = 0;
+ mScrollPositionX = 0;
+ mScrollPositionY = 0;
+ mStateData = nullptr;
+ mSrcdocData = Nothing();
+ mBaseURI = nullptr;
+ mLoadReplace = false;
+ mURIWasModified = false;
+ mScrollRestorationIsManual = false;
+ mPersist = false;
+ mHasUserInteraction = false;
+ mHasUserActivation = false;
+
+ mSharedState.Get()->mTriggeringPrincipal = aTriggeringPrincipal;
+ mSharedState.Get()->mPrincipalToInherit = aPrincipalToInherit;
+ mSharedState.Get()->mPartitionedPrincipalToInherit =
+ aPartitionedPrincipalToInherit;
+ mSharedState.Get()->mCsp = aCsp;
+ mSharedState.Get()->mContentType = aContentType;
+ mSharedState.Get()->mLayoutHistoryState = nullptr;
+}
+
+void SessionHistoryInfo::MaybeUpdateTitleFromURI() {
+ if (mTitle.IsEmpty() && mURI) {
+ // Default title is the URL.
+ nsAutoCString spec;
+ if (NS_SUCCEEDED(mURI->GetSpec(spec))) {
+ AppendUTF8toUTF16(spec, mTitle);
+ }
+ }
+}
+
+already_AddRefed<nsIInputStream> SessionHistoryInfo::GetPostData() const {
+ // Return a clone of our post data stream. Our caller will either be
+ // transferring this stream to a different SessionHistoryInfo, or passing it
+ // off to necko/another process which will consume it, and we want to preserve
+ // our local instance.
+ nsCOMPtr<nsIInputStream> postData;
+ if (mPostData) {
+ MOZ_ALWAYS_SUCCEEDS(
+ NS_CloneInputStream(mPostData, getter_AddRefs(postData)));
+ }
+ return postData.forget();
+}
+
+void SessionHistoryInfo::SetPostData(nsIInputStream* aPostData) {
+ MOZ_ASSERT_IF(aPostData, NS_InputStreamIsCloneable(aPostData));
+ mPostData = aPostData;
+}
+
+uint64_t SessionHistoryInfo::SharedId() const {
+ return mSharedState.Get()->mId;
+}
+
+nsILayoutHistoryState* SessionHistoryInfo::GetLayoutHistoryState() {
+ return mSharedState.Get()->mLayoutHistoryState;
+}
+
+void SessionHistoryInfo::SetLayoutHistoryState(nsILayoutHistoryState* aState) {
+ mSharedState.Get()->mLayoutHistoryState = aState;
+ if (mSharedState.Get()->mLayoutHistoryState) {
+ mSharedState.Get()->mLayoutHistoryState->SetScrollPositionOnly(
+ !mSharedState.Get()->mSaveLayoutState);
+ }
+}
+
+nsIPrincipal* SessionHistoryInfo::GetTriggeringPrincipal() const {
+ return mSharedState.Get()->mTriggeringPrincipal;
+}
+
+nsIPrincipal* SessionHistoryInfo::GetPrincipalToInherit() const {
+ return mSharedState.Get()->mPrincipalToInherit;
+}
+
+nsIPrincipal* SessionHistoryInfo::GetPartitionedPrincipalToInherit() const {
+ return mSharedState.Get()->mPartitionedPrincipalToInherit;
+}
+
+nsIContentSecurityPolicy* SessionHistoryInfo::GetCsp() const {
+ return mSharedState.Get()->mCsp;
+}
+
+uint32_t SessionHistoryInfo::GetCacheKey() const {
+ return mSharedState.Get()->mCacheKey;
+}
+
+void SessionHistoryInfo::SetCacheKey(uint32_t aCacheKey) {
+ mSharedState.Get()->mCacheKey = aCacheKey;
+}
+
+bool SessionHistoryInfo::IsSubFrame() const {
+ return mSharedState.Get()->mIsFrameNavigation;
+}
+
+void SessionHistoryInfo::SetSaveLayoutStateFlag(bool aSaveLayoutStateFlag) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ static_cast<SHEntrySharedParentState*>(mSharedState.Get())->mSaveLayoutState =
+ aSaveLayoutStateFlag;
+}
+
+void SessionHistoryInfo::FillLoadInfo(nsDocShellLoadState& aLoadState) const {
+ aLoadState.SetOriginalURI(mOriginalURI);
+ aLoadState.SetMaybeResultPrincipalURI(Some(mResultPrincipalURI));
+ aLoadState.SetUnstrippedURI(mUnstrippedURI);
+ aLoadState.SetLoadReplace(mLoadReplace);
+ nsCOMPtr<nsIInputStream> postData = GetPostData();
+ aLoadState.SetPostDataStream(postData);
+ aLoadState.SetReferrerInfo(mReferrerInfo);
+
+ aLoadState.SetTypeHint(mSharedState.Get()->mContentType);
+ aLoadState.SetTriggeringPrincipal(mSharedState.Get()->mTriggeringPrincipal);
+ aLoadState.SetPrincipalToInherit(mSharedState.Get()->mPrincipalToInherit);
+ aLoadState.SetPartitionedPrincipalToInherit(
+ mSharedState.Get()->mPartitionedPrincipalToInherit);
+ aLoadState.SetCsp(mSharedState.Get()->mCsp);
+
+ // Do not inherit principal from document (security-critical!);
+ uint32_t flags = nsDocShell::InternalLoad::INTERNAL_LOAD_FLAGS_NONE;
+
+ // Passing nullptr as aSourceDocShell gives the same behaviour as before
+ // aSourceDocShell was introduced. According to spec we should be passing
+ // the source browsing context that was used when the history entry was
+ // first created. bug 947716 has been created to address this issue.
+ nsAutoString srcdoc;
+ nsCOMPtr<nsIURI> baseURI;
+ if (mSrcdocData) {
+ srcdoc = mSrcdocData.value();
+ baseURI = mBaseURI;
+ flags |= nsDocShell::InternalLoad::INTERNAL_LOAD_FLAGS_IS_SRCDOC;
+ } else {
+ srcdoc = VoidString();
+ }
+ aLoadState.SetSrcdocData(srcdoc);
+ aLoadState.SetBaseURI(baseURI);
+ aLoadState.SetInternalLoadFlags(flags);
+
+ aLoadState.SetFirstParty(true);
+
+ // When we create a load state from the history info we already know if
+ // https-first was able to upgrade the request from http to https. There is no
+ // point in re-retrying to upgrade.
+ aLoadState.SetIsExemptFromHTTPSOnlyMode(true);
+}
+/* static */
+SessionHistoryInfo::SharedState SessionHistoryInfo::SharedState::Create(
+ nsIPrincipal* aTriggeringPrincipal, nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp, const nsACString& aContentType) {
+ if (XRE_IsParentProcess()) {
+ return SharedState(new SHEntrySharedParentState(
+ aTriggeringPrincipal, aPrincipalToInherit,
+ aPartitionedPrincipalToInherit, aCsp, aContentType));
+ }
+
+ return SharedState(MakeUnique<SHEntrySharedState>(
+ aTriggeringPrincipal, aPrincipalToInherit, aPartitionedPrincipalToInherit,
+ aCsp, aContentType));
+}
+
+SessionHistoryInfo::SharedState::SharedState() { Init(); }
+
+SessionHistoryInfo::SharedState::SharedState(
+ const SessionHistoryInfo::SharedState& aOther) {
+ Init(aOther);
+}
+
+SessionHistoryInfo::SharedState::SharedState(
+ const Maybe<const SessionHistoryInfo::SharedState&>& aOther) {
+ if (aOther.isSome()) {
+ Init(aOther.ref());
+ } else {
+ Init();
+ }
+}
+
+SessionHistoryInfo::SharedState::~SharedState() {
+ if (XRE_IsParentProcess()) {
+ mParent
+ .RefPtr<SHEntrySharedParentState>::~RefPtr<SHEntrySharedParentState>();
+ } else {
+ mChild.UniquePtr<SHEntrySharedState>::~UniquePtr<SHEntrySharedState>();
+ }
+}
+
+SessionHistoryInfo::SharedState& SessionHistoryInfo::SharedState::operator=(
+ const SessionHistoryInfo::SharedState& aOther) {
+ if (this != &aOther) {
+ if (XRE_IsParentProcess()) {
+ mParent = aOther.mParent;
+ } else {
+ mChild = MakeUnique<SHEntrySharedState>(*aOther.mChild);
+ }
+ }
+ return *this;
+}
+
+SHEntrySharedState* SessionHistoryInfo::SharedState::Get() const {
+ if (XRE_IsParentProcess()) {
+ return mParent;
+ }
+
+ return mChild.get();
+}
+
+void SessionHistoryInfo::SharedState::ChangeId(uint64_t aId) {
+ if (XRE_IsParentProcess()) {
+ mParent->ChangeId(aId);
+ } else {
+ mChild->mId = aId;
+ }
+}
+
+void SessionHistoryInfo::SharedState::Init() {
+ if (XRE_IsParentProcess()) {
+ new (&mParent)
+ RefPtr<SHEntrySharedParentState>(new SHEntrySharedParentState());
+ } else {
+ new (&mChild)
+ UniquePtr<SHEntrySharedState>(MakeUnique<SHEntrySharedState>());
+ }
+}
+
+void SessionHistoryInfo::SharedState::Init(
+ const SessionHistoryInfo::SharedState& aOther) {
+ if (XRE_IsParentProcess()) {
+ new (&mParent) RefPtr<SHEntrySharedParentState>(aOther.mParent);
+ } else {
+ new (&mChild) UniquePtr<SHEntrySharedState>(
+ MakeUnique<SHEntrySharedState>(*aOther.mChild));
+ }
+}
+
+static uint64_t gLoadingSessionHistoryInfoLoadId = 0;
+
+nsTHashMap<nsUint64HashKey, SessionHistoryEntry::LoadingEntry>*
+ SessionHistoryEntry::sLoadIdToEntry = nullptr;
+
+LoadingSessionHistoryInfo::LoadingSessionHistoryInfo(
+ SessionHistoryEntry* aEntry)
+ : mInfo(aEntry->Info()), mLoadId(++gLoadingSessionHistoryInfoLoadId) {
+ SessionHistoryEntry::SetByLoadId(mLoadId, aEntry);
+}
+
+LoadingSessionHistoryInfo::LoadingSessionHistoryInfo(
+ SessionHistoryEntry* aEntry, const LoadingSessionHistoryInfo* aInfo)
+ : mInfo(aEntry->Info()),
+ mLoadId(aInfo->mLoadId),
+ mLoadIsFromSessionHistory(aInfo->mLoadIsFromSessionHistory),
+ mOffset(aInfo->mOffset),
+ mLoadingCurrentEntry(aInfo->mLoadingCurrentEntry) {
+ MOZ_ASSERT(SessionHistoryEntry::GetByLoadId(mLoadId)->mEntry == aEntry);
+}
+
+LoadingSessionHistoryInfo::LoadingSessionHistoryInfo(
+ const SessionHistoryInfo& aInfo)
+ : mInfo(aInfo), mLoadId(UINT64_MAX) {}
+
+already_AddRefed<nsDocShellLoadState>
+LoadingSessionHistoryInfo::CreateLoadInfo() const {
+ RefPtr<nsDocShellLoadState> loadState(
+ new nsDocShellLoadState(mInfo.GetURI()));
+
+ mInfo.FillLoadInfo(*loadState);
+
+ loadState->SetLoadingSessionHistoryInfo(*this);
+
+ return loadState.forget();
+}
+
+static uint32_t gEntryID;
+
+SessionHistoryEntry::LoadingEntry* SessionHistoryEntry::GetByLoadId(
+ uint64_t aLoadId) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (!sLoadIdToEntry) {
+ return nullptr;
+ }
+
+ return sLoadIdToEntry->Lookup(aLoadId).DataPtrOrNull();
+}
+
+void SessionHistoryEntry::SetByLoadId(uint64_t aLoadId,
+ SessionHistoryEntry* aEntry) {
+ if (!sLoadIdToEntry) {
+ sLoadIdToEntry = new nsTHashMap<nsUint64HashKey, LoadingEntry>();
+ }
+
+ MOZ_LOG(
+ gSHLog, LogLevel::Verbose,
+ ("SessionHistoryEntry::SetByLoadId(%" PRIu64 " - %p)", aLoadId, aEntry));
+ sLoadIdToEntry->InsertOrUpdate(
+ aLoadId, LoadingEntry{
+ .mEntry = aEntry,
+ .mInfoSnapshotForValidation =
+ MakeUnique<SessionHistoryInfo>(aEntry->Info()),
+ });
+}
+
+void SessionHistoryEntry::RemoveLoadId(uint64_t aLoadId) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (!sLoadIdToEntry) {
+ return;
+ }
+
+ MOZ_LOG(gSHLog, LogLevel::Verbose,
+ ("SHEntry::RemoveLoadId(%" PRIu64 ")", aLoadId));
+ sLoadIdToEntry->Remove(aLoadId);
+}
+
+SessionHistoryEntry::SessionHistoryEntry()
+ : mInfo(new SessionHistoryInfo()), mID(++gEntryID) {
+ MOZ_ASSERT(mozilla::SessionHistoryInParent());
+}
+
+SessionHistoryEntry::SessionHistoryEntry(nsDocShellLoadState* aLoadState,
+ nsIChannel* aChannel)
+ : mInfo(new SessionHistoryInfo(aLoadState, aChannel)), mID(++gEntryID) {
+ MOZ_ASSERT(mozilla::SessionHistoryInParent());
+}
+
+SessionHistoryEntry::SessionHistoryEntry(SessionHistoryInfo* aInfo)
+ : mInfo(MakeUnique<SessionHistoryInfo>(*aInfo)), mID(++gEntryID) {
+ MOZ_ASSERT(mozilla::SessionHistoryInParent());
+}
+
+SessionHistoryEntry::SessionHistoryEntry(const SessionHistoryEntry& aEntry)
+ : mInfo(MakeUnique<SessionHistoryInfo>(*aEntry.mInfo)),
+ mParent(aEntry.mParent),
+ mID(aEntry.mID),
+ mBCHistoryLength(aEntry.mBCHistoryLength) {
+ MOZ_ASSERT(mozilla::SessionHistoryInParent());
+}
+
+SessionHistoryEntry::~SessionHistoryEntry() {
+ // Null out the mParent pointers on all our kids.
+ for (nsISHEntry* entry : mChildren) {
+ if (entry) {
+ entry->SetParent(nullptr);
+ }
+ }
+
+ if (sLoadIdToEntry) {
+ sLoadIdToEntry->RemoveIf(
+ [this](auto& aIter) { return aIter.Data().mEntry == this; });
+ if (sLoadIdToEntry->IsEmpty()) {
+ delete sLoadIdToEntry;
+ sLoadIdToEntry = nullptr;
+ }
+ }
+}
+
+NS_IMPL_ISUPPORTS(SessionHistoryEntry, nsISHEntry, SessionHistoryEntry,
+ nsISupportsWeakReference)
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetURI(nsIURI** aURI) {
+ nsCOMPtr<nsIURI> uri = mInfo->mURI;
+ uri.forget(aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetURI(nsIURI* aURI) {
+ mInfo->mURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetOriginalURI(nsIURI** aOriginalURI) {
+ nsCOMPtr<nsIURI> originalURI = mInfo->mOriginalURI;
+ originalURI.forget(aOriginalURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetOriginalURI(nsIURI* aOriginalURI) {
+ mInfo->mOriginalURI = aOriginalURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetResultPrincipalURI(nsIURI** aResultPrincipalURI) {
+ nsCOMPtr<nsIURI> resultPrincipalURI = mInfo->mResultPrincipalURI;
+ resultPrincipalURI.forget(aResultPrincipalURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetResultPrincipalURI(nsIURI* aResultPrincipalURI) {
+ mInfo->mResultPrincipalURI = aResultPrincipalURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetUnstrippedURI(nsIURI** aUnstrippedURI) {
+ nsCOMPtr<nsIURI> unstrippedURI = mInfo->mUnstrippedURI;
+ unstrippedURI.forget(aUnstrippedURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetUnstrippedURI(nsIURI* aUnstrippedURI) {
+ mInfo->mUnstrippedURI = aUnstrippedURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetLoadReplace(bool* aLoadReplace) {
+ *aLoadReplace = mInfo->mLoadReplace;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetLoadReplace(bool aLoadReplace) {
+ mInfo->mLoadReplace = aLoadReplace;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetTitle(nsAString& aTitle) {
+ aTitle = mInfo->mTitle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetTitle(const nsAString& aTitle) {
+ mInfo->SetTitle(aTitle);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetName(nsAString& aName) {
+ aName = mInfo->mName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetName(const nsAString& aName) {
+ mInfo->mName = aName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetIsSubFrame(bool* aIsSubFrame) {
+ *aIsSubFrame = SharedInfo()->mIsFrameNavigation;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetIsSubFrame(bool aIsSubFrame) {
+ SharedInfo()->mIsFrameNavigation = aIsSubFrame;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetHasUserInteraction(bool* aFlag) {
+ // The back button and menulist deal with root/top-level
+ // session history entries, thus we annotate only the root entry.
+ if (!mParent) {
+ *aFlag = mInfo->mHasUserInteraction;
+ } else {
+ nsCOMPtr<nsISHEntry> root = nsSHistory::GetRootSHEntry(this);
+ root->GetHasUserInteraction(aFlag);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetHasUserInteraction(bool aFlag) {
+ // The back button and menulist deal with root/top-level
+ // session history entries, thus we annotate only the root entry.
+ if (!mParent) {
+ mInfo->mHasUserInteraction = aFlag;
+ } else {
+ nsCOMPtr<nsISHEntry> root = nsSHistory::GetRootSHEntry(this);
+ root->SetHasUserInteraction(aFlag);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetHasUserActivation(bool* aFlag) {
+ *aFlag = mInfo->mHasUserActivation;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetHasUserActivation(bool aFlag) {
+ mInfo->mHasUserActivation = aFlag;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetReferrerInfo(nsIReferrerInfo** aReferrerInfo) {
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = mInfo->mReferrerInfo;
+ referrerInfo.forget(aReferrerInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
+ mInfo->mReferrerInfo = aReferrerInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetContentViewer(nsIContentViewer** aContentViewer) {
+ *aContentViewer = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetContentViewer(nsIContentViewer* aContentViewer) {
+ MOZ_CRASH("This lives in the child process");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetIsInBFCache(bool* aResult) {
+ *aResult = !!SharedInfo()->mFrameLoader;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetSticky(bool* aSticky) {
+ *aSticky = SharedInfo()->mSticky;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetSticky(bool aSticky) {
+ SharedInfo()->mSticky = aSticky;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetWindowState(nsISupports** aWindowState) {
+ MOZ_CRASH("This lives in the child process");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetWindowState(nsISupports* aWindowState) {
+ MOZ_CRASH("This lives in the child process");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetRefreshURIList(nsIMutableArray** aRefreshURIList) {
+ MOZ_CRASH("This lives in the child process");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetRefreshURIList(nsIMutableArray* aRefreshURIList) {
+ MOZ_CRASH("This lives in the child process");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetPostData(nsIInputStream** aPostData) {
+ *aPostData = mInfo->GetPostData().take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetPostData(nsIInputStream* aPostData) {
+ mInfo->SetPostData(aPostData);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetHasPostData(bool* aResult) {
+ *aResult = mInfo->HasPostData();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetLayoutHistoryState(
+ nsILayoutHistoryState** aLayoutHistoryState) {
+ nsCOMPtr<nsILayoutHistoryState> layoutHistoryState =
+ SharedInfo()->mLayoutHistoryState;
+ layoutHistoryState.forget(aLayoutHistoryState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetLayoutHistoryState(
+ nsILayoutHistoryState* aLayoutHistoryState) {
+ SharedInfo()->mLayoutHistoryState = aLayoutHistoryState;
+ if (SharedInfo()->mLayoutHistoryState) {
+ SharedInfo()->mLayoutHistoryState->SetScrollPositionOnly(
+ !SharedInfo()->mSaveLayoutState);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetParent(nsISHEntry** aParent) {
+ nsCOMPtr<nsISHEntry> parent = do_QueryReferent(mParent);
+ parent.forget(aParent);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetParent(nsISHEntry* aParent) {
+ mParent = do_GetWeakReference(aParent);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetLoadType(uint32_t* aLoadType) {
+ *aLoadType = mInfo->mLoadType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetLoadType(uint32_t aLoadType) {
+ mInfo->mLoadType = aLoadType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetID(uint32_t* aID) {
+ *aID = mID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetID(uint32_t aID) {
+ mID = aID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetCacheKey(uint32_t* aCacheKey) {
+ *aCacheKey = SharedInfo()->mCacheKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetCacheKey(uint32_t aCacheKey) {
+ SharedInfo()->mCacheKey = aCacheKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetSaveLayoutStateFlag(bool* aSaveLayoutStateFlag) {
+ *aSaveLayoutStateFlag = SharedInfo()->mSaveLayoutState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetSaveLayoutStateFlag(bool aSaveLayoutStateFlag) {
+ SharedInfo()->mSaveLayoutState = aSaveLayoutStateFlag;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetContentType(nsACString& aContentType) {
+ aContentType = SharedInfo()->mContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetContentType(const nsACString& aContentType) {
+ SharedInfo()->mContentType = aContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetURIWasModified(bool* aURIWasModified) {
+ *aURIWasModified = mInfo->mURIWasModified;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetURIWasModified(bool aURIWasModified) {
+ mInfo->mURIWasModified = aURIWasModified;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetTriggeringPrincipal(
+ nsIPrincipal** aTriggeringPrincipal) {
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal =
+ SharedInfo()->mTriggeringPrincipal;
+ triggeringPrincipal.forget(aTriggeringPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetTriggeringPrincipal(
+ nsIPrincipal* aTriggeringPrincipal) {
+ SharedInfo()->mTriggeringPrincipal = aTriggeringPrincipal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetPrincipalToInherit(nsIPrincipal** aPrincipalToInherit) {
+ nsCOMPtr<nsIPrincipal> principalToInherit = SharedInfo()->mPrincipalToInherit;
+ principalToInherit.forget(aPrincipalToInherit);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit) {
+ SharedInfo()->mPrincipalToInherit = aPrincipalToInherit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetPartitionedPrincipalToInherit(
+ nsIPrincipal** aPartitionedPrincipalToInherit) {
+ nsCOMPtr<nsIPrincipal> partitionedPrincipalToInherit =
+ SharedInfo()->mPartitionedPrincipalToInherit;
+ partitionedPrincipalToInherit.forget(aPartitionedPrincipalToInherit);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetPartitionedPrincipalToInherit(
+ nsIPrincipal* aPartitionedPrincipalToInherit) {
+ SharedInfo()->mPartitionedPrincipalToInherit = aPartitionedPrincipalToInherit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetCsp(nsIContentSecurityPolicy** aCsp) {
+ nsCOMPtr<nsIContentSecurityPolicy> csp = SharedInfo()->mCsp;
+ csp.forget(aCsp);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetCsp(nsIContentSecurityPolicy* aCsp) {
+ SharedInfo()->mCsp = aCsp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetStateData(nsIStructuredCloneContainer** aStateData) {
+ RefPtr<nsStructuredCloneContainer> stateData = mInfo->mStateData;
+ stateData.forget(aStateData);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetStateData(nsIStructuredCloneContainer* aStateData) {
+ mInfo->mStateData = static_cast<nsStructuredCloneContainer*>(aStateData);
+ return NS_OK;
+}
+
+const nsID& SessionHistoryEntry::DocshellID() const {
+ return SharedInfo()->mDocShellID;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetDocshellID(nsID& aDocshellID) {
+ aDocshellID = DocshellID();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetDocshellID(const nsID& aDocshellID) {
+ SharedInfo()->mDocShellID = aDocshellID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetIsSrcdocEntry(bool* aIsSrcdocEntry) {
+ *aIsSrcdocEntry = mInfo->mSrcdocData.isSome();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetSrcdocData(nsAString& aSrcdocData) {
+ aSrcdocData = mInfo->mSrcdocData.valueOr(EmptyString());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetSrcdocData(const nsAString& aSrcdocData) {
+ mInfo->mSrcdocData = Some(nsString(aSrcdocData));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetBaseURI(nsIURI** aBaseURI) {
+ nsCOMPtr<nsIURI> baseURI = mInfo->mBaseURI;
+ baseURI.forget(aBaseURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetBaseURI(nsIURI* aBaseURI) {
+ mInfo->mBaseURI = aBaseURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetScrollRestorationIsManual(
+ bool* aScrollRestorationIsManual) {
+ *aScrollRestorationIsManual = mInfo->mScrollRestorationIsManual;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetScrollRestorationIsManual(
+ bool aScrollRestorationIsManual) {
+ mInfo->mScrollRestorationIsManual = aScrollRestorationIsManual;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetLoadedInThisProcess(bool* aLoadedInThisProcess) {
+ // FIXME
+ //*aLoadedInThisProcess = mInfo->mLoadedInThisProcess;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetShistory(nsISHistory** aShistory) {
+ nsCOMPtr<nsISHistory> sHistory = do_QueryReferent(SharedInfo()->mSHistory);
+ sHistory.forget(aShistory);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetShistory(nsISHistory* aShistory) {
+ nsWeakPtr shistory = do_GetWeakReference(aShistory);
+ // mSHistory can not be changed once it's set
+ MOZ_ASSERT(!SharedInfo()->mSHistory || (SharedInfo()->mSHistory == shistory));
+ SharedInfo()->mSHistory = shistory;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetLastTouched(uint32_t* aLastTouched) {
+ *aLastTouched = SharedInfo()->mLastTouched;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetLastTouched(uint32_t aLastTouched) {
+ SharedInfo()->mLastTouched = aLastTouched;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetChildCount(int32_t* aChildCount) {
+ *aChildCount = mChildren.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetPersist(bool* aPersist) {
+ *aPersist = mInfo->mPersist;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetPersist(bool aPersist) {
+ mInfo->mPersist = aPersist;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetScrollPosition(int32_t* aX, int32_t* aY) {
+ *aX = mInfo->mScrollPositionX;
+ *aY = mInfo->mScrollPositionY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetScrollPosition(int32_t aX, int32_t aY) {
+ mInfo->mScrollPositionX = aX;
+ mInfo->mScrollPositionY = aY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+SessionHistoryEntry::GetViewerBounds(nsIntRect& bounds) {
+ bounds = SharedInfo()->mViewerBounds;
+}
+
+NS_IMETHODIMP_(void)
+SessionHistoryEntry::SetViewerBounds(const nsIntRect& bounds) {
+ SharedInfo()->mViewerBounds = bounds;
+}
+
+NS_IMETHODIMP_(void)
+SessionHistoryEntry::AddChildShell(nsIDocShellTreeItem* shell) {
+ MOZ_CRASH("This lives in the child process");
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::ChildShellAt(int32_t index,
+ nsIDocShellTreeItem** _retval) {
+ MOZ_CRASH("This lives in the child process");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP_(void)
+SessionHistoryEntry::ClearChildShells() {
+ MOZ_CRASH("This lives in the child process");
+}
+
+NS_IMETHODIMP_(void)
+SessionHistoryEntry::SyncPresentationState() {
+ MOZ_CRASH("This lives in the child process");
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::InitLayoutHistoryState(
+ nsILayoutHistoryState** aLayoutHistoryState) {
+ if (!SharedInfo()->mLayoutHistoryState) {
+ nsCOMPtr<nsILayoutHistoryState> historyState;
+ historyState = NS_NewLayoutHistoryState();
+ SetLayoutHistoryState(historyState);
+ }
+
+ return GetLayoutHistoryState(aLayoutHistoryState);
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::Create(
+ nsIURI* aURI, const nsAString& aTitle, nsIInputStream* aInputStream,
+ uint32_t aCacheKey, const nsACString& aContentType,
+ nsIPrincipal* aTriggeringPrincipal, nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp, const nsID& aDocshellID,
+ bool aDynamicCreation, nsIURI* aOriginalURI, nsIURI* aResultPrincipalURI,
+ nsIURI* aUnstrippedURI, bool aLoadReplace, nsIReferrerInfo* aReferrerInfo,
+ const nsAString& aSrcdoc, bool aSrcdocEntry, nsIURI* aBaseURI,
+ bool aSaveLayoutState, bool aExpired, bool aUserActivation) {
+ MOZ_CRASH("Might need to implement this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::Clone(nsISHEntry** aEntry) {
+ RefPtr<SessionHistoryEntry> entry = new SessionHistoryEntry(*this);
+
+ // These are not copied for some reason, we're not sure why.
+ entry->mInfo->mLoadType = 0;
+ entry->mInfo->mScrollPositionX = 0;
+ entry->mInfo->mScrollPositionY = 0;
+ entry->mInfo->mScrollRestorationIsManual = false;
+
+ entry->mInfo->mHasUserInteraction = false;
+
+ entry.forget(aEntry);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(nsDocShellEditorData*)
+SessionHistoryEntry::ForgetEditorData() {
+ MOZ_CRASH("This lives in the child process");
+ return nullptr;
+}
+
+NS_IMETHODIMP_(void)
+SessionHistoryEntry::SetEditorData(nsDocShellEditorData* aData) {
+ NS_WARNING("This lives in the child process");
+}
+
+NS_IMETHODIMP_(bool)
+SessionHistoryEntry::HasDetachedEditor() {
+ NS_WARNING("This lives in the child process");
+ return false;
+}
+
+NS_IMETHODIMP_(bool)
+SessionHistoryEntry::IsDynamicallyAdded() {
+ return SharedInfo()->mDynamicallyCreated;
+}
+
+void SessionHistoryEntry::SetWireframe(const Maybe<Wireframe>& aWireframe) {
+ mWireframe = aWireframe;
+}
+
+void SessionHistoryEntry::SetIsDynamicallyAdded(bool aDynamic) {
+ MOZ_ASSERT_IF(SharedInfo()->mDynamicallyCreated, aDynamic);
+ SharedInfo()->mDynamicallyCreated = aDynamic;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::HasDynamicallyAddedChild(bool* aHasDynamicallyAddedChild) {
+ for (const auto& child : mChildren) {
+ if (child && child->IsDynamicallyAdded()) {
+ *aHasDynamicallyAddedChild = true;
+ return NS_OK;
+ }
+ }
+ *aHasDynamicallyAddedChild = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(bool)
+SessionHistoryEntry::HasBFCacheEntry(SHEntrySharedParentState* aEntry) {
+ return SharedInfo() == aEntry;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::AdoptBFCacheEntry(nsISHEntry* aEntry) {
+ nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(aEntry);
+ NS_ENSURE_STATE(she && she->mInfo->mSharedState.Get());
+
+ mInfo->mSharedState =
+ static_cast<SessionHistoryEntry*>(aEntry)->mInfo->mSharedState;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::AbandonBFCacheEntry() {
+ MOZ_CRASH("This lives in the child process");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SharesDocumentWith(nsISHEntry* aEntry,
+ bool* aSharesDocumentWith) {
+ SessionHistoryEntry* entry = static_cast<SessionHistoryEntry*>(aEntry);
+
+ MOZ_ASSERT_IF(entry->SharedInfo() != SharedInfo(),
+ entry->SharedInfo()->GetId() != SharedInfo()->GetId());
+
+ *aSharesDocumentWith = entry->SharedInfo() == SharedInfo();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetLoadTypeAsHistory() {
+ mInfo->mLoadType = LOAD_HISTORY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::AddChild(nsISHEntry* aChild, int32_t aOffset,
+ bool aUseRemoteSubframes) {
+ nsCOMPtr<SessionHistoryEntry> child = do_QueryInterface(aChild);
+ MOZ_ASSERT_IF(aChild, child);
+ AddChild(child, aOffset, aUseRemoteSubframes);
+
+ return NS_OK;
+}
+
+void SessionHistoryEntry::AddChild(SessionHistoryEntry* aChild, int32_t aOffset,
+ bool aUseRemoteSubframes) {
+ if (aChild) {
+ aChild->SetParent(this);
+ }
+
+ if (aOffset < 0) {
+ mChildren.AppendElement(aChild);
+ return;
+ }
+
+ //
+ // Bug 52670: Ensure children are added in order.
+ //
+ // Later frames in the child list may load faster and get appended
+ // before earlier frames, causing session history to be scrambled.
+ // By growing the list here, they are added to the right position.
+
+ int32_t length = mChildren.Length();
+
+ // Assert that aOffset will not be so high as to grow us a lot.
+ NS_ASSERTION(aOffset < length + 1023, "Large frames array!\n");
+
+ // If the new child is dynamically added, try to add it to aOffset, but if
+ // there are non-dynamically added children, the child must be after those.
+ if (aChild && aChild->IsDynamicallyAdded()) {
+ int32_t lastNonDyn = aOffset - 1;
+ for (int32_t i = aOffset; i < length; ++i) {
+ SessionHistoryEntry* entry = mChildren[i];
+ if (entry) {
+ if (entry->IsDynamicallyAdded()) {
+ break;
+ }
+
+ lastNonDyn = i;
+ }
+ }
+
+ // If aOffset is larger than Length(), we must first truncate the array.
+ if (aOffset > length) {
+ mChildren.SetLength(aOffset);
+ }
+
+ mChildren.InsertElementAt(lastNonDyn + 1, aChild);
+
+ return;
+ }
+
+ // If the new child isn't dynamically added, it should be set to aOffset.
+ // If there are dynamically added children before that, those must be moved
+ // to be after aOffset.
+ if (length > 0) {
+ int32_t start = std::min(length - 1, aOffset);
+ int32_t dynEntryIndex = -1;
+ DebugOnly<SessionHistoryEntry*> dynEntry = nullptr;
+ for (int32_t i = start; i >= 0; --i) {
+ SessionHistoryEntry* entry = mChildren[i];
+ if (entry) {
+ if (!entry->IsDynamicallyAdded()) {
+ break;
+ }
+
+ dynEntryIndex = i;
+ dynEntry = entry;
+ }
+ }
+
+ if (dynEntryIndex >= 0) {
+ mChildren.InsertElementsAt(dynEntryIndex, aOffset - dynEntryIndex + 1);
+ NS_ASSERTION(mChildren[aOffset + 1] == dynEntry, "Whaat?");
+ }
+ }
+
+ // Make sure there isn't anything at aOffset.
+ if ((uint32_t)aOffset < mChildren.Length()) {
+ SessionHistoryEntry* oldChild = mChildren[aOffset];
+ if (oldChild && oldChild != aChild) {
+ // Under Fission, this can happen when a network-created iframe starts
+ // out in-process, moves out-of-process, and then switches back. At that
+ // point, we'll create a new network-created DocShell at the same index
+ // where we already have an entry for the original network-created
+ // DocShell.
+ //
+ // This should ideally stop being an issue once the Fission-aware
+ // session history rewrite is complete.
+ NS_ASSERTION(
+ aUseRemoteSubframes || NS_IsAboutBlank(oldChild->Info().GetURI()),
+ "Adding a child where we already have a child? This may misbehave");
+ oldChild->SetParent(nullptr);
+ }
+ } else {
+ mChildren.SetLength(aOffset + 1);
+ }
+
+ mChildren.ReplaceElementAt(aOffset, aChild);
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::RemoveChild(nsISHEntry* aChild) {
+ NS_ENSURE_TRUE(aChild, NS_ERROR_FAILURE);
+
+ nsCOMPtr<SessionHistoryEntry> child = do_QueryInterface(aChild);
+ MOZ_ASSERT(child);
+ RemoveChild(child);
+
+ return NS_OK;
+}
+
+void SessionHistoryEntry::RemoveChild(SessionHistoryEntry* aChild) {
+ bool childRemoved = false;
+ if (aChild->IsDynamicallyAdded()) {
+ childRemoved = mChildren.RemoveElement(aChild);
+ } else {
+ int32_t index = mChildren.IndexOf(aChild);
+ if (index >= 0) {
+ // Other alive non-dynamic child docshells still keep mChildOffset,
+ // so we don't want to change the indices here.
+ mChildren.ReplaceElementAt(index, nullptr);
+ childRemoved = true;
+ }
+ }
+
+ if (childRemoved) {
+ aChild->SetParent(nullptr);
+
+ // reduce the child count, i.e. remove empty children at the end
+ for (int32_t i = mChildren.Length() - 1; i >= 0 && !mChildren[i]; --i) {
+ mChildren.RemoveElementAt(i);
+ }
+ }
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetChildAt(int32_t aIndex, nsISHEntry** aChild) {
+ nsCOMPtr<nsISHEntry> child = mChildren.SafeElementAt(aIndex);
+ child.forget(aChild);
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+SessionHistoryEntry::GetChildSHEntryIfHasNoDynamicallyAddedChild(
+ int32_t aChildOffset, nsISHEntry** aChild) {
+ *aChild = nullptr;
+
+ bool dynamicallyAddedChild = false;
+ HasDynamicallyAddedChild(&dynamicallyAddedChild);
+ if (dynamicallyAddedChild) {
+ return;
+ }
+
+ // If the user did a shift-reload on this frameset page,
+ // we don't want to load the subframes from history.
+ if (IsForceReloadType(mInfo->mLoadType) || mInfo->mLoadType == LOAD_REFRESH) {
+ return;
+ }
+
+ /* Before looking for the subframe's url, check
+ * the expiration status of the parent. If the parent
+ * has expired from cache, then subframes will not be
+ * loaded from history in certain situations.
+ * If the user pressed reload and the parent frame has expired
+ * from cache, we do not want to load the child frame from history.
+ */
+ if (SharedInfo()->mExpired && (mInfo->mLoadType == LOAD_RELOAD_NORMAL)) {
+ // The parent has expired. Return null.
+ *aChild = nullptr;
+ return;
+ }
+ // Get the child subframe from session history.
+ GetChildAt(aChildOffset, aChild);
+ if (*aChild) {
+ // Set the parent's Load Type on the child
+ (*aChild)->SetLoadType(mInfo->mLoadType);
+ }
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::ReplaceChild(nsISHEntry* aNewChild) {
+ NS_ENSURE_STATE(aNewChild);
+
+ nsCOMPtr<SessionHistoryEntry> newChild = do_QueryInterface(aNewChild);
+ MOZ_ASSERT(newChild);
+ return ReplaceChild(newChild) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+bool SessionHistoryEntry::ReplaceChild(SessionHistoryEntry* aNewChild) {
+ const nsID& docshellID = aNewChild->DocshellID();
+
+ for (uint32_t i = 0; i < mChildren.Length(); ++i) {
+ if (mChildren[i] && docshellID == mChildren[i]->DocshellID()) {
+ mChildren[i]->SetParent(nullptr);
+ mChildren.ReplaceElementAt(i, aNewChild);
+ aNewChild->SetParent(this);
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP_(void)
+SessionHistoryEntry::ClearEntry() {
+ int32_t childCount = GetChildCount();
+ // Remove all children of this entry
+ for (int32_t i = childCount; i > 0; --i) {
+ nsCOMPtr<nsISHEntry> child;
+ GetChildAt(i - 1, getter_AddRefs(child));
+ RemoveChild(child);
+ }
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::CreateLoadInfo(nsDocShellLoadState** aLoadState) {
+ NS_WARNING("We shouldn't be calling this!");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetBfcacheID(uint64_t* aBfcacheID) {
+ *aBfcacheID = SharedInfo()->mId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::GetWireframe(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aOut) {
+ if (mWireframe.isNothing()) {
+ aOut.set(JS::NullValue());
+ } else if (NS_WARN_IF(!mWireframe->ToObjectInternal(aCx, aOut))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SessionHistoryEntry::SetWireframe(JSContext* aCx, JS::Handle<JS::Value> aArg) {
+ if (aArg.isNullOrUndefined()) {
+ mWireframe = Nothing();
+ return NS_OK;
+ }
+
+ Wireframe wireframe;
+ if (aArg.isObject() && wireframe.Init(aCx, aArg)) {
+ mWireframe = Some(std::move(wireframe));
+ return NS_OK;
+ }
+
+ return NS_ERROR_INVALID_ARG;
+}
+
+NS_IMETHODIMP_(void)
+SessionHistoryEntry::SyncTreesForSubframeNavigation(
+ nsISHEntry* aEntry, mozilla::dom::BrowsingContext* aTopBC,
+ mozilla::dom::BrowsingContext* aIgnoreBC) {
+ // XXX Keep this in sync with nsSHEntry::SyncTreesForSubframeNavigation.
+ //
+ // We need to sync up the browsing context and session history trees for
+ // subframe navigation. If the load was in a subframe, we forward up to
+ // the top browsing context, which will then recursively sync up all browsing
+ // contexts to their corresponding entries in the new session history tree. If
+ // we don't do this, then we can cache a content viewer on the wrong cloned
+ // entry, and subsequently restore it at the wrong time.
+ nsCOMPtr<nsISHEntry> newRootEntry = nsSHistory::GetRootSHEntry(aEntry);
+ if (newRootEntry) {
+ // newRootEntry is now the new root entry.
+ // Find the old root entry as well.
+
+ // Need a strong ref. on |oldRootEntry| so it isn't destroyed when
+ // SetChildHistoryEntry() does SwapHistoryEntries() (bug 304639).
+ nsCOMPtr<nsISHEntry> oldRootEntry = nsSHistory::GetRootSHEntry(this);
+
+ if (oldRootEntry) {
+ nsSHistory::SwapEntriesData data = {aIgnoreBC, newRootEntry, nullptr};
+ nsSHistory::SetChildHistoryEntry(oldRootEntry, aTopBC, 0, &data);
+ }
+ }
+}
+
+void SessionHistoryEntry::ReplaceWith(const SessionHistoryEntry& aSource) {
+ mInfo = MakeUnique<SessionHistoryInfo>(*aSource.mInfo);
+ mChildren.Clear();
+}
+
+SHEntrySharedParentState* SessionHistoryEntry::SharedInfo() const {
+ return static_cast<SHEntrySharedParentState*>(mInfo->mSharedState.Get());
+}
+
+void SessionHistoryEntry::SetFrameLoader(nsFrameLoader* aFrameLoader) {
+ MOZ_DIAGNOSTIC_ASSERT(!aFrameLoader || !SharedInfo()->mFrameLoader);
+ // If the pref is disabled, we still allow evicting the existing entries.
+ MOZ_RELEASE_ASSERT(!aFrameLoader || mozilla::BFCacheInParent());
+ SharedInfo()->SetFrameLoader(aFrameLoader);
+ if (aFrameLoader) {
+ if (BrowsingContext* bc = aFrameLoader->GetMaybePendingBrowsingContext()) {
+ bc->PreOrderWalk([&](BrowsingContext* aContext) {
+ if (BrowserParent* bp = aContext->Canonical()->GetBrowserParent()) {
+ bp->Deactivated();
+ }
+ });
+ }
+
+ // When a new frameloader is stored, try to evict some older
+ // frameloaders. Non-SHIP session history has a similar call in
+ // nsDocumentViewer::Show.
+ nsCOMPtr<nsISHistory> shistory;
+ GetShistory(getter_AddRefs(shistory));
+ if (shistory) {
+ int32_t index = 0;
+ shistory->GetIndex(&index);
+ shistory->EvictOutOfRangeContentViewers(index);
+ }
+ }
+}
+
+nsFrameLoader* SessionHistoryEntry::GetFrameLoader() {
+ return SharedInfo()->mFrameLoader;
+}
+
+void SessionHistoryEntry::SetInfo(SessionHistoryInfo* aInfo) {
+ // FIXME Assert that we're not changing shared state!
+ mInfo = MakeUnique<SessionHistoryInfo>(*aInfo);
+}
+
+} // namespace dom
+
+namespace ipc {
+
+void IPDLParamTraits<dom::SessionHistoryInfo>::Write(
+ IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const dom::SessionHistoryInfo& aParam) {
+ nsCOMPtr<nsIInputStream> postData = aParam.GetPostData();
+
+ Maybe<std::tuple<uint32_t, dom::ClonedMessageData>> stateData;
+ if (aParam.mStateData) {
+ stateData.emplace();
+ // FIXME: We should fail more aggressively if this fails, as currently we'll
+ // just early return and the deserialization will break.
+ NS_ENSURE_SUCCESS_VOID(
+ aParam.mStateData->GetFormatVersion(&std::get<0>(*stateData)));
+ NS_ENSURE_TRUE_VOID(
+ aParam.mStateData->BuildClonedMessageData(std::get<1>(*stateData)));
+ }
+
+ WriteIPDLParam(aWriter, aActor, aParam.mURI);
+ WriteIPDLParam(aWriter, aActor, aParam.mOriginalURI);
+ WriteIPDLParam(aWriter, aActor, aParam.mResultPrincipalURI);
+ WriteIPDLParam(aWriter, aActor, aParam.mUnstrippedURI);
+ WriteIPDLParam(aWriter, aActor, aParam.mReferrerInfo);
+ WriteIPDLParam(aWriter, aActor, aParam.mTitle);
+ WriteIPDLParam(aWriter, aActor, aParam.mName);
+ WriteIPDLParam(aWriter, aActor, postData);
+ WriteIPDLParam(aWriter, aActor, aParam.mLoadType);
+ WriteIPDLParam(aWriter, aActor, aParam.mScrollPositionX);
+ WriteIPDLParam(aWriter, aActor, aParam.mScrollPositionY);
+ WriteIPDLParam(aWriter, aActor, stateData);
+ WriteIPDLParam(aWriter, aActor, aParam.mSrcdocData);
+ WriteIPDLParam(aWriter, aActor, aParam.mBaseURI);
+ WriteIPDLParam(aWriter, aActor, aParam.mLoadReplace);
+ WriteIPDLParam(aWriter, aActor, aParam.mURIWasModified);
+ WriteIPDLParam(aWriter, aActor, aParam.mScrollRestorationIsManual);
+ WriteIPDLParam(aWriter, aActor, aParam.mPersist);
+ WriteIPDLParam(aWriter, aActor, aParam.mHasUserInteraction);
+ WriteIPDLParam(aWriter, aActor, aParam.mHasUserActivation);
+ WriteIPDLParam(aWriter, aActor, aParam.mSharedState.Get()->mId);
+ WriteIPDLParam(aWriter, aActor,
+ aParam.mSharedState.Get()->mTriggeringPrincipal);
+ WriteIPDLParam(aWriter, aActor,
+ aParam.mSharedState.Get()->mPrincipalToInherit);
+ WriteIPDLParam(aWriter, aActor,
+ aParam.mSharedState.Get()->mPartitionedPrincipalToInherit);
+ WriteIPDLParam(aWriter, aActor, aParam.mSharedState.Get()->mCsp);
+ WriteIPDLParam(aWriter, aActor, aParam.mSharedState.Get()->mContentType);
+ WriteIPDLParam(aWriter, aActor,
+ aParam.mSharedState.Get()->mLayoutHistoryState);
+ WriteIPDLParam(aWriter, aActor, aParam.mSharedState.Get()->mCacheKey);
+ WriteIPDLParam(aWriter, aActor,
+ aParam.mSharedState.Get()->mIsFrameNavigation);
+ WriteIPDLParam(aWriter, aActor, aParam.mSharedState.Get()->mSaveLayoutState);
+}
+
+bool IPDLParamTraits<dom::SessionHistoryInfo>::Read(
+ IPC::MessageReader* aReader, IProtocol* aActor,
+ dom::SessionHistoryInfo* aResult) {
+ Maybe<std::tuple<uint32_t, dom::ClonedMessageData>> stateData;
+ uint64_t sharedId;
+ if (!ReadIPDLParam(aReader, aActor, &aResult->mURI) ||
+ !ReadIPDLParam(aReader, aActor, &aResult->mOriginalURI) ||
+ !ReadIPDLParam(aReader, aActor, &aResult->mResultPrincipalURI) ||
+ !ReadIPDLParam(aReader, aActor, &aResult->mUnstrippedURI) ||
+ !ReadIPDLParam(aReader, aActor, &aResult->mReferrerInfo) ||
+ !ReadIPDLParam(aReader, aActor, &aResult->mTitle) ||
+ !ReadIPDLParam(aReader, aActor, &aResult->mName) ||
+ !ReadIPDLParam(aReader, aActor, &aResult->mPostData) ||
+ !ReadIPDLParam(aReader, aActor, &aResult->mLoadType) ||
+ !ReadIPDLParam(aReader, aActor, &aResult->mScrollPositionX) ||
+ !ReadIPDLParam(aReader, aActor, &aResult->mScrollPositionY) ||
+ !ReadIPDLParam(aReader, aActor, &stateData) ||
+ !ReadIPDLParam(aReader, aActor, &aResult->mSrcdocData) ||
+ !ReadIPDLParam(aReader, aActor, &aResult->mBaseURI) ||
+ !ReadIPDLParam(aReader, aActor, &aResult->mLoadReplace) ||
+ !ReadIPDLParam(aReader, aActor, &aResult->mURIWasModified) ||
+ !ReadIPDLParam(aReader, aActor, &aResult->mScrollRestorationIsManual) ||
+ !ReadIPDLParam(aReader, aActor, &aResult->mPersist) ||
+ !ReadIPDLParam(aReader, aActor, &aResult->mHasUserInteraction) ||
+ !ReadIPDLParam(aReader, aActor, &aResult->mHasUserActivation) ||
+ !ReadIPDLParam(aReader, aActor, &sharedId)) {
+ aActor->FatalError("Error reading fields for SessionHistoryInfo");
+ return false;
+ }
+
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal;
+ nsCOMPtr<nsIPrincipal> principalToInherit;
+ nsCOMPtr<nsIPrincipal> partitionedPrincipalToInherit;
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ nsCString contentType;
+ if (!ReadIPDLParam(aReader, aActor, &triggeringPrincipal) ||
+ !ReadIPDLParam(aReader, aActor, &principalToInherit) ||
+ !ReadIPDLParam(aReader, aActor, &partitionedPrincipalToInherit) ||
+ !ReadIPDLParam(aReader, aActor, &csp) ||
+ !ReadIPDLParam(aReader, aActor, &contentType)) {
+ aActor->FatalError("Error reading fields for SessionHistoryInfo");
+ return false;
+ }
+
+ // We should always see a cloneable input stream passed to SessionHistoryInfo.
+ // This is because it will be cloneable when first read in the parent process
+ // from the nsHttpChannel (which forces streams to be cloneable), and future
+ // streams in content will be wrapped in
+ // nsMIMEInputStream(RemoteLazyInputStream) which is also cloneable.
+ if (aResult->mPostData && !NS_InputStreamIsCloneable(aResult->mPostData)) {
+ aActor->FatalError(
+ "Unexpected non-cloneable postData for SessionHistoryInfo");
+ return false;
+ }
+
+ dom::SHEntrySharedParentState* sharedState = nullptr;
+ if (XRE_IsParentProcess()) {
+ sharedState = dom::SHEntrySharedParentState::Lookup(sharedId);
+ }
+
+ if (sharedState) {
+ aResult->mSharedState.Set(sharedState);
+
+ MOZ_ASSERT(triggeringPrincipal
+ ? triggeringPrincipal->Equals(
+ aResult->mSharedState.Get()->mTriggeringPrincipal)
+ : !aResult->mSharedState.Get()->mTriggeringPrincipal,
+ "We don't expect this to change!");
+ MOZ_ASSERT(principalToInherit
+ ? principalToInherit->Equals(
+ aResult->mSharedState.Get()->mPrincipalToInherit)
+ : !aResult->mSharedState.Get()->mPrincipalToInherit,
+ "We don't expect this to change!");
+ MOZ_ASSERT(
+ partitionedPrincipalToInherit
+ ? partitionedPrincipalToInherit->Equals(
+ aResult->mSharedState.Get()->mPartitionedPrincipalToInherit)
+ : !aResult->mSharedState.Get()->mPartitionedPrincipalToInherit,
+ "We don't expect this to change!");
+ MOZ_ASSERT(
+ csp ? nsCSPContext::Equals(csp, aResult->mSharedState.Get()->mCsp)
+ : !aResult->mSharedState.Get()->mCsp,
+ "We don't expect this to change!");
+ MOZ_ASSERT(contentType.Equals(aResult->mSharedState.Get()->mContentType),
+ "We don't expect this to change!");
+ } else {
+ aResult->mSharedState.ChangeId(sharedId);
+ aResult->mSharedState.Get()->mTriggeringPrincipal =
+ triggeringPrincipal.forget();
+ aResult->mSharedState.Get()->mPrincipalToInherit =
+ principalToInherit.forget();
+ aResult->mSharedState.Get()->mPartitionedPrincipalToInherit =
+ partitionedPrincipalToInherit.forget();
+ aResult->mSharedState.Get()->mCsp = csp.forget();
+ aResult->mSharedState.Get()->mContentType = contentType;
+ }
+
+ if (!ReadIPDLParam(aReader, aActor,
+ &aResult->mSharedState.Get()->mLayoutHistoryState) ||
+ !ReadIPDLParam(aReader, aActor,
+ &aResult->mSharedState.Get()->mCacheKey) ||
+ !ReadIPDLParam(aReader, aActor,
+ &aResult->mSharedState.Get()->mIsFrameNavigation) ||
+ !ReadIPDLParam(aReader, aActor,
+ &aResult->mSharedState.Get()->mSaveLayoutState)) {
+ aActor->FatalError("Error reading fields for SessionHistoryInfo");
+ return false;
+ }
+
+ if (stateData.isSome()) {
+ uint32_t version = std::get<0>(*stateData);
+ aResult->mStateData = new nsStructuredCloneContainer(version);
+ aResult->mStateData->StealFromClonedMessageData(std::get<1>(*stateData));
+ }
+ MOZ_ASSERT_IF(stateData.isNothing(), !aResult->mStateData);
+ return true;
+}
+
+void IPDLParamTraits<dom::LoadingSessionHistoryInfo>::Write(
+ IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const dom::LoadingSessionHistoryInfo& aParam) {
+ WriteIPDLParam(aWriter, aActor, aParam.mInfo);
+ WriteIPDLParam(aWriter, aActor, aParam.mLoadId);
+ WriteIPDLParam(aWriter, aActor, aParam.mLoadIsFromSessionHistory);
+ WriteIPDLParam(aWriter, aActor, aParam.mOffset);
+ WriteIPDLParam(aWriter, aActor, aParam.mLoadingCurrentEntry);
+ WriteIPDLParam(aWriter, aActor, aParam.mForceMaybeResetName);
+}
+
+bool IPDLParamTraits<dom::LoadingSessionHistoryInfo>::Read(
+ IPC::MessageReader* aReader, IProtocol* aActor,
+ dom::LoadingSessionHistoryInfo* aResult) {
+ if (!ReadIPDLParam(aReader, aActor, &aResult->mInfo) ||
+ !ReadIPDLParam(aReader, aActor, &aResult->mLoadId) ||
+ !ReadIPDLParam(aReader, aActor, &aResult->mLoadIsFromSessionHistory) ||
+ !ReadIPDLParam(aReader, aActor, &aResult->mOffset) ||
+ !ReadIPDLParam(aReader, aActor, &aResult->mLoadingCurrentEntry) ||
+ !ReadIPDLParam(aReader, aActor, &aResult->mForceMaybeResetName)) {
+ aActor->FatalError("Error reading fields for LoadingSessionHistoryInfo");
+ return false;
+ }
+
+ return true;
+}
+
+void IPDLParamTraits<nsILayoutHistoryState*>::Write(
+ IPC::MessageWriter* aWriter, IProtocol* aActor,
+ nsILayoutHistoryState* aParam) {
+ if (aParam) {
+ WriteIPDLParam(aWriter, aActor, true);
+ bool scrollPositionOnly = false;
+ nsTArray<nsCString> keys;
+ nsTArray<mozilla::PresState> states;
+ aParam->GetContents(&scrollPositionOnly, keys, states);
+ WriteIPDLParam(aWriter, aActor, scrollPositionOnly);
+ WriteIPDLParam(aWriter, aActor, keys);
+ WriteIPDLParam(aWriter, aActor, states);
+ } else {
+ WriteIPDLParam(aWriter, aActor, false);
+ }
+}
+
+bool IPDLParamTraits<nsILayoutHistoryState*>::Read(
+ IPC::MessageReader* aReader, IProtocol* aActor,
+ RefPtr<nsILayoutHistoryState>* aResult) {
+ bool hasLayoutHistoryState = false;
+ if (!ReadIPDLParam(aReader, aActor, &hasLayoutHistoryState)) {
+ aActor->FatalError("Error reading fields for nsILayoutHistoryState");
+ return false;
+ }
+
+ if (hasLayoutHistoryState) {
+ bool scrollPositionOnly = false;
+ nsTArray<nsCString> keys;
+ nsTArray<mozilla::PresState> states;
+ if (!ReadIPDLParam(aReader, aActor, &scrollPositionOnly) ||
+ !ReadIPDLParam(aReader, aActor, &keys) ||
+ !ReadIPDLParam(aReader, aActor, &states)) {
+ aActor->FatalError("Error reading fields for nsILayoutHistoryState");
+ }
+
+ if (keys.Length() != states.Length()) {
+ aActor->FatalError("Error reading fields for nsILayoutHistoryState");
+ return false;
+ }
+
+ *aResult = NS_NewLayoutHistoryState();
+ (*aResult)->SetScrollPositionOnly(scrollPositionOnly);
+ for (uint32_t i = 0; i < keys.Length(); ++i) {
+ PresState& state = states[i];
+ UniquePtr<PresState> newState = MakeUnique<PresState>(state);
+ (*aResult)->AddState(keys[i], std::move(newState));
+ }
+ }
+ return true;
+}
+
+void IPDLParamTraits<mozilla::dom::Wireframe>::Write(
+ IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const mozilla::dom::Wireframe& aParam) {
+ WriteParam(aWriter, aParam.mCanvasBackground);
+ WriteParam(aWriter, aParam.mRects);
+}
+
+bool IPDLParamTraits<mozilla::dom::Wireframe>::Read(
+ IPC::MessageReader* aReader, IProtocol* aActor,
+ mozilla::dom::Wireframe* aResult) {
+ return ReadParam(aReader, &aResult->mCanvasBackground) &&
+ ReadParam(aReader, &aResult->mRects);
+}
+
+} // namespace ipc
+} // namespace mozilla
+
+namespace IPC {
+// Allow sending mozilla::dom::WireframeRectType enums over IPC.
+template <>
+struct ParamTraits<mozilla::dom::WireframeRectType>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::WireframeRectType,
+ mozilla::dom::WireframeRectType::Image,
+ mozilla::dom::WireframeRectType::EndGuard_> {};
+
+template <>
+struct ParamTraits<mozilla::dom::WireframeTaggedRect> {
+ static void Write(MessageWriter* aWriter,
+ const mozilla::dom::WireframeTaggedRect& aParam);
+ static bool Read(MessageReader* aReader,
+ mozilla::dom::WireframeTaggedRect* aResult);
+};
+
+void ParamTraits<mozilla::dom::WireframeTaggedRect>::Write(
+ MessageWriter* aWriter, const mozilla::dom::WireframeTaggedRect& aParam) {
+ WriteParam(aWriter, aParam.mColor);
+ WriteParam(aWriter, aParam.mType);
+ WriteParam(aWriter, aParam.mX);
+ WriteParam(aWriter, aParam.mY);
+ WriteParam(aWriter, aParam.mWidth);
+ WriteParam(aWriter, aParam.mHeight);
+}
+
+bool ParamTraits<mozilla::dom::WireframeTaggedRect>::Read(
+ IPC::MessageReader* aReader, mozilla::dom::WireframeTaggedRect* aResult) {
+ return ReadParam(aReader, &aResult->mColor) &&
+ ReadParam(aReader, &aResult->mType) &&
+ ReadParam(aReader, &aResult->mX) && ReadParam(aReader, &aResult->mY) &&
+ ReadParam(aReader, &aResult->mWidth) &&
+ ReadParam(aReader, &aResult->mHeight);
+}
+} // namespace IPC
diff --git a/docshell/shistory/SessionHistoryEntry.h b/docshell/shistory/SessionHistoryEntry.h
new file mode 100644
index 0000000000..8eeb5ad2a9
--- /dev/null
+++ b/docshell/shistory/SessionHistoryEntry.h
@@ -0,0 +1,510 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_SessionHistoryEntry_h
+#define mozilla_dom_SessionHistoryEntry_h
+
+#include "mozilla/dom/DocumentBinding.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+#include "nsILayoutHistoryState.h"
+#include "nsISHEntry.h"
+#include "nsSHEntryShared.h"
+#include "nsStructuredCloneContainer.h"
+#include "nsTHashMap.h"
+#include "nsWeakReference.h"
+
+class nsDocShellLoadState;
+class nsIChannel;
+class nsIInputStream;
+class nsIReferrerInfo;
+class nsISHistory;
+class nsIURI;
+
+namespace mozilla::ipc {
+template <typename P>
+struct IPDLParamTraits;
+}
+
+namespace mozilla {
+namespace dom {
+
+struct LoadingSessionHistoryInfo;
+class SessionHistoryEntry;
+class SHEntrySharedParentState;
+
+// SessionHistoryInfo stores session history data for a load. It can be sent
+// over IPC and is used in both the parent and the child processes.
+class SessionHistoryInfo {
+ public:
+ SessionHistoryInfo() = default;
+ SessionHistoryInfo(const SessionHistoryInfo& aInfo) = default;
+ SessionHistoryInfo(nsDocShellLoadState* aLoadState, nsIChannel* aChannel);
+ SessionHistoryInfo(const SessionHistoryInfo& aSharedStateFrom, nsIURI* aURI);
+ SessionHistoryInfo(nsIURI* aURI, nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp,
+ const nsACString& aContentType);
+ SessionHistoryInfo(nsIChannel* aChannel, uint32_t aLoadType,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp);
+
+ void Reset(nsIURI* aURI, const nsID& aDocShellID, bool aDynamicCreation,
+ nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp, const nsACString& aContentType);
+
+ bool operator==(const SessionHistoryInfo& aInfo) const {
+ return false; // FIXME
+ }
+
+ nsIURI* GetURI() const { return mURI; }
+ void SetURI(nsIURI* aURI) { mURI = aURI; }
+
+ nsIURI* GetOriginalURI() const { return mOriginalURI; }
+ void SetOriginalURI(nsIURI* aOriginalURI) { mOriginalURI = aOriginalURI; }
+
+ nsIURI* GetUnstrippedURI() const { return mUnstrippedURI; }
+ void SetUnstrippedURI(nsIURI* aUnstrippedURI) {
+ mUnstrippedURI = aUnstrippedURI;
+ }
+
+ nsIURI* GetResultPrincipalURI() const { return mResultPrincipalURI; }
+ void SetResultPrincipalURI(nsIURI* aResultPrincipalURI) {
+ mResultPrincipalURI = aResultPrincipalURI;
+ }
+
+ nsIReferrerInfo* GetReferrerInfo() { return mReferrerInfo; }
+ void SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
+ mReferrerInfo = aReferrerInfo;
+ }
+
+ bool HasPostData() const { return mPostData; }
+ already_AddRefed<nsIInputStream> GetPostData() const;
+ void SetPostData(nsIInputStream* aPostData);
+
+ void GetScrollPosition(int32_t* aScrollPositionX, int32_t* aScrollPositionY) {
+ *aScrollPositionX = mScrollPositionX;
+ *aScrollPositionY = mScrollPositionY;
+ }
+
+ void SetScrollPosition(int32_t aScrollPositionX, int32_t aScrollPositionY) {
+ mScrollPositionX = aScrollPositionX;
+ mScrollPositionY = aScrollPositionY;
+ }
+
+ bool GetScrollRestorationIsManual() const {
+ return mScrollRestorationIsManual;
+ }
+ const nsAString& GetTitle() { return mTitle; }
+ void SetTitle(const nsAString& aTitle) {
+ mTitle = aTitle;
+ MaybeUpdateTitleFromURI();
+ }
+
+ const nsAString& GetName() { return mName; }
+ void SetName(const nsAString& aName) { mName = aName; }
+
+ void SetScrollRestorationIsManual(bool aIsManual) {
+ mScrollRestorationIsManual = aIsManual;
+ }
+
+ nsStructuredCloneContainer* GetStateData() const { return mStateData; }
+ void SetStateData(nsStructuredCloneContainer* aStateData) {
+ mStateData = aStateData;
+ }
+
+ void SetLoadReplace(bool aLoadReplace) { mLoadReplace = aLoadReplace; }
+
+ void SetURIWasModified(bool aURIWasModified) {
+ mURIWasModified = aURIWasModified;
+ }
+ bool GetURIWasModified() const { return mURIWasModified; }
+
+ void SetHasUserInteraction(bool aHasUserInteraction) {
+ mHasUserInteraction = aHasUserInteraction;
+ }
+ bool GetHasUserInteraction() const { return mHasUserInteraction; }
+
+ uint64_t SharedId() const;
+
+ nsILayoutHistoryState* GetLayoutHistoryState();
+ void SetLayoutHistoryState(nsILayoutHistoryState* aState);
+
+ nsIPrincipal* GetTriggeringPrincipal() const;
+
+ nsIPrincipal* GetPrincipalToInherit() const;
+
+ nsIPrincipal* GetPartitionedPrincipalToInherit() const;
+
+ nsIContentSecurityPolicy* GetCsp() const;
+
+ uint32_t GetCacheKey() const;
+ void SetCacheKey(uint32_t aCacheKey);
+
+ bool IsSubFrame() const;
+
+ bool SharesDocumentWith(const SessionHistoryInfo& aOther) const {
+ return SharedId() == aOther.SharedId();
+ }
+
+ void FillLoadInfo(nsDocShellLoadState& aLoadState) const;
+
+ uint32_t LoadType() { return mLoadType; }
+
+ void SetSaveLayoutStateFlag(bool aSaveLayoutStateFlag);
+
+ bool GetPersist() const { return mPersist; }
+
+ private:
+ friend class SessionHistoryEntry;
+ friend struct mozilla::ipc::IPDLParamTraits<SessionHistoryInfo>;
+
+ void MaybeUpdateTitleFromURI();
+
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsIURI> mResultPrincipalURI;
+ nsCOMPtr<nsIURI> mUnstrippedURI;
+ nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
+ nsString mTitle;
+ nsString mName;
+ nsCOMPtr<nsIInputStream> mPostData;
+ uint32_t mLoadType = 0;
+ int32_t mScrollPositionX = 0;
+ int32_t mScrollPositionY = 0;
+ RefPtr<nsStructuredCloneContainer> mStateData;
+ Maybe<nsString> mSrcdocData;
+ nsCOMPtr<nsIURI> mBaseURI;
+
+ bool mLoadReplace = false;
+ bool mURIWasModified = false;
+ bool mScrollRestorationIsManual = false;
+ bool mPersist = true;
+ bool mHasUserInteraction = false;
+ bool mHasUserActivation = false;
+
+ union SharedState {
+ SharedState();
+ explicit SharedState(const SharedState& aOther);
+ explicit SharedState(const Maybe<const SharedState&>& aOther);
+ ~SharedState();
+
+ SharedState& operator=(const SharedState& aOther);
+
+ SHEntrySharedState* Get() const;
+
+ void Set(SHEntrySharedParentState* aState) { mParent = aState; }
+
+ void ChangeId(uint64_t aId);
+
+ static SharedState Create(nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp,
+ const nsACString& aContentType);
+
+ private:
+ explicit SharedState(SHEntrySharedParentState* aParent)
+ : mParent(aParent) {}
+ explicit SharedState(UniquePtr<SHEntrySharedState>&& aChild)
+ : mChild(std::move(aChild)) {}
+
+ void Init();
+ void Init(const SharedState& aOther);
+
+ // In the parent process this holds a strong reference to the refcounted
+ // SHEntrySharedParentState. In the child processes this holds an owning
+ // pointer to a SHEntrySharedState.
+ RefPtr<SHEntrySharedParentState> mParent;
+ UniquePtr<SHEntrySharedState> mChild;
+ };
+
+ SharedState mSharedState;
+};
+
+struct LoadingSessionHistoryInfo {
+ LoadingSessionHistoryInfo() = default;
+ explicit LoadingSessionHistoryInfo(SessionHistoryEntry* aEntry);
+ // Initializes mInfo using aEntry and otherwise copies the values from aInfo.
+ LoadingSessionHistoryInfo(SessionHistoryEntry* aEntry,
+ const LoadingSessionHistoryInfo* aInfo);
+ // For about:blank only.
+ explicit LoadingSessionHistoryInfo(const SessionHistoryInfo& aInfo);
+
+ already_AddRefed<nsDocShellLoadState> CreateLoadInfo() const;
+
+ SessionHistoryInfo mInfo;
+
+ uint64_t mLoadId = 0;
+
+ // The following three member variables are used to inform about a load from
+ // the session history. The session-history-in-child approach has just
+ // an nsISHEntry in the nsDocShellLoadState and access to the nsISHistory,
+ // but session-history-in-parent needs to pass needed information explicitly
+ // to the relevant child process.
+ bool mLoadIsFromSessionHistory = false;
+ // mOffset and mLoadingCurrentEntry are relevant only if
+ // mLoadIsFromSessionHistory is true.
+ int32_t mOffset = 0;
+ // If we're loading from the current entry we want to treat it as not a
+ // same-document navigation (see nsDocShell::IsSameDocumentNavigation).
+ bool mLoadingCurrentEntry = false;
+ // If mForceMaybeResetName.isSome() is true then the parent process has
+ // determined whether the BC's name should be cleared and stored in session
+ // history (see https://html.spec.whatwg.org/#history-traversal step 4.2).
+ // This is used when we're replacing the BC for BFCache in the parent. In
+ // other cases mForceMaybeResetName.isSome() will be false and the child
+ // process should be able to make that determination itself.
+ Maybe<bool> mForceMaybeResetName;
+};
+
+// HistoryEntryCounterForBrowsingContext is used to count the number of entries
+// which are added to the session history for a particular browsing context.
+// If a SessionHistoryEntry is cloned because of navigation in some other
+// browsing context, that doesn't cause the counter value to be increased.
+// The browsing context specific counter is needed to make it easier to
+// synchronously update history.length value in a child process when
+// an iframe is removed from DOM.
+class HistoryEntryCounterForBrowsingContext {
+ public:
+ HistoryEntryCounterForBrowsingContext()
+ : mCounter(new RefCountedCounter()), mHasModified(false) {
+ ++(*this);
+ }
+
+ HistoryEntryCounterForBrowsingContext(
+ const HistoryEntryCounterForBrowsingContext& aOther)
+ : mCounter(aOther.mCounter), mHasModified(false) {}
+
+ HistoryEntryCounterForBrowsingContext(
+ HistoryEntryCounterForBrowsingContext&& aOther) = delete;
+
+ ~HistoryEntryCounterForBrowsingContext() {
+ if (mHasModified) {
+ --(*mCounter);
+ }
+ }
+
+ void CopyValueFrom(const HistoryEntryCounterForBrowsingContext& aOther) {
+ if (mHasModified) {
+ --(*mCounter);
+ }
+ mCounter = aOther.mCounter;
+ mHasModified = false;
+ }
+
+ HistoryEntryCounterForBrowsingContext& operator=(
+ const HistoryEntryCounterForBrowsingContext& aOther) = delete;
+
+ HistoryEntryCounterForBrowsingContext& operator++() {
+ mHasModified = true;
+ ++(*mCounter);
+ return *this;
+ }
+
+ operator uint32_t() const { return *mCounter; }
+
+ bool Modified() { return mHasModified; }
+
+ void SetModified(bool aModified) { mHasModified = aModified; }
+
+ void Reset() {
+ if (mHasModified) {
+ --(*mCounter);
+ }
+ mCounter = new RefCountedCounter();
+ mHasModified = false;
+ }
+
+ private:
+ class RefCountedCounter {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(
+ mozilla::dom::HistoryEntryCounterForBrowsingContext::RefCountedCounter)
+
+ RefCountedCounter& operator++() {
+ ++mCounter;
+ return *this;
+ }
+
+ RefCountedCounter& operator--() {
+ --mCounter;
+ return *this;
+ }
+
+ operator uint32_t() const { return mCounter; }
+
+ private:
+ ~RefCountedCounter() = default;
+
+ uint32_t mCounter = 0;
+ };
+
+ RefPtr<RefCountedCounter> mCounter;
+ bool mHasModified;
+};
+
+// SessionHistoryEntry is used to store session history data in the parent
+// process. It holds a SessionHistoryInfo, some state shared amongst multiple
+// SessionHistoryEntries, a parent and children.
+#define NS_SESSIONHISTORYENTRY_IID \
+ { \
+ 0x5b66a244, 0x8cec, 0x4caa, { \
+ 0xaa, 0x0a, 0x78, 0x92, 0xfd, 0x17, 0xa6, 0x67 \
+ } \
+ }
+
+class SessionHistoryEntry : public nsISHEntry, public nsSupportsWeakReference {
+ public:
+ SessionHistoryEntry(nsDocShellLoadState* aLoadState, nsIChannel* aChannel);
+ SessionHistoryEntry();
+ explicit SessionHistoryEntry(SessionHistoryInfo* aInfo);
+ explicit SessionHistoryEntry(const SessionHistoryEntry& aEntry);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISHENTRY
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_SESSIONHISTORYENTRY_IID)
+
+ bool IsInSessionHistory() {
+ SessionHistoryEntry* entry = this;
+ while (nsCOMPtr<SessionHistoryEntry> parent =
+ do_QueryReferent(entry->mParent)) {
+ entry = parent;
+ }
+ return entry->SharedInfo()->mSHistory &&
+ entry->SharedInfo()->mSHistory->IsAlive();
+ }
+
+ void ReplaceWith(const SessionHistoryEntry& aSource);
+
+ const SessionHistoryInfo& Info() const { return *mInfo; }
+
+ SHEntrySharedParentState* SharedInfo() const;
+
+ void SetFrameLoader(nsFrameLoader* aFrameLoader);
+ nsFrameLoader* GetFrameLoader();
+
+ void AddChild(SessionHistoryEntry* aChild, int32_t aOffset,
+ bool aUseRemoteSubframes);
+ void RemoveChild(SessionHistoryEntry* aChild);
+ // Finds the child with the same docshell ID as aNewChild, replaces it with
+ // aNewChild and returns true. If there is no child with the same docshell ID
+ // then it returns false.
+ bool ReplaceChild(SessionHistoryEntry* aNewChild);
+
+ void SetInfo(SessionHistoryInfo* aInfo);
+
+ bool ForInitialLoad() { return mForInitialLoad; }
+ void SetForInitialLoad(bool aForInitialLoad) {
+ mForInitialLoad = aForInitialLoad;
+ }
+
+ const nsID& DocshellID() const;
+
+ HistoryEntryCounterForBrowsingContext& BCHistoryLength() {
+ return mBCHistoryLength;
+ }
+
+ void SetBCHistoryLength(HistoryEntryCounterForBrowsingContext& aCounter) {
+ mBCHistoryLength.CopyValueFrom(aCounter);
+ }
+
+ void ClearBCHistoryLength() { mBCHistoryLength.Reset(); }
+
+ void SetIsDynamicallyAdded(bool aDynamic);
+
+ void SetWireframe(const Maybe<Wireframe>& aWireframe);
+
+ struct LoadingEntry {
+ // A pointer to the entry being loaded. Will be cleared by the
+ // SessionHistoryEntry destructor, at latest.
+ SessionHistoryEntry* mEntry;
+ // Snapshot of the entry's SessionHistoryInfo when the load started, to be
+ // used for validation purposes only.
+ UniquePtr<SessionHistoryInfo> mInfoSnapshotForValidation;
+ };
+
+ // Get an entry based on LoadingSessionHistoryInfo's mLoadId. Parent process
+ // only.
+ static LoadingEntry* GetByLoadId(uint64_t aLoadId);
+ static void SetByLoadId(uint64_t aLoadId, SessionHistoryEntry* aEntry);
+ static void RemoveLoadId(uint64_t aLoadId);
+
+ const nsTArray<RefPtr<SessionHistoryEntry>>& Children() { return mChildren; }
+
+ private:
+ friend struct LoadingSessionHistoryInfo;
+ virtual ~SessionHistoryEntry();
+
+ UniquePtr<SessionHistoryInfo> mInfo;
+ nsWeakPtr mParent;
+ uint32_t mID;
+ nsTArray<RefPtr<SessionHistoryEntry>> mChildren;
+ Maybe<Wireframe> mWireframe;
+
+ bool mForInitialLoad = false;
+
+ HistoryEntryCounterForBrowsingContext mBCHistoryLength;
+
+ static nsTHashMap<nsUint64HashKey, LoadingEntry>* sLoadIdToEntry;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(SessionHistoryEntry, NS_SESSIONHISTORYENTRY_IID)
+
+} // namespace dom
+
+namespace ipc {
+
+class IProtocol;
+
+// Allow sending SessionHistoryInfo objects over IPC.
+template <>
+struct IPDLParamTraits<dom::SessionHistoryInfo> {
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const dom::SessionHistoryInfo& aParam);
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ dom::SessionHistoryInfo* aResult);
+};
+
+// Allow sending LoadingSessionHistoryInfo objects over IPC.
+template <>
+struct IPDLParamTraits<dom::LoadingSessionHistoryInfo> {
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const dom::LoadingSessionHistoryInfo& aParam);
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ dom::LoadingSessionHistoryInfo* aResult);
+};
+
+// Allow sending nsILayoutHistoryState objects over IPC.
+template <>
+struct IPDLParamTraits<nsILayoutHistoryState*> {
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ nsILayoutHistoryState* aParam);
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ RefPtr<nsILayoutHistoryState>* aResult);
+};
+
+// Allow sending dom::Wireframe objects over IPC.
+template <>
+struct IPDLParamTraits<mozilla::dom::Wireframe> {
+ static void Write(IPC::MessageWriter* aWriter, IProtocol* aActor,
+ const mozilla::dom::Wireframe& aParam);
+ static bool Read(IPC::MessageReader* aReader, IProtocol* aActor,
+ mozilla::dom::Wireframe* aResult);
+};
+
+} // namespace ipc
+
+} // namespace mozilla
+
+inline nsISupports* ToSupports(mozilla::dom::SessionHistoryEntry* aEntry) {
+ return static_cast<nsISHEntry*>(aEntry);
+}
+
+#endif /* mozilla_dom_SessionHistoryEntry_h */
diff --git a/docshell/shistory/moz.build b/docshell/shistory/moz.build
new file mode 100644
index 0000000000..f3b86c207b
--- /dev/null
+++ b/docshell/shistory/moz.build
@@ -0,0 +1,42 @@
+# -*- Mode: python; 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/.
+
+XPIDL_SOURCES += [
+ "nsIBFCacheEntry.idl",
+ "nsISHEntry.idl",
+ "nsISHistory.idl",
+ "nsISHistoryListener.idl",
+]
+
+XPIDL_MODULE = "shistory"
+
+EXPORTS += [
+ "nsSHEntry.h",
+ "nsSHEntryShared.h",
+ "nsSHistory.h",
+]
+
+EXPORTS.mozilla.dom += [
+ "ChildSHistory.h",
+ "SessionHistoryEntry.h",
+]
+
+UNIFIED_SOURCES += [
+ "ChildSHistory.cpp",
+ "nsSHEntry.cpp",
+ "nsSHEntryShared.cpp",
+ "nsSHistory.cpp",
+ "SessionHistoryEntry.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/docshell/base",
+ "/dom/base",
+]
+
+FINAL_LIBRARY = "xul"
+
+include("/ipc/chromium/chromium-config.mozbuild")
diff --git a/docshell/shistory/nsIBFCacheEntry.idl b/docshell/shistory/nsIBFCacheEntry.idl
new file mode 100644
index 0000000000..2e24c67e35
--- /dev/null
+++ b/docshell/shistory/nsIBFCacheEntry.idl
@@ -0,0 +1,16 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * This interface lets you evict a document from the back/forward cache.
+ */
+[scriptable, builtinclass, uuid(a576060e-c7df-4d81-aa8c-5b52bd6ad25d)]
+interface nsIBFCacheEntry : nsISupports
+{
+ void RemoveFromBFCacheSync();
+ void RemoveFromBFCacheAsync();
+};
diff --git a/docshell/shistory/nsISHEntry.idl b/docshell/shistory/nsISHEntry.idl
new file mode 100644
index 0000000000..28161e8885
--- /dev/null
+++ b/docshell/shistory/nsISHEntry.idl
@@ -0,0 +1,476 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * The interface to nsISHentry. Each document or subframe in
+ * Session History will have a nsISHEntry associated with it which will
+ * hold all information required to recreate the document from history
+ */
+
+#include "nsISupports.idl"
+
+interface nsIContentSecurityPolicy;
+interface nsIMutableArray;
+interface nsILayoutHistoryState;
+interface nsIContentViewer;
+interface nsIURI;
+interface nsIInputStream;
+interface nsIDocShellTreeItem;
+interface nsIStructuredCloneContainer;
+interface nsIBFCacheEntry;
+interface nsIPrincipal;
+interface nsISHistory;
+interface nsIReferrerInfo;
+
+%{C++
+#include "nsRect.h"
+class nsDocShellEditorData;
+
+namespace mozilla {
+namespace dom {
+
+class SHEntrySharedParentState;
+
+}
+}
+class nsSHEntryShared;
+class nsDocShellLoadState;
+struct EntriesAndBrowsingContextData;
+%}
+[ref] native nsIntRect(nsIntRect);
+[ptr] native nsDocShellEditorDataPtr(nsDocShellEditorData);
+[ptr] native nsDocShellLoadStatePtr(nsDocShellLoadState);
+[ptr] native SHEntrySharedParentStatePtr(mozilla::dom::SHEntrySharedParentState);
+webidl BrowsingContext;
+
+[builtinclass, scriptable, uuid(0dad26b8-a259-42c7-93f1-2fa7fc076e45)]
+interface nsISHEntry : nsISupports
+{
+ /**
+ * The URI of the current entry.
+ */
+ [infallible] attribute nsIURI URI;
+
+ /**
+ * The original URI of the current entry. If an entry is the result of a
+ * redirect this attribute holds the original URI.
+ */
+ [infallible] attribute nsIURI originalURI;
+
+ /**
+ * URL as stored from nsILoadInfo.resultPrincipalURI. See nsILoadInfo
+ * for more details.
+ */
+ [infallible] attribute nsIURI resultPrincipalURI;
+
+ /**
+ * If non-null, the URI as it was before query stripping was performed.
+ */
+ [infallible] attribute nsIURI unstrippedURI;
+
+ /**
+ * This flag remembers whether channel has LOAD_REPLACE set.
+ */
+ [infallible] attribute boolean loadReplace;
+
+ /**
+ * The title of the current entry.
+ */
+ // XXX: make it [infallible] when AString supports that (bug 1491187).
+ attribute AString title;
+
+ /**
+ * The name of the browsing context.
+ */
+ attribute AString name;
+
+ /**
+ * Was the entry created as a result of a subframe navigation?
+ * - Will be 'false' when a frameset page is visited for the first time.
+ * - Will be 'true' for all history entries created as a result of a
+ * subframe navigation.
+ */
+ [infallible] attribute boolean isSubFrame;
+
+ /**
+ * Whether the user interacted with the page while this entry was active.
+ * This includes interactions with subframe documents associated with
+ * child entries that are rooted at this entry.
+ * This field will only be set on top-level entries.
+ */
+ [infallible] attribute boolean hasUserInteraction;
+
+ /**
+ * Whether the load that created this entry was triggered by user activation.
+ * (e.g.: The user clicked a link)
+ * Remembering this flag enables replaying the sec-fetch-* headers.
+ */
+ [infallible] attribute boolean hasUserActivation;
+
+ /** Referrer Info*/
+ [infallible] attribute nsIReferrerInfo referrerInfo;
+
+ /** Content viewer, for fast restoration of presentation */
+ [infallible] attribute nsIContentViewer contentViewer;
+
+ [infallible] readonly attribute boolean isInBFCache;
+
+ /** Whether the content viewer is marked "sticky" */
+ [infallible] attribute boolean sticky;
+
+ /** Saved state of the global window object */
+ [infallible] attribute nsISupports windowState;
+
+ /** Saved refresh URI list for the content viewer */
+ [infallible] attribute nsIMutableArray refreshURIList;
+
+ /** Post Data for the document */
+ [infallible] attribute nsIInputStream postData;
+ [infallible] readonly attribute boolean hasPostData;
+
+ /** LayoutHistoryState for scroll position and form values */
+ [infallible] attribute nsILayoutHistoryState layoutHistoryState;
+
+ /** parent of this entry */
+ [infallible] attribute nsISHEntry parent;
+
+ /**
+ * The loadType for this entry. This is typically loadHistory except
+ * when reload is pressed, it has the appropriate reload flag
+ */
+ [infallible] attribute unsigned long loadType;
+
+ /**
+ * An ID to help identify this entry from others during
+ * subframe navigation
+ */
+ [infallible] attribute unsigned long ID;
+
+ /** The cache key for the entry */
+ [infallible] attribute unsigned long cacheKey;
+
+ /** Should the layoutHistoryState be saved? */
+ [infallible] attribute boolean saveLayoutStateFlag;
+
+ /**
+ * attribute to indicate the content-type of the document that this
+ * is a session history entry for
+ */
+ // XXX: make it [infallible] when ACString supports that (bug 1491187).
+ attribute ACString contentType;
+
+ /**
+ * If we created this SHEntry via history.pushState or modified it via
+ * history.replaceState, and if we changed the SHEntry's URI via the
+ * push/replaceState call, and if the SHEntry's new URI differs from its
+ * old URI by more than just the hash, then we set this field to true.
+ *
+ * Additionally, if this SHEntry was created by calling pushState from a
+ * SHEntry whose URI was modified, this SHEntry's URIWasModified field is
+ * true.
+ */
+ [infallible] attribute boolean URIWasModified;
+
+ /**
+ * Get the principal, if any, that was associated with the channel
+ * that the document that was loaded to create this history entry
+ * came from.
+ */
+ [infallible] attribute nsIPrincipal triggeringPrincipal;
+
+ /**
+ * Get the principal, if any, that is used when the inherit flag
+ * is set.
+ */
+ [infallible] attribute nsIPrincipal principalToInherit;
+
+ /**
+ * Get the storage principal, if any, that is used when the inherit flag is
+ * set.
+ */
+ [infallible] attribute nsIPrincipal partitionedPrincipalToInherit;
+
+ /**
+ * Get the csp, if any, that was used for this document load. That
+ * is not the CSP that was applied to subresource loads within the
+ * document, but the CSP that was applied to this document load.
+ */
+ [infallible] attribute nsIContentSecurityPolicy csp;
+
+ /**
+ * Get/set data associated with this history state via a pushState() call,
+ * serialized using structured clone.
+ **/
+ [infallible] attribute nsIStructuredCloneContainer stateData;
+
+ /**
+ * The history ID of the docshell.
+ */
+ // Would be [infallible], but we don't support that property for nsIDPtr.
+ attribute nsIDRef docshellID;
+
+ /**
+ * True if this SHEntry corresponds to a document created by a srcdoc
+ * iframe. Set when a value is assigned to srcdocData.
+ */
+ [infallible] readonly attribute boolean isSrcdocEntry;
+
+ /**
+ * Contents of the srcdoc attribute in a srcdoc iframe to be loaded instead
+ * of the URI. Similar to a Data URI, this information is needed to
+ * recreate the document at a later stage.
+ * Setting this sets isSrcdocEntry to true
+ */
+ // XXX: make it [infallible] when AString supports that (bug 1491187).
+ attribute AString srcdocData;
+
+ /**
+ * When isSrcdocEntry is true, this contains the baseURI of the srcdoc
+ * document for use in situations where it cannot otherwise be determined,
+ * for example with view-source.
+ */
+ [infallible] attribute nsIURI baseURI;
+
+ /**
+ * Sets/gets the current scroll restoration state,
+ * if true == "manual", false == "auto".
+ */
+ [infallible] attribute boolean scrollRestorationIsManual;
+
+ /**
+ * Flag to indicate that the history entry was originally loaded in the
+ * current process. This flag does not survive a browser process switch.
+ */
+ [infallible] readonly attribute boolean loadedInThisProcess;
+
+ /**
+ * The session history it belongs to. This is set only on the root entries.
+ */
+ [noscript, infallible] attribute nsISHistory shistory;
+
+ /**
+ * A number that is assigned by the sHistory when the entry is activated
+ */
+ [noscript, infallible] attribute unsigned long lastTouched;
+
+ /**
+ * The current number of nsISHEntries which are immediate children of this
+ * SHEntry.
+ */
+ [infallible] readonly attribute long childCount;
+
+ /**
+ * When an entry is serving is within nsISHistory's array of entries, this
+ * property specifies if it should persist. If not it will be replaced by
+ * new additions to the list.
+ */
+ [infallible] attribute boolean persist;
+
+ /**
+ * Set/Get the visual viewport scroll position if session history is
+ * changed through anchor navigation or pushState.
+ */
+ void setScrollPosition(in long x, in long y);
+ void getScrollPosition(out long x, out long y);
+
+ /**
+ * Saved position and dimensions of the content viewer; we must adjust the
+ * root view's widget accordingly if this has changed when the presentation
+ * is restored.
+ */
+ [noscript, notxpcom] void getViewerBounds(in nsIntRect bounds);
+ [noscript, notxpcom] void setViewerBounds([const] in nsIntRect bounds);
+
+ /**
+ * Saved child docshells corresponding to contentViewer. The child shells
+ * are restored as children of the parent docshell, in this order, when the
+ * parent docshell restores a saved presentation.
+ */
+
+ /** Append a child shell to the end of our list. */
+ [noscript, notxpcom] void addChildShell(in nsIDocShellTreeItem shell);
+
+ /**
+ * Get the child shell at |index|; returns null if |index| is out of bounds.
+ */
+ [noscript] nsIDocShellTreeItem childShellAt(in long index);
+
+ /**
+ * Clear the child shell list.
+ */
+ [noscript, notxpcom] void clearChildShells();
+
+ /**
+ * Ensure that the cached presentation members are self-consistent.
+ * If either |contentViewer| or |windowState| are null, then all of the
+ * following members are cleared/reset:
+ * contentViewer, sticky, windowState, viewerBounds, childShells,
+ * refreshURIList.
+ */
+ [noscript, notxpcom] void syncPresentationState();
+
+ /**
+ * Initialises `layoutHistoryState` if it doesn't already exist
+ * and returns a reference to it.
+ */
+ nsILayoutHistoryState initLayoutHistoryState();
+
+ /** Additional ways to create an entry */
+ [noscript] void create(in nsIURI URI, in AString title,
+ in nsIInputStream inputStream,
+ in unsigned long cacheKey,
+ in ACString contentType,
+ in nsIPrincipal triggeringPrincipal,
+ in nsIPrincipal principalToInherit,
+ in nsIPrincipal partitionedPrincipalToInherit,
+ in nsIContentSecurityPolicy aCsp,
+ in nsIDRef docshellID,
+ in boolean dynamicCreation,
+ in nsIURI originalURI,
+ in nsIURI resultPrincipalURI,
+ in nsIURI unstrippedURI,
+ in bool loadReplace,
+ in nsIReferrerInfo referrerInfo,
+ in AString srcdoc,
+ in bool srcdocEntry,
+ in nsIURI baseURI,
+ in bool saveLayoutState,
+ in bool expired,
+ in bool userActivation);
+
+ nsISHEntry clone();
+
+ /**
+ * Gets the owning pointer to the editor data assosicated with
+ * this shistory entry. This forgets its pointer, so free it when
+ * you're done.
+ */
+ [noscript, notxpcom] nsDocShellEditorDataPtr forgetEditorData();
+
+ /**
+ * Sets the owning pointer to the editor data assosicated with
+ * this shistory entry. Unless forgetEditorData() is called, this
+ * shentry will destroy the editor data when it's destroyed.
+ */
+ [noscript, notxpcom] void setEditorData(in nsDocShellEditorDataPtr aData);
+
+ /** Returns true if this shistory entry is storing a detached editor. */
+ [noscript, notxpcom] boolean hasDetachedEditor();
+
+ /**
+ * Returns true if the related docshell was added because of
+ * dynamic addition of an iframe/frame.
+ */
+ [noscript, notxpcom] boolean isDynamicallyAdded();
+
+ /**
+ * Returns true if any of the child entries returns true
+ * when isDynamicallyAdded is called on it.
+ */
+ boolean hasDynamicallyAddedChild();
+
+ /**
+ * Does this SHEntry point to the given BFCache entry? If so, evicting
+ * the BFCache entry will evict the SHEntry, since the two entries
+ * correspond to the same document.
+ */
+ [noscript, notxpcom]
+ boolean hasBFCacheEntry(in SHEntrySharedParentStatePtr aEntry);
+
+ /**
+ * Adopt aEntry's BFCacheEntry, so now both this and aEntry point to
+ * aEntry's BFCacheEntry.
+ */
+ void adoptBFCacheEntry(in nsISHEntry aEntry);
+
+ /**
+ * Create a new BFCache entry and drop our reference to our old one. This
+ * call unlinks this SHEntry from any other SHEntries for its document.
+ */
+ void abandonBFCacheEntry();
+
+ /**
+ * Does this SHEntry correspond to the same document as aEntry? This is
+ * true iff the two SHEntries have the same BFCacheEntry. So in particular,
+ * sharesDocumentWith(aEntry) is guaranteed to return true if it's
+ * preceded by a call to adoptBFCacheEntry(aEntry).
+ */
+ boolean sharesDocumentWith(in nsISHEntry aEntry);
+
+ /**
+ * Sets an SHEntry to reflect that it is a history type load. This is the
+ * equivalent to doing
+ *
+ * shEntry.loadType = 4;
+ *
+ * in js, but is easier to maintain and less opaque.
+ */
+ void setLoadTypeAsHistory();
+
+ /**
+ * Add a new child SHEntry. If offset is -1 adds to the end of the list.
+ */
+ void AddChild(in nsISHEntry aChild, in long aOffset,
+ [optional,default(false)] in bool aUseRemoteSubframes);
+
+ /**
+ * Remove a child SHEntry.
+ */
+ [noscript] void RemoveChild(in nsISHEntry aChild);
+
+ /**
+ * Get child at an index.
+ */
+ nsISHEntry GetChildAt(in long aIndex);
+
+ /**
+ * If this entry has no dynamically added child, get the child SHEntry
+ * at the given offset. The loadtype of the returned entry is set
+ * to its parent's loadtype.
+ */
+ [notxpcom] void GetChildSHEntryIfHasNoDynamicallyAddedChild(in long aChildOffset,
+ out nsISHEntry aChild);
+
+ /**
+ * Replaces a child which is for the same docshell as aNewChild
+ * with aNewChild.
+ * @throw if nothing was replaced.
+ */
+ [noscript] void ReplaceChild(in nsISHEntry aNewChild);
+
+ /**
+ * Remove all children of this entry and call abandonBFCacheEntry.
+ */
+ [notxpcom] void ClearEntry();
+
+ /**
+ * Create nsDocShellLoadState and fill it with information.
+ * Don't set nsSHEntry here to avoid serializing it.
+ */
+ [noscript] nsDocShellLoadStatePtr CreateLoadInfo();
+
+ [infallible] readonly attribute unsigned long long bfcacheID;
+
+ /**
+ * Sync up the docshell and session history trees for subframe navigation.
+ *
+ * @param aEntry new entry
+ * @param aTopBC top BC corresponding to the root ancestor
+ of the docshell that called this method
+ * @param aIgnoreBC current BC
+ */
+ [notxpcom] void SyncTreesForSubframeNavigation(in nsISHEntry aEntry,
+ in BrowsingContext aTopBC,
+ in BrowsingContext aIgnoreBC);
+
+ /**
+ * If browser.history.collectWireframes is true, this will get populated
+ * with a Wireframe upon document navigation / pushState. This will only
+ * be set for nsISHEntry's accessed in the parent process with
+ * sessionHistoryInParent enabled. See Document.webidl for more details on
+ * what a Wireframe is.
+ */
+ [implicit_jscontext] attribute jsval wireframe;
+};
diff --git a/docshell/shistory/nsISHistory.idl b/docshell/shistory/nsISHistory.idl
new file mode 100644
index 0000000000..8ea23693d9
--- /dev/null
+++ b/docshell/shistory/nsISHistory.idl
@@ -0,0 +1,291 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIBFCacheEntry;
+interface nsISHEntry;
+interface nsISHistoryListener;
+interface nsIURI;
+webidl BrowsingContext;
+
+%{C++
+#include "nsTArrayForwardDeclare.h"
+#include "mozilla/Maybe.h"
+struct EntriesAndBrowsingContextData;
+namespace mozilla {
+namespace dom {
+class SHEntrySharedParentState;
+} // namespace dom
+} // namespace mozilla
+%}
+
+[ref] native nsDocshellIDArray(nsTArray<nsID>);
+native MaybeInt32(mozilla::Maybe<int32_t>);
+[ptr] native SHEntrySharedParentStatePtr(mozilla::dom::SHEntrySharedParentState);
+/**
+ * An interface to the primary properties of the Session History
+ * component. In an embedded browser environment, the nsIWebBrowser
+ * object creates an instance of session history for each open window.
+ * A handle to the session history object can be obtained from
+ * nsIWebNavigation. In a non-embedded situation, the owner of the
+ * session history component must create a instance of it and set
+ * it in the nsIWebNavigation object.
+ * This interface is accessible from javascript.
+ */
+
+[builtinclass, scriptable, uuid(7b807041-e60a-4384-935f-af3061d8b815)]
+interface nsISHistory: nsISupports
+{
+ /**
+ * A readonly property of the interface that returns
+ * the number of toplevel documents currently available
+ * in session history.
+ */
+ [infallible] readonly attribute long count;
+
+ /**
+ * The index of the current document in session history. Not infallible
+ * because setting can fail if the assigned value is out of range.
+ */
+ attribute long index;
+
+ /**
+ * A readonly property of the interface that returns
+ * the index of the last document that started to load and
+ * didn't finished yet. When document finishes the loading
+ * value -1 is returned.
+ */
+ [infallible] readonly attribute long requestedIndex;
+
+ /**
+ * Artifically set the |requestedIndex| for this nsISHEntry to the given
+ * index. This is used when resuming a cross-process load from a different
+ * process.
+ */
+ [noscript, notxpcom]
+ void internalSetRequestedIndex(in long aRequestedIndex);
+
+ /**
+ * Get the history entry at a given index. Returns non-null on success.
+ *
+ * @param index The index value whose entry is requested.
+ * The oldest entry is located at index == 0.
+ * @return The found entry; never null.
+ */
+ nsISHEntry getEntryAtIndex(in long aIndex);
+
+ /**
+ * Called to purge older documents from history.
+ * Documents can be removed from session history for various
+ * reasons. For example to control memory usage of the browser, to
+ * prevent users from loading documents from history, to erase evidence of
+ * prior page loads etc...
+ *
+ * @param numEntries The number of toplevel documents to be
+ * purged from history. During purge operation,
+ * the latest documents are maintained and older
+ * 'numEntries' documents are removed from history.
+ * @throws <code>NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA</code>
+ * Purge was vetod.
+ * @throws <code>NS_ERROR_FAILURE</code> numEntries is
+ * invalid or out of bounds with the size of history.
+ */
+ void purgeHistory(in long aNumEntries);
+
+ /**
+ * Called to register a listener for the session history component.
+ * Listeners are notified when pages are loaded or purged from history.
+ *
+ * @param aListener Listener object to be notified for all
+ * page loads that initiate in session history.
+ *
+ * @note A listener object must implement
+ * nsISHistoryListener and nsSupportsWeakReference
+ *
+ * @see nsISHistoryListener
+ * @see nsSupportsWeakReference
+ */
+ void addSHistoryListener(in nsISHistoryListener aListener);
+
+ /**
+ * Called to remove a listener for the session history component.
+ * Listeners are notified when pages are loaded from history.
+ *
+ * @param aListener Listener object to be removed from
+ * session history.
+ *
+ * @note A listener object must implement
+ * nsISHistoryListener and nsSupportsWeakReference
+ * @see nsISHistoryListener
+ * @see nsSupportsWeakReference
+ */
+ void removeSHistoryListener(in nsISHistoryListener aListener);
+
+ void reloadCurrentEntry();
+
+ /**
+ * Load the entry at the particular index.
+ */
+ [noscript]
+ void gotoIndex(in long aIndex, in boolean aUserActivation);
+
+ /**
+ * If an element exists at the particular index and
+ * whether it has user interaction.
+ */
+ [noscript,notxpcom]
+ boolean hasUserInteractionAtIndex(in long aIndex);
+
+ /**
+ * Called to obtain the index to a given history entry.
+ *
+ * @param aEntry The entry to obtain the index of.
+ *
+ * @return <code>NS_OK</code> index for the history entry
+ * is obtained successfully.
+ * <code>NS_ERROR_FAILURE</code> Error in obtaining
+ * index for the given history entry.
+ */
+ [noscript, notxpcom]
+ long getIndexOfEntry(in nsISHEntry aEntry);
+
+ /**
+ * Add a new Entry to the History List.
+ *
+ * @param aEntry The entry to add.
+ * @param aPersist If true this specifies that the entry should
+ * persist in the list. If false, this means that
+ * when new entries are added this element will not
+ * appear in the session history list.
+ */
+ void addEntry(in nsISHEntry aEntry, in boolean aPersist);
+
+ /**
+ * Update the index maintained by sessionHistory
+ */
+ void updateIndex();
+
+ /**
+ * Replace the nsISHEntry at a particular index
+ *
+ * @param aIndex The index at which the entry should be replaced.
+ * @param aReplaceEntry The replacement entry for the index.
+ */
+ void replaceEntry(in long aIndex, in nsISHEntry aReplaceEntry);
+
+ /**
+ * Notifies all registered session history listeners about an impending
+ * reload.
+ *
+ * @return Whether the operation can proceed.
+ */
+ boolean notifyOnHistoryReload();
+
+ /**
+ * Evict content viewers which don't lie in the "safe" range around aIndex.
+ * In practice, this should leave us with no more than gHistoryMaxViewers
+ * viewers associated with this SHistory object.
+ *
+ * Also make sure that the total number of content viewers in all windows is
+ * not greater than our global max; if it is, evict viewers as appropriate.
+ *
+ * @param aIndex The index around which the "safe" range is
+ * centered. In general, if you just navigated the
+ * history, aIndex should be the index history was
+ * navigated to.
+ */
+ void evictOutOfRangeContentViewers(in long aIndex);
+
+ /**
+ * Evict the content viewer associated with a bfcache entry that has timed
+ * out.
+ */
+ [noscript, notxpcom]
+ void evictExpiredContentViewerForEntry(in SHEntrySharedParentStatePtr aEntry);
+
+ /**
+ * Evict all the content viewers in this session history
+ */
+ void evictAllContentViewers();
+
+ /**
+ * Add a BFCache entry to expiration tracker so it gets evicted on
+ * expiration.
+ */
+ [noscript, notxpcom]
+ void addToExpirationTracker(in SHEntrySharedParentStatePtr aEntry);
+
+ /**
+ * Remove a BFCache entry from expiration tracker.
+ */
+ [noscript, notxpcom]
+ void removeFromExpirationTracker(in SHEntrySharedParentStatePtr aEntry);
+
+ /**
+ * Remove dynamic entries found at given index.
+ *
+ * @param aIndex Index to remove dynamic entries from. It will be
+ * passed to RemoveEntries as aStartIndex.
+ * @param aEntry (optional) The entry to start looking in for dynamic
+ * entries. Only the dynamic descendants of the
+ * entry will be removed. If not given, all dynamic
+ * entries at the index will be removed.
+ */
+ [noscript, notxpcom]
+ void RemoveDynEntries(in long aIndex, in nsISHEntry aEntry);
+
+ /**
+ * Similar to RemoveDynEntries, but instead of specifying an index, use the
+ * given BFCacheEntry to find the index and remove dynamic entries from the
+ * index.
+ *
+ * The method takes no effect if the bfcache entry is not or no longer hold
+ * by the SHistory instance.
+ *
+ * @param aEntry The bfcache entry to look up for index to remove
+ * dynamic entries from.
+ */
+ [noscript, notxpcom]
+ void RemoveDynEntriesForBFCacheEntry(in nsIBFCacheEntry aEntry);
+
+ /**
+ * Removes entries from the history if their docshellID is in
+ * aIDs array.
+ */
+ [noscript, notxpcom]
+ void RemoveEntries(in nsDocshellIDArray aIDs, in long aStartIndex);
+
+ /**
+ * Collect docshellIDs from aEntry's children and remove those
+ * entries from history.
+ *
+ * @param aEntry Children docshellID's will be collected from
+ * this entry and passed to RemoveEntries as aIDs.
+ */
+ [noscript, notxpcom]
+ void RemoveFrameEntries(in nsISHEntry aEntry);
+
+ [noscript]
+ void Reload(in unsigned long aReloadFlags);
+
+ [notxpcom] void EnsureCorrectEntryAtCurrIndex(in nsISHEntry aEntry);
+
+ [notxpcom] void EvictContentViewersOrReplaceEntry(in nsISHEntry aNewSHEntry, in bool aReplace);
+
+ nsISHEntry createEntry();
+
+ [noscript] void AddToRootSessionHistory(in bool aCloneChildren, in nsISHEntry aOSHE,
+ in BrowsingContext aRootBC, in nsISHEntry aEntry,
+ in unsigned long aLoadType,
+ in bool aShouldPersist,
+ out MaybeInt32 aPreviousEntryIndex,
+ out MaybeInt32 aLoadedEntryIndex);
+
+ [noscript] void AddChildSHEntryHelper(in nsISHEntry aCloneRef, in nsISHEntry aNewEntry,
+ in BrowsingContext aRootBC, in bool aCloneChildren);
+
+ [noscript, notxpcom] boolean isEmptyOrHasEntriesForSingleTopLevelPage();
+};
diff --git a/docshell/shistory/nsISHistoryListener.idl b/docshell/shistory/nsISHistoryListener.idl
new file mode 100644
index 0000000000..569cb25dca
--- /dev/null
+++ b/docshell/shistory/nsISHistoryListener.idl
@@ -0,0 +1,88 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+
+/**
+ * nsISHistoryListener defines the interface one can implement to receive
+ * notifications about activities in session history and (for reloads) to be
+ * able to cancel them.
+ *
+ * A session history listener will be notified when pages are added, removed
+ * and loaded from session history. In the case of reloads, it can prevent them
+ * from happening by returning false from the corresponding callback method.
+ *
+ * A session history listener can be registered on a particular nsISHistory
+ * instance via the nsISHistory::addSHistoryListener() method.
+ *
+ * Listener methods should not alter the session history. Things are likely to
+ * go haywire if they do.
+ */
+[scriptable, uuid(125c0833-746a-400e-9b89-d2d18545c08a)]
+interface nsISHistoryListener : nsISupports
+{
+ /**
+ * Called when a new document is added to session history. New documents are
+ * added to session history by docshell when new pages are loaded in a frame
+ * or content area, for example via nsIWebNavigation::loadURI()
+ *
+ * @param aNewURI The URI of the document to be added to session history.
+ * @param aOldIndex The index of the current history item before the
+ * operation.
+ */
+ void OnHistoryNewEntry(in nsIURI aNewURI, in long aOldIndex);
+
+ /**
+ * Called before the current document is reloaded, for example due to a
+ * nsIWebNavigation::reload() call.
+ */
+ boolean OnHistoryReload();
+
+ /**
+ * Called before navigating to a session history entry by index, for example,
+ * when nsIWebNavigation::gotoIndex() is called.
+ */
+ void OnHistoryGotoIndex();
+
+ /**
+ * Called before entries are removed from the start of session history.
+ * Entries can be removed from session history for various reasons, for
+ * example to control the memory usage of the browser, to prevent users from
+ * loading documents from history, to erase evidence of prior page loads, etc.
+ *
+ * To purge documents from session history call nsISHistory::PurgeHistory().
+ *
+ * @param aNumEntries The number of entries being removed.
+ */
+ void OnHistoryPurge(in long aNumEntries);
+
+ /**
+ * Called before entries are removed from the end of session history. This
+ * occurs when navigating to a new page while on a previous session entry.
+ *
+ * @param aNumEntries The number of entries being removed.
+ */
+ void OnHistoryTruncate(in long aNumEntries);
+
+ /**
+ * Called before an entry is replaced in the session history. Entries are
+ * replaced when navigating away from non-persistent history entries (such as
+ * about pages) and when history.replaceState is called.
+ */
+ void OnHistoryReplaceEntry();
+
+
+ /**
+ * Called whenever a content viewer is evicted. A content viewer is evicted
+ * whenever a bfcache entry has timed out or the number of total content
+ * viewers has exceeded the global max. This is used for testing only.
+ *
+ * @param aNumEvicted - number of content viewers evicted
+ */
+ void OnContentViewerEvicted(in unsigned long aNumEvicted);
+};
diff --git a/docshell/shistory/nsSHEntry.cpp b/docshell/shistory/nsSHEntry.cpp
new file mode 100644
index 0000000000..0b250c33ff
--- /dev/null
+++ b/docshell/shistory/nsSHEntry.cpp
@@ -0,0 +1,1131 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSHEntry.h"
+
+#include <algorithm>
+
+#include "nsDocShell.h"
+#include "nsDocShellEditorData.h"
+#include "nsDocShellLoadState.h"
+#include "nsDocShellLoadTypes.h"
+#include "nsIContentSecurityPolicy.h"
+#include "nsIContentViewer.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIInputStream.h"
+#include "nsILayoutHistoryState.h"
+#include "nsIMutableArray.h"
+#include "nsIStructuredCloneContainer.h"
+#include "nsIURI.h"
+#include "nsSHEntryShared.h"
+#include "nsSHistory.h"
+
+#include "mozilla/Logging.h"
+#include "nsIReferrerInfo.h"
+
+extern mozilla::LazyLogModule gPageCacheLog;
+
+static uint32_t gEntryID = 0;
+
+nsSHEntry::nsSHEntry()
+ : mShared(new nsSHEntryShared()),
+ mLoadType(0),
+ mID(++gEntryID), // SessionStore has special handling for 0 values.
+ mScrollPositionX(0),
+ mScrollPositionY(0),
+ mLoadReplace(false),
+ mURIWasModified(false),
+ mIsSrcdocEntry(false),
+ mScrollRestorationIsManual(false),
+ mLoadedInThisProcess(false),
+ mPersist(true),
+ mHasUserInteraction(false),
+ mHasUserActivation(false) {}
+
+nsSHEntry::nsSHEntry(const nsSHEntry& aOther)
+ : mShared(aOther.mShared),
+ mURI(aOther.mURI),
+ mOriginalURI(aOther.mOriginalURI),
+ mResultPrincipalURI(aOther.mResultPrincipalURI),
+ mUnstrippedURI(aOther.mUnstrippedURI),
+ mReferrerInfo(aOther.mReferrerInfo),
+ mTitle(aOther.mTitle),
+ mPostData(aOther.mPostData),
+ mLoadType(0), // XXX why not copy?
+ mID(aOther.mID),
+ mScrollPositionX(0), // XXX why not copy?
+ mScrollPositionY(0), // XXX why not copy?
+ mParent(aOther.mParent),
+ mStateData(aOther.mStateData),
+ mSrcdocData(aOther.mSrcdocData),
+ mBaseURI(aOther.mBaseURI),
+ mLoadReplace(aOther.mLoadReplace),
+ mURIWasModified(aOther.mURIWasModified),
+ mIsSrcdocEntry(aOther.mIsSrcdocEntry),
+ mScrollRestorationIsManual(false),
+ mLoadedInThisProcess(aOther.mLoadedInThisProcess),
+ mPersist(aOther.mPersist),
+ mHasUserInteraction(false),
+ mHasUserActivation(aOther.mHasUserActivation) {}
+
+nsSHEntry::~nsSHEntry() {
+ // Null out the mParent pointers on all our kids.
+ for (nsISHEntry* entry : mChildren) {
+ if (entry) {
+ entry->SetParent(nullptr);
+ }
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsSHEntry, nsISHEntry, nsISupportsWeakReference)
+
+NS_IMETHODIMP
+nsSHEntry::SetScrollPosition(int32_t aX, int32_t aY) {
+ mScrollPositionX = aX;
+ mScrollPositionY = aY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetScrollPosition(int32_t* aX, int32_t* aY) {
+ *aX = mScrollPositionX;
+ *aY = mScrollPositionY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetURIWasModified(bool* aOut) {
+ *aOut = mURIWasModified;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetURIWasModified(bool aIn) {
+ mURIWasModified = aIn;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetURI(nsIURI** aURI) {
+ *aURI = mURI;
+ NS_IF_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetURI(nsIURI* aURI) {
+ mURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetOriginalURI(nsIURI** aOriginalURI) {
+ *aOriginalURI = mOriginalURI;
+ NS_IF_ADDREF(*aOriginalURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetOriginalURI(nsIURI* aOriginalURI) {
+ mOriginalURI = aOriginalURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetResultPrincipalURI(nsIURI** aResultPrincipalURI) {
+ *aResultPrincipalURI = mResultPrincipalURI;
+ NS_IF_ADDREF(*aResultPrincipalURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetResultPrincipalURI(nsIURI* aResultPrincipalURI) {
+ mResultPrincipalURI = aResultPrincipalURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetUnstrippedURI(nsIURI** aUnstrippedURI) {
+ *aUnstrippedURI = mUnstrippedURI;
+ NS_IF_ADDREF(*aUnstrippedURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetUnstrippedURI(nsIURI* aUnstrippedURI) {
+ mUnstrippedURI = aUnstrippedURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetLoadReplace(bool* aLoadReplace) {
+ *aLoadReplace = mLoadReplace;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetLoadReplace(bool aLoadReplace) {
+ mLoadReplace = aLoadReplace;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetReferrerInfo(nsIReferrerInfo** aReferrerInfo) {
+ *aReferrerInfo = mReferrerInfo;
+ NS_IF_ADDREF(*aReferrerInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
+ mReferrerInfo = aReferrerInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetSticky(bool aSticky) {
+ mShared->mSticky = aSticky;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetSticky(bool* aSticky) {
+ *aSticky = mShared->mSticky;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetTitle(nsAString& aTitle) {
+ // Check for empty title...
+ if (mTitle.IsEmpty() && mURI) {
+ // Default title is the URL.
+ nsAutoCString spec;
+ if (NS_SUCCEEDED(mURI->GetSpec(spec))) {
+ AppendUTF8toUTF16(spec, mTitle);
+ }
+ }
+
+ aTitle = mTitle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetTitle(const nsAString& aTitle) {
+ mTitle = aTitle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetName(nsAString& aName) {
+ aName = mName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetName(const nsAString& aName) {
+ mName = aName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetPostData(nsIInputStream** aResult) {
+ *aResult = mPostData;
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetPostData(nsIInputStream* aPostData) {
+ mPostData = aPostData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetHasPostData(bool* aResult) {
+ *aResult = !!mPostData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetLayoutHistoryState(nsILayoutHistoryState** aResult) {
+ *aResult = mShared->mLayoutHistoryState;
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetLayoutHistoryState(nsILayoutHistoryState* aState) {
+ mShared->mLayoutHistoryState = aState;
+ if (mShared->mLayoutHistoryState) {
+ mShared->mLayoutHistoryState->SetScrollPositionOnly(
+ !mShared->mSaveLayoutState);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::InitLayoutHistoryState(nsILayoutHistoryState** aState) {
+ if (!mShared->mLayoutHistoryState) {
+ nsCOMPtr<nsILayoutHistoryState> historyState;
+ historyState = NS_NewLayoutHistoryState();
+ SetLayoutHistoryState(historyState);
+ }
+
+ nsCOMPtr<nsILayoutHistoryState> state = GetLayoutHistoryState();
+ state.forget(aState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetLoadType(uint32_t* aResult) {
+ *aResult = mLoadType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetLoadType(uint32_t aLoadType) {
+ mLoadType = aLoadType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetID(uint32_t* aResult) {
+ *aResult = mID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetID(uint32_t aID) {
+ mID = aID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetIsSubFrame(bool* aFlag) {
+ *aFlag = mShared->mIsFrameNavigation;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetIsSubFrame(bool aFlag) {
+ mShared->mIsFrameNavigation = aFlag;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetHasUserInteraction(bool* aFlag) {
+ // The back button and menulist deal with root/top-level
+ // session history entries, thus we annotate only the root entry.
+ if (!mParent) {
+ *aFlag = mHasUserInteraction;
+ } else {
+ nsCOMPtr<nsISHEntry> root = nsSHistory::GetRootSHEntry(this);
+ root->GetHasUserInteraction(aFlag);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetHasUserInteraction(bool aFlag) {
+ // The back button and menulist deal with root/top-level
+ // session history entries, thus we annotate only the root entry.
+ if (!mParent) {
+ mHasUserInteraction = aFlag;
+ } else {
+ nsCOMPtr<nsISHEntry> root = nsSHistory::GetRootSHEntry(this);
+ root->SetHasUserInteraction(aFlag);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetHasUserActivation(bool* aFlag) {
+ *aFlag = mHasUserActivation;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetHasUserActivation(bool aFlag) {
+ mHasUserActivation = aFlag;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetCacheKey(uint32_t* aResult) {
+ *aResult = mShared->mCacheKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetCacheKey(uint32_t aCacheKey) {
+ mShared->mCacheKey = aCacheKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetContentType(nsACString& aContentType) {
+ aContentType = mShared->mContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetContentType(const nsACString& aContentType) {
+ mShared->mContentType = aContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::Create(
+ nsIURI* aURI, const nsAString& aTitle, nsIInputStream* aInputStream,
+ uint32_t aCacheKey, const nsACString& aContentType,
+ nsIPrincipal* aTriggeringPrincipal, nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp, const nsID& aDocShellID,
+ bool aDynamicCreation, nsIURI* aOriginalURI, nsIURI* aResultPrincipalURI,
+ nsIURI* aUnstrippedURI, bool aLoadReplace, nsIReferrerInfo* aReferrerInfo,
+ const nsAString& aSrcdocData, bool aSrcdocEntry, nsIURI* aBaseURI,
+ bool aSaveLayoutState, bool aExpired, bool aUserActivation) {
+ MOZ_ASSERT(
+ aTriggeringPrincipal,
+ "need a valid triggeringPrincipal to create a session history entry");
+
+ mURI = aURI;
+ mTitle = aTitle;
+ mPostData = aInputStream;
+
+ // Set the LoadType by default to loadHistory during creation
+ mLoadType = LOAD_HISTORY;
+
+ mShared->mCacheKey = aCacheKey;
+ mShared->mContentType = aContentType;
+ mShared->mTriggeringPrincipal = aTriggeringPrincipal;
+ mShared->mPrincipalToInherit = aPrincipalToInherit;
+ mShared->mPartitionedPrincipalToInherit = aPartitionedPrincipalToInherit;
+ mShared->mCsp = aCsp;
+ mShared->mDocShellID = aDocShellID;
+ mShared->mDynamicallyCreated = aDynamicCreation;
+
+ // By default all entries are set false for subframe flag.
+ // nsDocShell::CloneAndReplace() which creates entries for
+ // all subframe navigations, sets the flag to true.
+ mShared->mIsFrameNavigation = false;
+
+ mHasUserInteraction = false;
+
+ mShared->mExpired = aExpired;
+
+ mIsSrcdocEntry = aSrcdocEntry;
+ mSrcdocData = aSrcdocData;
+
+ mBaseURI = aBaseURI;
+
+ mLoadedInThisProcess = true;
+
+ mOriginalURI = aOriginalURI;
+ mResultPrincipalURI = aResultPrincipalURI;
+ mUnstrippedURI = aUnstrippedURI;
+ mLoadReplace = aLoadReplace;
+ mReferrerInfo = aReferrerInfo;
+
+ mHasUserActivation = aUserActivation;
+
+ mShared->mLayoutHistoryState = nullptr;
+
+ mShared->mSaveLayoutState = aSaveLayoutState;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetParent(nsISHEntry** aResult) {
+ nsCOMPtr<nsISHEntry> parent = do_QueryReferent(mParent);
+ parent.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetParent(nsISHEntry* aParent) {
+ mParent = do_GetWeakReference(aParent);
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsSHEntry::SetViewerBounds(const nsIntRect& aBounds) {
+ mShared->mViewerBounds = aBounds;
+}
+
+NS_IMETHODIMP_(void)
+nsSHEntry::GetViewerBounds(nsIntRect& aBounds) {
+ aBounds = mShared->mViewerBounds;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetTriggeringPrincipal(nsIPrincipal** aTriggeringPrincipal) {
+ NS_IF_ADDREF(*aTriggeringPrincipal = mShared->mTriggeringPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetTriggeringPrincipal(nsIPrincipal* aTriggeringPrincipal) {
+ mShared->mTriggeringPrincipal = aTriggeringPrincipal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetPrincipalToInherit(nsIPrincipal** aPrincipalToInherit) {
+ NS_IF_ADDREF(*aPrincipalToInherit = mShared->mPrincipalToInherit);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetPrincipalToInherit(nsIPrincipal* aPrincipalToInherit) {
+ mShared->mPrincipalToInherit = aPrincipalToInherit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetPartitionedPrincipalToInherit(
+ nsIPrincipal** aPartitionedPrincipalToInherit) {
+ NS_IF_ADDREF(*aPartitionedPrincipalToInherit =
+ mShared->mPartitionedPrincipalToInherit);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetPartitionedPrincipalToInherit(
+ nsIPrincipal* aPartitionedPrincipalToInherit) {
+ mShared->mPartitionedPrincipalToInherit = aPartitionedPrincipalToInherit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetCsp(nsIContentSecurityPolicy** aCsp) {
+ NS_IF_ADDREF(*aCsp = mShared->mCsp);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetCsp(nsIContentSecurityPolicy* aCsp) {
+ mShared->mCsp = aCsp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::AdoptBFCacheEntry(nsISHEntry* aEntry) {
+ nsSHEntryShared* shared = static_cast<nsSHEntry*>(aEntry)->mShared;
+ NS_ENSURE_STATE(shared);
+
+ mShared = shared;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SharesDocumentWith(nsISHEntry* aEntry, bool* aOut) {
+ NS_ENSURE_ARG_POINTER(aOut);
+
+ *aOut = mShared == static_cast<nsSHEntry*>(aEntry)->mShared;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetIsSrcdocEntry(bool* aIsSrcdocEntry) {
+ *aIsSrcdocEntry = mIsSrcdocEntry;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetSrcdocData(nsAString& aSrcdocData) {
+ aSrcdocData = mSrcdocData;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetSrcdocData(const nsAString& aSrcdocData) {
+ mSrcdocData = aSrcdocData;
+ mIsSrcdocEntry = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetBaseURI(nsIURI** aBaseURI) {
+ *aBaseURI = mBaseURI;
+ NS_IF_ADDREF(*aBaseURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetBaseURI(nsIURI* aBaseURI) {
+ mBaseURI = aBaseURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetScrollRestorationIsManual(bool* aIsManual) {
+ *aIsManual = mScrollRestorationIsManual;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetScrollRestorationIsManual(bool aIsManual) {
+ mScrollRestorationIsManual = aIsManual;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetLoadedInThisProcess(bool* aLoadedInThisProcess) {
+ *aLoadedInThisProcess = mLoadedInThisProcess;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetChildCount(int32_t* aCount) {
+ *aCount = mChildren.Count();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::AddChild(nsISHEntry* aChild, int32_t aOffset,
+ bool aUseRemoteSubframes) {
+ if (aChild) {
+ NS_ENSURE_SUCCESS(aChild->SetParent(this), NS_ERROR_FAILURE);
+ }
+
+ if (aOffset < 0) {
+ mChildren.AppendObject(aChild);
+ return NS_OK;
+ }
+
+ //
+ // Bug 52670: Ensure children are added in order.
+ //
+ // Later frames in the child list may load faster and get appended
+ // before earlier frames, causing session history to be scrambled.
+ // By growing the list here, they are added to the right position.
+ //
+ // Assert that aOffset will not be so high as to grow us a lot.
+ //
+ NS_ASSERTION(aOffset < (mChildren.Count() + 1023), "Large frames array!\n");
+
+ bool newChildIsDyn = aChild ? aChild->IsDynamicallyAdded() : false;
+
+ // If the new child is dynamically added, try to add it to aOffset, but if
+ // there are non-dynamically added children, the child must be after those.
+ if (newChildIsDyn) {
+ int32_t lastNonDyn = aOffset - 1;
+ for (int32_t i = aOffset; i < mChildren.Count(); ++i) {
+ nsISHEntry* entry = mChildren[i];
+ if (entry) {
+ if (entry->IsDynamicallyAdded()) {
+ break;
+ } else {
+ lastNonDyn = i;
+ }
+ }
+ }
+ // InsertObjectAt allows only appending one object.
+ // If aOffset is larger than Count(), we must first manually
+ // set the capacity.
+ if (aOffset > mChildren.Count()) {
+ mChildren.SetCount(aOffset);
+ }
+ if (!mChildren.InsertObjectAt(aChild, lastNonDyn + 1)) {
+ NS_WARNING("Adding a child failed!");
+ aChild->SetParent(nullptr);
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ // If the new child isn't dynamically added, it should be set to aOffset.
+ // If there are dynamically added children before that, those must be
+ // moved to be after aOffset.
+ if (mChildren.Count() > 0) {
+ int32_t start = std::min(mChildren.Count() - 1, aOffset);
+ int32_t dynEntryIndex = -1;
+ nsISHEntry* dynEntry = nullptr;
+ for (int32_t i = start; i >= 0; --i) {
+ nsISHEntry* entry = mChildren[i];
+ if (entry) {
+ if (entry->IsDynamicallyAdded()) {
+ dynEntryIndex = i;
+ dynEntry = entry;
+ } else {
+ break;
+ }
+ }
+ }
+
+ if (dynEntry) {
+ nsCOMArray<nsISHEntry> tmp;
+ tmp.SetCount(aOffset - dynEntryIndex + 1);
+ mChildren.InsertObjectsAt(tmp, dynEntryIndex);
+ NS_ASSERTION(mChildren[aOffset + 1] == dynEntry, "Whaat?");
+ }
+ }
+
+ // Make sure there isn't anything at aOffset.
+ if (aOffset < mChildren.Count()) {
+ nsISHEntry* oldChild = mChildren[aOffset];
+ if (oldChild && oldChild != aChild) {
+ // Under Fission, this can happen when a network-created iframe starts
+ // out in-process, moves out-of-process, and then switches back. At that
+ // point, we'll create a new network-created DocShell at the same index
+ // where we already have an entry for the original network-created
+ // DocShell.
+ //
+ // This should ideally stop being an issue once the Fission-aware
+ // session history rewrite is complete.
+ NS_ASSERTION(
+ aUseRemoteSubframes,
+ "Adding a child where we already have a child? This may misbehave");
+ oldChild->SetParent(nullptr);
+ }
+ }
+
+ mChildren.ReplaceObjectAt(aChild, aOffset);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::RemoveChild(nsISHEntry* aChild) {
+ NS_ENSURE_TRUE(aChild, NS_ERROR_FAILURE);
+ bool childRemoved = false;
+ if (aChild->IsDynamicallyAdded()) {
+ childRemoved = mChildren.RemoveObject(aChild);
+ } else {
+ int32_t index = mChildren.IndexOfObject(aChild);
+ if (index >= 0) {
+ // Other alive non-dynamic child docshells still keep mChildOffset,
+ // so we don't want to change the indices here.
+ mChildren.ReplaceObjectAt(nullptr, index);
+ childRemoved = true;
+ }
+ }
+ if (childRemoved) {
+ aChild->SetParent(nullptr);
+
+ // reduce the child count, i.e. remove empty children at the end
+ for (int32_t i = mChildren.Count() - 1; i >= 0 && !mChildren[i]; --i) {
+ if (!mChildren.RemoveObjectAt(i)) {
+ break;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetChildAt(int32_t aIndex, nsISHEntry** aResult) {
+ if (aIndex >= 0 && aIndex < mChildren.Count()) {
+ *aResult = mChildren[aIndex];
+ // yes, mChildren can have holes in it. AddChild's offset parameter makes
+ // that possible.
+ NS_IF_ADDREF(*aResult);
+ } else {
+ *aResult = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsSHEntry::GetChildSHEntryIfHasNoDynamicallyAddedChild(int32_t aChildOffset,
+ nsISHEntry** aChild) {
+ *aChild = nullptr;
+
+ bool dynamicallyAddedChild = false;
+ HasDynamicallyAddedChild(&dynamicallyAddedChild);
+ if (dynamicallyAddedChild) {
+ return;
+ }
+
+ // If the user did a shift-reload on this frameset page,
+ // we don't want to load the subframes from history.
+ if (IsForceReloadType(mLoadType) || mLoadType == LOAD_REFRESH) {
+ return;
+ }
+
+ /* Before looking for the subframe's url, check
+ * the expiration status of the parent. If the parent
+ * has expired from cache, then subframes will not be
+ * loaded from history in certain situations.
+ * If the user pressed reload and the parent frame has expired
+ * from cache, we do not want to load the child frame from history.
+ */
+ if (mShared->mExpired && (mLoadType == LOAD_RELOAD_NORMAL)) {
+ // The parent has expired. Return null.
+ *aChild = nullptr;
+ return;
+ }
+ // Get the child subframe from session history.
+ GetChildAt(aChildOffset, aChild);
+ if (*aChild) {
+ // Set the parent's Load Type on the child
+ (*aChild)->SetLoadType(mLoadType);
+ }
+}
+
+NS_IMETHODIMP
+nsSHEntry::ReplaceChild(nsISHEntry* aNewEntry) {
+ NS_ENSURE_STATE(aNewEntry);
+
+ nsID docshellID;
+ aNewEntry->GetDocshellID(docshellID);
+
+ for (int32_t i = 0; i < mChildren.Count(); ++i) {
+ if (mChildren[i]) {
+ nsID childDocshellID;
+ nsresult rv = mChildren[i]->GetDocshellID(childDocshellID);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (docshellID == childDocshellID) {
+ mChildren[i]->SetParent(nullptr);
+ mChildren.ReplaceObjectAt(aNewEntry, i);
+ return aNewEntry->SetParent(this);
+ }
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP_(void) nsSHEntry::ClearEntry() {
+ int32_t childCount = GetChildCount();
+ // Remove all children of this entry
+ for (int32_t i = childCount - 1; i >= 0; i--) {
+ nsCOMPtr<nsISHEntry> child;
+ GetChildAt(i, getter_AddRefs(child));
+ RemoveChild(child);
+ }
+ AbandonBFCacheEntry();
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetStateData(nsIStructuredCloneContainer** aContainer) {
+ NS_IF_ADDREF(*aContainer = mStateData);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetStateData(nsIStructuredCloneContainer* aContainer) {
+ mStateData = aContainer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(bool)
+nsSHEntry::IsDynamicallyAdded() { return mShared->mDynamicallyCreated; }
+
+NS_IMETHODIMP
+nsSHEntry::HasDynamicallyAddedChild(bool* aAdded) {
+ *aAdded = false;
+ for (int32_t i = 0; i < mChildren.Count(); ++i) {
+ nsISHEntry* entry = mChildren[i];
+ if (entry) {
+ *aAdded = entry->IsDynamicallyAdded();
+ if (*aAdded) {
+ break;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetDocshellID(nsID& aID) {
+ aID = mShared->mDocShellID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetDocshellID(const nsID& aID) {
+ mShared->mDocShellID = aID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetLastTouched(uint32_t* aLastTouched) {
+ *aLastTouched = mShared->mLastTouched;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetLastTouched(uint32_t aLastTouched) {
+ mShared->mLastTouched = aLastTouched;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetShistory(nsISHistory** aSHistory) {
+ nsCOMPtr<nsISHistory> shistory(do_QueryReferent(mShared->mSHistory));
+ shistory.forget(aSHistory);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetShistory(nsISHistory* aSHistory) {
+ nsWeakPtr shistory = do_GetWeakReference(aSHistory);
+ // mSHistory can not be changed once it's set
+ MOZ_ASSERT(!mShared->mSHistory || (mShared->mSHistory == shistory));
+ mShared->mSHistory = shistory;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetLoadTypeAsHistory() {
+ // Set the LoadType by default to loadHistory during creation
+ mLoadType = LOAD_HISTORY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetPersist(bool* aPersist) {
+ *aPersist = mPersist;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetPersist(bool aPersist) {
+ mPersist = aPersist;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::CreateLoadInfo(nsDocShellLoadState** aLoadState) {
+ nsCOMPtr<nsIURI> uri = GetURI();
+ RefPtr<nsDocShellLoadState> loadState(new nsDocShellLoadState(uri));
+
+ nsCOMPtr<nsIURI> originalURI = GetOriginalURI();
+ loadState->SetOriginalURI(originalURI);
+
+ mozilla::Maybe<nsCOMPtr<nsIURI>> emplacedResultPrincipalURI;
+ nsCOMPtr<nsIURI> resultPrincipalURI = GetResultPrincipalURI();
+ emplacedResultPrincipalURI.emplace(std::move(resultPrincipalURI));
+ loadState->SetMaybeResultPrincipalURI(emplacedResultPrincipalURI);
+
+ nsCOMPtr<nsIURI> unstrippedURI = GetUnstrippedURI();
+ loadState->SetUnstrippedURI(unstrippedURI);
+
+ loadState->SetLoadReplace(GetLoadReplace());
+ nsCOMPtr<nsIInputStream> postData = GetPostData();
+ loadState->SetPostDataStream(postData);
+
+ nsAutoCString contentType;
+ GetContentType(contentType);
+ loadState->SetTypeHint(contentType);
+
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal = GetTriggeringPrincipal();
+ loadState->SetTriggeringPrincipal(triggeringPrincipal);
+ nsCOMPtr<nsIPrincipal> principalToInherit = GetPrincipalToInherit();
+ loadState->SetPrincipalToInherit(principalToInherit);
+ nsCOMPtr<nsIPrincipal> partitionedPrincipalToInherit =
+ GetPartitionedPrincipalToInherit();
+ loadState->SetPartitionedPrincipalToInherit(partitionedPrincipalToInherit);
+ nsCOMPtr<nsIContentSecurityPolicy> csp = GetCsp();
+ loadState->SetCsp(csp);
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = GetReferrerInfo();
+ loadState->SetReferrerInfo(referrerInfo);
+
+ // Do not inherit principal from document (security-critical!);
+ uint32_t flags = nsDocShell::InternalLoad::INTERNAL_LOAD_FLAGS_NONE;
+
+ // Passing nullptr as aSourceDocShell gives the same behaviour as before
+ // aSourceDocShell was introduced. According to spec we should be passing
+ // the source browsing context that was used when the history entry was
+ // first created. bug 947716 has been created to address this issue.
+ nsAutoString srcdoc;
+ nsCOMPtr<nsIURI> baseURI;
+ if (GetIsSrcdocEntry()) {
+ GetSrcdocData(srcdoc);
+ baseURI = GetBaseURI();
+ flags |= nsDocShell::InternalLoad::INTERNAL_LOAD_FLAGS_IS_SRCDOC;
+ } else {
+ srcdoc = VoidString();
+ }
+ loadState->SetSrcdocData(srcdoc);
+ loadState->SetBaseURI(baseURI);
+ loadState->SetInternalLoadFlags(flags);
+
+ loadState->SetFirstParty(true);
+
+ loadState->SetHasValidUserGestureActivation(GetHasUserActivation());
+
+ loadState->SetSHEntry(this);
+
+ // When we create a load state from the history entry we already know if
+ // https-first was able to upgrade the request from http to https. There is no
+ // point in re-retrying to upgrade.
+ loadState->SetIsExemptFromHTTPSOnlyMode(true);
+
+ loadState.forget(aLoadState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsSHEntry::SyncTreesForSubframeNavigation(
+ nsISHEntry* aEntry, mozilla::dom::BrowsingContext* aTopBC,
+ mozilla::dom::BrowsingContext* aIgnoreBC) {
+ // XXX Keep this in sync with
+ // SessionHistoryEntry::SyncTreesForSubframeNavigation
+ //
+ // We need to sync up the browsing context and session history trees for
+ // subframe navigation. If the load was in a subframe, we forward up to
+ // the top browsing context, which will then recursively sync up all browsing
+ // contexts to their corresponding entries in the new session history tree. If
+ // we don't do this, then we can cache a content viewer on the wrong cloned
+ // entry, and subsequently restore it at the wrong time.
+ nsCOMPtr<nsISHEntry> newRootEntry = nsSHistory::GetRootSHEntry(aEntry);
+ if (newRootEntry) {
+ // newRootEntry is now the new root entry.
+ // Find the old root entry as well.
+
+ // Need a strong ref. on |oldRootEntry| so it isn't destroyed when
+ // SetChildHistoryEntry() does SwapHistoryEntries() (bug 304639).
+ nsCOMPtr<nsISHEntry> oldRootEntry = nsSHistory::GetRootSHEntry(this);
+
+ if (oldRootEntry) {
+ nsSHistory::SwapEntriesData data = {aIgnoreBC, newRootEntry, nullptr};
+ nsSHistory::SetChildHistoryEntry(oldRootEntry, aTopBC, 0, &data);
+ }
+ }
+}
+
+void nsSHEntry::EvictContentViewer() {
+ nsCOMPtr<nsIContentViewer> viewer = GetContentViewer();
+ if (viewer) {
+ mShared->NotifyListenersContentViewerEvicted();
+ // Drop the presentation state before destroying the viewer, so that
+ // document teardown is able to correctly persist the state.
+ SetContentViewer(nullptr);
+ SyncPresentationState();
+ viewer->Destroy();
+ }
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetContentViewer(nsIContentViewer* aViewer) {
+ return GetState()->SetContentViewer(aViewer);
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetContentViewer(nsIContentViewer** aResult) {
+ *aResult = GetState()->mContentViewer;
+ NS_IF_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetIsInBFCache(bool* aResult) {
+ *aResult = !!GetState()->mContentViewer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::Clone(nsISHEntry** aResult) {
+ nsCOMPtr<nsISHEntry> entry = new nsSHEntry(*this);
+ entry.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetSaveLayoutStateFlag(bool* aFlag) {
+ *aFlag = mShared->mSaveLayoutState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetSaveLayoutStateFlag(bool aFlag) {
+ mShared->mSaveLayoutState = aFlag;
+ if (mShared->mLayoutHistoryState) {
+ mShared->mLayoutHistoryState->SetScrollPositionOnly(!aFlag);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetWindowState(nsISupports* aState) {
+ GetState()->mWindowState = aState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetWindowState(nsISupports** aState) {
+ NS_IF_ADDREF(*aState = GetState()->mWindowState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetRefreshURIList(nsIMutableArray** aList) {
+ NS_IF_ADDREF(*aList = GetState()->mRefreshURIList);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetRefreshURIList(nsIMutableArray* aList) {
+ GetState()->mRefreshURIList = aList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsSHEntry::AddChildShell(nsIDocShellTreeItem* aShell) {
+ MOZ_ASSERT(aShell, "Null child shell added to history entry");
+ GetState()->mChildShells.AppendObject(aShell);
+}
+
+NS_IMETHODIMP
+nsSHEntry::ChildShellAt(int32_t aIndex, nsIDocShellTreeItem** aShell) {
+ NS_IF_ADDREF(*aShell = GetState()->mChildShells.SafeObjectAt(aIndex));
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsSHEntry::ClearChildShells() { GetState()->mChildShells.Clear(); }
+
+NS_IMETHODIMP_(void)
+nsSHEntry::SyncPresentationState() { GetState()->SyncPresentationState(); }
+
+nsDocShellEditorData* nsSHEntry::ForgetEditorData() {
+ // XXX jlebar Check how this is used.
+ return GetState()->mEditorData.release();
+}
+
+void nsSHEntry::SetEditorData(nsDocShellEditorData* aData) {
+ NS_ASSERTION(!(aData && GetState()->mEditorData),
+ "We're going to overwrite an owning ref!");
+ if (GetState()->mEditorData != aData) {
+ GetState()->mEditorData = mozilla::WrapUnique(aData);
+ }
+}
+
+bool nsSHEntry::HasDetachedEditor() {
+ return GetState()->mEditorData != nullptr;
+}
+
+bool nsSHEntry::HasBFCacheEntry(
+ mozilla::dom::SHEntrySharedParentState* aEntry) {
+ return GetState() == aEntry;
+}
+
+NS_IMETHODIMP
+nsSHEntry::AbandonBFCacheEntry() {
+ mShared = GetState()->Duplicate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetBfcacheID(uint64_t* aBFCacheID) {
+ *aBFCacheID = mShared->GetId();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::GetWireframe(JSContext* aCx, JS::MutableHandle<JS::Value> aOut) {
+ aOut.set(JS::NullValue());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHEntry::SetWireframe(JSContext* aCx, JS::Handle<JS::Value> aArg) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/docshell/shistory/nsSHEntry.h b/docshell/shistory/nsSHEntry.h
new file mode 100644
index 0000000000..eaa3cf1ad4
--- /dev/null
+++ b/docshell/shistory/nsSHEntry.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSHEntry_h
+#define nsSHEntry_h
+
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsISHEntry.h"
+#include "nsString.h"
+#include "nsWeakReference.h"
+#include "mozilla/Attributes.h"
+
+class nsSHEntryShared;
+class nsIInputStream;
+class nsIURI;
+class nsIReferrerInfo;
+
+class nsSHEntry : public nsISHEntry, public nsSupportsWeakReference {
+ public:
+ nsSHEntry();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISHENTRY
+
+ virtual void EvictContentViewer();
+
+ static nsresult Startup();
+ static void Shutdown();
+
+ nsSHEntryShared* GetState() { return mShared; }
+
+ protected:
+ explicit nsSHEntry(const nsSHEntry& aOther);
+ virtual ~nsSHEntry();
+
+ // We share the state in here with other SHEntries which correspond to the
+ // same document.
+ RefPtr<nsSHEntryShared> mShared;
+
+ // See nsSHEntry.idl for comments on these members.
+ nsCOMPtr<nsIURI> mURI;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ nsCOMPtr<nsIURI> mResultPrincipalURI;
+ nsCOMPtr<nsIURI> mUnstrippedURI;
+ nsCOMPtr<nsIReferrerInfo> mReferrerInfo;
+ nsString mTitle;
+ nsString mName;
+ nsCOMPtr<nsIInputStream> mPostData;
+ uint32_t mLoadType;
+ uint32_t mID;
+ int32_t mScrollPositionX;
+ int32_t mScrollPositionY;
+ nsWeakPtr mParent;
+ nsCOMArray<nsISHEntry> mChildren;
+ nsCOMPtr<nsIStructuredCloneContainer> mStateData;
+ nsString mSrcdocData;
+ nsCOMPtr<nsIURI> mBaseURI;
+ bool mLoadReplace;
+ bool mURIWasModified;
+ bool mIsSrcdocEntry;
+ bool mScrollRestorationIsManual;
+ bool mLoadedInThisProcess;
+ bool mPersist;
+ bool mHasUserInteraction;
+ bool mHasUserActivation;
+};
+
+#endif /* nsSHEntry_h */
diff --git a/docshell/shistory/nsSHEntryShared.cpp b/docshell/shistory/nsSHEntryShared.cpp
new file mode 100644
index 0000000000..9b8bc3936d
--- /dev/null
+++ b/docshell/shistory/nsSHEntryShared.cpp
@@ -0,0 +1,343 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSHEntryShared.h"
+
+#include "nsArray.h"
+#include "nsContentUtils.h"
+#include "nsDocShellEditorData.h"
+#include "nsIContentViewer.h"
+#include "nsISHistory.h"
+#include "mozilla/dom/Document.h"
+#include "nsILayoutHistoryState.h"
+#include "nsIWebNavigation.h"
+#include "nsSHistory.h"
+#include "nsThreadUtils.h"
+#include "nsFrameLoader.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Preferences.h"
+
+namespace dom = mozilla::dom;
+
+namespace {
+uint64_t gSHEntrySharedID = 0;
+nsTHashMap<nsUint64HashKey, mozilla::dom::SHEntrySharedParentState*>*
+ sIdToSharedState = nullptr;
+} // namespace
+
+namespace mozilla {
+namespace dom {
+
+/* static */
+uint64_t SHEntrySharedState::GenerateId() {
+ return nsContentUtils::GenerateProcessSpecificId(++gSHEntrySharedID);
+}
+
+/* static */
+SHEntrySharedParentState* SHEntrySharedParentState::Lookup(uint64_t aId) {
+ MOZ_ASSERT(aId != 0);
+
+ return sIdToSharedState ? sIdToSharedState->Get(aId) : nullptr;
+}
+
+static void AddSHEntrySharedParentState(
+ SHEntrySharedParentState* aSharedState) {
+ MOZ_ASSERT(aSharedState->mId != 0);
+
+ if (!sIdToSharedState) {
+ sIdToSharedState =
+ new nsTHashMap<nsUint64HashKey, SHEntrySharedParentState*>();
+ }
+ sIdToSharedState->InsertOrUpdate(aSharedState->mId, aSharedState);
+}
+
+SHEntrySharedParentState::SHEntrySharedParentState() {
+ AddSHEntrySharedParentState(this);
+}
+
+SHEntrySharedParentState::SHEntrySharedParentState(
+ nsIPrincipal* aTriggeringPrincipal, nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp, const nsACString& aContentType)
+ : SHEntrySharedState(aTriggeringPrincipal, aPrincipalToInherit,
+ aPartitionedPrincipalToInherit, aCsp, aContentType) {
+ AddSHEntrySharedParentState(this);
+}
+
+SHEntrySharedParentState::~SHEntrySharedParentState() {
+ MOZ_ASSERT(mId != 0);
+
+ RefPtr<nsFrameLoader> loader = mFrameLoader;
+ SetFrameLoader(nullptr);
+ if (loader) {
+ if (NS_FAILED(NS_DispatchToCurrentThread(NS_NewRunnableFunction(
+ "SHEntrySharedParentState::~SHEntrySharedParentState",
+ [loader]() -> void { loader->AsyncDestroy(); })))) {
+ // Trigger AsyncDestroy immediately during shutdown.
+ loader->AsyncDestroy();
+ }
+ }
+
+ sIdToSharedState->Remove(mId);
+ if (sIdToSharedState->IsEmpty()) {
+ delete sIdToSharedState;
+ sIdToSharedState = nullptr;
+ }
+}
+
+void SHEntrySharedParentState::ChangeId(uint64_t aId) {
+ MOZ_ASSERT(aId != 0);
+
+ sIdToSharedState->Remove(mId);
+ mId = aId;
+ sIdToSharedState->InsertOrUpdate(mId, this);
+}
+
+void SHEntrySharedParentState::CopyFrom(SHEntrySharedParentState* aEntry) {
+ mDocShellID = aEntry->mDocShellID;
+ mTriggeringPrincipal = aEntry->mTriggeringPrincipal;
+ mPrincipalToInherit = aEntry->mPrincipalToInherit;
+ mPartitionedPrincipalToInherit = aEntry->mPartitionedPrincipalToInherit;
+ mCsp = aEntry->mCsp;
+ mSaveLayoutState = aEntry->mSaveLayoutState;
+ mContentType.Assign(aEntry->mContentType);
+ mIsFrameNavigation = aEntry->mIsFrameNavigation;
+ mSticky = aEntry->mSticky;
+ mDynamicallyCreated = aEntry->mDynamicallyCreated;
+ mCacheKey = aEntry->mCacheKey;
+ mLastTouched = aEntry->mLastTouched;
+}
+
+void dom::SHEntrySharedParentState::NotifyListenersContentViewerEvicted() {
+ if (nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory)) {
+ RefPtr<nsSHistory> nsshistory = static_cast<nsSHistory*>(shistory.get());
+ nsshistory->NotifyListenersContentViewerEvicted(1);
+ }
+}
+
+void SHEntrySharedChildState::CopyFrom(SHEntrySharedChildState* aEntry) {
+ mChildShells.AppendObjects(aEntry->mChildShells);
+}
+
+void SHEntrySharedParentState::SetFrameLoader(nsFrameLoader* aFrameLoader) {
+ // If expiration tracker is removing this object, IsTracked() returns false.
+ if (GetExpirationState()->IsTracked() && mFrameLoader) {
+ if (nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory)) {
+ shistory->RemoveFromExpirationTracker(this);
+ }
+ }
+
+ mFrameLoader = aFrameLoader;
+
+ if (mFrameLoader) {
+ if (nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory)) {
+ shistory->AddToExpirationTracker(this);
+ }
+ }
+}
+
+nsFrameLoader* SHEntrySharedParentState::GetFrameLoader() {
+ return mFrameLoader;
+}
+
+} // namespace dom
+} // namespace mozilla
+
+void nsSHEntryShared::Shutdown() {}
+
+nsSHEntryShared::~nsSHEntryShared() {
+ // The destruction can be caused by either the entry is removed from session
+ // history and no one holds the reference, or the whole session history is on
+ // destruction. We want to ensure that we invoke
+ // shistory->RemoveFromExpirationTracker for the former case.
+ RemoveFromExpirationTracker();
+
+ // Calling RemoveDynEntriesForBFCacheEntry on destruction is unnecessary since
+ // there couldn't be any SHEntry holding this shared entry, and we noticed
+ // that calling RemoveDynEntriesForBFCacheEntry in the middle of
+ // nsSHistory::Release can cause a crash, so set mSHistory to null explicitly
+ // before RemoveFromBFCacheSync.
+ mSHistory = nullptr;
+ if (mContentViewer) {
+ RemoveFromBFCacheSync();
+ }
+}
+
+NS_IMPL_QUERY_INTERFACE(nsSHEntryShared, nsIBFCacheEntry, nsIMutationObserver)
+NS_IMPL_ADDREF_INHERITED(nsSHEntryShared, dom::SHEntrySharedParentState)
+NS_IMPL_RELEASE_INHERITED(nsSHEntryShared, dom::SHEntrySharedParentState)
+
+already_AddRefed<nsSHEntryShared> nsSHEntryShared::Duplicate() {
+ RefPtr<nsSHEntryShared> newEntry = new nsSHEntryShared();
+
+ newEntry->dom::SHEntrySharedParentState::CopyFrom(this);
+ newEntry->dom::SHEntrySharedChildState::CopyFrom(this);
+
+ return newEntry.forget();
+}
+
+void nsSHEntryShared::RemoveFromExpirationTracker() {
+ nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory);
+ if (shistory && GetExpirationState()->IsTracked()) {
+ shistory->RemoveFromExpirationTracker(this);
+ }
+}
+
+void nsSHEntryShared::SyncPresentationState() {
+ if (mContentViewer && mWindowState) {
+ // If we have a content viewer and a window state, we should be ok.
+ return;
+ }
+
+ DropPresentationState();
+}
+
+void nsSHEntryShared::DropPresentationState() {
+ RefPtr<nsSHEntryShared> kungFuDeathGrip = this;
+
+ if (mDocument) {
+ mDocument->SetBFCacheEntry(nullptr);
+ mDocument->RemoveMutationObserver(this);
+ mDocument = nullptr;
+ }
+ if (mContentViewer) {
+ mContentViewer->ClearHistoryEntry();
+ }
+
+ RemoveFromExpirationTracker();
+ mContentViewer = nullptr;
+ mSticky = true;
+ mWindowState = nullptr;
+ mViewerBounds.SetRect(0, 0, 0, 0);
+ mChildShells.Clear();
+ mRefreshURIList = nullptr;
+ mEditorData = nullptr;
+}
+
+nsresult nsSHEntryShared::SetContentViewer(nsIContentViewer* aViewer) {
+ MOZ_ASSERT(!aViewer || !mContentViewer,
+ "SHEntryShared already contains viewer");
+
+ if (mContentViewer || !aViewer) {
+ DropPresentationState();
+ }
+
+ // If we're setting mContentViewer to null, state should already be cleared
+ // in the DropPresentationState() call above; If we're setting it to a
+ // non-null content viewer, the entry shouldn't have been tracked either.
+ MOZ_ASSERT(!GetExpirationState()->IsTracked());
+ mContentViewer = aViewer;
+
+ if (mContentViewer) {
+ // mSHistory is only set for root entries, but in general bfcache only
+ // applies to root entries as well. BFCache for subframe navigation has been
+ // disabled since 2005 in bug 304860.
+ if (nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory)) {
+ shistory->AddToExpirationTracker(this);
+ }
+
+ // Store observed document in strong pointer in case it is removed from
+ // the contentviewer
+ mDocument = mContentViewer->GetDocument();
+ if (mDocument) {
+ mDocument->SetBFCacheEntry(this);
+ mDocument->AddMutationObserver(this);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsSHEntryShared::RemoveFromBFCacheSync() {
+ MOZ_ASSERT(mContentViewer && mDocument, "we're not in the bfcache!");
+
+ // The call to DropPresentationState could drop the last reference, so hold
+ // |this| until RemoveDynEntriesForBFCacheEntry finishes.
+ RefPtr<nsSHEntryShared> kungFuDeathGrip = this;
+
+ // DropPresentationState would clear mContentViewer.
+ nsCOMPtr<nsIContentViewer> viewer = mContentViewer;
+ DropPresentationState();
+
+ if (viewer) {
+ viewer->Destroy();
+ }
+
+ // Now that we've dropped the viewer, we have to clear associated dynamic
+ // subframe entries.
+ nsCOMPtr<nsISHistory> shistory = do_QueryReferent(mSHistory);
+ if (shistory) {
+ shistory->RemoveDynEntriesForBFCacheEntry(this);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsSHEntryShared::RemoveFromBFCacheAsync() {
+ MOZ_ASSERT(mContentViewer && mDocument, "we're not in the bfcache!");
+
+ // Check it again to play safe in release builds.
+ if (!mDocument) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // DropPresentationState would clear mContentViewer & mDocument. Capture and
+ // release the references asynchronously so that the document doesn't get
+ // nuked mid-mutation.
+ nsCOMPtr<nsIContentViewer> viewer = mContentViewer;
+ RefPtr<dom::Document> document = mDocument;
+ RefPtr<nsSHEntryShared> self = this;
+ nsresult rv = mDocument->Dispatch(
+ mozilla::TaskCategory::Other,
+ NS_NewRunnableFunction(
+ "nsSHEntryShared::RemoveFromBFCacheAsync",
+ [self, viewer, document]() {
+ if (viewer) {
+ viewer->Destroy();
+ }
+
+ nsCOMPtr<nsISHistory> shistory = do_QueryReferent(self->mSHistory);
+ if (shistory) {
+ shistory->RemoveDynEntriesForBFCacheEntry(self);
+ }
+ }));
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch RemoveFromBFCacheAsync runnable.");
+ } else {
+ // Drop presentation. Only do this if we succeeded in posting the event
+ // since otherwise the document could be torn down mid-mutation, causing
+ // crashes.
+ DropPresentationState();
+ }
+
+ return NS_OK;
+}
+
+void nsSHEntryShared::CharacterDataChanged(nsIContent* aContent,
+ const CharacterDataChangeInfo&) {
+ RemoveFromBFCacheAsync();
+}
+
+void nsSHEntryShared::AttributeChanged(dom::Element* aElement,
+ int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue) {
+ RemoveFromBFCacheAsync();
+}
+
+void nsSHEntryShared::ContentAppended(nsIContent* aFirstNewContent) {
+ RemoveFromBFCacheAsync();
+}
+
+void nsSHEntryShared::ContentInserted(nsIContent* aChild) {
+ RemoveFromBFCacheAsync();
+}
+
+void nsSHEntryShared::ContentRemoved(nsIContent* aChild,
+ nsIContent* aPreviousSibling) {
+ RemoveFromBFCacheAsync();
+}
diff --git a/docshell/shistory/nsSHEntryShared.h b/docshell/shistory/nsSHEntryShared.h
new file mode 100644
index 0000000000..6bb6344202
--- /dev/null
+++ b/docshell/shistory/nsSHEntryShared.h
@@ -0,0 +1,219 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSHEntryShared_h__
+#define nsSHEntryShared_h__
+
+#include "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsExpirationTracker.h"
+#include "nsIBFCacheEntry.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsRect.h"
+#include "nsString.h"
+#include "nsStubMutationObserver.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+
+class nsSHEntry;
+class nsISHEntry;
+class nsISHistory;
+class nsIContentSecurityPolicy;
+class nsIContentViewer;
+class nsIDocShellTreeItem;
+class nsILayoutHistoryState;
+class nsIPrincipal;
+class nsDocShellEditorData;
+class nsFrameLoader;
+class nsIMutableArray;
+class nsSHistory;
+
+// A document may have multiple SHEntries, either due to hash navigations or
+// calls to history.pushState. SHEntries corresponding to the same document
+// share many members; in particular, they share state related to the
+// back/forward cache.
+//
+// The classes defined here are the vehicle for this sharing.
+//
+// Some of the state can only be stored in the process where we did the actual
+// load, because that's where the objects live (eg. the content viewer).
+
+namespace mozilla {
+namespace dom {
+class Document;
+
+/**
+ * SHEntrySharedState holds shared state both in the child process and in the
+ * parent process.
+ */
+struct SHEntrySharedState {
+ SHEntrySharedState() : mId(GenerateId()) {}
+ SHEntrySharedState(const SHEntrySharedState& aState) = default;
+ SHEntrySharedState(nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp,
+ const nsACString& aContentType)
+ : mId(GenerateId()),
+ mTriggeringPrincipal(aTriggeringPrincipal),
+ mPrincipalToInherit(aPrincipalToInherit),
+ mPartitionedPrincipalToInherit(aPartitionedPrincipalToInherit),
+ mCsp(aCsp),
+ mContentType(aContentType) {}
+
+ // These members aren't copied by SHEntrySharedParentState::CopyFrom() because
+ // they're specific to a particular content viewer.
+ uint64_t mId = 0;
+
+ // These members are copied by SHEntrySharedParentState::CopyFrom(). If you
+ // add a member here, be sure to update the CopyFrom() implementation.
+ nsCOMPtr<nsIPrincipal> mTriggeringPrincipal;
+ nsCOMPtr<nsIPrincipal> mPrincipalToInherit;
+ nsCOMPtr<nsIPrincipal> mPartitionedPrincipalToInherit;
+ nsCOMPtr<nsIContentSecurityPolicy> mCsp;
+ nsCString mContentType;
+ // Child side updates layout history state when page is being unloaded or
+ // moved to bfcache.
+ nsCOMPtr<nsILayoutHistoryState> mLayoutHistoryState;
+ uint32_t mCacheKey = 0;
+ bool mIsFrameNavigation = false;
+ bool mSaveLayoutState = true;
+
+ protected:
+ static uint64_t GenerateId();
+};
+
+/**
+ * SHEntrySharedParentState holds the shared state that can live in the parent
+ * process.
+ */
+class SHEntrySharedParentState : public SHEntrySharedState {
+ public:
+ friend class SessionHistoryInfo;
+
+ uint64_t GetId() const { return mId; }
+ void ChangeId(uint64_t aId);
+
+ void SetFrameLoader(nsFrameLoader* aFrameLoader);
+
+ nsFrameLoader* GetFrameLoader();
+
+ void NotifyListenersContentViewerEvicted();
+
+ nsExpirationState* GetExpirationState() { return &mExpirationState; }
+
+ SHEntrySharedParentState();
+ SHEntrySharedParentState(nsIPrincipal* aTriggeringPrincipal,
+ nsIPrincipal* aPrincipalToInherit,
+ nsIPrincipal* aPartitionedPrincipalToInherit,
+ nsIContentSecurityPolicy* aCsp,
+ const nsACString& aContentType);
+
+ // This returns the existing SHEntrySharedParentState that was registered for
+ // aId, if one exists.
+ static SHEntrySharedParentState* Lookup(uint64_t aId);
+
+ protected:
+ virtual ~SHEntrySharedParentState();
+ NS_INLINE_DECL_VIRTUAL_REFCOUNTING_WITH_DESTROY(SHEntrySharedParentState,
+ Destroy())
+
+ virtual void Destroy() { delete this; }
+
+ void CopyFrom(SHEntrySharedParentState* aSource);
+
+ // These members are copied by SHEntrySharedParentState::CopyFrom(). If you
+ // add a member here, be sure to update the CopyFrom() implementation.
+ nsID mDocShellID{};
+
+ nsIntRect mViewerBounds{0, 0, 0, 0};
+
+ uint32_t mLastTouched = 0;
+
+ // These members aren't copied by SHEntrySharedParentState::CopyFrom() because
+ // they're specific to a particular content viewer.
+ nsWeakPtr mSHistory;
+
+ RefPtr<nsFrameLoader> mFrameLoader;
+
+ nsExpirationState mExpirationState;
+
+ bool mSticky = true;
+ bool mDynamicallyCreated = false;
+
+ // This flag is about necko cache, not bfcache.
+ bool mExpired = false;
+};
+
+/**
+ * SHEntrySharedChildState holds the shared state that needs to live in the
+ * process where the document was loaded.
+ */
+class SHEntrySharedChildState {
+ protected:
+ void CopyFrom(SHEntrySharedChildState* aSource);
+
+ public:
+ // These members are copied by SHEntrySharedChildState::CopyFrom(). If you
+ // add a member here, be sure to update the CopyFrom() implementation.
+ nsCOMArray<nsIDocShellTreeItem> mChildShells;
+
+ // These members aren't copied by SHEntrySharedChildState::CopyFrom() because
+ // they're specific to a particular content viewer.
+ nsCOMPtr<nsIContentViewer> mContentViewer;
+ RefPtr<mozilla::dom::Document> mDocument;
+ nsCOMPtr<nsISupports> mWindowState;
+ // FIXME Move to parent?
+ nsCOMPtr<nsIMutableArray> mRefreshURIList;
+ UniquePtr<nsDocShellEditorData> mEditorData;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+/**
+ * nsSHEntryShared holds the shared state if the session history is not stored
+ * in the parent process, or if the load itself happens in the parent process.
+ * Note, since nsSHEntryShared inherits both SHEntrySharedParentState and
+ * SHEntrySharedChildState and those have some same member variables,
+ * the ones from SHEntrySharedParentState should be used.
+ */
+class nsSHEntryShared final : public nsIBFCacheEntry,
+ public nsStubMutationObserver,
+ public mozilla::dom::SHEntrySharedParentState,
+ public mozilla::dom::SHEntrySharedChildState {
+ public:
+ static void EnsureHistoryTracker();
+ static void Shutdown();
+
+ using SHEntrySharedParentState::SHEntrySharedParentState;
+
+ already_AddRefed<nsSHEntryShared> Duplicate();
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIBFCACHEENTRY
+
+ // The nsIMutationObserver bits we actually care about.
+ NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+
+ private:
+ ~nsSHEntryShared();
+
+ friend class nsSHEntry;
+
+ void RemoveFromExpirationTracker();
+ void SyncPresentationState();
+ void DropPresentationState();
+
+ nsresult SetContentViewer(nsIContentViewer* aViewer);
+};
+
+#endif
diff --git a/docshell/shistory/nsSHistory.cpp b/docshell/shistory/nsSHistory.cpp
new file mode 100644
index 0000000000..214646dde9
--- /dev/null
+++ b/docshell/shistory/nsSHistory.cpp
@@ -0,0 +1,2410 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsSHistory.h"
+
+#include <algorithm>
+
+#include "nsContentUtils.h"
+#include "nsCOMArray.h"
+#include "nsComponentManagerUtils.h"
+#include "nsDocShell.h"
+#include "nsFrameLoaderOwner.h"
+#include "nsHashKeys.h"
+#include "nsIContentViewer.h"
+#include "nsIDocShell.h"
+#include "nsDocShellLoadState.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsILayoutHistoryState.h"
+#include "nsIObserverService.h"
+#include "nsISHEntry.h"
+#include "nsISHistoryListener.h"
+#include "nsIURI.h"
+#include "nsIXULRuntime.h"
+#include "nsNetUtil.h"
+#include "nsTHashMap.h"
+#include "nsSHEntry.h"
+#include "SessionHistoryEntry.h"
+#include "nsTArray.h"
+#include "prsystem.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BrowsingContextGroup.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/RemoteWebProgressRequest.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ProcessPriorityManager.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPrefs_fission.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "nsIWebNavigation.h"
+#include "nsDocShellLoadTypes.h"
+#include "base/process.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+#define PREF_SHISTORY_SIZE "browser.sessionhistory.max_entries"
+#define PREF_SHISTORY_MAX_TOTAL_VIEWERS \
+ "browser.sessionhistory.max_total_viewers"
+#define CONTENT_VIEWER_TIMEOUT_SECONDS \
+ "browser.sessionhistory.contentViewerTimeout"
+// Observe fission.bfcacheInParent so that BFCache can be enabled/disabled when
+// the pref is changed.
+#define PREF_FISSION_BFCACHEINPARENT "fission.bfcacheInParent"
+
+// Default this to time out unused content viewers after 30 minutes
+#define CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT (30 * 60)
+
+static const char* kObservedPrefs[] = {PREF_SHISTORY_SIZE,
+ PREF_SHISTORY_MAX_TOTAL_VIEWERS,
+ PREF_FISSION_BFCACHEINPARENT, nullptr};
+
+static int32_t gHistoryMaxSize = 50;
+
+// List of all SHistory objects, used for content viewer cache eviction.
+// When being destroyed, this helper removes everything from the list to avoid
+// assertions when we leak.
+struct ListHelper {
+#ifdef DEBUG
+ ~ListHelper() { mList.clear(); }
+#endif // DEBUG
+
+ LinkedList<nsSHistory> mList;
+};
+
+static ListHelper gSHistoryList;
+// Max viewers allowed total, across all SHistory objects - negative default
+// means we will calculate how many viewers to cache based on total memory
+int32_t nsSHistory::sHistoryMaxTotalViewers = -1;
+
+// A counter that is used to be able to know the order in which
+// entries were touched, so that we can evict older entries first.
+static uint32_t gTouchCounter = 0;
+
+extern mozilla::LazyLogModule gSHLog;
+
+LazyLogModule gSHistoryLog("nsSHistory");
+
+#define LOG(format) MOZ_LOG(gSHistoryLog, mozilla::LogLevel::Debug, format)
+
+extern mozilla::LazyLogModule gPageCacheLog;
+extern mozilla::LazyLogModule gSHIPBFCacheLog;
+
+// This macro makes it easier to print a log message which includes a URI's
+// spec. Example use:
+//
+// nsIURI *uri = [...];
+// LOG_SPEC(("The URI is %s.", _spec), uri);
+//
+#define LOG_SPEC(format, uri) \
+ PR_BEGIN_MACRO \
+ if (MOZ_LOG_TEST(gSHistoryLog, LogLevel::Debug)) { \
+ nsAutoCString _specStr("(null)"_ns); \
+ if (uri) { \
+ _specStr = uri->GetSpecOrDefault(); \
+ } \
+ const char* _spec = _specStr.get(); \
+ LOG(format); \
+ } \
+ PR_END_MACRO
+
+// This macro makes it easy to log a message including an SHEntry's URI.
+// For example:
+//
+// nsCOMPtr<nsISHEntry> shentry = [...];
+// LOG_SHENTRY_SPEC(("shentry %p has uri %s.", shentry.get(), _spec), shentry);
+//
+#define LOG_SHENTRY_SPEC(format, shentry) \
+ PR_BEGIN_MACRO \
+ if (MOZ_LOG_TEST(gSHistoryLog, LogLevel::Debug)) { \
+ nsCOMPtr<nsIURI> uri = shentry->GetURI(); \
+ LOG_SPEC(format, uri); \
+ } \
+ PR_END_MACRO
+
+// Calls a F on all registered session history listeners.
+template <typename F>
+static void NotifyListeners(nsAutoTObserverArray<nsWeakPtr, 2>& aListeners,
+ F&& f) {
+ for (const nsWeakPtr& weakPtr : aListeners.EndLimitedRange()) {
+ nsCOMPtr<nsISHistoryListener> listener = do_QueryReferent(weakPtr);
+ if (listener) {
+ f(listener);
+ }
+ }
+}
+
+class MOZ_STACK_CLASS SHistoryChangeNotifier {
+ public:
+ explicit SHistoryChangeNotifier(nsSHistory* aHistory) {
+ // If we're already in an update, the outermost change notifier will
+ // update browsing context in the destructor.
+ if (!aHistory->HasOngoingUpdate()) {
+ aHistory->SetHasOngoingUpdate(true);
+ mSHistory = aHistory;
+ }
+ }
+
+ ~SHistoryChangeNotifier() {
+ if (mSHistory) {
+ MOZ_ASSERT(mSHistory->HasOngoingUpdate());
+ mSHistory->SetHasOngoingUpdate(false);
+
+ RefPtr<BrowsingContext> rootBC = mSHistory->GetBrowsingContext();
+ if (mozilla::SessionHistoryInParent() && rootBC) {
+ rootBC->Canonical()->HistoryCommitIndexAndLength();
+ }
+ }
+ }
+
+ RefPtr<nsSHistory> mSHistory;
+};
+
+enum HistCmd { HIST_CMD_GOTOINDEX, HIST_CMD_RELOAD };
+
+class nsSHistoryObserver final : public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ nsSHistoryObserver() {}
+
+ static void PrefChanged(const char* aPref, void* aSelf);
+ void PrefChanged(const char* aPref);
+
+ protected:
+ ~nsSHistoryObserver() {}
+};
+
+StaticRefPtr<nsSHistoryObserver> gObserver;
+
+NS_IMPL_ISUPPORTS(nsSHistoryObserver, nsIObserver)
+
+// static
+void nsSHistoryObserver::PrefChanged(const char* aPref, void* aSelf) {
+ static_cast<nsSHistoryObserver*>(aSelf)->PrefChanged(aPref);
+}
+
+void nsSHistoryObserver::PrefChanged(const char* aPref) {
+ nsSHistory::UpdatePrefs();
+ nsSHistory::GloballyEvictContentViewers();
+}
+
+NS_IMETHODIMP
+nsSHistoryObserver::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, "cacheservice:empty-cache") ||
+ !strcmp(aTopic, "memory-pressure")) {
+ nsSHistory::GloballyEvictAllContentViewers();
+ }
+
+ return NS_OK;
+}
+
+void nsSHistory::EvictContentViewerForEntry(nsISHEntry* aEntry) {
+ nsCOMPtr<nsIContentViewer> viewer = aEntry->GetContentViewer();
+ if (viewer) {
+ LOG_SHENTRY_SPEC(("Evicting content viewer 0x%p for "
+ "owning SHEntry 0x%p at %s.",
+ viewer.get(), aEntry, _spec),
+ aEntry);
+
+ // Drop the presentation state before destroying the viewer, so that
+ // document teardown is able to correctly persist the state.
+ NotifyListenersContentViewerEvicted(1);
+ aEntry->SetContentViewer(nullptr);
+ aEntry->SyncPresentationState();
+ viewer->Destroy();
+ } else if (nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(aEntry)) {
+ if (RefPtr<nsFrameLoader> frameLoader = she->GetFrameLoader()) {
+ nsCOMPtr<nsFrameLoaderOwner> owner =
+ do_QueryInterface(frameLoader->GetOwnerContent());
+ RefPtr<nsFrameLoader> currentFrameLoader;
+ if (owner) {
+ currentFrameLoader = owner->GetFrameLoader();
+ }
+
+ // Only destroy non-current frameloader when evicting from the bfcache.
+ if (currentFrameLoader != frameLoader) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
+ ("nsSHistory::EvictContentViewerForEntry "
+ "destroying an nsFrameLoader."));
+ NotifyListenersContentViewerEvicted(1);
+ she->SetFrameLoader(nullptr);
+ frameLoader->Destroy();
+ }
+ }
+ }
+
+ // When dropping bfcache, we have to remove associated dynamic entries as
+ // well.
+ int32_t index = GetIndexOfEntry(aEntry);
+ if (index != -1) {
+ RemoveDynEntries(index, aEntry);
+ }
+}
+
+nsSHistory::nsSHistory(BrowsingContext* aRootBC)
+ : mRootBC(aRootBC->Id()),
+ mHasOngoingUpdate(false),
+ mIndex(-1),
+ mRequestedIndex(-1),
+ mRootDocShellID(aRootBC->GetHistoryID()) {
+ static bool sCalledStartup = false;
+ if (!sCalledStartup) {
+ Startup();
+ sCalledStartup = true;
+ }
+
+ // Add this new SHistory object to the list
+ gSHistoryList.mList.insertBack(this);
+
+ // Init mHistoryTracker on setting mRootBC so we can bind its event
+ // target to the tabGroup.
+ mHistoryTracker = mozilla::MakeUnique<HistoryTracker>(
+ this,
+ mozilla::Preferences::GetUint(CONTENT_VIEWER_TIMEOUT_SECONDS,
+ CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT),
+ GetCurrentSerialEventTarget());
+}
+
+nsSHistory::~nsSHistory() {
+ // Clear mEntries explicitly here so that the destructor of the entries
+ // can still access nsSHistory in a reasonable way.
+ mEntries.Clear();
+}
+
+NS_IMPL_ADDREF(nsSHistory)
+NS_IMPL_RELEASE(nsSHistory)
+
+NS_INTERFACE_MAP_BEGIN(nsSHistory)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISHistory)
+ NS_INTERFACE_MAP_ENTRY(nsISHistory)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END
+
+// static
+uint32_t nsSHistory::CalcMaxTotalViewers() {
+// This value allows tweaking how fast the allowed amount of content viewers
+// grows with increasing amounts of memory. Larger values mean slower growth.
+#ifdef ANDROID
+# define MAX_TOTAL_VIEWERS_BIAS 15.9
+#else
+# define MAX_TOTAL_VIEWERS_BIAS 14
+#endif
+
+ // Calculate an estimate of how many ContentViewers we should cache based
+ // on RAM. This assumes that the average ContentViewer is 4MB (conservative)
+ // and caps the max at 8 ContentViewers
+ //
+ // TODO: Should we split the cache memory betw. ContentViewer caching and
+ // nsCacheService?
+ //
+ // RAM | ContentViewers | on Android
+ // -------------------------------------
+ // 32 Mb 0 0
+ // 64 Mb 1 0
+ // 128 Mb 2 0
+ // 256 Mb 3 1
+ // 512 Mb 5 2
+ // 768 Mb 6 2
+ // 1024 Mb 8 3
+ // 2048 Mb 8 5
+ // 3072 Mb 8 7
+ // 4096 Mb 8 8
+ uint64_t bytes = PR_GetPhysicalMemorySize();
+
+ if (bytes == 0) {
+ return 0;
+ }
+
+ // Conversion from unsigned int64_t to double doesn't work on all platforms.
+ // We need to truncate the value at INT64_MAX to make sure we don't
+ // overflow.
+ if (bytes > INT64_MAX) {
+ bytes = INT64_MAX;
+ }
+
+ double kBytesD = (double)(bytes >> 10);
+
+ // This is essentially the same calculation as for nsCacheService,
+ // except that we divide the final memory calculation by 4, since
+ // we assume each ContentViewer takes on average 4MB
+ uint32_t viewers = 0;
+ double x = std::log(kBytesD) / std::log(2.0) - MAX_TOTAL_VIEWERS_BIAS;
+ if (x > 0) {
+ viewers = (uint32_t)(x * x - x + 2.001); // add .001 for rounding
+ viewers /= 4;
+ }
+
+ // Cap it off at 8 max
+ if (viewers > 8) {
+ viewers = 8;
+ }
+ return viewers;
+}
+
+// static
+void nsSHistory::UpdatePrefs() {
+ Preferences::GetInt(PREF_SHISTORY_SIZE, &gHistoryMaxSize);
+ if (mozilla::SessionHistoryInParent() && !mozilla::BFCacheInParent()) {
+ sHistoryMaxTotalViewers = 0;
+ return;
+ }
+
+ Preferences::GetInt(PREF_SHISTORY_MAX_TOTAL_VIEWERS,
+ &sHistoryMaxTotalViewers);
+ // If the pref is negative, that means we calculate how many viewers
+ // we think we should cache, based on total memory
+ if (sHistoryMaxTotalViewers < 0) {
+ sHistoryMaxTotalViewers = CalcMaxTotalViewers();
+ }
+}
+
+// static
+nsresult nsSHistory::Startup() {
+ UpdatePrefs();
+
+ // The goal of this is to unbreak users who have inadvertently set their
+ // session history size to less than the default value.
+ int32_t defaultHistoryMaxSize =
+ Preferences::GetInt(PREF_SHISTORY_SIZE, 50, PrefValueKind::Default);
+ if (gHistoryMaxSize < defaultHistoryMaxSize) {
+ gHistoryMaxSize = defaultHistoryMaxSize;
+ }
+
+ // Allow the user to override the max total number of cached viewers,
+ // but keep the per SHistory cached viewer limit constant
+ if (!gObserver) {
+ gObserver = new nsSHistoryObserver();
+ Preferences::RegisterCallbacks(nsSHistoryObserver::PrefChanged,
+ kObservedPrefs, gObserver.get());
+
+ nsCOMPtr<nsIObserverService> obsSvc =
+ mozilla::services::GetObserverService();
+ if (obsSvc) {
+ // Observe empty-cache notifications so tahat clearing the disk/memory
+ // cache will also evict all content viewers.
+ obsSvc->AddObserver(gObserver, "cacheservice:empty-cache", false);
+
+ // Same for memory-pressure notifications
+ obsSvc->AddObserver(gObserver, "memory-pressure", false);
+ }
+ }
+
+ return NS_OK;
+}
+
+// static
+void nsSHistory::Shutdown() {
+ if (gObserver) {
+ Preferences::UnregisterCallbacks(nsSHistoryObserver::PrefChanged,
+ kObservedPrefs, gObserver.get());
+
+ nsCOMPtr<nsIObserverService> obsSvc =
+ mozilla::services::GetObserverService();
+ if (obsSvc) {
+ obsSvc->RemoveObserver(gObserver, "cacheservice:empty-cache");
+ obsSvc->RemoveObserver(gObserver, "memory-pressure");
+ }
+ gObserver = nullptr;
+ }
+}
+
+// static
+already_AddRefed<nsISHEntry> nsSHistory::GetRootSHEntry(nsISHEntry* aEntry) {
+ nsCOMPtr<nsISHEntry> rootEntry = aEntry;
+ nsCOMPtr<nsISHEntry> result = nullptr;
+ while (rootEntry) {
+ result = rootEntry;
+ rootEntry = result->GetParent();
+ }
+
+ return result.forget();
+}
+
+// static
+nsresult nsSHistory::WalkHistoryEntries(nsISHEntry* aRootEntry,
+ BrowsingContext* aBC,
+ WalkHistoryEntriesFunc aCallback,
+ void* aData) {
+ NS_ENSURE_TRUE(aRootEntry, NS_ERROR_FAILURE);
+
+ int32_t childCount = aRootEntry->GetChildCount();
+ for (int32_t i = 0; i < childCount; i++) {
+ nsCOMPtr<nsISHEntry> childEntry;
+ aRootEntry->GetChildAt(i, getter_AddRefs(childEntry));
+ if (!childEntry) {
+ // childEntry can be null for valid reasons, for example if the
+ // docshell at index i never loaded anything useful.
+ // Remember to clone also nulls in the child array (bug 464064).
+ aCallback(nullptr, nullptr, i, aData);
+ continue;
+ }
+
+ BrowsingContext* childBC = nullptr;
+ if (aBC) {
+ for (BrowsingContext* child : aBC->Children()) {
+ // If the SH pref is on and we are in the parent process, update
+ // canonical BC directly
+ bool foundChild = false;
+ if (mozilla::SessionHistoryInParent() && XRE_IsParentProcess()) {
+ if (child->Canonical()->HasHistoryEntry(childEntry)) {
+ childBC = child;
+ foundChild = true;
+ }
+ }
+
+ nsDocShell* docshell = static_cast<nsDocShell*>(child->GetDocShell());
+ if (docshell && docshell->HasHistoryEntry(childEntry)) {
+ childBC = docshell->GetBrowsingContext();
+ foundChild = true;
+ }
+
+ // XXX Simplify this once the old and new session history
+ // implementations don't run at the same time.
+ if (foundChild) {
+ break;
+ }
+ }
+ }
+
+ nsresult rv = aCallback(childEntry, childBC, i, aData);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+// callback data for WalkHistoryEntries
+struct MOZ_STACK_CLASS CloneAndReplaceData {
+ CloneAndReplaceData(uint32_t aCloneID, nsISHEntry* aReplaceEntry,
+ bool aCloneChildren, nsISHEntry* aDestTreeParent)
+ : cloneID(aCloneID),
+ cloneChildren(aCloneChildren),
+ replaceEntry(aReplaceEntry),
+ destTreeParent(aDestTreeParent) {}
+
+ uint32_t cloneID;
+ bool cloneChildren;
+ nsISHEntry* replaceEntry;
+ nsISHEntry* destTreeParent;
+ nsCOMPtr<nsISHEntry> resultEntry;
+};
+
+nsresult nsSHistory::CloneAndReplaceChild(nsISHEntry* aEntry,
+ BrowsingContext* aOwnerBC,
+ int32_t aChildIndex, void* aData) {
+ nsCOMPtr<nsISHEntry> dest;
+
+ CloneAndReplaceData* data = static_cast<CloneAndReplaceData*>(aData);
+ uint32_t cloneID = data->cloneID;
+ nsISHEntry* replaceEntry = data->replaceEntry;
+
+ if (!aEntry) {
+ if (data->destTreeParent) {
+ data->destTreeParent->AddChild(nullptr, aChildIndex);
+ }
+ return NS_OK;
+ }
+
+ uint32_t srcID = aEntry->GetID();
+
+ nsresult rv = NS_OK;
+ if (srcID == cloneID) {
+ // Replace the entry
+ dest = replaceEntry;
+ } else {
+ // Clone the SHEntry...
+ rv = aEntry->Clone(getter_AddRefs(dest));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ dest->SetIsSubFrame(true);
+
+ if (srcID != cloneID || data->cloneChildren) {
+ // Walk the children
+ CloneAndReplaceData childData(cloneID, replaceEntry, data->cloneChildren,
+ dest);
+ rv = nsSHistory::WalkHistoryEntries(aEntry, aOwnerBC, CloneAndReplaceChild,
+ &childData);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (srcID != cloneID && aOwnerBC) {
+ nsSHistory::HandleEntriesToSwapInDocShell(aOwnerBC, aEntry, dest);
+ }
+
+ if (data->destTreeParent) {
+ data->destTreeParent->AddChild(dest, aChildIndex);
+ }
+ data->resultEntry = dest;
+ return rv;
+}
+
+// static
+nsresult nsSHistory::CloneAndReplace(
+ nsISHEntry* aSrcEntry, BrowsingContext* aOwnerBC, uint32_t aCloneID,
+ nsISHEntry* aReplaceEntry, bool aCloneChildren, nsISHEntry** aDestEntry) {
+ NS_ENSURE_ARG_POINTER(aDestEntry);
+ NS_ENSURE_TRUE(aReplaceEntry, NS_ERROR_FAILURE);
+ CloneAndReplaceData data(aCloneID, aReplaceEntry, aCloneChildren, nullptr);
+ nsresult rv = CloneAndReplaceChild(aSrcEntry, aOwnerBC, 0, &data);
+ data.resultEntry.swap(*aDestEntry);
+ return rv;
+}
+
+// static
+void nsSHistory::WalkContiguousEntries(
+ nsISHEntry* aEntry, const std::function<void(nsISHEntry*)>& aCallback) {
+ MOZ_ASSERT(aEntry);
+
+ nsCOMPtr<nsISHistory> shistory = aEntry->GetShistory();
+ if (!shistory) {
+ // If there is no session history in the entry, it means this is not a root
+ // entry. So, we can return from here.
+ return;
+ }
+
+ int32_t index = shistory->GetIndexOfEntry(aEntry);
+ int32_t count = shistory->GetCount();
+
+ nsCOMPtr<nsIURI> targetURI = aEntry->GetURI();
+
+ // First, call the callback on the input entry.
+ aCallback(aEntry);
+
+ // Walk backward to find the entries that have the same origin as the
+ // input entry.
+ for (int32_t i = index - 1; i >= 0; i--) {
+ RefPtr<nsISHEntry> entry;
+ shistory->GetEntryAtIndex(i, getter_AddRefs(entry));
+ if (entry) {
+ nsCOMPtr<nsIURI> uri = entry->GetURI();
+ if (NS_FAILED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI(
+ targetURI, uri, false, false))) {
+ break;
+ }
+
+ aCallback(entry);
+ }
+ }
+
+ // Then, Walk forward.
+ for (int32_t i = index + 1; i < count; i++) {
+ RefPtr<nsISHEntry> entry;
+ shistory->GetEntryAtIndex(i, getter_AddRefs(entry));
+ if (entry) {
+ nsCOMPtr<nsIURI> uri = entry->GetURI();
+ if (NS_FAILED(nsContentUtils::GetSecurityManager()->CheckSameOriginURI(
+ targetURI, uri, false, false))) {
+ break;
+ }
+
+ aCallback(entry);
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsSHistory::AddChildSHEntryHelper(nsISHEntry* aCloneRef, nsISHEntry* aNewEntry,
+ BrowsingContext* aRootBC,
+ bool aCloneChildren) {
+ MOZ_ASSERT(aRootBC->IsTop());
+
+ /* You are currently in the rootDocShell.
+ * You will get here when a subframe has a new url
+ * to load and you have walked up the tree all the
+ * way to the top to clone the current SHEntry hierarchy
+ * and replace the subframe where a new url was loaded with
+ * a new entry.
+ */
+ nsCOMPtr<nsISHEntry> child;
+ nsCOMPtr<nsISHEntry> currentHE;
+ int32_t index = mIndex;
+ if (index < 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ GetEntryAtIndex(index, getter_AddRefs(currentHE));
+ NS_ENSURE_TRUE(currentHE, NS_ERROR_FAILURE);
+
+ nsresult rv = NS_OK;
+ uint32_t cloneID = aCloneRef->GetID();
+ rv = nsSHistory::CloneAndReplace(currentHE, aRootBC, cloneID, aNewEntry,
+ aCloneChildren, getter_AddRefs(child));
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = AddEntry(child, true);
+ if (NS_SUCCEEDED(rv)) {
+ child->SetDocshellID(aRootBC->GetHistoryID());
+ }
+ }
+
+ return rv;
+}
+
+nsresult nsSHistory::SetChildHistoryEntry(nsISHEntry* aEntry,
+ BrowsingContext* aBC,
+ int32_t aEntryIndex, void* aData) {
+ SwapEntriesData* data = static_cast<SwapEntriesData*>(aData);
+ if (!aBC || aBC == data->ignoreBC) {
+ return NS_OK;
+ }
+
+ nsISHEntry* destTreeRoot = data->destTreeRoot;
+
+ nsCOMPtr<nsISHEntry> destEntry;
+
+ if (data->destTreeParent) {
+ // aEntry is a clone of some child of destTreeParent, but since the
+ // trees aren't necessarily in sync, we'll have to locate it.
+ // Note that we could set aShell's entry to null if we don't find a
+ // corresponding entry under destTreeParent.
+
+ uint32_t targetID = aEntry->GetID();
+
+ // First look at the given index, since this is the common case.
+ nsCOMPtr<nsISHEntry> entry;
+ data->destTreeParent->GetChildAt(aEntryIndex, getter_AddRefs(entry));
+ if (entry && entry->GetID() == targetID) {
+ destEntry.swap(entry);
+ } else {
+ int32_t childCount;
+ data->destTreeParent->GetChildCount(&childCount);
+ for (int32_t i = 0; i < childCount; ++i) {
+ data->destTreeParent->GetChildAt(i, getter_AddRefs(entry));
+ if (!entry) {
+ continue;
+ }
+
+ if (entry->GetID() == targetID) {
+ destEntry.swap(entry);
+ break;
+ }
+ }
+ }
+ } else {
+ destEntry = destTreeRoot;
+ }
+
+ nsSHistory::HandleEntriesToSwapInDocShell(aBC, aEntry, destEntry);
+ // Now handle the children of aEntry.
+ SwapEntriesData childData = {data->ignoreBC, destTreeRoot, destEntry};
+ return nsSHistory::WalkHistoryEntries(aEntry, aBC, SetChildHistoryEntry,
+ &childData);
+}
+
+// static
+void nsSHistory::HandleEntriesToSwapInDocShell(
+ mozilla::dom::BrowsingContext* aBC, nsISHEntry* aOldEntry,
+ nsISHEntry* aNewEntry) {
+ bool shPref = mozilla::SessionHistoryInParent();
+ if (aBC->IsInProcess() || !shPref) {
+ nsDocShell* docshell = static_cast<nsDocShell*>(aBC->GetDocShell());
+ if (docshell) {
+ docshell->SwapHistoryEntries(aOldEntry, aNewEntry);
+ }
+ } else {
+ // FIXME Bug 1633988: Need to update entries?
+ }
+
+ // XXX Simplify this once the old and new session history implementations
+ // don't run at the same time.
+ if (shPref && XRE_IsParentProcess()) {
+ aBC->Canonical()->SwapHistoryEntries(aOldEntry, aNewEntry);
+ }
+}
+
+void nsSHistory::UpdateRootBrowsingContextState(BrowsingContext* aRootBC) {
+ if (aRootBC && aRootBC->EverAttached()) {
+ bool sameDocument = IsEmptyOrHasEntriesForSingleTopLevelPage();
+ if (sameDocument != aRootBC->GetIsSingleToplevelInHistory()) {
+ // If the browsing context is discarded then its session history is
+ // invalid and will go away.
+ Unused << aRootBC->SetIsSingleToplevelInHistory(sameDocument);
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsSHistory::AddToRootSessionHistory(bool aCloneChildren, nsISHEntry* aOSHE,
+ BrowsingContext* aRootBC,
+ nsISHEntry* aEntry, uint32_t aLoadType,
+ bool aShouldPersist,
+ Maybe<int32_t>* aPreviousEntryIndex,
+ Maybe<int32_t>* aLoadedEntryIndex) {
+ MOZ_ASSERT(aRootBC->IsTop());
+
+ nsresult rv = NS_OK;
+
+ // If we need to clone our children onto the new session
+ // history entry, do so now.
+ if (aCloneChildren && aOSHE) {
+ uint32_t cloneID = aOSHE->GetID();
+ nsCOMPtr<nsISHEntry> newEntry;
+ nsSHistory::CloneAndReplace(aOSHE, aRootBC, cloneID, aEntry, true,
+ getter_AddRefs(newEntry));
+ NS_ASSERTION(aEntry == newEntry,
+ "The new session history should be in the new entry");
+ }
+ // This is the root docshell
+ bool addToSHistory = !LOAD_TYPE_HAS_FLAGS(
+ aLoadType, nsIWebNavigation::LOAD_FLAGS_REPLACE_HISTORY);
+ if (!addToSHistory) {
+ // Replace current entry in session history; If the requested index is
+ // valid, it indicates the loading was triggered by a history load, and
+ // we should replace the entry at requested index instead.
+ int32_t index = GetIndexForReplace();
+
+ // Replace the current entry with the new entry
+ if (index >= 0) {
+ rv = ReplaceEntry(index, aEntry);
+ } else {
+ // If we're trying to replace an inexistant shistory entry, append.
+ addToSHistory = true;
+ }
+ }
+ if (addToSHistory) {
+ // Add to session history
+ *aPreviousEntryIndex = Some(mIndex);
+ rv = AddEntry(aEntry, aShouldPersist);
+ *aLoadedEntryIndex = Some(mIndex);
+ MOZ_LOG(gPageCacheLog, LogLevel::Verbose,
+ ("Previous index: %d, Loaded index: %d",
+ aPreviousEntryIndex->value(), aLoadedEntryIndex->value()));
+ }
+ if (NS_SUCCEEDED(rv)) {
+ aEntry->SetDocshellID(aRootBC->GetHistoryID());
+ }
+ return rv;
+}
+
+/* Add an entry to the History list at mIndex and
+ * increment the index to point to the new entry
+ */
+NS_IMETHODIMP
+nsSHistory::AddEntry(nsISHEntry* aSHEntry, bool aPersist) {
+ NS_ENSURE_ARG(aSHEntry);
+
+ nsCOMPtr<nsISHistory> shistoryOfEntry = aSHEntry->GetShistory();
+ if (shistoryOfEntry && shistoryOfEntry != this) {
+ NS_WARNING(
+ "The entry has been associated to another nsISHistory instance. "
+ "Try nsISHEntry.clone() and nsISHEntry.abandonBFCacheEntry() "
+ "first if you're copying an entry from another nsISHistory.");
+ return NS_ERROR_FAILURE;
+ }
+
+ aSHEntry->SetShistory(this);
+
+ // If we have a root docshell, update the docshell id of the root shentry to
+ // match the id of that docshell
+ RefPtr<BrowsingContext> rootBC = GetBrowsingContext();
+ if (rootBC) {
+ aSHEntry->SetDocshellID(mRootDocShellID);
+ }
+
+ if (mIndex >= 0) {
+ MOZ_ASSERT(mIndex < Length(), "Index out of range!");
+ if (mIndex >= Length()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mEntries[mIndex] && !mEntries[mIndex]->GetPersist()) {
+ NotifyListeners(mListeners, [](auto l) { l->OnHistoryReplaceEntry(); });
+ aSHEntry->SetPersist(aPersist);
+ mEntries[mIndex] = aSHEntry;
+ UpdateRootBrowsingContextState();
+ return NS_OK;
+ }
+ }
+ SHistoryChangeNotifier change(this);
+
+ int32_t truncating = Length() - 1 - mIndex;
+ if (truncating > 0) {
+ NotifyListeners(mListeners,
+ [truncating](auto l) { l->OnHistoryTruncate(truncating); });
+ }
+
+ nsCOMPtr<nsIURI> uri = aSHEntry->GetURI();
+ NotifyListeners(mListeners,
+ [&uri, this](auto l) { l->OnHistoryNewEntry(uri, mIndex); });
+
+ // Remove all entries after the current one, add the new one, and set the
+ // new one as the current one.
+ MOZ_ASSERT(mIndex >= -1);
+ aSHEntry->SetPersist(aPersist);
+ mEntries.TruncateLength(mIndex + 1);
+ mEntries.AppendElement(aSHEntry);
+ mIndex++;
+ if (mIndex > 0) {
+ UpdateEntryLength(mEntries[mIndex - 1], mEntries[mIndex], false);
+ }
+
+ // Purge History list if it is too long
+ if (gHistoryMaxSize >= 0 && Length() > gHistoryMaxSize) {
+ PurgeHistory(Length() - gHistoryMaxSize);
+ }
+
+ UpdateRootBrowsingContextState();
+
+ return NS_OK;
+}
+
+void nsSHistory::NotifyOnHistoryReplaceEntry() {
+ NotifyListeners(mListeners, [](auto l) { l->OnHistoryReplaceEntry(); });
+}
+
+/* Get size of the history list */
+NS_IMETHODIMP
+nsSHistory::GetCount(int32_t* aResult) {
+ MOZ_ASSERT(aResult, "null out param?");
+ *aResult = Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::GetIndex(int32_t* aResult) {
+ MOZ_ASSERT(aResult, "null out param?");
+ *aResult = mIndex;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::SetIndex(int32_t aIndex) {
+ if (aIndex < 0 || aIndex >= Length()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mIndex = aIndex;
+ return NS_OK;
+}
+
+/* Get the requestedIndex */
+NS_IMETHODIMP
+nsSHistory::GetRequestedIndex(int32_t* aResult) {
+ MOZ_ASSERT(aResult, "null out param?");
+ *aResult = mRequestedIndex;
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsSHistory::InternalSetRequestedIndex(int32_t aRequestedIndex) {
+ MOZ_ASSERT(aRequestedIndex >= -1 && aRequestedIndex < Length());
+ mRequestedIndex = aRequestedIndex;
+}
+
+NS_IMETHODIMP
+nsSHistory::GetEntryAtIndex(int32_t aIndex, nsISHEntry** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ if (aIndex < 0 || aIndex >= Length()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aResult = mEntries[aIndex];
+ NS_ADDREF(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(int32_t)
+nsSHistory::GetIndexOfEntry(nsISHEntry* aSHEntry) {
+ for (int32_t i = 0; i < Length(); i++) {
+ if (aSHEntry == mEntries[i]) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+static void LogEntry(nsISHEntry* aEntry, int32_t aIndex, int32_t aTotal,
+ const nsCString& aPrefix, bool aIsCurrent) {
+ if (!aEntry) {
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ (" %s+- %i SH Entry null\n", aPrefix.get(), aIndex));
+ return;
+ }
+
+ nsCOMPtr<nsIURI> uri = aEntry->GetURI();
+ nsAutoString title, name;
+ aEntry->GetTitle(title);
+ aEntry->GetName(name);
+
+ SHEntrySharedParentState* shared;
+ if (mozilla::SessionHistoryInParent()) {
+ shared = static_cast<SessionHistoryEntry*>(aEntry)->SharedInfo();
+ } else {
+ shared = static_cast<nsSHEntry*>(aEntry)->GetState();
+ }
+
+ nsID docShellId;
+ aEntry->GetDocshellID(docShellId);
+
+ int32_t childCount = aEntry->GetChildCount();
+
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ ("%s%s+- %i SH Entry %p %" PRIu64 " %s\n", aIsCurrent ? ">" : " ",
+ aPrefix.get(), aIndex, aEntry, shared->GetId(),
+ nsIDToCString(docShellId).get()));
+
+ nsCString prefix(aPrefix);
+ if (aIndex < aTotal - 1) {
+ prefix.AppendLiteral("| ");
+ } else {
+ prefix.AppendLiteral(" ");
+ }
+
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ (" %s%s URL = %s\n", prefix.get(), childCount > 0 ? "|" : " ",
+ uri->GetSpecOrDefault().get()));
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ (" %s%s Title = %s\n", prefix.get(), childCount > 0 ? "|" : " ",
+ NS_LossyConvertUTF16toASCII(title).get()));
+ MOZ_LOG(gSHLog, LogLevel::Debug,
+ (" %s%s Name = %s\n", prefix.get(), childCount > 0 ? "|" : " ",
+ NS_LossyConvertUTF16toASCII(name).get()));
+ MOZ_LOG(
+ gSHLog, LogLevel::Debug,
+ (" %s%s Is in BFCache = %s\n", prefix.get(), childCount > 0 ? "|" : " ",
+ aEntry->GetIsInBFCache() ? "true" : "false"));
+
+ nsCOMPtr<nsISHEntry> prevChild;
+ for (int32_t i = 0; i < childCount; ++i) {
+ nsCOMPtr<nsISHEntry> child;
+ aEntry->GetChildAt(i, getter_AddRefs(child));
+ LogEntry(child, i, childCount, prefix, false);
+ child.swap(prevChild);
+ }
+}
+
+void nsSHistory::LogHistory() {
+ if (!MOZ_LOG_TEST(gSHLog, LogLevel::Debug)) {
+ return;
+ }
+
+ MOZ_LOG(gSHLog, LogLevel::Debug, ("nsSHistory %p\n", this));
+ int32_t length = Length();
+ for (int32_t i = 0; i < length; i++) {
+ LogEntry(mEntries[i], i, length, EmptyCString(), i == mIndex);
+ }
+}
+
+void nsSHistory::WindowIndices(int32_t aIndex, int32_t* aOutStartIndex,
+ int32_t* aOutEndIndex) {
+ *aOutStartIndex = std::max(0, aIndex - nsSHistory::VIEWER_WINDOW);
+ *aOutEndIndex = std::min(Length() - 1, aIndex + nsSHistory::VIEWER_WINDOW);
+}
+
+static void MarkAsInitialEntry(
+ SessionHistoryEntry* aEntry,
+ nsTHashMap<nsIDHashKey, SessionHistoryEntry*>& aHashtable) {
+ if (!aEntry->BCHistoryLength().Modified()) {
+ ++(aEntry->BCHistoryLength());
+ }
+ aHashtable.InsertOrUpdate(aEntry->DocshellID(), aEntry);
+ for (const RefPtr<SessionHistoryEntry>& entry : aEntry->Children()) {
+ if (entry) {
+ MarkAsInitialEntry(entry, aHashtable);
+ }
+ }
+}
+
+static void ClearEntries(SessionHistoryEntry* aEntry) {
+ aEntry->ClearBCHistoryLength();
+ for (const RefPtr<SessionHistoryEntry>& entry : aEntry->Children()) {
+ if (entry) {
+ ClearEntries(entry);
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsSHistory::PurgeHistory(int32_t aNumEntries) {
+ if (Length() <= 0 || aNumEntries <= 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ SHistoryChangeNotifier change(this);
+
+ aNumEntries = std::min(aNumEntries, Length());
+
+ NotifyListeners(mListeners,
+ [aNumEntries](auto l) { l->OnHistoryPurge(aNumEntries); });
+
+ // Set all the entries hanging of the first entry that we keep
+ // (mEntries[aNumEntries]) as being created as the result of a load
+ // (so contributing one to their BCHistoryLength).
+ nsTHashMap<nsIDHashKey, SessionHistoryEntry*> docshellIDToEntry;
+ if (aNumEntries != Length()) {
+ nsCOMPtr<SessionHistoryEntry> she =
+ do_QueryInterface(mEntries[aNumEntries]);
+ if (she) {
+ MarkAsInitialEntry(she, docshellIDToEntry);
+ }
+ }
+
+ // Reset the BCHistoryLength of all the entries that we're removing to a new
+ // counter with value 0 while decreasing their contribution to a shared
+ // BCHistoryLength. The end result is that they don't contribute to the
+ // BCHistoryLength of any other entry anymore.
+ for (int32_t i = 0; i < aNumEntries; ++i) {
+ nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(mEntries[i]);
+ if (she) {
+ ClearEntries(she);
+ }
+ }
+
+ RefPtr<BrowsingContext> rootBC = GetBrowsingContext();
+ if (rootBC) {
+ rootBC->PreOrderWalk([&docshellIDToEntry](BrowsingContext* aBC) {
+ SessionHistoryEntry* entry = docshellIDToEntry.Get(aBC->GetHistoryID());
+ Unused << aBC->SetHistoryEntryCount(
+ entry ? uint32_t(entry->BCHistoryLength()) : 0);
+ });
+ }
+
+ // Remove the first `aNumEntries` entries.
+ mEntries.RemoveElementsAt(0, aNumEntries);
+
+ // Adjust the indices, but don't let them go below -1.
+ mIndex -= aNumEntries;
+ mIndex = std::max(mIndex, -1);
+ mRequestedIndex -= aNumEntries;
+ mRequestedIndex = std::max(mRequestedIndex, -1);
+
+ if (rootBC && rootBC->GetDocShell()) {
+ rootBC->GetDocShell()->HistoryPurged(aNumEntries);
+ }
+
+ UpdateRootBrowsingContextState(rootBC);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::AddSHistoryListener(nsISHistoryListener* aListener) {
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ // Check if the listener supports Weak Reference. This is a must.
+ // This listener functionality is used by embedders and we want to
+ // have the right ownership with who ever listens to SHistory
+ nsWeakPtr listener = do_GetWeakReference(aListener);
+ if (!listener) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mListeners.AppendElementUnlessExists(listener);
+ return NS_OK;
+}
+
+void nsSHistory::NotifyListenersContentViewerEvicted(uint32_t aNumEvicted) {
+ NotifyListeners(mListeners, [aNumEvicted](auto l) {
+ l->OnContentViewerEvicted(aNumEvicted);
+ });
+}
+
+NS_IMETHODIMP
+nsSHistory::RemoveSHistoryListener(nsISHistoryListener* aListener) {
+ // Make sure the listener that wants to be removed is the
+ // one we have in store.
+ nsWeakPtr listener = do_GetWeakReference(aListener);
+ mListeners.RemoveElement(listener);
+ return NS_OK;
+}
+
+/* Replace an entry in the History list at a particular index.
+ * Do not update index or count.
+ */
+NS_IMETHODIMP
+nsSHistory::ReplaceEntry(int32_t aIndex, nsISHEntry* aReplaceEntry) {
+ NS_ENSURE_ARG(aReplaceEntry);
+
+ if (aIndex < 0 || aIndex >= Length()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsISHistory> shistoryOfEntry = aReplaceEntry->GetShistory();
+ if (shistoryOfEntry && shistoryOfEntry != this) {
+ NS_WARNING(
+ "The entry has been associated to another nsISHistory instance. "
+ "Try nsISHEntry.clone() and nsISHEntry.abandonBFCacheEntry() "
+ "first if you're copying an entry from another nsISHistory.");
+ return NS_ERROR_FAILURE;
+ }
+
+ aReplaceEntry->SetShistory(this);
+
+ NotifyListeners(mListeners, [](auto l) { l->OnHistoryReplaceEntry(); });
+
+ aReplaceEntry->SetPersist(true);
+ mEntries[aIndex] = aReplaceEntry;
+
+ UpdateRootBrowsingContextState();
+
+ return NS_OK;
+}
+
+// Calls OnHistoryReload on all registered session history listeners.
+// Listeners may return 'false' to cancel an action so make sure that we
+// set the return value to 'false' if one of the listeners wants to cancel.
+NS_IMETHODIMP
+nsSHistory::NotifyOnHistoryReload(bool* aCanReload) {
+ *aCanReload = true;
+
+ for (const nsWeakPtr& weakPtr : mListeners.EndLimitedRange()) {
+ nsCOMPtr<nsISHistoryListener> listener = do_QueryReferent(weakPtr);
+ if (listener) {
+ bool retval = true;
+
+ if (NS_SUCCEEDED(listener->OnHistoryReload(&retval)) && !retval) {
+ *aCanReload = false;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::EvictOutOfRangeContentViewers(int32_t aIndex) {
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
+ ("nsSHistory::EvictOutOfRangeContentViewers %i", aIndex));
+
+ // Check our per SHistory object limit in the currently navigated SHistory
+ EvictOutOfRangeWindowContentViewers(aIndex);
+ // Check our total limit across all SHistory objects
+ GloballyEvictContentViewers();
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsSHistory::EvictContentViewersOrReplaceEntry(nsISHEntry* aNewSHEntry,
+ bool aReplace) {
+ if (!aReplace) {
+ int32_t curIndex;
+ GetIndex(&curIndex);
+ if (curIndex > -1) {
+ EvictOutOfRangeContentViewers(curIndex);
+ }
+ } else {
+ nsCOMPtr<nsISHEntry> rootSHEntry = nsSHistory::GetRootSHEntry(aNewSHEntry);
+
+ int32_t index = GetIndexOfEntry(rootSHEntry);
+ if (index > -1) {
+ ReplaceEntry(index, rootSHEntry);
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsSHistory::EvictAllContentViewers() {
+ // XXXbz we don't actually do a good job of evicting things as we should, so
+ // we might have viewers quite far from mIndex. So just evict everything.
+ for (int32_t i = 0; i < Length(); i++) {
+ EvictContentViewerForEntry(mEntries[i]);
+ }
+
+ return NS_OK;
+}
+
+static void FinishRestore(CanonicalBrowsingContext* aBrowsingContext,
+ nsDocShellLoadState* aLoadState,
+ SessionHistoryEntry* aEntry,
+ nsFrameLoader* aFrameLoader, bool aCanSave) {
+ MOZ_ASSERT(aEntry);
+ MOZ_ASSERT(aFrameLoader);
+
+ aEntry->SetFrameLoader(nullptr);
+
+ nsCOMPtr<nsISHistory> shistory = aEntry->GetShistory();
+ int32_t indexOfHistoryLoad =
+ shistory ? shistory->GetIndexOfEntry(aEntry) : -1;
+
+ nsCOMPtr<nsFrameLoaderOwner> frameLoaderOwner =
+ do_QueryInterface(aBrowsingContext->GetEmbedderElement());
+ if (frameLoaderOwner && aFrameLoader->GetMaybePendingBrowsingContext() &&
+ indexOfHistoryLoad >= 0) {
+ RefPtr<BrowsingContextWebProgress> webProgress =
+ aBrowsingContext->GetWebProgress();
+ if (webProgress) {
+ // Synthesize a STATE_START WebProgress state change event from here
+ // in order to ensure emitting it on the BrowsingContext we navigate
+ // *from* instead of the BrowsingContext we navigate *to*. This will fire
+ // before and the next one will be ignored by BrowsingContextWebProgress:
+ // https://searchfox.org/mozilla-central/rev/77f0b36028b2368e342c982ea47609040b399d89/docshell/base/BrowsingContextWebProgress.cpp#196-203
+ nsCOMPtr<nsIURI> nextURI = aEntry->GetURI();
+ nsCOMPtr<nsIURI> nextOriginalURI = aEntry->GetOriginalURI();
+ nsCOMPtr<nsIRequest> request = MakeAndAddRef<RemoteWebProgressRequest>(
+ nextURI, nextOriginalURI ? nextOriginalURI : nextURI,
+ ""_ns /* aMatchedList */);
+ webProgress->OnStateChange(webProgress, request,
+ nsIWebProgressListener::STATE_START |
+ nsIWebProgressListener::STATE_IS_DOCUMENT |
+ nsIWebProgressListener::STATE_IS_REQUEST |
+ nsIWebProgressListener::STATE_IS_WINDOW |
+ nsIWebProgressListener::STATE_IS_NETWORK,
+ NS_OK);
+ }
+
+ RefPtr<CanonicalBrowsingContext> loadingBC =
+ aFrameLoader->GetMaybePendingBrowsingContext()->Canonical();
+ RefPtr<nsFrameLoader> currentFrameLoader =
+ frameLoaderOwner->GetFrameLoader();
+ // The current page can be bfcached, store the
+ // nsFrameLoader in the current SessionHistoryEntry.
+ RefPtr<SessionHistoryEntry> currentSHEntry =
+ aBrowsingContext->GetActiveSessionHistoryEntry();
+ if (currentSHEntry) {
+ // Update layout history state now, before we change the IsInBFCache flag
+ // and the active session history entry.
+ aBrowsingContext->SynchronizeLayoutHistoryState();
+
+ if (aCanSave) {
+ currentSHEntry->SetFrameLoader(currentFrameLoader);
+ Unused << aBrowsingContext->SetIsInBFCache(true);
+ }
+ }
+
+ if (aBrowsingContext->IsActive()) {
+ loadingBC->PreOrderWalk([&](BrowsingContext* aContext) {
+ if (BrowserParent* bp = aContext->Canonical()->GetBrowserParent()) {
+ ProcessPriorityManager::BrowserPriorityChanged(bp, true);
+ }
+ });
+ }
+
+ if (aEntry) {
+ aEntry->SetWireframe(Nothing());
+ }
+
+ // ReplacedBy will swap the entry back.
+ aBrowsingContext->SetActiveSessionHistoryEntry(aEntry);
+ loadingBC->SetActiveSessionHistoryEntry(nullptr);
+ NavigationIsolationOptions options;
+ aBrowsingContext->ReplacedBy(loadingBC, options);
+
+ // Assuming we still have the session history, update the index.
+ if (loadingBC->GetSessionHistory()) {
+ shistory->InternalSetRequestedIndex(indexOfHistoryLoad);
+ shistory->UpdateIndex();
+ }
+ loadingBC->HistoryCommitIndexAndLength();
+
+ // ResetSHEntryHasUserInteractionCache(); ?
+ // browser.navigation.requireUserInteraction is still
+ // disabled everywhere.
+
+ frameLoaderOwner->RestoreFrameLoaderFromBFCache(aFrameLoader);
+ // EvictOutOfRangeContentViewers is called here explicitly to
+ // possibly evict the now in the bfcache document.
+ // HistoryCommitIndexAndLength might not have evicted that before the
+ // FrameLoader swap.
+ shistory->EvictOutOfRangeContentViewers(indexOfHistoryLoad);
+
+ // The old page can't be stored in the bfcache,
+ // destroy the nsFrameLoader.
+ if (!aCanSave && currentFrameLoader) {
+ currentFrameLoader->Destroy();
+ }
+
+ Unused << loadingBC->SetIsInBFCache(false);
+
+ // We need to call this after we've restored the page from BFCache (see
+ // SetIsInBFCache(false) above), so that the page is not frozen anymore and
+ // the right focus events are fired.
+ frameLoaderOwner->UpdateFocusAndMouseEnterStateAfterFrameLoaderChange();
+
+ return;
+ }
+
+ aFrameLoader->Destroy();
+
+ // Fall back to do a normal load.
+ aBrowsingContext->LoadURI(aLoadState, false);
+}
+
+/* static */
+void nsSHistory::LoadURIOrBFCache(LoadEntryResult& aLoadEntry) {
+ if (mozilla::BFCacheInParent() && aLoadEntry.mBrowsingContext->IsTop()) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ RefPtr<nsDocShellLoadState> loadState = aLoadEntry.mLoadState;
+ RefPtr<CanonicalBrowsingContext> canonicalBC =
+ aLoadEntry.mBrowsingContext->Canonical();
+ nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(loadState->SHEntry());
+ nsCOMPtr<SessionHistoryEntry> currentShe =
+ canonicalBC->GetActiveSessionHistoryEntry();
+ MOZ_ASSERT(she);
+ RefPtr<nsFrameLoader> frameLoader = she->GetFrameLoader();
+ if (frameLoader &&
+ (!currentShe || (she->SharedInfo() != currentShe->SharedInfo() &&
+ !currentShe->GetFrameLoader()))) {
+ bool canSave = (!currentShe || currentShe->GetSaveLayoutStateFlag()) &&
+ canonicalBC->AllowedInBFCache(Nothing(), nullptr);
+
+ MOZ_LOG(gSHIPBFCacheLog, LogLevel::Debug,
+ ("nsSHistory::LoadURIOrBFCache "
+ "saving presentation=%i",
+ canSave));
+
+ nsCOMPtr<nsFrameLoaderOwner> frameLoaderOwner =
+ do_QueryInterface(canonicalBC->GetEmbedderElement());
+ if (frameLoaderOwner) {
+ RefPtr<nsFrameLoader> currentFrameLoader =
+ frameLoaderOwner->GetFrameLoader();
+ if (currentFrameLoader &&
+ currentFrameLoader->GetMaybePendingBrowsingContext()) {
+ if (WindowGlobalParent* wgp =
+ currentFrameLoader->GetMaybePendingBrowsingContext()
+ ->Canonical()
+ ->GetCurrentWindowGlobal()) {
+ wgp->PermitUnload([canonicalBC, loadState, she, frameLoader,
+ currentFrameLoader, canSave](bool aAllow) {
+ if (aAllow && !canonicalBC->IsReplaced()) {
+ FinishRestore(canonicalBC, loadState, she, frameLoader,
+ canSave && canonicalBC->AllowedInBFCache(
+ Nothing(), nullptr));
+ } else if (currentFrameLoader->GetMaybePendingBrowsingContext()) {
+ nsISHistory* shistory =
+ currentFrameLoader->GetMaybePendingBrowsingContext()
+ ->Canonical()
+ ->GetSessionHistory();
+ if (shistory) {
+ shistory->InternalSetRequestedIndex(-1);
+ }
+ }
+ });
+ return;
+ }
+ }
+ }
+
+ FinishRestore(canonicalBC, loadState, she, frameLoader, canSave);
+ return;
+ }
+ if (frameLoader) {
+ she->SetFrameLoader(nullptr);
+ frameLoader->Destroy();
+ }
+ }
+
+ RefPtr<BrowsingContext> bc = aLoadEntry.mBrowsingContext;
+ RefPtr<nsDocShellLoadState> loadState = aLoadEntry.mLoadState;
+ bc->LoadURI(loadState, false);
+}
+
+/* static */
+void nsSHistory::LoadURIs(nsTArray<LoadEntryResult>& aLoadResults) {
+ for (LoadEntryResult& loadEntry : aLoadResults) {
+ LoadURIOrBFCache(loadEntry);
+ }
+}
+
+NS_IMETHODIMP
+nsSHistory::Reload(uint32_t aReloadFlags) {
+ nsTArray<LoadEntryResult> loadResults;
+ nsresult rv = Reload(aReloadFlags, loadResults);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (loadResults.IsEmpty()) {
+ return NS_OK;
+ }
+
+ LoadURIs(loadResults);
+ return NS_OK;
+}
+
+nsresult nsSHistory::Reload(uint32_t aReloadFlags,
+ nsTArray<LoadEntryResult>& aLoadResults) {
+ MOZ_ASSERT(aLoadResults.IsEmpty());
+
+ uint32_t loadType;
+ if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY &&
+ aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) {
+ loadType = LOAD_RELOAD_BYPASS_PROXY_AND_CACHE;
+ } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY) {
+ loadType = LOAD_RELOAD_BYPASS_PROXY;
+ } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE) {
+ loadType = LOAD_RELOAD_BYPASS_CACHE;
+ } else if (aReloadFlags & nsIWebNavigation::LOAD_FLAGS_CHARSET_CHANGE) {
+ loadType = LOAD_RELOAD_CHARSET_CHANGE;
+ } else {
+ loadType = LOAD_RELOAD_NORMAL;
+ }
+
+ // We are reloading. Send Reload notifications.
+ // nsDocShellLoadFlagType is not public, where as nsIWebNavigation
+ // is public. So send the reload notifications with the
+ // nsIWebNavigation flags.
+ bool canNavigate = true;
+ MOZ_ALWAYS_SUCCEEDS(NotifyOnHistoryReload(&canNavigate));
+ if (!canNavigate) {
+ return NS_OK;
+ }
+
+ nsresult rv = LoadEntry(
+ mIndex, loadType, HIST_CMD_RELOAD, aLoadResults, /* aSameEpoch */ false,
+ /* aLoadCurrentEntry */ true,
+ aReloadFlags & nsIWebNavigation::LOAD_FLAGS_USER_ACTIVATION);
+ if (NS_FAILED(rv)) {
+ aLoadResults.Clear();
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::ReloadCurrentEntry() {
+ nsTArray<LoadEntryResult> loadResults;
+ nsresult rv = ReloadCurrentEntry(loadResults);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LoadURIs(loadResults);
+ return NS_OK;
+}
+
+nsresult nsSHistory::ReloadCurrentEntry(
+ nsTArray<LoadEntryResult>& aLoadResults) {
+ // Notify listeners
+ NotifyListeners(mListeners, [](auto l) { l->OnHistoryGotoIndex(); });
+
+ return LoadEntry(mIndex, LOAD_HISTORY, HIST_CMD_RELOAD, aLoadResults,
+ /* aSameEpoch */ false, /* aLoadCurrentEntry */ true,
+ /* aUserActivation */ false);
+}
+
+void nsSHistory::EvictOutOfRangeWindowContentViewers(int32_t aIndex) {
+ // XXX rename method to EvictContentViewersExceptAroundIndex, or something.
+
+ // We need to release all content viewers that are no longer in the range
+ //
+ // aIndex - VIEWER_WINDOW to aIndex + VIEWER_WINDOW
+ //
+ // to ensure that this SHistory object isn't responsible for more than
+ // VIEWER_WINDOW content viewers. But our job is complicated by the
+ // fact that two entries which are related by either hash navigations or
+ // history.pushState will have the same content viewer.
+ //
+ // To illustrate the issue, suppose VIEWER_WINDOW = 3 and we have four
+ // linked entries in our history. Suppose we then add a new content
+ // viewer and call into this function. So the history looks like:
+ //
+ // A A A A B
+ // + *
+ //
+ // where the letters are content viewers and + and * denote the beginning and
+ // end of the range aIndex +/- VIEWER_WINDOW.
+ //
+ // Although one copy of the content viewer A exists outside the range, we
+ // don't want to evict A, because it has other copies in range!
+ //
+ // We therefore adjust our eviction strategy to read:
+ //
+ // Evict each content viewer outside the range aIndex -/+
+ // VIEWER_WINDOW, unless that content viewer also appears within the
+ // range.
+ //
+ // (Note that it's entirely legal to have two copies of one content viewer
+ // separated by a different content viewer -- call pushState twice, go back
+ // once, and refresh -- so we can't rely on identical viewers only appearing
+ // adjacent to one another.)
+
+ if (aIndex < 0) {
+ return;
+ }
+ NS_ENSURE_TRUE_VOID(aIndex < Length());
+
+ // Calculate the range that's safe from eviction.
+ int32_t startSafeIndex, endSafeIndex;
+ WindowIndices(aIndex, &startSafeIndex, &endSafeIndex);
+
+ LOG(
+ ("EvictOutOfRangeWindowContentViewers(index=%d), "
+ "Length()=%d. Safe range [%d, %d]",
+ aIndex, Length(), startSafeIndex, endSafeIndex));
+
+ // The content viewers in range aIndex -/+ VIEWER_WINDOW will not be
+ // evicted. Collect a set of them so we don't accidentally evict one of them
+ // if it appears outside this range.
+ nsCOMArray<nsIContentViewer> safeViewers;
+ nsTArray<RefPtr<nsFrameLoader>> safeFrameLoaders;
+ for (int32_t i = startSafeIndex; i <= endSafeIndex; i++) {
+ nsCOMPtr<nsIContentViewer> viewer = mEntries[i]->GetContentViewer();
+ if (viewer) {
+ safeViewers.AppendObject(viewer);
+ } else if (nsCOMPtr<SessionHistoryEntry> she =
+ do_QueryInterface(mEntries[i])) {
+ nsFrameLoader* frameLoader = she->GetFrameLoader();
+ if (frameLoader) {
+ safeFrameLoaders.AppendElement(frameLoader);
+ }
+ }
+ }
+
+ // Walk the SHistory list and evict any content viewers that aren't safe.
+ // (It's important that the condition checks Length(), rather than a cached
+ // copy of Length(), because the length might change between iterations.)
+ for (int32_t i = 0; i < Length(); i++) {
+ nsCOMPtr<nsISHEntry> entry = mEntries[i];
+ nsCOMPtr<nsIContentViewer> viewer = entry->GetContentViewer();
+ if (viewer) {
+ if (safeViewers.IndexOf(viewer) == -1) {
+ EvictContentViewerForEntry(entry);
+ }
+ } else if (nsCOMPtr<SessionHistoryEntry> she =
+ do_QueryInterface(mEntries[i])) {
+ nsFrameLoader* frameLoader = she->GetFrameLoader();
+ if (frameLoader) {
+ if (!safeFrameLoaders.Contains(frameLoader)) {
+ EvictContentViewerForEntry(entry);
+ }
+ }
+ }
+ }
+}
+
+namespace {
+
+class EntryAndDistance {
+ public:
+ EntryAndDistance(nsSHistory* aSHistory, nsISHEntry* aEntry, uint32_t aDist)
+ : mSHistory(aSHistory),
+ mEntry(aEntry),
+ mViewer(aEntry->GetContentViewer()),
+ mLastTouched(mEntry->GetLastTouched()),
+ mDistance(aDist) {
+ nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(aEntry);
+ if (she) {
+ mFrameLoader = she->GetFrameLoader();
+ }
+ NS_ASSERTION(mViewer || mFrameLoader,
+ "Entry should have a content viewer or frame loader.");
+ }
+
+ bool operator<(const EntryAndDistance& aOther) const {
+ // Compare distances first, and fall back to last-accessed times.
+ if (aOther.mDistance != this->mDistance) {
+ return this->mDistance < aOther.mDistance;
+ }
+
+ return this->mLastTouched < aOther.mLastTouched;
+ }
+
+ bool operator==(const EntryAndDistance& aOther) const {
+ // This is a little silly; we need == so the default comaprator can be
+ // instantiated, but this function is never actually called when we sort
+ // the list of EntryAndDistance objects.
+ return aOther.mDistance == this->mDistance &&
+ aOther.mLastTouched == this->mLastTouched;
+ }
+
+ RefPtr<nsSHistory> mSHistory;
+ nsCOMPtr<nsISHEntry> mEntry;
+ nsCOMPtr<nsIContentViewer> mViewer;
+ RefPtr<nsFrameLoader> mFrameLoader;
+ uint32_t mLastTouched;
+ int32_t mDistance;
+};
+
+} // namespace
+
+// static
+void nsSHistory::GloballyEvictContentViewers() {
+ // First, collect from each SHistory object the entries which have a cached
+ // content viewer. Associate with each entry its distance from its SHistory's
+ // current index.
+
+ nsTArray<EntryAndDistance> entries;
+
+ for (auto shist : gSHistoryList.mList) {
+ // Maintain a list of the entries which have viewers and belong to
+ // this particular shist object. We'll add this list to the global list,
+ // |entries|, eventually.
+ nsTArray<EntryAndDistance> shEntries;
+
+ // Content viewers are likely to exist only within shist->mIndex -/+
+ // VIEWER_WINDOW, so only search within that range.
+ //
+ // A content viewer might exist outside that range due to either:
+ //
+ // * history.pushState or hash navigations, in which case a copy of the
+ // content viewer should exist within the range, or
+ //
+ // * bugs which cause us not to call nsSHistory::EvictContentViewers()
+ // often enough. Once we do call EvictContentViewers() for the
+ // SHistory object in question, we'll do a full search of its history
+ // and evict the out-of-range content viewers, so we don't bother here.
+ //
+ int32_t startIndex, endIndex;
+ shist->WindowIndices(shist->mIndex, &startIndex, &endIndex);
+ for (int32_t i = startIndex; i <= endIndex; i++) {
+ nsCOMPtr<nsISHEntry> entry = shist->mEntries[i];
+ nsCOMPtr<nsIContentViewer> contentViewer = entry->GetContentViewer();
+
+ bool found = false;
+ bool hasContentViewerOrFrameLoader = false;
+ if (contentViewer) {
+ hasContentViewerOrFrameLoader = true;
+ // Because one content viewer might belong to multiple SHEntries, we
+ // have to search through shEntries to see if we already know
+ // about this content viewer. If we find the viewer, update its
+ // distance from the SHistory's index and continue.
+ for (uint32_t j = 0; j < shEntries.Length(); j++) {
+ EntryAndDistance& container = shEntries[j];
+ if (container.mViewer == contentViewer) {
+ container.mDistance =
+ std::min(container.mDistance, DeprecatedAbs(i - shist->mIndex));
+ found = true;
+ break;
+ }
+ }
+ } else if (nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(entry)) {
+ if (RefPtr<nsFrameLoader> frameLoader = she->GetFrameLoader()) {
+ hasContentViewerOrFrameLoader = true;
+ // Similar search as above but using frameloader.
+ for (uint32_t j = 0; j < shEntries.Length(); j++) {
+ EntryAndDistance& container = shEntries[j];
+ if (container.mFrameLoader == frameLoader) {
+ container.mDistance = std::min(container.mDistance,
+ DeprecatedAbs(i - shist->mIndex));
+ found = true;
+ break;
+ }
+ }
+ }
+ }
+
+ // If we didn't find a EntryAndDistance for this content viewer /
+ // frameloader, make a new one.
+ if (hasContentViewerOrFrameLoader && !found) {
+ EntryAndDistance container(shist, entry,
+ DeprecatedAbs(i - shist->mIndex));
+ shEntries.AppendElement(container);
+ }
+ }
+
+ // We've found all the entries belonging to shist which have viewers.
+ // Add those entries to our global list and move on.
+ entries.AppendElements(shEntries);
+ }
+
+ // We now have collected all cached content viewers. First check that we
+ // have enough that we actually need to evict some.
+ if ((int32_t)entries.Length() <= sHistoryMaxTotalViewers) {
+ return;
+ }
+
+ // If we need to evict, sort our list of entries and evict the largest
+ // ones. (We could of course get better algorithmic complexity here by using
+ // a heap or something more clever. But sHistoryMaxTotalViewers isn't large,
+ // so let's not worry about it.)
+ entries.Sort();
+
+ for (int32_t i = entries.Length() - 1; i >= sHistoryMaxTotalViewers; --i) {
+ (entries[i].mSHistory)->EvictContentViewerForEntry(entries[i].mEntry);
+ }
+}
+
+nsresult nsSHistory::FindEntryForBFCache(SHEntrySharedParentState* aEntry,
+ nsISHEntry** aResult,
+ int32_t* aResultIndex) {
+ *aResult = nullptr;
+ *aResultIndex = -1;
+
+ int32_t startIndex, endIndex;
+ WindowIndices(mIndex, &startIndex, &endIndex);
+
+ for (int32_t i = startIndex; i <= endIndex; ++i) {
+ nsCOMPtr<nsISHEntry> shEntry = mEntries[i];
+
+ // Does shEntry have the same BFCacheEntry as the argument to this method?
+ if (shEntry->HasBFCacheEntry(aEntry)) {
+ shEntry.forget(aResult);
+ *aResultIndex = i;
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP_(void)
+nsSHistory::EvictExpiredContentViewerForEntry(
+ SHEntrySharedParentState* aEntry) {
+ int32_t index;
+ nsCOMPtr<nsISHEntry> shEntry;
+ FindEntryForBFCache(aEntry, getter_AddRefs(shEntry), &index);
+
+ if (index == mIndex) {
+ NS_WARNING("How did the current SHEntry expire?");
+ }
+
+ if (shEntry) {
+ EvictContentViewerForEntry(shEntry);
+ }
+}
+
+NS_IMETHODIMP_(void)
+nsSHistory::AddToExpirationTracker(SHEntrySharedParentState* aEntry) {
+ RefPtr<SHEntrySharedParentState> entry = aEntry;
+ if (!mHistoryTracker || !entry) {
+ return;
+ }
+
+ mHistoryTracker->AddObject(entry);
+ return;
+}
+
+NS_IMETHODIMP_(void)
+nsSHistory::RemoveFromExpirationTracker(SHEntrySharedParentState* aEntry) {
+ RefPtr<SHEntrySharedParentState> entry = aEntry;
+ MOZ_ASSERT(mHistoryTracker && !mHistoryTracker->IsEmpty());
+ if (!mHistoryTracker || !entry) {
+ return;
+ }
+
+ mHistoryTracker->RemoveObject(entry);
+}
+
+// Evicts all content viewers in all history objects. This is very
+// inefficient, because it requires a linear search through all SHistory
+// objects for each viewer to be evicted. However, this method is called
+// infrequently -- only when the disk or memory cache is cleared.
+
+// static
+void nsSHistory::GloballyEvictAllContentViewers() {
+ int32_t maxViewers = sHistoryMaxTotalViewers;
+ sHistoryMaxTotalViewers = 0;
+ GloballyEvictContentViewers();
+ sHistoryMaxTotalViewers = maxViewers;
+}
+
+void GetDynamicChildren(nsISHEntry* aEntry, nsTArray<nsID>& aDocshellIDs) {
+ int32_t count = aEntry->GetChildCount();
+ for (int32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsISHEntry> child;
+ aEntry->GetChildAt(i, getter_AddRefs(child));
+ if (child) {
+ if (child->IsDynamicallyAdded()) {
+ child->GetDocshellID(*aDocshellIDs.AppendElement());
+ } else {
+ GetDynamicChildren(child, aDocshellIDs);
+ }
+ }
+ }
+}
+
+bool RemoveFromSessionHistoryEntry(nsISHEntry* aRoot,
+ nsTArray<nsID>& aDocshellIDs) {
+ bool didRemove = false;
+ int32_t childCount = aRoot->GetChildCount();
+ for (int32_t i = childCount - 1; i >= 0; --i) {
+ nsCOMPtr<nsISHEntry> child;
+ aRoot->GetChildAt(i, getter_AddRefs(child));
+ if (child) {
+ nsID docshelldID;
+ child->GetDocshellID(docshelldID);
+ if (aDocshellIDs.Contains(docshelldID)) {
+ didRemove = true;
+ aRoot->RemoveChild(child);
+ } else if (RemoveFromSessionHistoryEntry(child, aDocshellIDs)) {
+ didRemove = true;
+ }
+ }
+ }
+ return didRemove;
+}
+
+bool RemoveChildEntries(nsISHistory* aHistory, int32_t aIndex,
+ nsTArray<nsID>& aEntryIDs) {
+ nsCOMPtr<nsISHEntry> root;
+ aHistory->GetEntryAtIndex(aIndex, getter_AddRefs(root));
+ return root ? RemoveFromSessionHistoryEntry(root, aEntryIDs) : false;
+}
+
+bool IsSameTree(nsISHEntry* aEntry1, nsISHEntry* aEntry2) {
+ if (!aEntry1 && !aEntry2) {
+ return true;
+ }
+ if ((!aEntry1 && aEntry2) || (aEntry1 && !aEntry2)) {
+ return false;
+ }
+ uint32_t id1 = aEntry1->GetID();
+ uint32_t id2 = aEntry2->GetID();
+ if (id1 != id2) {
+ return false;
+ }
+
+ int32_t count1 = aEntry1->GetChildCount();
+ int32_t count2 = aEntry2->GetChildCount();
+ // We allow null entries in the end of the child list.
+ int32_t count = std::max(count1, count2);
+ for (int32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsISHEntry> child1, child2;
+ aEntry1->GetChildAt(i, getter_AddRefs(child1));
+ aEntry2->GetChildAt(i, getter_AddRefs(child2));
+ if (!IsSameTree(child1, child2)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool nsSHistory::RemoveDuplicate(int32_t aIndex, bool aKeepNext) {
+ NS_ASSERTION(aIndex >= 0, "aIndex must be >= 0!");
+ NS_ASSERTION(aIndex != 0 || aKeepNext,
+ "If we're removing index 0 we must be keeping the next");
+ NS_ASSERTION(aIndex != mIndex, "Shouldn't remove mIndex!");
+
+ int32_t compareIndex = aKeepNext ? aIndex + 1 : aIndex - 1;
+
+ nsresult rv;
+ nsCOMPtr<nsISHEntry> root1, root2;
+ rv = GetEntryAtIndex(aIndex, getter_AddRefs(root1));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ rv = GetEntryAtIndex(compareIndex, getter_AddRefs(root2));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ SHistoryChangeNotifier change(this);
+
+ if (IsSameTree(root1, root2)) {
+ if (aIndex < compareIndex) {
+ // If we're removing the entry with the lower index we need to move its
+ // BCHistoryLength to the entry we're keeping. If we're removing the entry
+ // with the higher index then it shouldn't have a modified
+ // BCHistoryLength.
+ UpdateEntryLength(root1, root2, true);
+ }
+ nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(root1);
+ if (she) {
+ ClearEntries(she);
+ }
+ mEntries.RemoveElementAt(aIndex);
+
+ // FIXME Bug 1546350: Reimplement history listeners.
+ // if (mRootBC && mRootBC->GetDocShell()) {
+ // static_cast<nsDocShell*>(mRootBC->GetDocShell())
+ // ->HistoryEntryRemoved(aIndex);
+ //}
+
+ // Adjust our indices to reflect the removed entry.
+ if (mIndex > aIndex) {
+ mIndex = mIndex - 1;
+ }
+
+ // NB: If the entry we are removing is the entry currently
+ // being navigated to (mRequestedIndex) then we adjust the index
+ // only if we're not keeping the next entry (because if we are keeping
+ // the next entry (because the current is a duplicate of the next), then
+ // that entry slides into the spot that we're currently pointing to.
+ // We don't do this adjustment for mIndex because mIndex cannot equal
+ // aIndex.
+
+ // NB: We don't need to guard on mRequestedIndex being nonzero here,
+ // because either they're strictly greater than aIndex which is at least
+ // zero, or they are equal to aIndex in which case aKeepNext must be true
+ // if aIndex is zero.
+ if (mRequestedIndex > aIndex || (mRequestedIndex == aIndex && !aKeepNext)) {
+ mRequestedIndex = mRequestedIndex - 1;
+ }
+
+ return true;
+ }
+ return false;
+}
+
+NS_IMETHODIMP_(void)
+nsSHistory::RemoveEntries(nsTArray<nsID>& aIDs, int32_t aStartIndex) {
+ bool didRemove;
+ RemoveEntries(aIDs, aStartIndex, &didRemove);
+ if (didRemove) {
+ RefPtr<BrowsingContext> rootBC = GetBrowsingContext();
+ if (rootBC && rootBC->GetDocShell()) {
+ rootBC->GetDocShell()->DispatchLocationChangeEvent();
+ }
+ }
+}
+
+void nsSHistory::RemoveEntries(nsTArray<nsID>& aIDs, int32_t aStartIndex,
+ bool* aDidRemove) {
+ SHistoryChangeNotifier change(this);
+
+ int32_t index = aStartIndex;
+ while (index >= 0 && RemoveChildEntries(this, --index, aIDs)) {
+ }
+ int32_t minIndex = index;
+ index = aStartIndex;
+ while (index >= 0 && RemoveChildEntries(this, index++, aIDs)) {
+ }
+
+ // We need to remove duplicate nsSHEntry trees.
+ *aDidRemove = false;
+ while (index > minIndex) {
+ if (index != mIndex && RemoveDuplicate(index, index < mIndex)) {
+ *aDidRemove = true;
+ }
+ --index;
+ }
+
+ UpdateRootBrowsingContextState();
+}
+
+void nsSHistory::RemoveFrameEntries(nsISHEntry* aEntry) {
+ int32_t count = aEntry->GetChildCount();
+ AutoTArray<nsID, 16> ids;
+ for (int32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsISHEntry> child;
+ aEntry->GetChildAt(i, getter_AddRefs(child));
+ if (child) {
+ child->GetDocshellID(*ids.AppendElement());
+ }
+ }
+ RemoveEntries(ids, mIndex);
+}
+
+void nsSHistory::RemoveDynEntries(int32_t aIndex, nsISHEntry* aEntry) {
+ // Remove dynamic entries which are at the index and belongs to the container.
+ nsCOMPtr<nsISHEntry> entry(aEntry);
+ if (!entry) {
+ GetEntryAtIndex(aIndex, getter_AddRefs(entry));
+ }
+
+ if (entry) {
+ AutoTArray<nsID, 16> toBeRemovedEntries;
+ GetDynamicChildren(entry, toBeRemovedEntries);
+ if (toBeRemovedEntries.Length()) {
+ RemoveEntries(toBeRemovedEntries, aIndex);
+ }
+ }
+}
+
+void nsSHistory::RemoveDynEntriesForBFCacheEntry(nsIBFCacheEntry* aBFEntry) {
+ int32_t index;
+ nsCOMPtr<nsISHEntry> shEntry;
+ FindEntryForBFCache(static_cast<nsSHEntryShared*>(aBFEntry),
+ getter_AddRefs(shEntry), &index);
+ if (shEntry) {
+ RemoveDynEntries(index, shEntry);
+ }
+}
+
+NS_IMETHODIMP
+nsSHistory::UpdateIndex() {
+ SHistoryChangeNotifier change(this);
+
+ // Update the actual index with the right value.
+ if (mIndex != mRequestedIndex && mRequestedIndex != -1) {
+ mIndex = mRequestedIndex;
+ }
+
+ mRequestedIndex = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSHistory::GotoIndex(int32_t aIndex, bool aUserActivation) {
+ nsTArray<LoadEntryResult> loadResults;
+ nsresult rv = GotoIndex(aIndex, loadResults, /*aSameEpoch*/ false,
+ aIndex == mIndex, aUserActivation);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ LoadURIs(loadResults);
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+nsSHistory::EnsureCorrectEntryAtCurrIndex(nsISHEntry* aEntry) {
+ int index = mRequestedIndex == -1 ? mIndex : mRequestedIndex;
+ if (index > -1 && (mEntries[index] != aEntry)) {
+ ReplaceEntry(index, aEntry);
+ }
+}
+
+nsresult nsSHistory::GotoIndex(int32_t aIndex,
+ nsTArray<LoadEntryResult>& aLoadResults,
+ bool aSameEpoch, bool aLoadCurrentEntry,
+ bool aUserActivation) {
+ return LoadEntry(aIndex, LOAD_HISTORY, HIST_CMD_GOTOINDEX, aLoadResults,
+ aSameEpoch, aLoadCurrentEntry, aUserActivation);
+}
+
+NS_IMETHODIMP_(bool)
+nsSHistory::HasUserInteractionAtIndex(int32_t aIndex) {
+ nsCOMPtr<nsISHEntry> entry;
+ GetEntryAtIndex(aIndex, getter_AddRefs(entry));
+ if (!entry) {
+ return false;
+ }
+ return entry->GetHasUserInteraction();
+}
+
+nsresult nsSHistory::LoadNextPossibleEntry(
+ int32_t aNewIndex, long aLoadType, uint32_t aHistCmd,
+ nsTArray<LoadEntryResult>& aLoadResults, bool aLoadCurrentEntry,
+ bool aUserActivation) {
+ mRequestedIndex = -1;
+ if (aNewIndex < mIndex) {
+ return LoadEntry(aNewIndex - 1, aLoadType, aHistCmd, aLoadResults,
+ /*aSameEpoch*/ false, aLoadCurrentEntry, aUserActivation);
+ }
+ if (aNewIndex > mIndex) {
+ return LoadEntry(aNewIndex + 1, aLoadType, aHistCmd, aLoadResults,
+ /*aSameEpoch*/ false, aLoadCurrentEntry, aUserActivation);
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsSHistory::LoadEntry(int32_t aIndex, long aLoadType,
+ uint32_t aHistCmd,
+ nsTArray<LoadEntryResult>& aLoadResults,
+ bool aSameEpoch, bool aLoadCurrentEntry,
+ bool aUserActivation) {
+ MOZ_LOG(gSHistoryLog, LogLevel::Debug,
+ ("LoadEntry(%d, 0x%lx, %u)", aIndex, aLoadType, aHistCmd));
+ RefPtr<BrowsingContext> rootBC = GetBrowsingContext();
+ if (!rootBC) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aIndex < 0 || aIndex >= Length()) {
+ MOZ_LOG(gSHistoryLog, LogLevel::Debug, ("Index out of range"));
+ // The index is out of range.
+ // Clear the requested index in case it had bogus value. This way the next
+ // load succeeds if the offset is reasonable.
+ mRequestedIndex = -1;
+
+ return NS_ERROR_FAILURE;
+ }
+
+ int32_t originalRequestedIndex = mRequestedIndex;
+ int32_t previousRequest = mRequestedIndex > -1 ? mRequestedIndex : mIndex;
+ int32_t requestedOffset = aIndex - previousRequest;
+
+ // This is a normal local history navigation.
+
+ nsCOMPtr<nsISHEntry> prevEntry;
+ nsCOMPtr<nsISHEntry> nextEntry;
+ GetEntryAtIndex(mIndex, getter_AddRefs(prevEntry));
+ GetEntryAtIndex(aIndex, getter_AddRefs(nextEntry));
+ if (!nextEntry || !prevEntry) {
+ mRequestedIndex = -1;
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mozilla::SessionHistoryInParent()) {
+ if (aHistCmd == HIST_CMD_GOTOINDEX) {
+ // https://html.spec.whatwg.org/#history-traversal:
+ // To traverse the history
+ // "If entry has a different Document object than the current entry, then
+ // run the following substeps: Remove any tasks queued by the history
+ // traversal task source..."
+ //
+ // aSameEpoch is true only if the navigations would have been
+ // generated in the same spin of the event loop (i.e. history.go(-2);
+ // history.go(-1)) and from the same content process.
+ if (aSameEpoch) {
+ bool same_doc = false;
+ prevEntry->SharesDocumentWith(nextEntry, &same_doc);
+ if (!same_doc) {
+ MOZ_LOG(
+ gSHistoryLog, LogLevel::Debug,
+ ("Aborting GotoIndex %d - same epoch and not same doc", aIndex));
+ // Ignore this load. Before SessionHistoryInParent, this would
+ // have been dropped in InternalLoad after we filter out SameDoc
+ // loads.
+ return NS_ERROR_FAILURE;
+ }
+ }
+ }
+ }
+ // Keep note of requested history index in mRequestedIndex; after all bailouts
+ mRequestedIndex = aIndex;
+
+ // Remember that this entry is getting loaded at this point in the sequence
+
+ nextEntry->SetLastTouched(++gTouchCounter);
+
+ // Get the uri for the entry we are about to visit
+ nsCOMPtr<nsIURI> nextURI = nextEntry->GetURI();
+
+ MOZ_ASSERT(nextURI, "nextURI can't be null");
+
+ // Send appropriate listener notifications.
+ if (aHistCmd == HIST_CMD_GOTOINDEX) {
+ // We are going somewhere else. This is not reload either
+ NotifyListeners(mListeners, [](auto l) { l->OnHistoryGotoIndex(); });
+ }
+
+ if (mRequestedIndex == mIndex) {
+ // Possibly a reload case
+ InitiateLoad(nextEntry, rootBC, aLoadType, aLoadResults, aLoadCurrentEntry,
+ aUserActivation, requestedOffset);
+ return NS_OK;
+ }
+
+ // Going back or forward.
+ bool differenceFound = LoadDifferingEntries(
+ prevEntry, nextEntry, rootBC, aLoadType, aLoadResults, aLoadCurrentEntry,
+ aUserActivation, requestedOffset);
+ if (!differenceFound) {
+ // LoadNextPossibleEntry will change the offset by one, and in order
+ // to keep track of the requestedOffset, need to reset mRequestedIndex to
+ // the value it had initially.
+ mRequestedIndex = originalRequestedIndex;
+ // We did not find any differences. Go further in the history.
+ return LoadNextPossibleEntry(aIndex, aLoadType, aHistCmd, aLoadResults,
+ aLoadCurrentEntry, aUserActivation);
+ }
+
+ return NS_OK;
+}
+
+bool nsSHistory::LoadDifferingEntries(nsISHEntry* aPrevEntry,
+ nsISHEntry* aNextEntry,
+ BrowsingContext* aParent, long aLoadType,
+ nsTArray<LoadEntryResult>& aLoadResults,
+ bool aLoadCurrentEntry,
+ bool aUserActivation, int32_t aOffset) {
+ MOZ_ASSERT(aPrevEntry && aNextEntry && aParent);
+
+ uint32_t prevID = aPrevEntry->GetID();
+ uint32_t nextID = aNextEntry->GetID();
+
+ // Check the IDs to verify if the pages are different.
+ if (prevID != nextID) {
+ // Set the Subframe flag if not navigating the root docshell.
+ aNextEntry->SetIsSubFrame(aParent->Id() != mRootBC);
+ InitiateLoad(aNextEntry, aParent, aLoadType, aLoadResults,
+ aLoadCurrentEntry, aUserActivation, aOffset);
+ return true;
+ }
+
+ // The entries are the same, so compare any child frames
+ int32_t pcnt = aPrevEntry->GetChildCount();
+ int32_t ncnt = aNextEntry->GetChildCount();
+
+ // Create an array for child browsing contexts.
+ nsTArray<RefPtr<BrowsingContext>> browsingContexts;
+ aParent->GetChildren(browsingContexts);
+
+ // Search for something to load next.
+ bool differenceFound = false;
+ for (int32_t i = 0; i < ncnt; ++i) {
+ // First get an entry which may cause a new page to be loaded.
+ nsCOMPtr<nsISHEntry> nChild;
+ aNextEntry->GetChildAt(i, getter_AddRefs(nChild));
+ if (!nChild) {
+ continue;
+ }
+ nsID docshellID;
+ nChild->GetDocshellID(docshellID);
+
+ // Then find the associated docshell.
+ RefPtr<BrowsingContext> bcChild;
+ for (const RefPtr<BrowsingContext>& bc : browsingContexts) {
+ if (bc->GetHistoryID() == docshellID) {
+ bcChild = bc;
+ break;
+ }
+ }
+ if (!bcChild) {
+ continue;
+ }
+
+ // Then look at the previous entries to see if there was
+ // an entry for the docshell.
+ nsCOMPtr<nsISHEntry> pChild;
+ for (int32_t k = 0; k < pcnt; ++k) {
+ nsCOMPtr<nsISHEntry> child;
+ aPrevEntry->GetChildAt(k, getter_AddRefs(child));
+ if (child) {
+ nsID dID;
+ child->GetDocshellID(dID);
+ if (dID == docshellID) {
+ pChild = child;
+ break;
+ }
+ }
+ }
+ if (!pChild) {
+ continue;
+ }
+
+ // Finally recursively call this method.
+ // This will either load a new page to shell or some subshell or
+ // do nothing.
+ if (LoadDifferingEntries(pChild, nChild, bcChild, aLoadType, aLoadResults,
+ aLoadCurrentEntry, aUserActivation, aOffset)) {
+ differenceFound = true;
+ }
+ }
+ return differenceFound;
+}
+
+void nsSHistory::InitiateLoad(nsISHEntry* aFrameEntry,
+ BrowsingContext* aFrameBC, long aLoadType,
+ nsTArray<LoadEntryResult>& aLoadResults,
+ bool aLoadCurrentEntry, bool aUserActivation,
+ int32_t aOffset) {
+ MOZ_ASSERT(aFrameBC && aFrameEntry);
+
+ LoadEntryResult* loadResult = aLoadResults.AppendElement();
+ loadResult->mBrowsingContext = aFrameBC;
+
+ nsCOMPtr<nsIURI> newURI = aFrameEntry->GetURI();
+ RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(newURI);
+
+ loadState->SetHasValidUserGestureActivation(aUserActivation);
+
+ // At the time we initiate a history entry load we already know if https-first
+ // was able to upgrade the request from http to https. There is no point in
+ // re-retrying to upgrade.
+ loadState->SetIsExemptFromHTTPSOnlyMode(true);
+
+ /* Set the loadType in the SHEntry too to what was passed on.
+ * This will be passed on to child subframes later in nsDocShell,
+ * so that proper loadType is maintained through out a frameset
+ */
+ aFrameEntry->SetLoadType(aLoadType);
+
+ loadState->SetLoadType(aLoadType);
+
+ loadState->SetSHEntry(aFrameEntry);
+
+ // If we're loading the current entry we want to treat it as not a
+ // same-document navigation (see nsDocShell::IsSameDocumentNavigation), so
+ // record that here in the LoadingSessionHistoryEntry.
+ bool loadingCurrentEntry;
+ if (mozilla::SessionHistoryInParent()) {
+ loadingCurrentEntry = aLoadCurrentEntry;
+ } else {
+ loadingCurrentEntry =
+ aFrameBC->GetDocShell() &&
+ nsDocShell::Cast(aFrameBC->GetDocShell())->IsOSHE(aFrameEntry);
+ }
+ loadState->SetLoadIsFromSessionHistory(aOffset, loadingCurrentEntry);
+
+ if (mozilla::SessionHistoryInParent()) {
+ nsCOMPtr<SessionHistoryEntry> she = do_QueryInterface(aFrameEntry);
+ aFrameBC->Canonical()->AddLoadingSessionHistoryEntry(
+ loadState->GetLoadingSessionHistoryInfo()->mLoadId, she);
+ }
+
+ nsCOMPtr<nsIURI> originalURI = aFrameEntry->GetOriginalURI();
+ loadState->SetOriginalURI(originalURI);
+
+ loadState->SetLoadReplace(aFrameEntry->GetLoadReplace());
+
+ loadState->SetLoadFlags(nsIWebNavigation::LOAD_FLAGS_NONE);
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal =
+ aFrameEntry->GetTriggeringPrincipal();
+ loadState->SetTriggeringPrincipal(triggeringPrincipal);
+ loadState->SetFirstParty(false);
+ nsCOMPtr<nsIContentSecurityPolicy> csp = aFrameEntry->GetCsp();
+ loadState->SetCsp(csp);
+
+ loadResult->mLoadState = std::move(loadState);
+}
+
+NS_IMETHODIMP
+nsSHistory::CreateEntry(nsISHEntry** aEntry) {
+ nsCOMPtr<nsISHEntry> entry;
+ if (XRE_IsParentProcess() && mozilla::SessionHistoryInParent()) {
+ entry = new SessionHistoryEntry();
+ } else {
+ entry = new nsSHEntry();
+ }
+ entry.forget(aEntry);
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(bool)
+nsSHistory::IsEmptyOrHasEntriesForSingleTopLevelPage() {
+ if (mEntries.IsEmpty()) {
+ return true;
+ }
+
+ nsISHEntry* entry = mEntries[0];
+ size_t length = mEntries.Length();
+ for (size_t i = 1; i < length; ++i) {
+ bool sharesDocument = false;
+ mEntries[i]->SharesDocumentWith(entry, &sharesDocument);
+ if (!sharesDocument) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static void CollectEntries(
+ nsTHashMap<nsIDHashKey, SessionHistoryEntry*>& aHashtable,
+ SessionHistoryEntry* aEntry) {
+ aHashtable.InsertOrUpdate(aEntry->DocshellID(), aEntry);
+ for (const RefPtr<SessionHistoryEntry>& entry : aEntry->Children()) {
+ if (entry) {
+ CollectEntries(aHashtable, entry);
+ }
+ }
+}
+
+static void UpdateEntryLength(
+ nsTHashMap<nsIDHashKey, SessionHistoryEntry*>& aHashtable,
+ SessionHistoryEntry* aNewEntry, bool aMove) {
+ SessionHistoryEntry* oldEntry = aHashtable.Get(aNewEntry->DocshellID());
+ if (oldEntry) {
+ MOZ_ASSERT(oldEntry->GetID() != aNewEntry->GetID() || !aMove ||
+ !aNewEntry->BCHistoryLength().Modified());
+ aNewEntry->SetBCHistoryLength(oldEntry->BCHistoryLength());
+ if (oldEntry->GetID() != aNewEntry->GetID()) {
+ MOZ_ASSERT(!aMove);
+ // If we have a new id then aNewEntry was created for a new load, so
+ // record that in BCHistoryLength.
+ ++aNewEntry->BCHistoryLength();
+ } else if (aMove) {
+ // We're moving the BCHistoryLength from the old entry to the new entry,
+ // so we need to let the old entry know that it's not contributing to its
+ // BCHistoryLength, and the new one that it does if the old one was
+ // before.
+ aNewEntry->BCHistoryLength().SetModified(
+ oldEntry->BCHistoryLength().Modified());
+ oldEntry->BCHistoryLength().SetModified(false);
+ }
+ }
+
+ for (const RefPtr<SessionHistoryEntry>& entry : aNewEntry->Children()) {
+ if (entry) {
+ UpdateEntryLength(aHashtable, entry, aMove);
+ }
+ }
+}
+
+void nsSHistory::UpdateEntryLength(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry,
+ bool aMove) {
+ nsCOMPtr<SessionHistoryEntry> oldSHE = do_QueryInterface(aOldEntry);
+ nsCOMPtr<SessionHistoryEntry> newSHE = do_QueryInterface(aNewEntry);
+
+ if (!oldSHE || !newSHE) {
+ return;
+ }
+
+ nsTHashMap<nsIDHashKey, SessionHistoryEntry*> docshellIDToEntry;
+ CollectEntries(docshellIDToEntry, oldSHE);
+
+ ::UpdateEntryLength(docshellIDToEntry, newSHE, aMove);
+}
diff --git a/docshell/shistory/nsSHistory.h b/docshell/shistory/nsSHistory.h
new file mode 100644
index 0000000000..d029a55c6c
--- /dev/null
+++ b/docshell/shistory/nsSHistory.h
@@ -0,0 +1,343 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSHistory_h
+#define nsSHistory_h
+
+#include "nsCOMPtr.h"
+#include "nsDocShellLoadState.h"
+#include "nsExpirationTracker.h"
+#include "nsISHistory.h"
+#include "nsSHEntryShared.h"
+#include "nsSimpleEnumerator.h"
+#include "nsTObserverArray.h"
+#include "nsWeakReference.h"
+
+#include "mozilla/dom/ipc/IdType.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/UniquePtr.h"
+
+class nsIDocShell;
+class nsDocShell;
+class nsSHistoryObserver;
+class nsISHEntry;
+
+namespace mozilla {
+namespace dom {
+class LoadSHEntryResult;
+}
+} // namespace mozilla
+
+class nsSHistory : public mozilla::LinkedListElement<nsSHistory>,
+ public nsISHistory,
+ public nsSupportsWeakReference {
+ public:
+ // The timer based history tracker is used to evict bfcache on expiration.
+ class HistoryTracker final
+ : public nsExpirationTracker<mozilla::dom::SHEntrySharedParentState, 3> {
+ public:
+ explicit HistoryTracker(nsSHistory* aSHistory, uint32_t aTimeout,
+ nsIEventTarget* aEventTarget)
+ : nsExpirationTracker(1000 * aTimeout / 2, "HistoryTracker",
+ aEventTarget) {
+ MOZ_ASSERT(aSHistory);
+ mSHistory = aSHistory;
+ }
+
+ protected:
+ virtual void NotifyExpired(
+ mozilla::dom::SHEntrySharedParentState* aObj) override {
+ RemoveObject(aObj);
+ mSHistory->EvictExpiredContentViewerForEntry(aObj);
+ }
+
+ private:
+ // HistoryTracker is owned by nsSHistory; it always outlives HistoryTracker
+ // so it's safe to use raw pointer here.
+ nsSHistory* mSHistory;
+ };
+
+ // Structure used in SetChildHistoryEntry
+ struct SwapEntriesData {
+ mozilla::dom::BrowsingContext*
+ ignoreBC; // constant; the browsing context to ignore
+ nsISHEntry* destTreeRoot; // constant; the root of the dest tree
+ nsISHEntry* destTreeParent; // constant; the node under destTreeRoot
+ // whose children will correspond to aEntry
+ };
+
+ explicit nsSHistory(mozilla::dom::BrowsingContext* aRootBC);
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISHISTORY
+
+ // One time initialization method
+ static nsresult Startup();
+ static void Shutdown();
+ static void UpdatePrefs();
+
+ // Max number of total cached content viewers. If the pref
+ // browser.sessionhistory.max_total_viewers is negative, then
+ // this value is calculated based on the total amount of memory.
+ // Otherwise, it comes straight from the pref.
+ static uint32_t GetMaxTotalViewers() { return sHistoryMaxTotalViewers; }
+
+ // Get the root SHEntry from a given entry.
+ static already_AddRefed<nsISHEntry> GetRootSHEntry(nsISHEntry* aEntry);
+
+ // Callback prototype for WalkHistoryEntries.
+ // `aEntry` is the child history entry, `aBC` is its corresponding browsing
+ // context, `aChildIndex` is the child's index in its parent entry, and
+ // `aData` is the opaque pointer passed to WalkHistoryEntries. Both structs
+ // that are passed as `aData` to this function have a field
+ // `aEntriesToUpdate`, which is an array of entries we need to update in
+ // docshell, if the 'SH in parent' pref is on (which implies that this method
+ // is executed in the parent)
+ typedef nsresult (*WalkHistoryEntriesFunc)(nsISHEntry* aEntry,
+ mozilla::dom::BrowsingContext* aBC,
+ int32_t aChildIndex, void* aData);
+
+ // Clone a session history tree for subframe navigation.
+ // The tree rooted at |aSrcEntry| will be cloned into |aDestEntry|, except
+ // for the entry with id |aCloneID|, which will be replaced with
+ // |aReplaceEntry|. |aSrcShell| is a (possibly null) docshell which
+ // corresponds to |aSrcEntry| via its mLSHE or mOHE pointers, and will
+ // have that pointer updated to point to the cloned history entry.
+ // If aCloneChildren is true then the children of the entry with id
+ // |aCloneID| will be cloned into |aReplaceEntry|.
+ static nsresult CloneAndReplace(nsISHEntry* aSrcEntry,
+ mozilla::dom::BrowsingContext* aOwnerBC,
+ uint32_t aCloneID, nsISHEntry* aReplaceEntry,
+ bool aCloneChildren, nsISHEntry** aDestEntry);
+
+ // Child-walking callback for CloneAndReplace
+ static nsresult CloneAndReplaceChild(nsISHEntry* aEntry,
+ mozilla::dom::BrowsingContext* aOwnerBC,
+ int32_t aChildIndex, void* aData);
+
+ // Child-walking callback for SetHistoryEntry
+ static nsresult SetChildHistoryEntry(nsISHEntry* aEntry,
+ mozilla::dom::BrowsingContext* aBC,
+ int32_t aEntryIndex, void* aData);
+
+ // For each child of aRootEntry, find the corresponding shell which is
+ // a child of aBC, and call aCallback. The opaque pointer aData
+ // is passed to the callback.
+ static nsresult WalkHistoryEntries(nsISHEntry* aRootEntry,
+ mozilla::dom::BrowsingContext* aBC,
+ WalkHistoryEntriesFunc aCallback,
+ void* aData);
+
+ // This function finds all entries that are contiguous and same-origin with
+ // the aEntry. And call the aCallback on them, including the aEntry. This only
+ // works for the root entries. It will do nothing for non-root entries.
+ static void WalkContiguousEntries(
+ nsISHEntry* aEntry, const std::function<void(nsISHEntry*)>& aCallback);
+
+ nsTArray<nsCOMPtr<nsISHEntry>>& Entries() { return mEntries; }
+
+ void NotifyOnHistoryReplaceEntry();
+
+ void RemoveEntries(nsTArray<nsID>& aIDs, int32_t aStartIndex,
+ bool* aDidRemove);
+
+ // The size of the window of SHEntries which can have alive viewers in the
+ // bfcache around the currently active SHEntry.
+ //
+ // We try to keep viewers for SHEntries between index - VIEWER_WINDOW and
+ // index + VIEWER_WINDOW alive.
+ static const int32_t VIEWER_WINDOW = 3;
+
+ struct LoadEntryResult {
+ RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext;
+ RefPtr<nsDocShellLoadState> mLoadState;
+ };
+
+ static void LoadURIs(nsTArray<LoadEntryResult>& aLoadResults);
+ static void LoadURIOrBFCache(LoadEntryResult& aLoadEntry);
+
+ // If this doesn't return an error then either aLoadResult is set to nothing,
+ // in which case the caller should ignore the load, or it returns a valid
+ // LoadEntryResult in aLoadResult which the caller should use to do the load.
+ nsresult Reload(uint32_t aReloadFlags,
+ nsTArray<LoadEntryResult>& aLoadResults);
+ nsresult ReloadCurrentEntry(nsTArray<LoadEntryResult>& aLoadResults);
+ nsresult GotoIndex(int32_t aIndex, nsTArray<LoadEntryResult>& aLoadResults,
+ bool aSameEpoch, bool aLoadCurrentEntry,
+ bool aUserActivation);
+
+ void WindowIndices(int32_t aIndex, int32_t* aOutStartIndex,
+ int32_t* aOutEndIndex);
+ void NotifyListenersContentViewerEvicted(uint32_t aNumEvicted);
+
+ int32_t Length() { return int32_t(mEntries.Length()); }
+ int32_t Index() { return mIndex; }
+ already_AddRefed<mozilla::dom::BrowsingContext> GetBrowsingContext() {
+ return mozilla::dom::BrowsingContext::Get(mRootBC);
+ }
+ bool HasOngoingUpdate() { return mHasOngoingUpdate; }
+ void SetHasOngoingUpdate(bool aVal) { mHasOngoingUpdate = aVal; }
+
+ void SetBrowsingContext(mozilla::dom::BrowsingContext* aRootBC) {
+ uint64_t newID = aRootBC ? aRootBC->Id() : 0;
+ if (mRootBC != newID) {
+ mRootBC = newID;
+ UpdateRootBrowsingContextState(aRootBC);
+ }
+ }
+
+ int32_t GetIndexForReplace() {
+ // Replace current entry in session history; If the requested index is
+ // valid, it indicates the loading was triggered by a history load, and
+ // we should replace the entry at requested index instead.
+ return mRequestedIndex == -1 ? mIndex : mRequestedIndex;
+ }
+
+ // Update the root browsing context state when adding, removing or
+ // replacing entries.
+ void UpdateRootBrowsingContextState() {
+ RefPtr<mozilla::dom::BrowsingContext> rootBC(GetBrowsingContext());
+ UpdateRootBrowsingContextState(rootBC);
+ }
+
+ void GetEpoch(uint64_t& aEpoch,
+ mozilla::Maybe<mozilla::dom::ContentParentId>& aId) const {
+ aEpoch = mEpoch;
+ aId = mEpochParentId;
+ }
+ void SetEpoch(uint64_t aEpoch,
+ mozilla::Maybe<mozilla::dom::ContentParentId> aId) {
+ mEpoch = aEpoch;
+ mEpochParentId = aId;
+ }
+
+ void LogHistory();
+
+ protected:
+ virtual ~nsSHistory();
+
+ uint64_t mRootBC;
+
+ private:
+ friend class nsSHistoryObserver;
+
+ void UpdateRootBrowsingContextState(
+ mozilla::dom::BrowsingContext* aBrowsingContext);
+
+ bool LoadDifferingEntries(nsISHEntry* aPrevEntry, nsISHEntry* aNextEntry,
+ mozilla::dom::BrowsingContext* aParent,
+ long aLoadType,
+ nsTArray<LoadEntryResult>& aLoadResults,
+ bool aLoadCurrentEntry, bool aUserActivation,
+ int32_t aOffset);
+ void InitiateLoad(nsISHEntry* aFrameEntry,
+ mozilla::dom::BrowsingContext* aFrameBC, long aLoadType,
+ nsTArray<LoadEntryResult>& aLoadResult,
+ bool aLoadCurrentEntry, bool aUserActivation,
+ int32_t aOffset);
+
+ nsresult LoadEntry(int32_t aIndex, long aLoadType, uint32_t aHistCmd,
+ nsTArray<LoadEntryResult>& aLoadResults, bool aSameEpoch,
+ bool aLoadCurrentEntry, bool aUserActivation);
+
+ // Find the history entry for a given bfcache entry. It only looks up between
+ // the range where alive viewers may exist (i.e nsSHistory::VIEWER_WINDOW).
+ nsresult FindEntryForBFCache(mozilla::dom::SHEntrySharedParentState* aEntry,
+ nsISHEntry** aResult, int32_t* aResultIndex);
+
+ // Evict content viewers in this window which don't lie in the "safe" range
+ // around aIndex.
+ virtual void EvictOutOfRangeWindowContentViewers(int32_t aIndex);
+ void EvictContentViewerForEntry(nsISHEntry* aEntry);
+ static void GloballyEvictContentViewers();
+ static void GloballyEvictAllContentViewers();
+
+ // Calculates a max number of total
+ // content viewers to cache, based on amount of total memory
+ static uint32_t CalcMaxTotalViewers();
+
+ nsresult LoadNextPossibleEntry(int32_t aNewIndex, long aLoadType,
+ uint32_t aHistCmd,
+ nsTArray<LoadEntryResult>& aLoadResults,
+ bool aLoadCurrentEntry, bool aUserActivation);
+
+ // aIndex is the index of the entry which may be removed.
+ // If aKeepNext is true, aIndex is compared to aIndex + 1,
+ // otherwise comparison is done to aIndex - 1.
+ bool RemoveDuplicate(int32_t aIndex, bool aKeepNext);
+
+ // We need to update entries in docshell and browsing context.
+ // If our docshell is located in parent or 'SH in parent' pref is off we can
+ // update it directly, Otherwise, we have two choices. If the browsing context
+ // that owns the docshell is in the same process as the process who called us
+ // over IPC, then we save entries that need to be updated in a list, and once
+ // we have returned from the IPC call, we update the docshell in the child
+ // process. Otherwise, if the browsing context is in a different process, we
+ // do a nested IPC call to that process to update the docshell in that
+ // process.
+ static void HandleEntriesToSwapInDocShell(mozilla::dom::BrowsingContext* aBC,
+ nsISHEntry* aOldEntry,
+ nsISHEntry* aNewEntry);
+
+ void UpdateEntryLength(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry,
+ bool aMove);
+
+ protected:
+ bool mHasOngoingUpdate;
+ nsTArray<nsCOMPtr<nsISHEntry>> mEntries; // entries are never null
+ private:
+ // Track all bfcache entries and evict on expiration.
+ mozilla::UniquePtr<HistoryTracker> mHistoryTracker;
+
+ int32_t mIndex; // -1 means "no index"
+ int32_t mRequestedIndex; // -1 means "no requested index"
+
+ // Session History listeners
+ nsAutoTObserverArray<nsWeakPtr, 2> mListeners;
+
+ nsID mRootDocShellID;
+
+ // Max viewers allowed total, across all SHistory objects
+ static int32_t sHistoryMaxTotalViewers;
+
+ // The epoch (and id) tell us what navigations occured within the same
+ // event-loop spin in the child. We need to know this in order to
+ // implement spec requirements for dropping pending navigations when we
+ // do a history navigation, if it's not same-document. Content processes
+ // update the epoch via a runnable on each ::Go (including AsyncGo).
+ uint64_t mEpoch = 0;
+ mozilla::Maybe<mozilla::dom::ContentParentId> mEpochParentId;
+};
+
+// CallerWillNotifyHistoryIndexAndLengthChanges is used to prevent
+// SHistoryChangeNotifier to send automatic index and length updates.
+// When that is done, it is up to the caller to explicitly send those updates.
+// This is needed in cases when the update is a reaction to some change in a
+// child process and child process passes a changeId to the parent side.
+class MOZ_STACK_CLASS CallerWillNotifyHistoryIndexAndLengthChanges {
+ public:
+ explicit CallerWillNotifyHistoryIndexAndLengthChanges(
+ nsISHistory* aSHistory) {
+ nsSHistory* shistory = static_cast<nsSHistory*>(aSHistory);
+ if (shistory && !shistory->HasOngoingUpdate()) {
+ shistory->SetHasOngoingUpdate(true);
+ mSHistory = shistory;
+ }
+ }
+
+ ~CallerWillNotifyHistoryIndexAndLengthChanges() {
+ if (mSHistory) {
+ mSHistory->SetHasOngoingUpdate(false);
+ }
+ }
+
+ RefPtr<nsSHistory> mSHistory;
+};
+
+inline nsISupports* ToSupports(nsSHistory* aObj) {
+ return static_cast<nsISHistory*>(aObj);
+}
+
+#endif /* nsSHistory */
diff --git a/docshell/test/browser/Bug1622420Child.sys.mjs b/docshell/test/browser/Bug1622420Child.sys.mjs
new file mode 100644
index 0000000000..c5520d5943
--- /dev/null
+++ b/docshell/test/browser/Bug1622420Child.sys.mjs
@@ -0,0 +1,9 @@
+export class Bug1622420Child extends JSWindowActorChild {
+ receiveMessage(msg) {
+ switch (msg.name) {
+ case "hasWindowContextForTopBC":
+ return !!this.browsingContext.top.currentWindowContext;
+ }
+ return null;
+ }
+}
diff --git a/docshell/test/browser/Bug422543Child.sys.mjs b/docshell/test/browser/Bug422543Child.sys.mjs
new file mode 100644
index 0000000000..524ac33ffd
--- /dev/null
+++ b/docshell/test/browser/Bug422543Child.sys.mjs
@@ -0,0 +1,98 @@
+class SHistoryListener {
+ constructor() {
+ this.retval = true;
+ this.last = "initial";
+ }
+
+ OnHistoryNewEntry(aNewURI) {
+ this.last = "newentry";
+ }
+
+ OnHistoryGotoIndex() {
+ this.last = "gotoindex";
+ }
+
+ OnHistoryPurge() {
+ this.last = "purge";
+ }
+
+ OnHistoryReload() {
+ this.last = "reload";
+ return this.retval;
+ }
+
+ OnHistoryReplaceEntry() {}
+}
+SHistoryListener.prototype.QueryInterface = ChromeUtils.generateQI([
+ "nsISHistoryListener",
+ "nsISupportsWeakReference",
+]);
+
+let listeners;
+
+export class Bug422543Child extends JSWindowActorChild {
+ constructor() {
+ super();
+ }
+
+ actorCreated() {
+ if (listeners) {
+ return;
+ }
+
+ this.shistory = this.docShell.nsIWebNavigation.sessionHistory;
+ listeners = [new SHistoryListener(), new SHistoryListener()];
+
+ for (let listener of listeners) {
+ this.shistory.legacySHistory.addSHistoryListener(listener);
+ }
+ }
+
+ cleanup() {
+ for (let listener of listeners) {
+ this.shistory.legacySHistory.removeSHistoryListener(listener);
+ }
+ this.shistory = null;
+ listeners = null;
+ return {};
+ }
+
+ getListenerStatus() {
+ return listeners.map(l => l.last);
+ }
+
+ resetListeners() {
+ for (let listener of listeners) {
+ listener.last = "initial";
+ }
+
+ return {};
+ }
+
+ notifyReload() {
+ let history = this.shistory.legacySHistory;
+ let rval = history.notifyOnHistoryReload();
+ return { rval };
+ }
+
+ setRetval({ num, val }) {
+ listeners[num].retval = val;
+ return {};
+ }
+
+ receiveMessage(msg) {
+ switch (msg.name) {
+ case "cleanup":
+ return this.cleanup();
+ case "getListenerStatus":
+ return this.getListenerStatus();
+ case "notifyReload":
+ return this.notifyReload();
+ case "resetListeners":
+ return this.resetListeners();
+ case "setRetval":
+ return this.setRetval(msg.data);
+ }
+ return null;
+ }
+}
diff --git a/docshell/test/browser/browser.ini b/docshell/test/browser/browser.ini
new file mode 100644
index 0000000000..fc3e06d9a3
--- /dev/null
+++ b/docshell/test/browser/browser.ini
@@ -0,0 +1,237 @@
+[DEFAULT]
+support-files =
+ Bug422543Child.sys.mjs
+ dummy_page.html
+ favicon_bug655270.ico
+ file_bug234628-1-child.html
+ file_bug234628-1.html
+ file_bug234628-10-child.xhtml
+ file_bug234628-10.html
+ file_bug234628-11-child.xhtml
+ file_bug234628-11-child.xhtml^headers^
+ file_bug234628-11.html
+ file_bug234628-2-child.html
+ file_bug234628-2.html
+ file_bug234628-3-child.html
+ file_bug234628-3.html
+ file_bug234628-4-child.html
+ file_bug234628-4.html
+ file_bug234628-5-child.html
+ file_bug234628-5.html
+ file_bug234628-6-child.html
+ file_bug234628-6-child.html^headers^
+ file_bug234628-6.html
+ file_bug234628-8-child.html
+ file_bug234628-8.html
+ file_bug234628-9-child.html
+ file_bug234628-9.html
+ file_bug420605.html
+ file_bug503832.html
+ file_bug655270.html
+ file_bug670318.html
+ file_bug673087-1.html
+ file_bug673087-1.html^headers^
+ file_bug673087-1-child.html
+ file_bug673087-2.html
+ file_bug852909.pdf
+ file_bug852909.png
+ file_bug1046022.html
+ file_bug1206879.html
+ file_bug1328501.html
+ file_bug1328501_frame.html
+ file_bug1328501_framescript.js
+ file_bug1543077-3-child.html
+ file_bug1543077-3.html
+ file_multiple_pushState.html
+ file_onbeforeunload_0.html
+ file_onbeforeunload_1.html
+ file_onbeforeunload_2.html
+ file_onbeforeunload_3.html
+ print_postdata.sjs
+ test-form_sjis.html
+ browser_timelineMarkers-frame-02.js
+ head.js
+ frame-head.js
+ file_data_load_inherit_csp.html
+ file_click_link_within_view_source.html
+ onload_message.html
+ onpageshow_message.html
+ file_cross_process_csp_inheritance.html
+ file_open_about_blank.html
+ file_slow_load.sjs
+ file_bug1648464-1.html
+ file_bug1648464-1-child.html
+ file_bug1688368-1.sjs
+ file_bug1691153.html
+ file_bug1716290-1.sjs
+ file_bug1716290-2.sjs
+ file_bug1716290-3.sjs
+ file_bug1716290-4.sjs
+ file_bug1736248-1.html
+
+[browser_alternate_fixup_middle_click_link.js]
+https_first_disabled = true
+[browser_backforward_userinteraction.js]
+support-files =
+ dummy_iframe_page.html
+skip-if =
+ os == "linux" && bits == 64 && !debug # Bug 1607713
+[browser_backforward_userinteraction_about.js]
+[browser_backforward_userinteraction_systemprincipal.js]
+skip-if =
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+[browser_bug1543077-3.js]
+[browser_bug1594938.js]
+[browser_bug1206879.js]
+https_first_disabled = true
+[browser_bug1309900_crossProcessHistoryNavigation.js]
+https_first_disabled = true
+[browser_bug1328501.js]
+https_first_disabled = true
+[browser_bug1347823.js]
+[browser_bug134911.js]
+[browser_bug1415918_beforeunload_options.js]
+https_first_disabled = true
+[browser_bug1622420.js]
+support-files =
+ file_bug1622420.html
+ Bug1622420Child.sys.mjs
+[browser_bug1673702.js]
+https_first_disabled = true
+skip-if =
+ os == "linux" && bits == 64 && os_version == "18.04" && debug # Bug 1674513
+ os == "win" # Bug 1674513
+support-files =
+ file_bug1673702.json
+ file_bug1673702.json^headers^
+[browser_bug1674464.js]
+https_first_disabled = true
+skip-if = !fission || !crashreporter # On a crash we only keep history when fission is enabled.
+[browser_bug1719178.js]
+[browser_bug1757005.js]
+[browser_bug1769189.js]
+[browser_bug1798780.js]
+[browser_bug234628-1.js]
+[browser_bug234628-10.js]
+[browser_bug234628-11.js]
+[browser_bug234628-2.js]
+[browser_bug234628-3.js]
+[browser_bug234628-4.js]
+[browser_bug234628-5.js]
+[browser_bug234628-6.js]
+[browser_bug234628-8.js]
+[browser_bug234628-9.js]
+[browser_bug349769.js]
+[browser_bug388121-1.js]
+[browser_bug388121-2.js]
+[browser_bug420605.js]
+skip-if = verify
+[browser_bug422543.js]
+https_first_disabled = true
+[browser_bug441169.js]
+[browser_bug503832.js]
+skip-if = verify
+[browser_bug554155.js]
+[browser_bug655270.js]
+[browser_bug655273.js]
+[browser_bug670318.js]
+skip-if = os == "linux" && (debug || asan || tsan) # Bug 1717403
+[browser_bug673087-1.js]
+[browser_bug673087-2.js]
+[browser_bug673467.js]
+[browser_bug852909.js]
+skip-if = (verify && debug && (os == 'win'))
+[browser_bug92473.js]
+[browser_csp_sandbox_no_script_js_uri.js]
+support-files =
+ file_csp_sandbox_no_script_js_uri.html
+ file_csp_sandbox_no_script_js_uri.html^headers^
+[browser_data_load_inherit_csp.js]
+[browser_dataURI_unique_opaque_origin.js]
+https_first_disabled = true
+[browser_frameloader_swap_with_bfcache.js]
+[browser_backforward_restore_scroll.js]
+https_first_disabled = true
+support-files =
+ file_backforward_restore_scroll.html
+ file_backforward_restore_scroll.html^headers^
+[browser_targetTopLevelLinkClicksToBlank.js]
+[browser_title_in_session_history.js]
+skip-if = !sessionHistoryInParent
+[browser_uriFixupIntegration.js]
+[browser_uriFixupAlternateRedirects.js]
+https_first_disabled = true
+support-files =
+ redirect_to_example.sjs
+[browser_loadURI_postdata.js]
+[browser_multiple_pushState.js]
+https_first_disabled = true
+[browser_onbeforeunload_frame.js]
+support-files = head_browser_onbeforeunload.js
+[browser_onbeforeunload_parent.js]
+support-files = head_browser_onbeforeunload.js
+[browser_onbeforeunload_navigation.js]
+skip-if = (os == 'win' && !debug) # bug 1300351
+[browser_onunload_stop.js]
+https_first_disabled = true
+[browser_overlink.js]
+support-files =
+ overlink_test.html
+[browser_platform_emulation.js]
+[browser_search_notification.js]
+[browser_tab_touch_events.js]
+[browser_timelineMarkers-01.js]
+[browser_timelineMarkers-02.js]
+skip-if = true # Bug 1220415
+[browser_ua_emulation.js]
+[browser_history_triggeringprincipal_viewsource.js]
+https_first_disabled = true
+[browser_click_link_within_view_source.js]
+[browser_browsingContext-01.js]
+https_first_disabled = true
+[browser_browsingContext-02.js]
+https_first_disabled = true
+[browser_browsingContext-getAllBrowsingContextsInSubtree.js]
+[browser_browsingContext-getWindowByName.js]
+[browser_browsingContext-embedder.js]
+[browser_browsingContext-webProgress.js]
+skip-if =
+ os == "linux" && bits == 64 && !debug # Bug 1721261
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+https_first_disabled = true
+[browser_csp_uir.js]
+support-files =
+ file_csp_uir.html
+ file_csp_uir_dummy.html
+[browser_cross_process_csp_inheritance.js]
+https_first_disabled = true
+[browser_tab_replace_while_loading.js]
+skip-if = (os == 'linux' && bits == 64 && os_version == '18.04') || (os == "win") # Bug 1604237, Bug 1671794
+[browser_browsing_context_attached.js]
+https_first_disabled = true
+[browser_browsing_context_discarded.js]
+https_first_disabled = true
+[browser_fall_back_to_https.js]
+https_first_disabled = true
+skip-if = (os == 'mac')
+[browser_badCertDomainFixup.js]
+[browser_viewsource_chrome_to_content.js]
+[browser_viewsource_multipart.js]
+support-files =
+ file_basic_multipart.sjs
+[browser_bug1648464-1.js]
+[browser_bug1688368-1.js]
+[browser_bug1691153.js]
+https_first_disabled = true
+[browser_bug1705872.js]
+[browser_isInitialDocument.js]
+https_first_disabled = true
+[browser_bug1716290-1.js]
+[browser_bug1716290-2.js]
+[browser_bug1716290-3.js]
+[browser_bug1716290-4.js]
+[browser_bfcache_copycommand.js]
+skip-if =
+ os == "linux" && bits == 64 # Bug 1730593
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+[browser_bug1736248-1.js]
diff --git a/docshell/test/browser/browser_alternate_fixup_middle_click_link.js b/docshell/test/browser/browser_alternate_fixup_middle_click_link.js
new file mode 100644
index 0000000000..8a55e12e52
--- /dev/null
+++ b/docshell/test/browser/browser_alternate_fixup_middle_click_link.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Check that we don't do alternate fixup when users middle-click
+ * broken links in the content document.
+ */
+add_task(async function test_alt_fixup_middle_click() {
+ await BrowserTestUtils.withNewTab("about:blank", async browser => {
+ await SpecialPowers.spawn(browser, [], () => {
+ let link = content.document.createElement("a");
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ link.href = "http://example/foo";
+ link.textContent = "Me, me, click me!";
+ content.document.body.append(link);
+ });
+ let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser);
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "a[href]",
+ { button: 1 },
+ browser
+ );
+ let tab = await newTabPromise;
+ let { browsingContext } = tab.linkedBrowser;
+ // Account for the possibility of a race, where the error page has already loaded:
+ if (
+ !browsingContext.currentWindowGlobal?.documentURI.spec.startsWith(
+ "about:neterror"
+ )
+ ) {
+ await BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ null,
+ true
+ );
+ }
+ // TBH, if the test fails, we probably force-crash because we try to reach
+ // *www.* example.com, which isn't proxied by the test infrastructure so
+ // will forcibly abort the test. But we need some asserts so they might as
+ // well be meaningful:
+ is(
+ tab.linkedBrowser.currentURI.spec,
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example/foo",
+ "URL for tab should be correct."
+ );
+
+ ok(
+ browsingContext.currentWindowGlobal.documentURI.spec.startsWith(
+ "about:neterror"
+ ),
+ "Should be showing error page."
+ );
+ BrowserTestUtils.removeTab(tab);
+ });
+});
diff --git a/docshell/test/browser/browser_backforward_restore_scroll.js b/docshell/test/browser/browser_backforward_restore_scroll.js
new file mode 100644
index 0000000000..2d1dfe624b
--- /dev/null
+++ b/docshell/test/browser/browser_backforward_restore_scroll.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const ROOT = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://mochi.test:8888"
+);
+const URL1 = ROOT + "file_backforward_restore_scroll.html";
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+const URL2 = "http://example.net/";
+
+const SCROLL0 = 500;
+const SCROLL1 = 1000;
+
+function promiseBrowserLoaded(url) {
+ return BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, url);
+}
+
+add_task(async function test() {
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, URL1);
+
+ // Scroll the 2 frames.
+ let children = gBrowser.selectedBrowser.browsingContext.children;
+ await SpecialPowers.spawn(children[0], [SCROLL0], scrollY =>
+ content.scrollTo(0, scrollY)
+ );
+ await SpecialPowers.spawn(children[1], [SCROLL1], scrollY =>
+ content.scrollTo(0, scrollY)
+ );
+
+ // Navigate forwards then backwards.
+ let loaded = promiseBrowserLoaded(URL2);
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, URL2);
+ await loaded;
+
+ loaded = promiseBrowserLoaded(URL1);
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
+ content.history.back();
+ });
+ await loaded;
+
+ // And check the results.
+ children = gBrowser.selectedBrowser.browsingContext.children;
+ await SpecialPowers.spawn(children[0], [SCROLL0], scrollY => {
+ Assert.equal(content.scrollY, scrollY, "frame 0 has correct scroll");
+ });
+ await SpecialPowers.spawn(children[1], [SCROLL1], scrollY => {
+ Assert.equal(content.scrollY, scrollY, "frame 1 has correct scroll");
+ });
+
+ gBrowser.removeTab(gBrowser.selectedTab);
+});
diff --git a/docshell/test/browser/browser_backforward_userinteraction.js b/docshell/test/browser/browser_backforward_userinteraction.js
new file mode 100644
index 0000000000..264299c902
--- /dev/null
+++ b/docshell/test/browser/browser_backforward_userinteraction.js
@@ -0,0 +1,374 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PAGE =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "dummy_page.html";
+const IFRAME_PAGE =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "dummy_iframe_page.html";
+
+async function assertMenulist(entries, baseURL = TEST_PAGE) {
+ // Wait for the session data to be flushed before continuing the test
+ await new Promise(resolve =>
+ SessionStore.getSessionHistory(gBrowser.selectedTab, resolve)
+ );
+
+ let backButton = document.getElementById("back-button");
+ let contextMenu = document.getElementById("backForwardMenu");
+
+ info("waiting for the history menu to open");
+
+ let popupShownPromise = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popupshown"
+ );
+ EventUtils.synthesizeMouseAtCenter(backButton, {
+ type: "contextmenu",
+ button: 2,
+ });
+ await popupShownPromise;
+
+ ok(true, "history menu opened");
+
+ let nodes = contextMenu.childNodes;
+
+ is(
+ nodes.length,
+ entries.length,
+ "Has the expected number of contextMenu entries"
+ );
+
+ for (let i = 0; i < entries.length; i++) {
+ let node = nodes[i];
+ is(
+ node.getAttribute("uri").replace(/[?|#]/, "!"),
+ baseURL + "!entry=" + entries[i],
+ "contextMenu node has the correct uri"
+ );
+ }
+
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popuphidden"
+ );
+ contextMenu.hidePopup();
+ await popupHiddenPromise;
+}
+
+// There are different ways of loading a page, but they should exhibit roughly the same
+// back-forward behavior for the purpose of requiring user interaction. Thus, we
+// have a utility function that runs the same test with a parameterized method of loading
+// new URLs.
+async function runTopLevelTest(loadMethod, useHashes = false) {
+ let p = useHashes ? "#" : "?";
+
+ // Test with both pref on and off
+ for (let requireUserInteraction of [true, false]) {
+ Services.prefs.setBoolPref(
+ "browser.navigation.requireUserInteraction",
+ requireUserInteraction
+ );
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_PAGE + p + "entry=0"
+ );
+ let browser = tab.linkedBrowser;
+
+ assertBackForwardState(false, false);
+
+ await loadMethod(TEST_PAGE + p + "entry=1");
+
+ assertBackForwardState(true, false);
+ await assertMenulist([1, 0]);
+
+ await loadMethod(TEST_PAGE + p + "entry=2");
+
+ assertBackForwardState(true, false);
+ await assertMenulist(requireUserInteraction ? [2, 0] : [2, 1, 0]);
+
+ await loadMethod(TEST_PAGE + p + "entry=3");
+
+ info("Adding user interaction for entry=3");
+ // Add some user interaction to entry 3
+ await BrowserTestUtils.synthesizeMouse(
+ "body",
+ 0,
+ 0,
+ {},
+ browser.browsingContext,
+ true
+ );
+
+ assertBackForwardState(true, false);
+ await assertMenulist(requireUserInteraction ? [3, 0] : [3, 2, 1, 0]);
+
+ await loadMethod(TEST_PAGE + p + "entry=4");
+
+ assertBackForwardState(true, false);
+ await assertMenulist(requireUserInteraction ? [4, 3, 0] : [4, 3, 2, 1, 0]);
+
+ info("Adding user interaction for entry=4");
+ // Add some user interaction to entry 4
+ await BrowserTestUtils.synthesizeMouse(
+ "body",
+ 0,
+ 0,
+ {},
+ browser.browsingContext,
+ true
+ );
+
+ await loadMethod(TEST_PAGE + p + "entry=5");
+
+ assertBackForwardState(true, false);
+ await assertMenulist(
+ requireUserInteraction ? [5, 4, 3, 0] : [5, 4, 3, 2, 1, 0]
+ );
+
+ await goBack(TEST_PAGE + p + "entry=4");
+ await goBack(TEST_PAGE + p + "entry=3");
+
+ if (!requireUserInteraction) {
+ await goBack(TEST_PAGE + p + "entry=2");
+ await goBack(TEST_PAGE + p + "entry=1");
+ }
+
+ assertBackForwardState(true, true);
+ await assertMenulist(
+ requireUserInteraction ? [5, 4, 3, 0] : [5, 4, 3, 2, 1, 0]
+ );
+
+ await goBack(TEST_PAGE + p + "entry=0");
+
+ assertBackForwardState(false, true);
+
+ if (!requireUserInteraction) {
+ await goForward(TEST_PAGE + p + "entry=1");
+ await goForward(TEST_PAGE + p + "entry=2");
+ }
+
+ await goForward(TEST_PAGE + p + "entry=3");
+
+ assertBackForwardState(true, true);
+ await assertMenulist(
+ requireUserInteraction ? [5, 4, 3, 0] : [5, 4, 3, 2, 1, 0]
+ );
+
+ await goForward(TEST_PAGE + p + "entry=4");
+
+ assertBackForwardState(true, true);
+ await assertMenulist(
+ requireUserInteraction ? [5, 4, 3, 0] : [5, 4, 3, 2, 1, 0]
+ );
+
+ await goForward(TEST_PAGE + p + "entry=5");
+
+ assertBackForwardState(true, false);
+ await assertMenulist(
+ requireUserInteraction ? [5, 4, 3, 0] : [5, 4, 3, 2, 1, 0]
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ }
+
+ Services.prefs.clearUserPref("browser.navigation.requireUserInteraction");
+}
+
+async function runIframeTest(loadMethod) {
+ // Test with both pref on and off
+ for (let requireUserInteraction of [true, false]) {
+ Services.prefs.setBoolPref(
+ "browser.navigation.requireUserInteraction",
+ requireUserInteraction
+ );
+
+ // First test the boring case where we only have one iframe.
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ IFRAME_PAGE + "?entry=0"
+ );
+ let browser = tab.linkedBrowser;
+
+ assertBackForwardState(false, false);
+
+ await loadMethod(TEST_PAGE + "?sub_entry=1", "frame1");
+
+ assertBackForwardState(true, false);
+ await assertMenulist([0, 0], IFRAME_PAGE);
+
+ await loadMethod(TEST_PAGE + "?sub_entry=2", "frame1");
+
+ assertBackForwardState(true, false);
+ await assertMenulist(
+ requireUserInteraction ? [0, 0] : [0, 0, 0],
+ IFRAME_PAGE
+ );
+
+ let bc = await SpecialPowers.spawn(browser, [], function () {
+ return content.document.getElementById("frame1").browsingContext;
+ });
+
+ // Add some user interaction to sub entry 2
+ await BrowserTestUtils.synthesizeMouse("body", 0, 0, {}, bc, true);
+
+ await loadMethod(TEST_PAGE + "?sub_entry=3", "frame1");
+
+ assertBackForwardState(true, false);
+ await assertMenulist(
+ requireUserInteraction ? [0, 0, 0] : [0, 0, 0, 0],
+ IFRAME_PAGE
+ );
+
+ await loadMethod(TEST_PAGE + "?sub_entry=4", "frame1");
+
+ assertBackForwardState(true, false);
+ await assertMenulist(
+ requireUserInteraction ? [0, 0, 0] : [0, 0, 0, 0, 0],
+ IFRAME_PAGE
+ );
+
+ if (!requireUserInteraction) {
+ await goBack(TEST_PAGE + "?sub_entry=3", true);
+ }
+
+ await goBack(TEST_PAGE + "?sub_entry=2", true);
+
+ assertBackForwardState(true, true);
+ await assertMenulist(
+ requireUserInteraction ? [0, 0, 0] : [0, 0, 0, 0, 0],
+ IFRAME_PAGE
+ );
+
+ await loadMethod(IFRAME_PAGE + "?entry=1");
+
+ assertBackForwardState(true, false);
+ await assertMenulist(
+ requireUserInteraction ? [1, 0, 0] : [1, 0, 0, 0],
+ IFRAME_PAGE
+ );
+
+ BrowserTestUtils.removeTab(tab);
+
+ // Two iframes, now we're talking.
+ tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ IFRAME_PAGE + "?entry=0"
+ );
+ browser = tab.linkedBrowser;
+
+ await loadMethod(IFRAME_PAGE + "?entry=1");
+
+ assertBackForwardState(true, false);
+ await assertMenulist(requireUserInteraction ? [1, 0] : [1, 0], IFRAME_PAGE);
+
+ // Add some user interaction to frame 1.
+ bc = await SpecialPowers.spawn(browser, [], function () {
+ return content.document.getElementById("frame1").browsingContext;
+ });
+ await BrowserTestUtils.synthesizeMouse("body", 0, 0, {}, bc, true);
+
+ // Add some user interaction to frame 2.
+ bc = await SpecialPowers.spawn(browser, [], function () {
+ return content.document.getElementById("frame2").browsingContext;
+ });
+ await BrowserTestUtils.synthesizeMouse("body", 0, 0, {}, bc, true);
+
+ // Navigate frame 2.
+ await loadMethod(TEST_PAGE + "?sub_entry=1", "frame2");
+
+ assertBackForwardState(true, false);
+ await assertMenulist(
+ requireUserInteraction ? [1, 1, 0] : [1, 1, 0],
+ IFRAME_PAGE
+ );
+
+ // Add some user interaction to frame 1, again.
+ bc = await SpecialPowers.spawn(browser, [], function () {
+ return content.document.getElementById("frame1").browsingContext;
+ });
+ await BrowserTestUtils.synthesizeMouse("body", 0, 0, {}, bc, true);
+
+ // Navigate frame 2, again.
+ await loadMethod(TEST_PAGE + "?sub_entry=2", "frame2");
+
+ assertBackForwardState(true, false);
+ await assertMenulist(
+ requireUserInteraction ? [1, 1, 1, 0] : [1, 1, 1, 0],
+ IFRAME_PAGE
+ );
+
+ await goBack(TEST_PAGE + "?sub_entry=1", true);
+
+ assertBackForwardState(true, true);
+ await assertMenulist(
+ requireUserInteraction ? [1, 1, 1, 0] : [1, 1, 1, 0],
+ IFRAME_PAGE
+ );
+
+ await goBack(TEST_PAGE + "?sub_entry=0", true);
+
+ assertBackForwardState(true, true);
+ await assertMenulist(
+ requireUserInteraction ? [1, 1, 1, 0] : [1, 1, 1, 0],
+ IFRAME_PAGE
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ }
+
+ Services.prefs.clearUserPref("browser.navigation.requireUserInteraction");
+}
+
+// Test that when the pref is flipped, we are skipping history
+// entries without user interaction when following links with hash URIs.
+add_task(async function test_hashURI() {
+ async function followLinkHash(url) {
+ info(`Creating and following a link to ${url}`);
+ let browser = gBrowser.selectedBrowser;
+ let loaded = BrowserTestUtils.waitForLocationChange(gBrowser, url);
+ await SpecialPowers.spawn(browser, [url], function (url) {
+ let a = content.document.createElement("a");
+ a.href = url;
+ content.document.body.appendChild(a);
+ a.click();
+ });
+ await loaded;
+ info(`Loaded ${url}`);
+ }
+
+ await runTopLevelTest(followLinkHash, true);
+});
+
+// Test that when the pref is flipped, we are skipping history
+// entries without user interaction when using history.pushState.
+add_task(async function test_pushState() {
+ await runTopLevelTest(pushState);
+});
+
+// Test that when the pref is flipped, we are skipping history
+// entries without user interaction when following a link.
+add_task(async function test_followLink() {
+ await runTopLevelTest(followLink);
+});
+
+// Test that when the pref is flipped, we are skipping history
+// entries without user interaction when navigating inside an iframe
+// using history.pushState.
+add_task(async function test_iframe_pushState() {
+ await runIframeTest(pushState);
+});
+
+// Test that when the pref is flipped, we are skipping history
+// entries without user interaction when navigating inside an iframe
+// by following links.
+add_task(async function test_iframe_followLink() {
+ await runIframeTest(followLink);
+});
diff --git a/docshell/test/browser/browser_backforward_userinteraction_about.js b/docshell/test/browser/browser_backforward_userinteraction_about.js
new file mode 100644
index 0000000000..606fcc45c5
--- /dev/null
+++ b/docshell/test/browser/browser_backforward_userinteraction_about.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PAGE =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "dummy_page.html";
+
+// Regression test for navigating back after visiting an about: page
+// loaded in the parent process.
+add_task(async function test_about_back() {
+ // Test with both pref on and off
+ for (let requireUserInteraction of [true, false]) {
+ Services.prefs.setBoolPref(
+ "browser.navigation.requireUserInteraction",
+ requireUserInteraction
+ );
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_PAGE + "?entry=0"
+ );
+ let browser = tab.linkedBrowser;
+ assertBackForwardState(false, false);
+
+ await followLink(TEST_PAGE + "?entry=1");
+ assertBackForwardState(true, false);
+
+ await followLink(TEST_PAGE + "?entry=2");
+ assertBackForwardState(true, false);
+
+ // Add some user interaction to entry 2
+ await BrowserTestUtils.synthesizeMouse("body", 0, 0, {}, browser, true);
+
+ await loadURI("about:config");
+ assertBackForwardState(true, false);
+
+ await goBack(TEST_PAGE + "?entry=2");
+ assertBackForwardState(true, true);
+
+ if (!requireUserInteraction) {
+ await goBack(TEST_PAGE + "?entry=1");
+ assertBackForwardState(true, true);
+ }
+
+ await goBack(TEST_PAGE + "?entry=0");
+ assertBackForwardState(false, true);
+
+ if (!requireUserInteraction) {
+ await goForward(TEST_PAGE + "?entry=1");
+ assertBackForwardState(true, true);
+ }
+
+ await goForward(TEST_PAGE + "?entry=2");
+ assertBackForwardState(true, true);
+
+ await goForward("about:config");
+ assertBackForwardState(true, false);
+
+ BrowserTestUtils.removeTab(tab);
+ }
+
+ Services.prefs.clearUserPref("browser.navigation.requireUserInteraction");
+});
diff --git a/docshell/test/browser/browser_backforward_userinteraction_systemprincipal.js b/docshell/test/browser/browser_backforward_userinteraction_systemprincipal.js
new file mode 100644
index 0000000000..289ed1d133
--- /dev/null
+++ b/docshell/test/browser/browser_backforward_userinteraction_systemprincipal.js
@@ -0,0 +1,112 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PAGE =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "dummy_page.html";
+
+async function runTest(privilegedLoad) {
+ let prefVals;
+ // Test with both pref on and off, unless parent-controlled pref is enabled.
+ // This distinction can be removed once SHIP is enabled by default.
+ if (
+ Services.prefs.getBoolPref("browser.tabs.documentchannel.parent-controlled")
+ ) {
+ prefVals = [false];
+ } else {
+ prefVals = [true, false];
+ }
+
+ for (let requireUserInteraction of prefVals) {
+ Services.prefs.setBoolPref(
+ "browser.navigation.requireUserInteraction",
+ requireUserInteraction
+ );
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_PAGE + "?entry=0"
+ );
+
+ assertBackForwardState(false, false);
+
+ await followLink(TEST_PAGE + "?entry=1");
+
+ assertBackForwardState(true, false);
+
+ await followLink(TEST_PAGE + "?entry=2");
+
+ assertBackForwardState(true, false);
+
+ await followLink(TEST_PAGE + "?entry=3");
+
+ assertBackForwardState(true, false);
+
+ // Entry 4 will be added through a user action in browser chrome,
+ // giving user interaction to entry 3. Entry 4 should not gain automatic
+ // user interaction.
+ await privilegedLoad(TEST_PAGE + "?entry=4");
+
+ assertBackForwardState(true, false);
+
+ await followLink(TEST_PAGE + "?entry=5");
+
+ assertBackForwardState(true, false);
+
+ if (!requireUserInteraction) {
+ await goBack(TEST_PAGE + "?entry=4");
+ }
+ await goBack(TEST_PAGE + "?entry=3");
+
+ if (!requireUserInteraction) {
+ await goBack(TEST_PAGE + "?entry=2");
+ await goBack(TEST_PAGE + "?entry=1");
+ }
+
+ assertBackForwardState(true, true);
+
+ await goBack(TEST_PAGE + "?entry=0");
+
+ assertBackForwardState(false, true);
+
+ if (!requireUserInteraction) {
+ await goForward(TEST_PAGE + "?entry=1");
+ await goForward(TEST_PAGE + "?entry=2");
+ }
+
+ await goForward(TEST_PAGE + "?entry=3");
+
+ assertBackForwardState(true, true);
+
+ if (!requireUserInteraction) {
+ await goForward(TEST_PAGE + "?entry=4");
+ }
+
+ await goForward(TEST_PAGE + "?entry=5");
+
+ assertBackForwardState(true, false);
+
+ BrowserTestUtils.removeTab(tab);
+ }
+
+ Services.prefs.clearUserPref("browser.navigation.requireUserInteraction");
+}
+
+// Test that we add a user interaction flag to the previous site when loading
+// a new site from user interaction with privileged UI, e.g. through the
+// URL bar.
+add_task(async function test_urlBar() {
+ await runTest(async function (url) {
+ info(`Loading ${url} via the URL bar.`);
+ let browser = gBrowser.selectedBrowser;
+ let loaded = BrowserTestUtils.browserLoaded(browser, false, url);
+ gURLBar.focus();
+ gURLBar.value = url;
+ gURLBar.goButton.click();
+ await loaded;
+ });
+});
diff --git a/docshell/test/browser/browser_badCertDomainFixup.js b/docshell/test/browser/browser_badCertDomainFixup.js
new file mode 100644
index 0000000000..2db3eb6701
--- /dev/null
+++ b/docshell/test/browser/browser_badCertDomainFixup.js
@@ -0,0 +1,92 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// This test checks if we are correctly fixing https URLs by prefixing
+// with www. when we encounter a SSL_ERROR_BAD_CERT_DOMAIN error.
+// For example, https://example.com -> https://www.example.com.
+
+const PREF_BAD_CERT_DOMAIN_FIX_ENABLED =
+ "security.bad_cert_domain_error.url_fix_enabled";
+const PREF_ALLOW_HIJACKING_LOCALHOST =
+ "network.proxy.allow_hijacking_localhost";
+
+const BAD_CERT_DOMAIN_ERROR_URL = "https://badcertdomain.example.com:443";
+const FIXED_URL = "https://www.badcertdomain.example.com/";
+
+const BAD_CERT_DOMAIN_ERROR_URL2 =
+ "https://mismatch.badcertdomain.example.com:443";
+const IPV4_ADDRESS = "https://127.0.0.3:433";
+const BAD_CERT_DOMAIN_ERROR_PORT = "https://badcertdomain.example.com:82";
+
+async function verifyErrorPage(errorPageURL) {
+ let certErrorLoaded = BrowserTestUtils.waitForErrorPage(
+ gBrowser.selectedBrowser
+ );
+ BrowserTestUtils.loadURIString(gBrowser, errorPageURL);
+ await certErrorLoaded;
+
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () {
+ let ec;
+ await ContentTaskUtils.waitForCondition(() => {
+ ec = content.document.getElementById("errorCode");
+ return ec.textContent;
+ }, "Error code has been set inside the advanced button panel");
+ is(
+ ec.textContent,
+ "SSL_ERROR_BAD_CERT_DOMAIN",
+ "Correct error code is shown"
+ );
+ });
+}
+
+// Test that "www." is prefixed to a https url when we encounter a bad cert domain
+// error if the "www." form is included in the certificate's subjectAltNames.
+add_task(async function prefixBadCertDomain() {
+ // Turn off the pref and ensure that we show the error page as expected.
+ Services.prefs.setBoolPref(PREF_BAD_CERT_DOMAIN_FIX_ENABLED, false);
+
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
+ await verifyErrorPage(BAD_CERT_DOMAIN_ERROR_URL);
+ info("Cert error is shown as expected when the fixup pref is disabled");
+
+ // Turn on the pref and test that we fix the HTTPS URL.
+ Services.prefs.setBoolPref(PREF_BAD_CERT_DOMAIN_FIX_ENABLED, true);
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
+ let loadSuccessful = BrowserTestUtils.browserLoaded(
+ gBrowser.selectedBrowser,
+ false,
+ FIXED_URL
+ );
+ BrowserTestUtils.loadURIString(gBrowser, BAD_CERT_DOMAIN_ERROR_URL);
+ await loadSuccessful;
+
+ info("The URL was fixed as expected");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+// Test that we don't prefix "www." to a https url when we encounter a bad cert domain
+// error under certain conditions.
+add_task(async function ignoreBadCertDomain() {
+ Services.prefs.setBoolPref(PREF_BAD_CERT_DOMAIN_FIX_ENABLED, true);
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
+
+ // Test for when "www." form is not present in the certificate.
+ await verifyErrorPage(BAD_CERT_DOMAIN_ERROR_URL2);
+ info("Certificate error was shown as expected");
+
+ // Test that urls with IP addresses are not fixed.
+ Services.prefs.setBoolPref(PREF_ALLOW_HIJACKING_LOCALHOST, true);
+ await verifyErrorPage(IPV4_ADDRESS);
+ Services.prefs.clearUserPref(PREF_ALLOW_HIJACKING_LOCALHOST);
+ info("Certificate error was shown as expected for an IP address");
+
+ // Test that urls with ports are not fixed.
+ await verifyErrorPage(BAD_CERT_DOMAIN_ERROR_PORT);
+ info("Certificate error was shown as expected for a host with port");
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
diff --git a/docshell/test/browser/browser_bfcache_copycommand.js b/docshell/test/browser/browser_bfcache_copycommand.js
new file mode 100644
index 0000000000..178aee4913
--- /dev/null
+++ b/docshell/test/browser/browser_bfcache_copycommand.js
@@ -0,0 +1,98 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function dummyPageURL(domain, query = "") {
+ return (
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ `https://${domain}`
+ ) +
+ "dummy_page.html" +
+ query
+ );
+}
+
+async function openContextMenu(browser) {
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ let awaitPopupShown = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popupshown"
+ );
+ await BrowserTestUtils.synthesizeMouse(
+ "body",
+ 1,
+ 1,
+ {
+ type: "contextmenu",
+ button: 2,
+ },
+ browser
+ );
+ await awaitPopupShown;
+}
+
+async function closeContextMenu() {
+ let contextMenu = document.getElementById("contentAreaContextMenu");
+ let awaitPopupHidden = BrowserTestUtils.waitForEvent(
+ contextMenu,
+ "popuphidden"
+ );
+ contextMenu.hidePopup();
+ await awaitPopupHidden;
+}
+
+async function testWithDomain(domain) {
+ // Passing a query to make sure the next load is never a same-document
+ // navigation.
+ let dummy = dummyPageURL("example.org", "?start");
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, dummy);
+ let browser = tab.linkedBrowser;
+
+ let sel = await SpecialPowers.spawn(browser, [], function () {
+ let sel = content.getSelection();
+ sel.removeAllRanges();
+ sel.selectAllChildren(content.document.body);
+ return sel.toString();
+ });
+
+ await openContextMenu(browser);
+
+ let copyItem = document.getElementById("context-copy");
+ ok(!copyItem.disabled, "Copy item should be enabled if text is selected.");
+
+ await closeContextMenu();
+
+ let loaded = BrowserTestUtils.browserLoaded(browser, false, null, true);
+ BrowserTestUtils.loadURIString(browser, dummyPageURL(domain));
+ await loaded;
+ loaded = BrowserTestUtils.waitForLocationChange(gBrowser, dummy);
+ browser.goBack();
+ await loaded;
+
+ let sel2 = await SpecialPowers.spawn(browser, [], function () {
+ return content.getSelection().toString();
+ });
+ is(sel, sel2, "Selection should remain when coming out of BFCache.");
+
+ await openContextMenu(browser);
+
+ ok(!copyItem.disabled, "Copy item should be enabled if text is selected.");
+
+ await closeContextMenu();
+
+ await BrowserTestUtils.removeTab(tab);
+}
+
+add_task(async function testValidSameOrigin() {
+ await testWithDomain("example.org");
+});
+
+add_task(async function testValidCrossOrigin() {
+ await testWithDomain("example.com");
+});
+
+add_task(async function testInvalid() {
+ await testWithDomain("this.is.invalid");
+});
diff --git a/docshell/test/browser/browser_browsingContext-01.js b/docshell/test/browser/browser_browsingContext-01.js
new file mode 100644
index 0000000000..95831d2567
--- /dev/null
+++ b/docshell/test/browser/browser_browsingContext-01.js
@@ -0,0 +1,180 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const URL = "about:blank";
+
+async function getBrowsingContextId(browser, id) {
+ return SpecialPowers.spawn(browser, [id], async function (id) {
+ let contextId = content.window.docShell.browsingContext.id;
+
+ let frames = [content.window];
+ while (frames.length) {
+ let frame = frames.pop();
+ let target = frame.document.getElementById(id);
+ if (target) {
+ contextId = target.docShell.browsingContext.id;
+ break;
+ }
+
+ frames = frames.concat(Array.from(frame.frames));
+ }
+
+ return contextId;
+ });
+}
+
+async function addFrame(browser, id, parentId) {
+ return SpecialPowers.spawn(
+ browser,
+ [{ parentId, id }],
+ async function ({ parentId, id }) {
+ let parent = null;
+ if (parentId) {
+ let frames = [content.window];
+ while (frames.length) {
+ let frame = frames.pop();
+ let target = frame.document.getElementById(parentId);
+ if (target) {
+ parent = target.contentWindow.document.body;
+ break;
+ }
+ frames = frames.concat(Array.from(frame.frames));
+ }
+ } else {
+ parent = content.document.body;
+ }
+
+ let frame = await new Promise(resolve => {
+ let frame = content.document.createElement("iframe");
+ frame.id = id || "";
+ frame.url = "about:blank";
+ frame.onload = () => resolve(frame);
+ parent.appendChild(frame);
+ });
+
+ return frame.contentWindow.docShell.browsingContext.id;
+ }
+ );
+}
+
+async function removeFrame(browser, id) {
+ return SpecialPowers.spawn(browser, [id], async function (id) {
+ let frames = [content.window];
+ while (frames.length) {
+ let frame = frames.pop();
+ let target = frame.document.getElementById(id);
+ if (target) {
+ target.remove();
+ break;
+ }
+
+ frames = frames.concat(Array.from(frame.frames));
+ }
+ });
+}
+
+function getBrowsingContextById(id) {
+ return BrowsingContext.get(id);
+}
+
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: URL },
+ async function (browser) {
+ let topId = await getBrowsingContextId(browser, "");
+ let topContext = getBrowsingContextById(topId);
+ isnot(topContext, null);
+ is(topContext.parent, null);
+ is(
+ topId,
+ browser.browsingContext.id,
+ "<browser> has the correct browsingContext"
+ );
+ is(
+ browser.browserId,
+ topContext.browserId,
+ "browsing context should have a correct <browser> id"
+ );
+
+ let id0 = await addFrame(browser, "frame0");
+ let browsingContext0 = getBrowsingContextById(id0);
+ isnot(browsingContext0, null);
+ is(browsingContext0.parent, topContext);
+
+ await removeFrame(browser, "frame0");
+
+ is(topContext.children.indexOf(browsingContext0), -1);
+
+ // TODO(farre): Handle browsingContext removal [see Bug 1486719].
+ todo_isnot(browsingContext0.parent, topContext);
+ }
+ );
+});
+
+add_task(async function () {
+ // If Fission is disabled, the pref is no-op.
+ await SpecialPowers.pushPrefEnv({ set: [["fission.bfcacheInParent", true]] });
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url:
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com"
+ ) + "dummy_page.html",
+ },
+ async function (browser) {
+ let path = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com"
+ );
+ await SpecialPowers.spawn(browser, [path], async function (path) {
+ var bc = new content.BroadcastChannel("browser_browsingContext");
+ function waitForMessage(command) {
+ let p = new Promise(resolve => {
+ bc.addEventListener("message", e => resolve(e), { once: true });
+ });
+ command();
+ return p;
+ }
+
+ // Open a new window and wait for the message.
+ let e1 = await waitForMessage(_ =>
+ content.window.open(path + "onpageshow_message.html", "", "noopener")
+ );
+
+ is(e1.data, "pageshow", "Got page show");
+
+ let e2 = await waitForMessage(_ => bc.postMessage("createiframe"));
+ is(e2.data.framesLength, 1, "Here we should have an iframe");
+
+ let e3 = await waitForMessage(_ => bc.postMessage("nextpage"));
+
+ is(e3.data.event, "load");
+ is(e3.data.framesLength, 0, "Here there shouldn't be an iframe");
+
+ // Return to the previous document. N.B. we expect to trigger
+ // BFCache here, hence we wait for pageshow.
+ let e4 = await waitForMessage(_ => bc.postMessage("back"));
+
+ is(e4.data, "pageshow");
+
+ let e5 = await waitForMessage(_ => bc.postMessage("queryframes"));
+ is(e5.data.framesLength, 1, "And again there should be an iframe");
+
+ is(e5.outerWindowId, e2.outerWindowId, "BF cache cached outer window");
+ is(e5.browsingContextId, e2.browsingContextId, "BF cache cached BC");
+
+ let e6 = await waitForMessage(_ => bc.postMessage("close"));
+ is(e6.data, "closed");
+
+ bc.close();
+ });
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_browsingContext-02.js b/docshell/test/browser/browser_browsingContext-02.js
new file mode 100644
index 0000000000..8439b869b0
--- /dev/null
+++ b/docshell/test/browser/browser_browsingContext-02.js
@@ -0,0 +1,235 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:blank" },
+ async function (browser) {
+ const BASE1 = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com"
+ );
+ const BASE2 = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://test1.example.com"
+ );
+ const URL = BASE1 + "onload_message.html";
+ let sixth = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ URL + "#sixth",
+ true,
+ true
+ );
+ let seventh = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ URL + "#seventh",
+ true,
+ true
+ );
+ let browserIds = await SpecialPowers.spawn(
+ browser,
+ [{ base1: BASE1, base2: BASE2 }],
+ async function ({ base1, base2 }) {
+ let top = content;
+ top.name = "top";
+ top.location.href += "#top";
+
+ let contexts = {
+ top: top.location.href,
+ first: base1 + "dummy_page.html#first",
+ third: base2 + "dummy_page.html#third",
+ second: base1 + "dummy_page.html#second",
+ fourth: base2 + "dummy_page.html#fourth",
+ fifth: base1 + "dummy_page.html#fifth",
+ sixth: base1 + "onload_message.html#sixth",
+ seventh: base1 + "onload_message.html#seventh",
+ };
+
+ function addFrame(target, name) {
+ return content.SpecialPowers.spawn(
+ target,
+ [name, contexts[name]],
+ async (name, context) => {
+ let doc = this.content.document;
+
+ let frame = doc.createElement("iframe");
+ doc.body.appendChild(frame);
+ frame.name = name;
+ frame.src = context;
+ await new Promise(resolve => {
+ frame.addEventListener("load", resolve, { once: true });
+ });
+ return frame.browsingContext;
+ }
+ );
+ }
+
+ function addWindow(target, name, { options, resolve }) {
+ return content.SpecialPowers.spawn(
+ target,
+ [name, contexts[name], options, resolve],
+ (name, context, options, resolve) => {
+ let win = this.content.open(context, name, options);
+ let bc = win && win.docShell.browsingContext;
+
+ if (resolve) {
+ return new Promise(resolve =>
+ this.content.addEventListener("message", () => resolve(bc))
+ );
+ }
+ return Promise.resolve({ name });
+ }
+ );
+ }
+
+ // We're going to create a tree that looks like the
+ // following.
+ //
+ // top sixth seventh
+ // / \
+ // / \ /
+ // first second
+ // / \ /
+ // / \
+ // third fourth - - -
+ // /
+ // /
+ // fifth
+ //
+ // The idea is to have one top level non-auxiliary browsing
+ // context, five nested, one top level auxiliary with an
+ // opener, and one top level without an opener. Given that
+ // set of related and one unrelated browsing contexts we
+ // wish to confirm that targeting is able to find
+ // appropriate browsing contexts.
+
+ // WindowGlobalChild.findBrowsingContextWithName requires access
+ // checks, which can only be performed in the process of the accessor
+ // WindowGlobalChild.
+ function findWithName(bc, name) {
+ return content.SpecialPowers.spawn(bc, [name], name => {
+ return content.windowGlobalChild.findBrowsingContextWithName(
+ name
+ );
+ });
+ }
+
+ async function reachable(start, target) {
+ info(start.name, target.name);
+ is(
+ await findWithName(start, target.name),
+ target,
+ [start.name, "can reach", target.name].join(" ")
+ );
+ }
+
+ async function unreachable(start, target) {
+ is(
+ await findWithName(start, target.name),
+ null,
+ [start.name, "can't reach", target.name].join(" ")
+ );
+ }
+
+ let first = await addFrame(top, "first");
+ info("first");
+ let second = await addFrame(top, "second");
+ info("second");
+ let third = await addFrame(first, "third");
+ info("third");
+ let fourth = await addFrame(first, "fourth");
+ info("fourth");
+ let fifth = await addFrame(fourth, "fifth");
+ info("fifth");
+ let sixth = await addWindow(fourth, "sixth", { resolve: true });
+ info("sixth");
+ let seventh = await addWindow(fourth, "seventh", {
+ options: ["noopener"],
+ });
+ info("seventh");
+
+ let origin1 = [first, second, fifth, sixth];
+ let origin2 = [third, fourth];
+
+ let topBC = BrowsingContext.getFromWindow(top);
+ let frames = new Map([
+ [topBC, [topBC, first, second, third, fourth, fifth, sixth]],
+ [first, [topBC, ...origin1, third, fourth]],
+ [second, [topBC, ...origin1, third, fourth]],
+ [third, [topBC, ...origin2, fifth, sixth]],
+ [fourth, [topBC, ...origin2, fifth, sixth]],
+ [fifth, [topBC, ...origin1, third, fourth]],
+ [sixth, [...origin1, third, fourth]],
+ ]);
+
+ for (let [start, accessible] of frames) {
+ for (let frame of frames.keys()) {
+ if (accessible.includes(frame)) {
+ await reachable(start, frame);
+ } else {
+ await unreachable(start, frame);
+ }
+ }
+ await unreachable(start, seventh);
+ }
+
+ let topBrowserId = topBC.browserId;
+ ok(topBrowserId > 0, "Should have a browser ID.");
+ for (let [name, bc] of Object.entries({
+ first,
+ second,
+ third,
+ fourth,
+ fifth,
+ })) {
+ is(
+ bc.browserId,
+ topBrowserId,
+ `${name} frame should have the same browserId as top.`
+ );
+ }
+
+ ok(sixth.browserId > 0, "sixth should have a browserId.");
+ isnot(
+ sixth.browserId,
+ topBrowserId,
+ "sixth frame should have a different browserId to top."
+ );
+
+ return [topBrowserId, sixth.browserId];
+ }
+ );
+
+ [sixth, seventh] = await Promise.all([sixth, seventh]);
+
+ is(
+ browser.browserId,
+ browserIds[0],
+ "browser should have the right browserId."
+ );
+ is(
+ browser.browsingContext.browserId,
+ browserIds[0],
+ "browser's BrowsingContext should have the right browserId."
+ );
+ is(
+ sixth.linkedBrowser.browserId,
+ browserIds[1],
+ "sixth should have the right browserId."
+ );
+ is(
+ sixth.linkedBrowser.browsingContext.browserId,
+ browserIds[1],
+ "sixth's BrowsingContext should have the right browserId."
+ );
+
+ for (let tab of [sixth, seventh]) {
+ BrowserTestUtils.removeTab(tab);
+ }
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_browsingContext-embedder.js b/docshell/test/browser/browser_browsingContext-embedder.js
new file mode 100644
index 0000000000..9473a46eb4
--- /dev/null
+++ b/docshell/test/browser/browser_browsingContext-embedder.js
@@ -0,0 +1,156 @@
+"use strict";
+
+function observeOnce(topic) {
+ return new Promise(resolve => {
+ Services.obs.addObserver(function observer(aSubject, aTopic) {
+ if (topic == aTopic) {
+ Services.obs.removeObserver(observer, topic);
+ setTimeout(() => resolve(aSubject), 0);
+ }
+ }, topic);
+ });
+}
+
+add_task(async function runTest() {
+ let fissionWindow = await BrowserTestUtils.openNewBrowserWindow({
+ fission: true,
+ remote: true,
+ });
+
+ info(`chrome, parent`);
+ let chromeBC = fissionWindow.docShell.browsingContext;
+ ok(chromeBC.currentWindowGlobal, "Should have a current WindowGlobal");
+ is(chromeBC.embedderWindowGlobal, null, "chrome has no embedder global");
+ is(chromeBC.embedderElement, null, "chrome has no embedder element");
+ is(chromeBC.parent, null, "chrome has no parent");
+
+ // Open a new tab, and check that basic frames work out.
+ let tab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser: fissionWindow.gBrowser,
+ });
+
+ info(`root, parent`);
+ let rootBC = tab.linkedBrowser.browsingContext;
+ ok(rootBC.currentWindowGlobal, "[parent] root has a window global");
+ is(
+ rootBC.embedderWindowGlobal,
+ chromeBC.currentWindowGlobal,
+ "[parent] root has chrome as embedder global"
+ );
+ is(
+ rootBC.embedderElement,
+ tab.linkedBrowser,
+ "[parent] root has browser as embedder element"
+ );
+ is(rootBC.parent, null, "[parent] root has no parent");
+
+ // Test with an in-process frame
+ let frameId = await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ info(`root, child`);
+ let rootBC = content.docShell.browsingContext;
+ is(rootBC.embedderElement, null, "[child] root has no embedder");
+ is(rootBC.parent, null, "[child] root has no parent");
+
+ info(`frame, child`);
+ let iframe = content.document.createElement("iframe");
+ content.document.body.appendChild(iframe);
+
+ let frameBC = iframe.contentWindow.docShell.browsingContext;
+ is(frameBC.embedderElement, iframe, "[child] frame embedded within iframe");
+ is(frameBC.parent, rootBC, "[child] frame has root as parent");
+
+ return frameBC.id;
+ });
+
+ info(`frame, parent`);
+ let frameBC = BrowsingContext.get(frameId);
+ ok(frameBC.currentWindowGlobal, "[parent] frame has a window global");
+ is(
+ frameBC.embedderWindowGlobal,
+ rootBC.currentWindowGlobal,
+ "[parent] frame has root as embedder global"
+ );
+ is(frameBC.embedderElement, null, "[parent] frame has no embedder element");
+ is(frameBC.parent, rootBC, "[parent] frame has root as parent");
+
+ // Test with an out-of-process iframe.
+
+ let oopID = await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ info(`creating oop iframe`);
+ let oop = content.document.createElement("iframe");
+ oop.setAttribute("src", "https://example.com");
+ content.document.body.appendChild(oop);
+
+ await new Promise(resolve => {
+ oop.addEventListener("load", resolve, { once: true });
+ });
+
+ info(`oop frame, child`);
+ let oopBC = oop.frameLoader.browsingContext;
+ is(oopBC.embedderElement, oop, "[child] oop frame embedded within iframe");
+ is(
+ oopBC.parent,
+ content.docShell.browsingContext,
+ "[child] frame has root as parent"
+ );
+
+ return oopBC.id;
+ });
+
+ info(`oop frame, parent`);
+ let oopBC = BrowsingContext.get(oopID);
+ is(
+ oopBC.embedderWindowGlobal,
+ rootBC.currentWindowGlobal,
+ "[parent] oop frame has root as embedder global"
+ );
+ is(oopBC.embedderElement, null, "[parent] oop frame has no embedder element");
+ is(oopBC.parent, rootBC, "[parent] oop frame has root as parent");
+
+ info(`waiting for oop window global`);
+ ok(oopBC.currentWindowGlobal, "[parent] oop frame has a window global");
+
+ // Open a new window, and adopt |tab| into it.
+
+ let newWindow = await BrowserTestUtils.openNewBrowserWindow({
+ fission: true,
+ remote: true,
+ });
+
+ info(`new chrome, parent`);
+ let newChromeBC = newWindow.docShell.browsingContext;
+ ok(newChromeBC.currentWindowGlobal, "Should have a current WindowGlobal");
+ is(
+ newChromeBC.embedderWindowGlobal,
+ null,
+ "new chrome has no embedder global"
+ );
+ is(newChromeBC.embedderElement, null, "new chrome has no embedder element");
+ is(newChromeBC.parent, null, "new chrome has no parent");
+
+ isnot(newChromeBC, chromeBC, "different chrome browsing context");
+
+ info(`adopting tab`);
+ let newTab = newWindow.gBrowser.adoptTab(tab);
+
+ is(
+ newTab.linkedBrowser.browsingContext,
+ rootBC,
+ "[parent] root browsing context survived"
+ );
+ is(
+ rootBC.embedderWindowGlobal,
+ newChromeBC.currentWindowGlobal,
+ "[parent] embedder window global updated"
+ );
+ is(
+ rootBC.embedderElement,
+ newTab.linkedBrowser,
+ "[parent] embedder element updated"
+ );
+ is(rootBC.parent, null, "[parent] root has no parent");
+
+ info(`closing window`);
+ await BrowserTestUtils.closeWindow(newWindow);
+ await BrowserTestUtils.closeWindow(fissionWindow);
+});
diff --git a/docshell/test/browser/browser_browsingContext-getAllBrowsingContextsInSubtree.js b/docshell/test/browser/browser_browsingContext-getAllBrowsingContextsInSubtree.js
new file mode 100644
index 0000000000..076383ad87
--- /dev/null
+++ b/docshell/test/browser/browser_browsingContext-getAllBrowsingContextsInSubtree.js
@@ -0,0 +1,53 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+async function addFrame(url) {
+ let iframe = content.document.createElement("iframe");
+ await new Promise(resolve => {
+ iframe.addEventListener("load", resolve, { once: true });
+ iframe.src = url;
+ content.document.body.appendChild(iframe);
+ });
+ return iframe.browsingContext;
+}
+
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:blank" },
+ async browser => {
+ // Add 15 example.com frames to the toplevel document.
+ let frames = await Promise.all(
+ Array.from({ length: 15 }).map(_ =>
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ SpecialPowers.spawn(browser, ["http://example.com/"], addFrame)
+ )
+ );
+
+ // Add an example.org subframe to each example.com frame.
+ let subframes = await Promise.all(
+ Array.from({ length: 15 }).map((_, i) =>
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ SpecialPowers.spawn(frames[i], ["http://example.org/"], addFrame)
+ )
+ );
+
+ Assert.deepEqual(
+ subframes[0].getAllBrowsingContextsInSubtree(),
+ [subframes[0]],
+ "Childless context only has self in subtree"
+ );
+ Assert.deepEqual(
+ frames[0].getAllBrowsingContextsInSubtree(),
+ [frames[0], subframes[0]],
+ "Single-child context has 2 contexts in subtree"
+ );
+ Assert.deepEqual(
+ browser.browsingContext.getAllBrowsingContextsInSubtree(),
+ [browser.browsingContext, ...frames, ...subframes],
+ "Toplevel context has all subtree contexts"
+ );
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_browsingContext-getWindowByName.js b/docshell/test/browser/browser_browsingContext-getWindowByName.js
new file mode 100644
index 0000000000..e57691b270
--- /dev/null
+++ b/docshell/test/browser/browser_browsingContext-getWindowByName.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function addWindow(name) {
+ var blank = Cc["@mozilla.org/supports-string;1"].createInstance(
+ Ci.nsISupportsString
+ );
+ blank.data = "about:blank";
+ let promise = BrowserTestUtils.waitForNewWindow({
+ anyWindow: true,
+ url: "about:blank",
+ });
+ Services.ww.openWindow(
+ null,
+ AppConstants.BROWSER_CHROME_URL,
+ name,
+ "chrome,dialog=no",
+ blank
+ );
+ return promise;
+}
+
+add_task(async function () {
+ let windows = [await addWindow("first"), await addWindow("second")];
+
+ for (let w of windows) {
+ isnot(w, null);
+ is(Services.ww.getWindowByName(w.name, null), w, `Found ${w.name}`);
+ }
+
+ await Promise.all(windows.map(BrowserTestUtils.closeWindow));
+});
diff --git a/docshell/test/browser/browser_browsingContext-webProgress.js b/docshell/test/browser/browser_browsingContext-webProgress.js
new file mode 100644
index 0000000000..54b5400efd
--- /dev/null
+++ b/docshell/test/browser/browser_browsingContext-webProgress.js
@@ -0,0 +1,226 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function () {
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+ const browser = tab.linkedBrowser;
+ const aboutBlankBrowsingContext = browser.browsingContext;
+ const { webProgress } = aboutBlankBrowsingContext;
+ ok(
+ webProgress,
+ "Got a WebProgress interface on BrowsingContext in the parent process"
+ );
+ is(
+ webProgress.browsingContext,
+ browser.browsingContext,
+ "WebProgress.browsingContext refers to the right BrowsingContext"
+ );
+
+ const onLocationChanged = waitForNextLocationChange(webProgress);
+ const newLocation = "data:text/html;charset=utf-8,first-page";
+ let loaded = BrowserTestUtils.browserLoaded(browser);
+ BrowserTestUtils.loadURIString(browser, newLocation);
+ await loaded;
+
+ const firstPageBrowsingContext = browser.browsingContext;
+ const isBfcacheInParentEnabled =
+ SpecialPowers.Services.appinfo.sessionHistoryInParent &&
+ SpecialPowers.Services.prefs.getBoolPref("fission.bfcacheInParent");
+ if (isBfcacheInParentEnabled) {
+ isnot(
+ aboutBlankBrowsingContext,
+ firstPageBrowsingContext,
+ "With bfcache in parent, navigations spawn a new BrowsingContext"
+ );
+ } else {
+ is(
+ aboutBlankBrowsingContext,
+ firstPageBrowsingContext,
+ "Without bfcache in parent, navigations reuse the same BrowsingContext"
+ );
+ }
+
+ info("Wait for onLocationChange to be fired");
+ {
+ const { browsingContext, location, request, flags } =
+ await onLocationChanged;
+ is(
+ browsingContext,
+ firstPageBrowsingContext,
+ "Location change fires on the new BrowsingContext"
+ );
+ ok(location instanceof Ci.nsIURI);
+ is(location.spec, newLocation);
+ ok(request instanceof Ci.nsIChannel);
+ is(request.URI.spec, newLocation);
+ is(flags, 0);
+ }
+
+ const onIframeLocationChanged = waitForNextLocationChange(webProgress);
+ const iframeLocation = "data:text/html;charset=utf-8,iframe";
+ const iframeBC = await SpecialPowers.spawn(
+ browser,
+ [iframeLocation],
+ async url => {
+ const iframe = content.document.createElement("iframe");
+ await new Promise(resolve => {
+ iframe.addEventListener("load", resolve, { once: true });
+ iframe.src = url;
+ content.document.body.appendChild(iframe);
+ });
+
+ return iframe.browsingContext;
+ }
+ );
+ ok(
+ iframeBC.webProgress,
+ "The iframe BrowsingContext also exposes a WebProgress"
+ );
+ {
+ const { browsingContext, location, request, flags } =
+ await onIframeLocationChanged;
+ is(
+ browsingContext,
+ iframeBC,
+ "Iframe location change fires on the iframe BrowsingContext"
+ );
+ ok(location instanceof Ci.nsIURI);
+ is(location.spec, iframeLocation);
+ ok(request instanceof Ci.nsIChannel);
+ is(request.URI.spec, iframeLocation);
+ is(flags, 0);
+ }
+
+ const onSecondLocationChanged = waitForNextLocationChange(webProgress);
+ const onSecondPageDocumentStart = waitForNextDocumentStart(webProgress);
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ const secondLocation = "http://example.com/document-builder.sjs?html=com";
+ loaded = BrowserTestUtils.browserLoaded(browser);
+ BrowserTestUtils.loadURIString(browser, secondLocation);
+ await loaded;
+
+ const secondPageBrowsingContext = browser.browsingContext;
+ if (isBfcacheInParentEnabled) {
+ isnot(
+ firstPageBrowsingContext,
+ secondPageBrowsingContext,
+ "With bfcache in parent, navigations spawn a new BrowsingContext"
+ );
+ } else {
+ is(
+ firstPageBrowsingContext,
+ secondPageBrowsingContext,
+ "Without bfcache in parent, navigations reuse the same BrowsingContext"
+ );
+ }
+ {
+ const { browsingContext, location, request, flags } =
+ await onSecondLocationChanged;
+ is(
+ browsingContext,
+ secondPageBrowsingContext,
+ "Second location change fires on the new BrowsingContext"
+ );
+ ok(location instanceof Ci.nsIURI);
+ is(location.spec, secondLocation);
+ ok(request instanceof Ci.nsIChannel);
+ is(request.URI.spec, secondLocation);
+ is(flags, 0);
+ }
+ {
+ const { browsingContext, request } = await onSecondPageDocumentStart;
+ is(
+ browsingContext,
+ firstPageBrowsingContext,
+ "STATE_START, when navigating to another process, fires on the BrowsingContext we navigate *from*"
+ );
+ ok(request instanceof Ci.nsIChannel);
+ is(request.URI.spec, secondLocation);
+ }
+
+ const onBackLocationChanged = waitForNextLocationChange(webProgress, true);
+ const onBackDocumentStart = waitForNextDocumentStart(webProgress);
+ browser.goBack();
+
+ {
+ const { browsingContext, location, request, flags } =
+ await onBackLocationChanged;
+ is(
+ browsingContext,
+ firstPageBrowsingContext,
+ "location change, when navigating back, fires on the BrowsingContext we navigate *to*"
+ );
+ ok(location instanceof Ci.nsIURI);
+ is(location.spec, newLocation);
+ ok(request instanceof Ci.nsIChannel);
+ is(request.URI.spec, newLocation);
+ is(flags, 0);
+ }
+ {
+ const { browsingContext, request } = await onBackDocumentStart;
+ is(
+ browsingContext,
+ secondPageBrowsingContext,
+ "STATE_START, when navigating back, fires on the BrowsingContext we navigate *from*"
+ );
+ ok(request instanceof Ci.nsIChannel);
+ is(request.URI.spec, newLocation);
+ }
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+function waitForNextLocationChange(webProgress, onlyTopLevel = false) {
+ return new Promise(resolve => {
+ const wpl = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+ onLocationChange(progress, request, location, flags) {
+ if (onlyTopLevel && progress.browsingContext.parent) {
+ // Ignore non-toplevel.
+ return;
+ }
+ webProgress.removeProgressListener(wpl, Ci.nsIWebProgress.NOTIFY_ALL);
+ resolve({
+ browsingContext: progress.browsingContext,
+ location,
+ request,
+ flags,
+ });
+ },
+ };
+ // Add a strong reference to the progress listener.
+ resolve.wpl = wpl;
+ webProgress.addProgressListener(wpl, Ci.nsIWebProgress.NOTIFY_ALL);
+ });
+}
+
+function waitForNextDocumentStart(webProgress) {
+ return new Promise(resolve => {
+ const wpl = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+ onStateChange(progress, request, flags, status) {
+ if (
+ flags & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT &&
+ flags & Ci.nsIWebProgressListener.STATE_START
+ ) {
+ webProgress.removeProgressListener(wpl, Ci.nsIWebProgress.NOTIFY_ALL);
+ resolve({ browsingContext: progress.browsingContext, request });
+ }
+ },
+ };
+ // Add a strong reference to the progress listener.
+ resolve.wpl = wpl;
+ webProgress.addProgressListener(wpl, Ci.nsIWebProgress.NOTIFY_ALL);
+ });
+}
diff --git a/docshell/test/browser/browser_browsing_context_attached.js b/docshell/test/browser/browser_browsing_context_attached.js
new file mode 100644
index 0000000000..60ef5e4aa6
--- /dev/null
+++ b/docshell/test/browser/browser_browsing_context_attached.js
@@ -0,0 +1,179 @@
+"use strict";
+
+const TEST_PATH =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com"
+ ) + "dummy_page.html";
+
+const TOPIC = "browsing-context-attached";
+
+async function observeAttached(callback) {
+ let attached = new Map();
+
+ function observer(subject, topic, data) {
+ is(topic, TOPIC, "observing correct topic");
+ ok(BrowsingContext.isInstance(subject), "subject to be a BrowsingContext");
+ is(typeof data, "string", "data to be a String");
+ info(`*** bc id=${subject.id}, why=${data}`);
+ attached.set(subject.id, { browsingContext: subject, why: data });
+ }
+
+ Services.obs.addObserver(observer, TOPIC);
+ try {
+ await callback();
+ return attached;
+ } finally {
+ Services.obs.removeObserver(observer, TOPIC);
+ }
+}
+
+add_task(async function toplevelForNewWindow() {
+ let win;
+
+ let attached = await observeAttached(async () => {
+ win = await BrowserTestUtils.openNewBrowserWindow();
+ });
+
+ ok(
+ attached.has(win.browsingContext.id),
+ "got notification for window's chrome browsing context"
+ );
+ is(
+ attached.get(win.browsingContext.id).why,
+ "attach",
+ "expected reason for chrome browsing context"
+ );
+
+ ok(
+ attached.has(win.gBrowser.selectedBrowser.browsingContext.id),
+ "got notification for toplevel browsing context"
+ );
+ is(
+ attached.get(win.gBrowser.selectedBrowser.browsingContext.id).why,
+ "attach",
+ "expected reason for toplevel browsing context"
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function toplevelForNewTab() {
+ let tab;
+
+ let attached = await observeAttached(async () => {
+ tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ });
+
+ ok(
+ !attached.has(window.browsingContext.id),
+ "no notification for the current window's chrome browsing context"
+ );
+ ok(
+ attached.has(tab.linkedBrowser.browsingContext.id),
+ "got notification for toplevel browsing context"
+ );
+ is(
+ attached.get(tab.linkedBrowser.browsingContext.id).why,
+ "attach",
+ "expected reason for toplevel browsing context"
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function subframe() {
+ let browsingContext;
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ let attached = await observeAttached(async () => {
+ browsingContext = await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ let iframe = content.document.createElement("iframe");
+ content.document.body.appendChild(iframe);
+ iframe.contentWindow.location = "https://example.com/";
+ return iframe.browsingContext;
+ });
+ });
+
+ ok(
+ !attached.has(window.browsingContext.id),
+ "no notification for the current window's chrome browsing context"
+ );
+ ok(
+ !attached.has(tab.linkedBrowser.browsingContext.id),
+ "no notification for toplevel browsing context"
+ );
+ ok(
+ attached.has(browsingContext.id),
+ "got notification for frame's browsing context"
+ );
+ is(
+ attached.get(browsingContext.id).why,
+ "attach",
+ "expected reason for frame's browsing context"
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function toplevelReplacedBy() {
+ let tab;
+
+ let attached = await observeAttached(async () => {
+ tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:robots");
+ });
+
+ const firstContext = tab.linkedBrowser.browsingContext;
+ ok(
+ attached.has(firstContext.id),
+ "got notification for initial toplevel browsing context"
+ );
+ is(
+ attached.get(firstContext.id).why,
+ "attach",
+ "expected reason for initial toplevel browsing context"
+ );
+
+ attached = await observeAttached(async () => {
+ await loadURI(TEST_PATH);
+ });
+ const secondContext = tab.linkedBrowser.browsingContext;
+ ok(
+ attached.has(secondContext.id),
+ "got notification for replaced toplevel browsing context"
+ );
+ isnot(secondContext, firstContext, "browsing context to be replaced");
+ is(
+ attached.get(secondContext.id).why,
+ "replace",
+ "expected reason for replaced toplevel browsing context"
+ );
+ is(
+ secondContext.browserId,
+ firstContext.browserId,
+ "browserId has been kept"
+ );
+
+ attached = await observeAttached(async () => {
+ await loadURI("about:robots");
+ });
+ const thirdContext = tab.linkedBrowser.browsingContext;
+ ok(
+ attached.has(thirdContext.id),
+ "got notification for replaced toplevel browsing context"
+ );
+ isnot(thirdContext, secondContext, "browsing context to be replaced");
+ is(
+ attached.get(thirdContext.id).why,
+ "replace",
+ "expected reason for replaced toplevel browsing context"
+ );
+ is(
+ thirdContext.browserId,
+ secondContext.browserId,
+ "browserId has been kept"
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/docshell/test/browser/browser_browsing_context_discarded.js b/docshell/test/browser/browser_browsing_context_discarded.js
new file mode 100644
index 0000000000..a300737d4f
--- /dev/null
+++ b/docshell/test/browser/browser_browsing_context_discarded.js
@@ -0,0 +1,65 @@
+"use strict";
+
+const TOPIC = "browsing-context-discarded";
+
+async function observeDiscarded(browsingContexts, callback) {
+ let discarded = [];
+
+ let promise = BrowserUtils.promiseObserved(TOPIC, subject => {
+ ok(BrowsingContext.isInstance(subject), "subject to be a BrowsingContext");
+ discarded.push(subject);
+
+ return browsingContexts.every(item => discarded.includes(item));
+ });
+ await callback();
+ await promise;
+
+ return discarded;
+}
+
+add_task(async function toplevelForNewWindow() {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ let browsingContext = win.gBrowser.selectedBrowser.browsingContext;
+
+ await observeDiscarded([win.browsingContext, browsingContext], async () => {
+ await BrowserTestUtils.closeWindow(win);
+ });
+});
+
+add_task(async function toplevelForNewTab() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ let browsingContext = tab.linkedBrowser.browsingContext;
+
+ let discarded = await observeDiscarded([browsingContext], () => {
+ BrowserTestUtils.removeTab(tab);
+ });
+
+ ok(
+ !discarded.includes(window.browsingContext),
+ "no notification for the current window's chrome browsing context"
+ );
+});
+
+add_task(async function subframe() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ let browsingContext = await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ let iframe = content.document.createElement("iframe");
+ content.document.body.appendChild(iframe);
+ iframe.contentWindow.location = "https://example.com/";
+ return iframe.browsingContext;
+ });
+
+ let discarded = await observeDiscarded([browsingContext], async () => {
+ await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ let iframe = content.document.querySelector("iframe");
+ iframe.remove();
+ });
+ });
+
+ ok(
+ !discarded.includes(tab.browsingContext),
+ "no notification for toplevel browsing context"
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/docshell/test/browser/browser_bug1206879.js b/docshell/test/browser/browser_bug1206879.js
new file mode 100644
index 0000000000..38e17633b8
--- /dev/null
+++ b/docshell/test/browser/browser_bug1206879.js
@@ -0,0 +1,50 @@
+add_task(async function () {
+ let url =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/"
+ ) + "file_bug1206879.html";
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url, true);
+
+ let numLocationChanges = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ async function () {
+ let webprogress = content.docShell.QueryInterface(Ci.nsIWebProgress);
+ let locationChangeCount = 0;
+ let listener = {
+ onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+ info("onLocationChange: " + aLocation.spec);
+ locationChangeCount++;
+ this.resolve();
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+ };
+ let locationPromise = new Promise((resolve, reject) => {
+ listener.resolve = resolve;
+ });
+ webprogress.addProgressListener(
+ listener,
+ Ci.nsIWebProgress.NOTIFY_LOCATION
+ );
+
+ content.frames[0].history.pushState(null, null, "foo");
+
+ await locationPromise;
+ webprogress.removeProgressListener(listener);
+
+ return locationChangeCount;
+ }
+ );
+
+ gBrowser.removeTab(tab);
+ is(
+ numLocationChanges,
+ 1,
+ "pushState with a different URI should cause a LocationChange event."
+ );
+});
diff --git a/docshell/test/browser/browser_bug1309900_crossProcessHistoryNavigation.js b/docshell/test/browser/browser_bug1309900_crossProcessHistoryNavigation.js
new file mode 100644
index 0000000000..9b99698367
--- /dev/null
+++ b/docshell/test/browser/browser_bug1309900_crossProcessHistoryNavigation.js
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+add_task(async function runTests() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.navigation.requireUserInteraction", false]],
+ });
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:about"
+ );
+
+ registerCleanupFunction(function () {
+ gBrowser.removeTab(tab);
+ });
+
+ let browser = tab.linkedBrowser;
+
+ let loaded = BrowserTestUtils.browserLoaded(browser);
+ BrowserTestUtils.loadURIString(browser, "about:config");
+ let href = await loaded;
+ is(href, "about:config", "Check about:config loaded");
+
+ // Using a dummy onunload listener to disable the bfcache as that can prevent
+ // the test browser load detection mechanism from working.
+ loaded = BrowserTestUtils.browserLoaded(browser);
+ BrowserTestUtils.loadURIString(
+ browser,
+ "data:text/html,<body%20onunload=''><iframe></iframe></body>"
+ );
+ href = await loaded;
+ is(
+ href,
+ "data:text/html,<body%20onunload=''><iframe></iframe></body>",
+ "Check data URL loaded"
+ );
+
+ loaded = BrowserTestUtils.browserLoaded(browser);
+ browser.goBack();
+ href = await loaded;
+ is(href, "about:config", "Check we've gone back to about:config");
+
+ loaded = BrowserTestUtils.browserLoaded(browser);
+ browser.goForward();
+ href = await loaded;
+ is(
+ href,
+ "data:text/html,<body%20onunload=''><iframe></iframe></body>",
+ "Check we've gone forward to data URL"
+ );
+});
diff --git a/docshell/test/browser/browser_bug1328501.js b/docshell/test/browser/browser_bug1328501.js
new file mode 100644
index 0000000000..2be74b04d0
--- /dev/null
+++ b/docshell/test/browser/browser_bug1328501.js
@@ -0,0 +1,69 @@
+const HTML_URL =
+ "http://mochi.test:8888/browser/docshell/test/browser/file_bug1328501.html";
+const FRAME_URL =
+ "http://mochi.test:8888/browser/docshell/test/browser/file_bug1328501_frame.html";
+const FRAME_SCRIPT_URL =
+ "chrome://mochitests/content/browser/docshell/test/browser/file_bug1328501_framescript.js";
+add_task(async function testMultiFrameRestore() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.navigation.requireUserInteraction", false],
+ // Disable bfcache so that dummy_page.html doesn't enter there.
+ // The actual test page does already prevent bfcache and the test
+ // is just for http-on-opening-request handling in the child process.
+ ["browser.sessionhistory.max_total_viewers", 0],
+ ],
+ });
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: HTML_URL },
+ async function (browser) {
+ // Navigate 2 subframes and load about:blank.
+ let browserLoaded = BrowserTestUtils.browserLoaded(browser);
+ await SpecialPowers.spawn(
+ browser,
+ [FRAME_URL],
+ async function (FRAME_URL) {
+ function frameLoaded(frame) {
+ frame.contentWindow.location = FRAME_URL;
+ return new Promise(r => (frame.onload = r));
+ }
+ let frame1 = content.document.querySelector("#testFrame1");
+ let frame2 = content.document.querySelector("#testFrame2");
+ ok(frame1, "check found testFrame1");
+ ok(frame2, "check found testFrame2");
+ await frameLoaded(frame1);
+ await frameLoaded(frame2);
+ content.location = "dummy_page.html";
+ }
+ );
+ await browserLoaded;
+
+ // Load a frame script to query nsIDOMWindow on "http-on-opening-request",
+ // which will force about:blank content viewers being created.
+ browser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false);
+
+ // The frame script also forwards frames-loaded.
+ let framesLoaded = BrowserTestUtils.waitForMessage(
+ browser.messageManager,
+ "test:frames-loaded"
+ );
+
+ browser.goBack();
+ await framesLoaded;
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(r => setTimeout(r, 1000));
+ await SpecialPowers.spawn(browser, [FRAME_URL], FRAME_URL => {
+ is(
+ content.document.querySelector("#testFrame1").contentWindow.location
+ .href,
+ FRAME_URL
+ );
+ is(
+ content.document.querySelector("#testFrame2").contentWindow.location
+ .href,
+ FRAME_URL
+ );
+ });
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_bug1347823.js b/docshell/test/browser/browser_bug1347823.js
new file mode 100644
index 0000000000..53a968dc12
--- /dev/null
+++ b/docshell/test/browser/browser_bug1347823.js
@@ -0,0 +1,91 @@
+/**
+ * Test that session history's expiration tracker would remove bfcache on
+ * expiration.
+ */
+
+// With bfcache not expired.
+add_task(async function testValidCache() {
+ // Make an unrealistic large timeout.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.sessionhistory.contentViewerTimeout", 86400],
+ // If Fission is disabled, the pref is no-op.
+ ["fission.bfcacheInParent", true],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "data:text/html;charset=utf-8,pageA1" },
+ async function (browser) {
+ // Make a simple modification for bfcache testing.
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.body.textContent = "modified";
+ });
+
+ // Load a random page.
+ BrowserTestUtils.loadURIString(
+ browser,
+ "data:text/html;charset=utf-8,pageA2"
+ );
+ await BrowserTestUtils.browserLoaded(browser);
+
+ // Go back and verify text content.
+ let awaitPageShow = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "pageshow"
+ );
+ browser.goBack();
+ await awaitPageShow;
+ await SpecialPowers.spawn(browser, [], () => {
+ is(content.document.body.textContent, "modified");
+ });
+ }
+ );
+});
+
+// With bfcache expired.
+add_task(async function testExpiredCache() {
+ // Make bfcache timeout in 1 sec.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.sessionhistory.contentViewerTimeout", 1],
+ // If Fission is disabled, the pref is no-op.
+ ["fission.bfcacheInParent", true],
+ ],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "data:text/html;charset=utf-8,pageB1" },
+ async function (browser) {
+ // Make a simple modification for bfcache testing.
+ await SpecialPowers.spawn(browser, [], () => {
+ content.document.body.textContent = "modified";
+ });
+
+ // Load a random page.
+ BrowserTestUtils.loadURIString(
+ browser,
+ "data:text/html;charset=utf-8,pageB2"
+ );
+ await BrowserTestUtils.browserLoaded(browser);
+
+ // Wait for 3 times of expiration timeout, hopefully it's evicted...
+ await SpecialPowers.spawn(browser, [], () => {
+ return new Promise(resolve => {
+ content.setTimeout(resolve, 5000);
+ });
+ });
+
+ // Go back and verify text content.
+ let awaitPageShow = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "pageshow"
+ );
+ browser.goBack();
+ await awaitPageShow;
+ await SpecialPowers.spawn(browser, [], () => {
+ is(content.document.body.textContent, "pageB1");
+ });
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_bug134911.js b/docshell/test/browser/browser_bug134911.js
new file mode 100644
index 0000000000..9b52eeab47
--- /dev/null
+++ b/docshell/test/browser/browser_bug134911.js
@@ -0,0 +1,57 @@
+const TEXT = {
+ /* The test text decoded correctly as Shift_JIS */
+ rightText:
+ "\u30E6\u30CB\u30B3\u30FC\u30C9\u306F\u3001\u3059\u3079\u3066\u306E\u6587\u5B57\u306B\u56FA\u6709\u306E\u756A\u53F7\u3092\u4ED8\u4E0E\u3057\u307E\u3059",
+
+ enteredText1: "The quick brown fox jumps over the lazy dog",
+ enteredText2:
+ "\u03BE\u03B5\u03C3\u03BA\u03B5\u03C0\u03AC\u03B6\u03C9\u0020\u03C4\u1F74\u03BD\u0020\u03C8\u03C5\u03C7\u03BF\u03C6\u03B8\u03CC\u03C1\u03B1\u0020\u03B2\u03B4\u03B5\u03BB\u03C5\u03B3\u03BC\u03AF\u03B1",
+};
+
+function test() {
+ waitForExplicitFinish();
+
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ rootDir + "test-form_sjis.html"
+ );
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(afterOpen);
+}
+
+function afterOpen() {
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(
+ afterChangeCharset
+ );
+
+ SpecialPowers.spawn(gBrowser.selectedBrowser, [TEXT], function (TEXT) {
+ content.document.getElementById("testtextarea").value = TEXT.enteredText1;
+ content.document.getElementById("testinput").value = TEXT.enteredText2;
+ }).then(() => {
+ /* Force the page encoding to Shift_JIS */
+ BrowserForceEncodingDetection();
+ });
+}
+
+function afterChangeCharset() {
+ SpecialPowers.spawn(gBrowser.selectedBrowser, [TEXT], function (TEXT) {
+ is(
+ content.document.getElementById("testpar").innerHTML,
+ TEXT.rightText,
+ "encoding successfully changed"
+ );
+ is(
+ content.document.getElementById("testtextarea").value,
+ TEXT.enteredText1,
+ "text preserved in <textarea>"
+ );
+ is(
+ content.document.getElementById("testinput").value,
+ TEXT.enteredText2,
+ "text preserved in <input>"
+ );
+ }).then(() => {
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+}
diff --git a/docshell/test/browser/browser_bug1415918_beforeunload_options.js b/docshell/test/browser/browser_bug1415918_beforeunload_options.js
new file mode 100644
index 0000000000..4aec24cda4
--- /dev/null
+++ b/docshell/test/browser/browser_bug1415918_beforeunload_options.js
@@ -0,0 +1,162 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com"
+);
+
+const { PromptTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromptTestUtils.sys.mjs"
+);
+
+SimpleTest.requestFlakyTimeout("Needs to test a timeout");
+
+function delay(msec) {
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ return new Promise(resolve => setTimeout(resolve, msec));
+}
+
+function allowNextNavigation(browser) {
+ return PromptTestUtils.handleNextPrompt(
+ browser,
+ { modalType: Services.prompt.MODAL_TYPE_CONTENT, promptType: "confirmEx" },
+ { buttonNumClick: 0 }
+ );
+}
+
+function cancelNextNavigation(browser) {
+ return PromptTestUtils.handleNextPrompt(
+ browser,
+ { modalType: Services.prompt.MODAL_TYPE_CONTENT, promptType: "confirmEx" },
+ { buttonNumClick: 1 }
+ );
+}
+
+add_task(async function test() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.require_user_interaction_for_beforeunload", false]],
+ });
+
+ const permitUnloadTimeout = Services.prefs.getIntPref(
+ "dom.beforeunload_timeout_ms"
+ );
+
+ let url = TEST_PATH + "dummy_page.html";
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
+ let browser = tab.linkedBrowser;
+
+ await SpecialPowers.spawn(browser.browsingContext, [], () => {
+ content.addEventListener("beforeunload", event => {
+ event.preventDefault();
+ });
+ });
+
+ /*
+ * Check condition where beforeunload handlers request a prompt.
+ */
+
+ // Prompt is shown, user clicks OK.
+
+ let promptShownPromise = allowNextNavigation(browser);
+ ok(browser.permitUnload().permitUnload, "permit unload should be true");
+ await promptShownPromise;
+
+ // Prompt is shown, user clicks CANCEL.
+ promptShownPromise = cancelNextNavigation(browser);
+ ok(!browser.permitUnload().permitUnload, "permit unload should be false");
+ await promptShownPromise;
+
+ // Prompt is not shown, don't permit unload.
+ let promptShown = false;
+ let shownCallback = () => {
+ promptShown = true;
+ };
+
+ browser.addEventListener("DOMWillOpenModalDialog", shownCallback);
+ ok(
+ !browser.permitUnload("dontUnload").permitUnload,
+ "permit unload should be false"
+ );
+ ok(!promptShown, "prompt should not have been displayed");
+
+ // Prompt is not shown, permit unload.
+ promptShown = false;
+ ok(
+ browser.permitUnload("unload").permitUnload,
+ "permit unload should be true"
+ );
+ ok(!promptShown, "prompt should not have been displayed");
+ browser.removeEventListener("DOMWillOpenModalDialog", shownCallback);
+
+ promptShownPromise = PromptTestUtils.waitForPrompt(browser, {
+ modalType: Services.prompt.MODAL_TYPE_CONTENT,
+ promptType: "confirmEx",
+ });
+
+ let promptDismissed = false;
+ let closedCallback = () => {
+ promptDismissed = true;
+ };
+
+ browser.addEventListener("DOMModalDialogClosed", closedCallback);
+
+ let promise = browser.asyncPermitUnload();
+
+ let promiseResolved = false;
+ promise.then(() => {
+ promiseResolved = true;
+ });
+
+ let dialog = await promptShownPromise;
+ ok(!promiseResolved, "Should not have resolved promise yet");
+
+ await delay(permitUnloadTimeout * 1.5);
+
+ ok(!promptDismissed, "Should not have dismissed prompt yet");
+ ok(!promiseResolved, "Should not have resolved promise yet");
+
+ await PromptTestUtils.handlePrompt(dialog, { buttonNumClick: 1 });
+
+ let { permitUnload } = await promise;
+ ok(promptDismissed, "Should have dismissed prompt");
+ ok(!permitUnload, "Should not have permitted unload");
+
+ browser.removeEventListener("DOMModalDialogClosed", closedCallback);
+
+ promptShownPromise = allowNextNavigation(browser);
+
+ /*
+ * Check condition where no one requests a prompt. In all cases,
+ * permitUnload should be true, and all handlers fired.
+ */
+ url += "?1";
+ BrowserTestUtils.loadURIString(browser, url);
+ await BrowserTestUtils.browserLoaded(browser, false, url);
+ await promptShownPromise;
+
+ promptShown = false;
+ browser.addEventListener("DOMWillOpenModalDialog", shownCallback);
+
+ ok(browser.permitUnload().permitUnload, "permit unload should be true");
+ ok(!promptShown, "prompt should not have been displayed");
+
+ promptShown = false;
+ ok(
+ browser.permitUnload("dontUnload").permitUnload,
+ "permit unload should be true"
+ );
+ ok(!promptShown, "prompt should not have been displayed");
+
+ promptShown = false;
+ ok(
+ browser.permitUnload("unload").permitUnload,
+ "permit unload should be true"
+ );
+ ok(!promptShown, "prompt should not have been displayed");
+
+ browser.removeEventListener("DOMWillOpenModalDialog", shownCallback);
+
+ await BrowserTestUtils.removeTab(tab, { skipPermitUnload: true });
+});
diff --git a/docshell/test/browser/browser_bug1543077-3.js b/docshell/test/browser/browser_bug1543077-3.js
new file mode 100644
index 0000000000..7cef4aef10
--- /dev/null
+++ b/docshell/test/browser/browser_bug1543077-3.js
@@ -0,0 +1,46 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug1543077-3.html",
+ afterOpen,
+ afterChangeCharset
+ );
+}
+
+function afterOpen() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u3042"),
+ 136,
+ "Parent doc should be ISO-2022-JP initially"
+ );
+
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u3042"),
+ 92,
+ "Child doc should be ISO-2022-JP initially"
+ );
+}
+
+function afterChangeCharset() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u3042"),
+ 136,
+ "Parent doc should decode as ISO-2022-JP subsequently"
+ );
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u3042"),
+ 92,
+ "Child doc should decode as ISO-2022-JP subsequently"
+ );
+
+ is(
+ content.document.characterSet,
+ "ISO-2022-JP",
+ "Parent doc should report ISO-2022-JP subsequently"
+ );
+ is(
+ content.frames[0].document.characterSet,
+ "ISO-2022-JP",
+ "Child doc should report ISO-2022-JP subsequently"
+ );
+}
diff --git a/docshell/test/browser/browser_bug1594938.js b/docshell/test/browser/browser_bug1594938.js
new file mode 100644
index 0000000000..569afe6901
--- /dev/null
+++ b/docshell/test/browser/browser_bug1594938.js
@@ -0,0 +1,100 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test for Bug 1594938
+ *
+ * If a session history listener blocks reloads we shouldn't crash.
+ */
+
+add_task(async function test() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "https://example.com/" },
+ async function (browser) {
+ if (!SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ await SpecialPowers.spawn(browser, [], async function () {
+ let history = this.content.docShell.QueryInterface(
+ Ci.nsIWebNavigation
+ ).sessionHistory;
+
+ let testDone = {};
+ testDone.promise = new Promise(resolve => {
+ testDone.resolve = resolve;
+ });
+
+ let listenerCalled = false;
+ let listener = {
+ OnHistoryNewEntry: aNewURI => {},
+ OnHistoryReload: () => {
+ listenerCalled = true;
+ this.content.setTimeout(() => {
+ testDone.resolve();
+ });
+ return false;
+ },
+ OnHistoryGotoIndex: () => {},
+ OnHistoryPurge: () => {},
+ OnHistoryReplaceEntry: () => {},
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsISHistoryListener",
+ "nsISupportsWeakReference",
+ ]),
+ };
+
+ history.legacySHistory.addSHistoryListener(listener);
+
+ history.reload(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
+ await testDone.promise;
+
+ Assert.ok(listenerCalled, "reloads were blocked");
+
+ history.legacySHistory.removeSHistoryListener(listener);
+ });
+
+ return;
+ }
+
+ let history = browser.browsingContext.sessionHistory;
+
+ let testDone = {};
+ testDone.promise = new Promise(resolve => {
+ testDone.resolve = resolve;
+ });
+
+ let listenerCalled = false;
+ let listener = {
+ OnHistoryNewEntry: aNewURI => {},
+ OnHistoryReload: () => {
+ listenerCalled = true;
+ setTimeout(() => {
+ testDone.resolve();
+ });
+ return false;
+ },
+ OnHistoryGotoIndex: () => {},
+ OnHistoryPurge: () => {},
+ OnHistoryReplaceEntry: () => {},
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsISHistoryListener",
+ "nsISupportsWeakReference",
+ ]),
+ };
+
+ history.addSHistoryListener(listener);
+
+ await SpecialPowers.spawn(browser, [], () => {
+ let history = this.content.docShell.QueryInterface(
+ Ci.nsIWebNavigation
+ ).sessionHistory;
+ history.reload(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
+ });
+ await testDone.promise;
+
+ Assert.ok(listenerCalled, "reloads were blocked");
+
+ history.removeSHistoryListener(listener);
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_bug1622420.js b/docshell/test/browser/browser_bug1622420.js
new file mode 100644
index 0000000000..81026611e4
--- /dev/null
+++ b/docshell/test/browser/browser_bug1622420.js
@@ -0,0 +1,31 @@
+const ACTOR = "Bug1622420";
+
+add_task(async function test() {
+ let base = getRootDirectory(gTestPath).slice(0, -1);
+ ChromeUtils.registerWindowActor(ACTOR, {
+ allFrames: true,
+ child: {
+ esModuleURI: `${base}/Bug1622420Child.sys.mjs`,
+ },
+ });
+
+ registerCleanupFunction(async () => {
+ gBrowser.removeTab(tab);
+
+ ChromeUtils.unregisterWindowActor(ACTOR);
+ });
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.org/browser/docshell/test/browser/file_bug1622420.html"
+ );
+ let childBC = tab.linkedBrowser.browsingContext.children[0];
+ let success = await childBC.currentWindowGlobal
+ .getActor(ACTOR)
+ .sendQuery("hasWindowContextForTopBC");
+ ok(
+ success,
+ "Should have a WindowContext for the top BrowsingContext in the process of a child BrowsingContext"
+ );
+});
diff --git a/docshell/test/browser/browser_bug1648464-1.js b/docshell/test/browser/browser_bug1648464-1.js
new file mode 100644
index 0000000000..c2a8093a3d
--- /dev/null
+++ b/docshell/test/browser/browser_bug1648464-1.js
@@ -0,0 +1,46 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug1648464-1.html",
+ afterOpen,
+ afterChangeCharset
+ );
+}
+
+function afterOpen() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u00A4"),
+ 146,
+ "Parent doc should be windows-1252 initially"
+ );
+
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u00A4"),
+ 95,
+ "Child doc should be windows-1252 initially"
+ );
+}
+
+function afterChangeCharset() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u3042"),
+ 146,
+ "Parent doc should decode as EUC-JP subsequently"
+ );
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u3042"),
+ 95,
+ "Child doc should decode as EUC-JP subsequently"
+ );
+
+ is(
+ content.document.characterSet,
+ "EUC-JP",
+ "Parent doc should report EUC-JP subsequently"
+ );
+ is(
+ content.frames[0].document.characterSet,
+ "EUC-JP",
+ "Child doc should report EUC-JP subsequently"
+ );
+}
diff --git a/docshell/test/browser/browser_bug1673702.js b/docshell/test/browser/browser_bug1673702.js
new file mode 100644
index 0000000000..02c48191b8
--- /dev/null
+++ b/docshell/test/browser/browser_bug1673702.js
@@ -0,0 +1,27 @@
+const DUMMY =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.org/browser/docshell/test/browser/dummy_page.html";
+const JSON =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/browser/docshell/test/browser/file_bug1673702.json";
+
+add_task(async function test_backAndReload() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: DUMMY },
+ async function (browser) {
+ info("Start JSON load.");
+ BrowserTestUtils.loadURIString(browser, JSON);
+ await BrowserTestUtils.waitForLocationChange(gBrowser, JSON);
+
+ info("JSON load has started, go back.");
+ browser.goBack();
+ await BrowserTestUtils.browserStopped(browser);
+
+ info("Reload.");
+ BrowserReload();
+ await BrowserTestUtils.waitForLocationChange(gBrowser);
+
+ is(browser.documentURI.spec, DUMMY);
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_bug1674464.js b/docshell/test/browser/browser_bug1674464.js
new file mode 100644
index 0000000000..4078cfe6c7
--- /dev/null
+++ b/docshell/test/browser/browser_bug1674464.js
@@ -0,0 +1,38 @@
+const DUMMY_1 =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.org/browser/docshell/test/browser/dummy_page.html";
+const DUMMY_2 =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/browser/docshell/test/browser/dummy_page.html";
+
+add_task(async function test_backAndReload() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: DUMMY_1 },
+ async function (browser) {
+ await BrowserTestUtils.crashFrame(browser);
+
+ info("Start second load.");
+ BrowserTestUtils.loadURIString(browser, DUMMY_2);
+ await BrowserTestUtils.waitForLocationChange(gBrowser, DUMMY_2);
+
+ browser.goBack();
+ await BrowserTestUtils.waitForLocationChange(gBrowser);
+
+ is(
+ browser.browsingContext.childSessionHistory.index,
+ 0,
+ "We should have gone back to the first page"
+ );
+ is(
+ browser.browsingContext.childSessionHistory.count,
+ 2,
+ "If a tab crashes after a load has finished we shouldn't have an entry for about:tabcrashed"
+ );
+ is(
+ browser.documentURI.spec,
+ DUMMY_1,
+ "If a tab crashes after a load has finished we shouldn't have an entry for about:tabcrashed"
+ );
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_bug1688368-1.js b/docshell/test/browser/browser_bug1688368-1.js
new file mode 100644
index 0000000000..04fc3dd9a8
--- /dev/null
+++ b/docshell/test/browser/browser_bug1688368-1.js
@@ -0,0 +1,24 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug1688368-1.sjs",
+ afterOpen,
+ afterChangeCharset
+ );
+}
+
+function afterOpen() {
+ is(
+ content.document.body.textContent.indexOf("â"),
+ 0,
+ "Doc should be windows-1252 initially"
+ );
+}
+
+function afterChangeCharset() {
+ is(
+ content.document.body.textContent.indexOf("☃"),
+ 0,
+ "Doc should be UTF-8 subsequently"
+ );
+}
diff --git a/docshell/test/browser/browser_bug1691153.js b/docshell/test/browser/browser_bug1691153.js
new file mode 100644
index 0000000000..70d7f91856
--- /dev/null
+++ b/docshell/test/browser/browser_bug1691153.js
@@ -0,0 +1,73 @@
+"use strict";
+
+add_task(async () => {
+ const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com"
+ );
+
+ const HTML_URI = TEST_PATH + "file_bug1691153.html";
+
+ // Opening the page that contains the iframe
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ let browser = tab.linkedBrowser;
+ let browserLoaded = BrowserTestUtils.browserLoaded(
+ browser,
+ true,
+ HTML_URI,
+ true
+ );
+ info("new tab loaded");
+
+ BrowserTestUtils.loadURIString(browser, HTML_URI);
+ await browserLoaded;
+ info("The test page has loaded!");
+
+ let first_message_promise = SpecialPowers.spawn(
+ browser,
+ [],
+ async function () {
+ let blobPromise = new Promise((resolve, reject) => {
+ content.addEventListener("message", event => {
+ if (event.data.bloburl) {
+ info("Sanity check: recvd blob URL as " + event.data.bloburl);
+ resolve(event.data.bloburl);
+ }
+ });
+ });
+ content.postMessage("getblob", "*");
+ return blobPromise;
+ }
+ );
+ info("The test page has loaded!");
+ let blob_url = await first_message_promise;
+
+ Assert.ok(blob_url.startsWith("blob:"), "Sanity check: recvd blob");
+ info(`Received blob URL message from content: ${blob_url}`);
+ // try to open the blob in a new tab, manually created by the user
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ blob_url,
+ true,
+ false,
+ true
+ );
+
+ let principal = gBrowser.selectedTab.linkedBrowser._contentPrincipal;
+ Assert.ok(
+ !principal.isSystemPrincipal,
+ "Newly opened blob shouldn't be Systemprincipal"
+ );
+ Assert.ok(
+ !principal.isExpandedPrincipal,
+ "Newly opened blob shouldn't be ExpandedPrincipal"
+ );
+ Assert.ok(
+ principal.isContentPrincipal,
+ "Newly opened blob tab should be ContentPrincipal"
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ BrowserTestUtils.removeTab(tab2);
+});
diff --git a/docshell/test/browser/browser_bug1705872.js b/docshell/test/browser/browser_bug1705872.js
new file mode 100644
index 0000000000..5c92228694
--- /dev/null
+++ b/docshell/test/browser/browser_bug1705872.js
@@ -0,0 +1,74 @@
+"use strict";
+
+async function doLoadAndGoBack(browser, ext) {
+ let loaded = BrowserTestUtils.browserLoaded(browser);
+ BrowserTestUtils.loadURIString(browser, "https://example.com/");
+ await ext.awaitMessage("redir-handled");
+ await loaded;
+
+ let pageShownPromise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "pageshow",
+ true
+ );
+ await SpecialPowers.spawn(browser, [], () => {
+ content.history.back();
+ });
+ return pageShownPromise;
+}
+
+add_task(async function test_back() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["webRequest", "webRequestBlocking", "https://example.com/"],
+ web_accessible_resources: ["test.html"],
+ },
+ files: {
+ "test.html":
+ "<!DOCTYPE html><html><head><title>Test add-on</title></head><body></body></html>",
+ },
+ background: () => {
+ let { browser } = this;
+ browser.webRequest.onHeadersReceived.addListener(
+ details => {
+ if (details.statusCode != 200) {
+ return undefined;
+ }
+ browser.test.sendMessage("redir-handled");
+ return { redirectUrl: browser.runtime.getURL("test.html") };
+ },
+ {
+ urls: ["https://example.com/"],
+ types: ["main_frame"],
+ },
+ ["blocking"]
+ );
+ },
+ });
+
+ await extension.startup();
+
+ await BrowserTestUtils.withNewTab("about:home", async function (browser) {
+ await doLoadAndGoBack(browser, extension);
+
+ await SpecialPowers.spawn(browser, [], () => {
+ is(
+ content.document.documentURI,
+ "about:home",
+ "Gone back to the right page"
+ );
+ });
+
+ await doLoadAndGoBack(browser, extension);
+
+ await SpecialPowers.spawn(browser, [], () => {
+ is(
+ content.document.documentURI,
+ "about:home",
+ "Gone back to the right page"
+ );
+ });
+ });
+
+ await extension.unload();
+});
diff --git a/docshell/test/browser/browser_bug1716290-1.js b/docshell/test/browser/browser_bug1716290-1.js
new file mode 100644
index 0000000000..48add6f0eb
--- /dev/null
+++ b/docshell/test/browser/browser_bug1716290-1.js
@@ -0,0 +1,24 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug1716290-1.sjs",
+ afterOpen,
+ afterChangeCharset
+ );
+}
+
+function afterOpen() {
+ is(
+ content.document.characterSet,
+ "Shift_JIS",
+ "Doc should report Shift_JIS initially"
+ );
+}
+
+function afterChangeCharset() {
+ is(
+ content.document.characterSet,
+ "windows-1252",
+ "Doc should report windows-1252 subsequently (detector should override header)"
+ );
+}
diff --git a/docshell/test/browser/browser_bug1716290-2.js b/docshell/test/browser/browser_bug1716290-2.js
new file mode 100644
index 0000000000..a33c61f076
--- /dev/null
+++ b/docshell/test/browser/browser_bug1716290-2.js
@@ -0,0 +1,24 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug1716290-2.sjs",
+ afterOpen,
+ afterChangeCharset
+ );
+}
+
+function afterOpen() {
+ is(
+ content.document.characterSet,
+ "Shift_JIS",
+ "Doc should report Shift_JIS initially"
+ );
+}
+
+function afterChangeCharset() {
+ is(
+ content.document.characterSet,
+ "windows-1252",
+ "Doc should report windows-1252 subsequently (detector should override meta resolving to the replacement encoding)"
+ );
+}
diff --git a/docshell/test/browser/browser_bug1716290-3.js b/docshell/test/browser/browser_bug1716290-3.js
new file mode 100644
index 0000000000..f7e6ca5bbd
--- /dev/null
+++ b/docshell/test/browser/browser_bug1716290-3.js
@@ -0,0 +1,24 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug1716290-3.sjs",
+ afterOpen,
+ afterChangeCharset
+ );
+}
+
+function afterOpen() {
+ is(
+ content.document.characterSet,
+ "Shift_JIS",
+ "Doc should report Shift_JIS initially"
+ );
+}
+
+function afterChangeCharset() {
+ is(
+ content.document.characterSet,
+ "replacement",
+ "Doc should report replacement subsequently (non-ASCII-compatible HTTP header should override detector)"
+ );
+}
diff --git a/docshell/test/browser/browser_bug1716290-4.js b/docshell/test/browser/browser_bug1716290-4.js
new file mode 100644
index 0000000000..ab0d46d7ff
--- /dev/null
+++ b/docshell/test/browser/browser_bug1716290-4.js
@@ -0,0 +1,24 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug1716290-4.sjs",
+ afterOpen,
+ afterChangeCharset
+ );
+}
+
+function afterOpen() {
+ is(
+ content.document.characterSet,
+ "Shift_JIS",
+ "Doc should report Shift_JIS initially"
+ );
+}
+
+function afterChangeCharset() {
+ is(
+ content.document.characterSet,
+ "UTF-16BE",
+ "Doc should report UTF-16BE subsequently (BOM should override detector)"
+ );
+}
diff --git a/docshell/test/browser/browser_bug1719178.js b/docshell/test/browser/browser_bug1719178.js
new file mode 100644
index 0000000000..6c4fc2d15f
--- /dev/null
+++ b/docshell/test/browser/browser_bug1719178.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+SimpleTest.requestFlakyTimeout(
+ "The test needs to let objects die asynchronously."
+);
+
+add_task(async function test_accessing_shistory() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:preferences"
+ );
+ let sh = tab.linkedBrowser.browsingContext.sessionHistory;
+ ok(sh, "Should have SessionHistory object");
+ gBrowser.removeTab(tab);
+ tab = null;
+ for (let i = 0; i < 5; ++i) {
+ SpecialPowers.Services.obs.notifyObservers(
+ null,
+ "memory-pressure",
+ "heap-minimize"
+ );
+ SpecialPowers.DOMWindowUtils.garbageCollect();
+ await new Promise(function (r) {
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ setTimeout(r, 50);
+ });
+ }
+
+ try {
+ sh.reloadCurrentEntry();
+ } catch (ex) {}
+ ok(true, "This test shouldn't crash.");
+});
diff --git a/docshell/test/browser/browser_bug1736248-1.js b/docshell/test/browser/browser_bug1736248-1.js
new file mode 100644
index 0000000000..d9cbe4fd85
--- /dev/null
+++ b/docshell/test/browser/browser_bug1736248-1.js
@@ -0,0 +1,34 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug1736248-1.html",
+ afterOpen,
+ afterChangeCharset
+ );
+}
+
+function afterOpen() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u00C3"),
+ 1064,
+ "Doc should be windows-1252 initially"
+ );
+ is(
+ content.document.characterSet,
+ "windows-1252",
+ "Doc should report windows-1252 initially"
+ );
+}
+
+function afterChangeCharset() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u00E4"),
+ 1064,
+ "Doc should be UTF-8 subsequently"
+ );
+ is(
+ content.document.characterSet,
+ "UTF-8",
+ "Doc should report UTF-8 subsequently"
+ );
+}
diff --git a/docshell/test/browser/browser_bug1757005.js b/docshell/test/browser/browser_bug1757005.js
new file mode 100644
index 0000000000..90010a9b7a
--- /dev/null
+++ b/docshell/test/browser/browser_bug1757005.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function () {
+ // (1) Load one page with bfcache disabled and another one with bfcache enabled.
+ // (2) Check that BrowsingContext.getCurrentTopByBrowserId(browserId) returns
+ // the expected browsing context both in the parent process and in the child process.
+ // (3) Go back and then forward
+ // (4) Run the same checks as in step 2 again.
+
+ let url1 = "data:text/html,<body onunload='/* disable bfcache */'>";
+ let url2 = "data:text/html,page2";
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: url1,
+ },
+ async function (browser) {
+ info("Initial load");
+
+ let loaded = BrowserTestUtils.browserLoaded(browser);
+ BrowserTestUtils.loadURIString(browser, url2);
+ await loaded;
+ info("Second page loaded");
+
+ let browserId = browser.browserId;
+ ok(!!browser.browsingContext, "Should have a BrowsingContext. (1)");
+ is(
+ BrowsingContext.getCurrentTopByBrowserId(browserId),
+ browser.browsingContext,
+ "Should get the correct browsingContext(1)"
+ );
+
+ await ContentTask.spawn(browser, browserId, async function (browserId) {
+ Assert.ok(
+ BrowsingContext.getCurrentTopByBrowserId(browserId) ==
+ docShell.browsingContext
+ );
+ Assert.ok(docShell.browsingContext.browserId == browserId);
+ });
+
+ let awaitPageShow = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "pageshow"
+ );
+ browser.goBack();
+ await awaitPageShow;
+ info("Back");
+
+ awaitPageShow = BrowserTestUtils.waitForContentEvent(browser, "pageshow");
+ browser.goForward();
+ await awaitPageShow;
+ info("Forward");
+
+ ok(!!browser.browsingContext, "Should have a BrowsingContext. (2)");
+ is(
+ BrowsingContext.getCurrentTopByBrowserId(browserId),
+ browser.browsingContext,
+ "Should get the correct BrowsingContext. (2)"
+ );
+
+ await ContentTask.spawn(browser, browserId, async function (browserId) {
+ Assert.ok(
+ BrowsingContext.getCurrentTopByBrowserId(browserId) ==
+ docShell.browsingContext
+ );
+ Assert.ok(docShell.browsingContext.browserId == browserId);
+ });
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_bug1769189.js b/docshell/test/browser/browser_bug1769189.js
new file mode 100644
index 0000000000..08cb4f9002
--- /dev/null
+++ b/docshell/test/browser/browser_bug1769189.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_beforeUnload_and_replaceState() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: "data:text/html,<script>window.addEventListener('beforeunload', () => { window.history.replaceState(true, ''); });</script>",
+ },
+ async function (browser) {
+ let initialState = await SpecialPowers.spawn(browser, [], () => {
+ return content.history.state;
+ });
+
+ is(initialState, null, "history.state should be initially null.");
+
+ let awaitPageShow = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "pageshow"
+ );
+ BrowserReload();
+ await awaitPageShow;
+
+ let updatedState = await SpecialPowers.spawn(browser, [], () => {
+ return content.history.state;
+ });
+ is(updatedState, true, "history.state should have been updated.");
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_bug1798780.js b/docshell/test/browser/browser_bug1798780.js
new file mode 100644
index 0000000000..efc0ed9561
--- /dev/null
+++ b/docshell/test/browser/browser_bug1798780.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// The test loads an initial page and then another page which does enough
+// fragment navigations so that when going back to the initial page and then
+// forward to the last page, the initial page is evicted from the bfcache.
+add_task(async function testBFCacheEviction() {
+ // Make an unrealistic large timeout.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.sessionhistory.contentViewerTimeout", 86400]],
+ });
+
+ const uri = "data:text/html,initial page";
+ const uri2 =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "dummy_page.html";
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: uri },
+ async function (browser) {
+ BrowserTestUtils.loadURIString(browser, uri2);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ let awaitPageShow = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "pageshow"
+ );
+ await SpecialPowers.spawn(browser, [], async function () {
+ content.location.hash = "1";
+ content.location.hash = "2";
+ content.location.hash = "3";
+ content.history.go(-4);
+ });
+
+ await awaitPageShow;
+
+ let awaitPageShow2 = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "pageshow"
+ );
+ await SpecialPowers.spawn(browser, [], async function () {
+ content.history.go(4);
+ });
+ await awaitPageShow2;
+ ok(true, "Didn't time out.");
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_bug234628-1.js b/docshell/test/browser/browser_bug234628-1.js
new file mode 100644
index 0000000000..566da65bca
--- /dev/null
+++ b/docshell/test/browser/browser_bug234628-1.js
@@ -0,0 +1,47 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug234628-1.html",
+ afterOpen,
+ afterChangeCharset
+ );
+}
+
+function afterOpen() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u20AC"),
+ 129,
+ "Parent doc should be windows-1252 initially"
+ );
+
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u20AC"),
+ 85,
+ "Child doc should be windows-1252 initially"
+ );
+}
+
+function afterChangeCharset() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u20AC"),
+ 129,
+ "Parent doc should be windows-1252 subsequently"
+ );
+
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u20AC"),
+ 85,
+ "Child doc should be windows-1252 subsequently"
+ );
+
+ is(
+ content.document.characterSet,
+ "windows-1252",
+ "Parent doc should report windows-1252 subsequently"
+ );
+ is(
+ content.frames[0].document.characterSet,
+ "windows-1252",
+ "Child doc should report windows-1252 subsequently"
+ );
+}
diff --git a/docshell/test/browser/browser_bug234628-10.js b/docshell/test/browser/browser_bug234628-10.js
new file mode 100644
index 0000000000..8fb51cf27c
--- /dev/null
+++ b/docshell/test/browser/browser_bug234628-10.js
@@ -0,0 +1,46 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug234628-10.html",
+ afterOpen,
+ afterChangeCharset
+ );
+}
+
+function afterOpen() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u20AC"),
+ 151,
+ "Parent doc should be windows-1252 initially"
+ );
+
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u20AC"),
+ 71,
+ "Child doc should be utf-8 initially"
+ );
+}
+
+function afterChangeCharset() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u20AC"),
+ 151,
+ "Parent doc should be windows-1252 initially"
+ );
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u20AC"),
+ 71,
+ "Child doc should decode as utf-8 subsequently"
+ );
+
+ is(
+ content.document.characterSet,
+ "windows-1252",
+ "Parent doc should report windows-1252 subsequently"
+ );
+ is(
+ content.frames[0].document.characterSet,
+ "UTF-8",
+ "Child doc should report UTF-8 subsequently"
+ );
+}
diff --git a/docshell/test/browser/browser_bug234628-11.js b/docshell/test/browser/browser_bug234628-11.js
new file mode 100644
index 0000000000..d11645ff76
--- /dev/null
+++ b/docshell/test/browser/browser_bug234628-11.js
@@ -0,0 +1,46 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug234628-11.html",
+ afterOpen,
+ afterChangeCharset
+ );
+}
+
+function afterOpen() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u20AC"),
+ 193,
+ "Parent doc should be windows-1252 initially"
+ );
+
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u20AC"),
+ 107,
+ "Child doc should be utf-8 initially"
+ );
+}
+
+function afterChangeCharset() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u20AC"),
+ 193,
+ "Parent doc should be windows-1252 subsequently"
+ );
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u20AC"),
+ 107,
+ "Child doc should decode as utf-8 subsequently"
+ );
+
+ is(
+ content.document.characterSet,
+ "windows-1252",
+ "Parent doc should report windows-1252 subsequently"
+ );
+ is(
+ content.frames[0].document.characterSet,
+ "UTF-8",
+ "Child doc should report UTF-8 subsequently"
+ );
+}
diff --git a/docshell/test/browser/browser_bug234628-2.js b/docshell/test/browser/browser_bug234628-2.js
new file mode 100644
index 0000000000..da93dc2ac2
--- /dev/null
+++ b/docshell/test/browser/browser_bug234628-2.js
@@ -0,0 +1,49 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug234628-2.html",
+ afterOpen,
+ afterChangeCharset
+ );
+}
+
+function afterOpen() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u20AC"),
+ 129,
+ "Parent doc should be windows-1252 initially"
+ );
+
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf(
+ "\u00E2\u201A\u00AC"
+ ),
+ 78,
+ "Child doc should be windows-1252 initially"
+ );
+}
+
+function afterChangeCharset() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u20AC"),
+ 129,
+ "Parent doc should be windows-1252 subsequently"
+ );
+
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u20AC"),
+ 78,
+ "Child doc should be UTF-8 subsequently"
+ );
+
+ is(
+ content.document.characterSet,
+ "windows-1252",
+ "Parent doc should report windows-1252 subsequently"
+ );
+ is(
+ content.frames[0].document.characterSet,
+ "UTF-8",
+ "Child doc should report UTF-8 subsequently"
+ );
+}
diff --git a/docshell/test/browser/browser_bug234628-3.js b/docshell/test/browser/browser_bug234628-3.js
new file mode 100644
index 0000000000..8a143b51a6
--- /dev/null
+++ b/docshell/test/browser/browser_bug234628-3.js
@@ -0,0 +1,47 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug234628-3.html",
+ afterOpen,
+ afterChangeCharset
+ );
+}
+
+function afterOpen() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u20AC"),
+ 118,
+ "Parent doc should be windows-1252 initially"
+ );
+
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u20AC"),
+ 73,
+ "Child doc should be utf-8 initially"
+ );
+}
+
+function afterChangeCharset() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u20AC"),
+ 118,
+ "Parent doc should be windows-1252 subsequently"
+ );
+
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u20AC"),
+ 73,
+ "Child doc should be utf-8 subsequently"
+ );
+
+ is(
+ content.document.characterSet,
+ "windows-1252",
+ "Parent doc should report windows-1252 subsequently"
+ );
+ is(
+ content.frames[0].document.characterSet,
+ "UTF-8",
+ "Child doc should report UTF-8 subsequently"
+ );
+}
diff --git a/docshell/test/browser/browser_bug234628-4.js b/docshell/test/browser/browser_bug234628-4.js
new file mode 100644
index 0000000000..19ec0f8dbf
--- /dev/null
+++ b/docshell/test/browser/browser_bug234628-4.js
@@ -0,0 +1,46 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug234628-4.html",
+ afterOpen,
+ afterChangeCharset
+ );
+}
+
+function afterOpen() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u20AC"),
+ 132,
+ "Parent doc should be windows-1252 initially"
+ );
+
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u20AC"),
+ 79,
+ "Child doc should be utf-8 initially"
+ );
+}
+
+function afterChangeCharset() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u20AC"),
+ 132,
+ "Parent doc should decode as windows-1252 subsequently"
+ );
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u20AC"),
+ 79,
+ "Child doc should decode as utf-8 subsequently"
+ );
+
+ is(
+ content.document.characterSet,
+ "windows-1252",
+ "Parent doc should report windows-1252 subsequently"
+ );
+ is(
+ content.frames[0].document.characterSet,
+ "UTF-8",
+ "Child doc should report UTF-8 subsequently"
+ );
+}
diff --git a/docshell/test/browser/browser_bug234628-5.js b/docshell/test/browser/browser_bug234628-5.js
new file mode 100644
index 0000000000..77753ed78d
--- /dev/null
+++ b/docshell/test/browser/browser_bug234628-5.js
@@ -0,0 +1,46 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug234628-5.html",
+ afterOpen,
+ afterChangeCharset
+ );
+}
+
+function afterOpen() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u20AC"),
+ 146,
+ "Parent doc should be windows-1252 initially"
+ );
+
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u20AC"),
+ 87,
+ "Child doc should be utf-16 initially"
+ );
+}
+
+function afterChangeCharset() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u20AC"),
+ 146,
+ "Parent doc should be windows-1252 subsequently"
+ );
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u20AC"),
+ 87,
+ "Child doc should decode as utf-16 subsequently"
+ );
+
+ is(
+ content.document.characterSet,
+ "windows-1252",
+ "Parent doc should report windows-1252 subsequently"
+ );
+ is(
+ content.frames[0].document.characterSet,
+ "UTF-16LE",
+ "Child doc should report UTF-16LE subsequently"
+ );
+}
diff --git a/docshell/test/browser/browser_bug234628-6.js b/docshell/test/browser/browser_bug234628-6.js
new file mode 100644
index 0000000000..88ff6c1a82
--- /dev/null
+++ b/docshell/test/browser/browser_bug234628-6.js
@@ -0,0 +1,47 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug234628-6.html",
+ afterOpen,
+ afterChangeCharset
+ );
+}
+
+function afterOpen() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u20AC"),
+ 190,
+ "Parent doc should be windows-1252 initially"
+ );
+
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u20AC"),
+ 109,
+ "Child doc should be utf-16 initially"
+ );
+}
+
+function afterChangeCharset() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u20AC"),
+ 190,
+ "Parent doc should be windows-1252 subsequently"
+ );
+
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u20AC"),
+ 109,
+ "Child doc should be utf-16 subsequently"
+ );
+
+ is(
+ content.document.characterSet,
+ "windows-1252",
+ "Parent doc should report windows-1252 subsequently"
+ );
+ is(
+ content.frames[0].document.characterSet,
+ "UTF-16BE",
+ "Child doc should report UTF-16 subsequently"
+ );
+}
diff --git a/docshell/test/browser/browser_bug234628-8.js b/docshell/test/browser/browser_bug234628-8.js
new file mode 100644
index 0000000000..024a3d4d64
--- /dev/null
+++ b/docshell/test/browser/browser_bug234628-8.js
@@ -0,0 +1,18 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetCheck(rootDir + "file_bug234628-8.html", afterOpen);
+}
+
+function afterOpen() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u0402"),
+ 156,
+ "Parent doc should be windows-1251"
+ );
+
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u0402"),
+ 99,
+ "Child doc should be windows-1251"
+ );
+}
diff --git a/docshell/test/browser/browser_bug234628-9.js b/docshell/test/browser/browser_bug234628-9.js
new file mode 100644
index 0000000000..ceb7dc4e63
--- /dev/null
+++ b/docshell/test/browser/browser_bug234628-9.js
@@ -0,0 +1,18 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetCheck(rootDir + "file_bug234628-9.html", afterOpen);
+}
+
+function afterOpen() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u20AC"),
+ 145,
+ "Parent doc should be UTF-16"
+ );
+
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u20AC"),
+ 96,
+ "Child doc should be windows-1252"
+ );
+}
diff --git a/docshell/test/browser/browser_bug349769.js b/docshell/test/browser/browser_bug349769.js
new file mode 100644
index 0000000000..92e8f78543
--- /dev/null
+++ b/docshell/test/browser/browser_bug349769.js
@@ -0,0 +1,79 @@
+add_task(async function test() {
+ const uris = [undefined, "about:blank"];
+
+ function checkContentProcess(newBrowser, uri) {
+ return ContentTask.spawn(newBrowser, [uri], async function (uri) {
+ var prin = content.document.nodePrincipal;
+ Assert.notEqual(
+ prin,
+ null,
+ "Loaded principal must not be null when adding " + uri
+ );
+ Assert.notEqual(
+ prin,
+ undefined,
+ "Loaded principal must not be undefined when loading " + uri
+ );
+
+ Assert.equal(
+ prin.isSystemPrincipal,
+ false,
+ "Loaded principal must not be system when loading " + uri
+ );
+ });
+ }
+
+ for (var uri of uris) {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser },
+ async function (newBrowser) {
+ let loadedPromise = BrowserTestUtils.browserLoaded(newBrowser);
+ BrowserTestUtils.loadURIString(newBrowser, uri);
+
+ var prin = newBrowser.contentPrincipal;
+ isnot(
+ prin,
+ null,
+ "Forced principal must not be null when loading " + uri
+ );
+ isnot(
+ prin,
+ undefined,
+ "Forced principal must not be undefined when loading " + uri
+ );
+ is(
+ prin.isSystemPrincipal,
+ false,
+ "Forced principal must not be system when loading " + uri
+ );
+
+ // Belt-and-suspenders e10s check: make sure that the same checks hold
+ // true in the content process.
+ await checkContentProcess(newBrowser, uri);
+
+ await loadedPromise;
+
+ prin = newBrowser.contentPrincipal;
+ isnot(
+ prin,
+ null,
+ "Loaded principal must not be null when adding " + uri
+ );
+ isnot(
+ prin,
+ undefined,
+ "Loaded principal must not be undefined when loading " + uri
+ );
+ is(
+ prin.isSystemPrincipal,
+ false,
+ "Loaded principal must not be system when loading " + uri
+ );
+
+ // Belt-and-suspenders e10s check: make sure that the same checks hold
+ // true in the content process.
+ await checkContentProcess(newBrowser, uri);
+ }
+ );
+ }
+});
diff --git a/docshell/test/browser/browser_bug388121-1.js b/docshell/test/browser/browser_bug388121-1.js
new file mode 100644
index 0000000000..07c476f11b
--- /dev/null
+++ b/docshell/test/browser/browser_bug388121-1.js
@@ -0,0 +1,22 @@
+add_task(async function test() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:blank" },
+ async function (newBrowser) {
+ await SpecialPowers.spawn(newBrowser, [], async function () {
+ var prin = content.document.nodePrincipal;
+ Assert.notEqual(prin, null, "Loaded principal must not be null");
+ Assert.notEqual(
+ prin,
+ undefined,
+ "Loaded principal must not be undefined"
+ );
+
+ Assert.equal(
+ prin.isSystemPrincipal,
+ false,
+ "Loaded principal must not be system"
+ );
+ });
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_bug388121-2.js b/docshell/test/browser/browser_bug388121-2.js
new file mode 100644
index 0000000000..695ff1079c
--- /dev/null
+++ b/docshell/test/browser/browser_bug388121-2.js
@@ -0,0 +1,74 @@
+function test() {
+ waitForExplicitFinish();
+
+ var w;
+ var iteration = 1;
+ const uris = ["", "about:blank"];
+ var uri;
+ var origWgp;
+
+ function testLoad() {
+ let wgp = w.gBrowser.selectedBrowser.browsingContext.currentWindowGlobal;
+ if (wgp == origWgp) {
+ // Go back to polling
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ setTimeout(testLoad, 10);
+ return;
+ }
+ var prin = wgp.documentPrincipal;
+ isnot(prin, null, "Loaded principal must not be null when adding " + uri);
+ isnot(
+ prin,
+ undefined,
+ "Loaded principal must not be undefined when loading " + uri
+ );
+ is(
+ prin.isSystemPrincipal,
+ false,
+ "Loaded principal must not be system when loading " + uri
+ );
+ w.close();
+
+ if (iteration == uris.length) {
+ finish();
+ } else {
+ ++iteration;
+ doTest();
+ }
+ }
+
+ function doTest() {
+ uri = uris[iteration - 1];
+ window.open(uri, "_blank", "width=10,height=10,noopener");
+ w = Services.wm.getMostRecentWindow("navigator:browser");
+ origWgp = w.gBrowser.selectedBrowser.browsingContext.currentWindowGlobal;
+ var prin = origWgp.documentPrincipal;
+ if (!uri) {
+ uri = undefined;
+ }
+ isnot(prin, null, "Forced principal must not be null when loading " + uri);
+ isnot(
+ prin,
+ undefined,
+ "Forced principal must not be undefined when loading " + uri
+ );
+ is(
+ prin.isSystemPrincipal,
+ false,
+ "Forced principal must not be system when loading " + uri
+ );
+ if (uri == undefined) {
+ // No actual load here, so just move along.
+ w.close();
+ ++iteration;
+ doTest();
+ } else {
+ // Need to poll, because load listeners on the content window won't
+ // survive the load.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ setTimeout(testLoad, 10);
+ }
+ }
+
+ doTest();
+}
diff --git a/docshell/test/browser/browser_bug420605.js b/docshell/test/browser/browser_bug420605.js
new file mode 100644
index 0000000000..9a18ab2c9f
--- /dev/null
+++ b/docshell/test/browser/browser_bug420605.js
@@ -0,0 +1,131 @@
+/* Test for Bug 420605
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=420605
+ */
+
+const { PlacesTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PlacesTestUtils.sys.mjs"
+);
+
+add_task(async function test() {
+ var pageurl =
+ "http://mochi.test:8888/browser/docshell/test/browser/file_bug420605.html";
+ var fragmenturl =
+ "http://mochi.test:8888/browser/docshell/test/browser/file_bug420605.html#firefox";
+
+ /* Queries nsINavHistoryService and returns a single history entry
+ * for a given URI */
+ function getNavHistoryEntry(aURI) {
+ var options = PlacesUtils.history.getNewQueryOptions();
+ options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY;
+ options.maxResults = 1;
+
+ var query = PlacesUtils.history.getNewQuery();
+ query.uri = aURI;
+ var result = PlacesUtils.history.executeQuery(query, options);
+ result.root.containerOpen = true;
+
+ if (!result.root.childCount) {
+ return null;
+ }
+ return result.root.getChild(0);
+ }
+
+ // We'll save the favicon URL of the orignal page here and check that the
+ // page with a hash has the same favicon.
+ var originalFavicon;
+
+ // Control flow in this test is a bit complicated.
+ //
+ // When the page loads, onPageLoad (the DOMContentLoaded handler) and
+ // favicon-changed are both called, in some order. Once
+ // they've both run, we click a fragment link in the content page
+ // (clickLinkIfReady), which should trigger another favicon-changed event,
+ // this time for the fragment's URL.
+
+ var _clickLinkTimes = 0;
+ function clickLinkIfReady() {
+ _clickLinkTimes++;
+ if (_clickLinkTimes == 2) {
+ BrowserTestUtils.synthesizeMouseAtCenter(
+ "#firefox-link",
+ {},
+ gBrowser.selectedBrowser
+ );
+ }
+ }
+
+ function onPageLoad() {
+ clickLinkIfReady();
+ }
+
+ // Make sure neither of the test pages haven't been loaded before.
+ var info = getNavHistoryEntry(makeURI(pageurl));
+ ok(!info, "The test page must not have been visited already.");
+ info = getNavHistoryEntry(makeURI(fragmenturl));
+ ok(!info, "The fragment test page must not have been visited already.");
+
+ let promiseIcon1 = PlacesTestUtils.waitForNotification(
+ "favicon-changed",
+ events =>
+ events.some(e => {
+ if (e.url == pageurl) {
+ ok(
+ e.faviconUrl,
+ "Favicon value is not null for page without fragment."
+ );
+ originalFavicon = e.faviconUrl;
+
+ // Now that the favicon has loaded, click on fragment link.
+ // This should trigger the |case fragmenturl| below.
+ clickLinkIfReady();
+ return true;
+ }
+ return false;
+ })
+ );
+ let promiseIcon2 = PlacesTestUtils.waitForNotification(
+ "favicon-changed",
+ events =>
+ events.some(e => {
+ if (e.url == fragmenturl) {
+ // If the fragment URL's favicon isn't set, this branch won't
+ // be called and the test will time out.
+ is(
+ e.faviconUrl,
+ originalFavicon,
+ "New favicon should be same as original favicon."
+ );
+ ok(
+ e.faviconUrl,
+ "Favicon value is not null for page without fragment."
+ );
+ originalFavicon = e.faviconUrl;
+
+ // Now that the favicon has loaded, click on fragment link.
+ // This should trigger the |case fragmenturl| below.
+ clickLinkIfReady();
+ return true;
+ }
+ return false;
+ })
+ );
+
+ // Now open the test page in a new tab.
+ gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
+ BrowserTestUtils.waitForContentEvent(
+ gBrowser.selectedBrowser,
+ "DOMContentLoaded",
+ true
+ ).then(onPageLoad);
+ BrowserTestUtils.loadURIString(gBrowser.selectedBrowser, pageurl);
+
+ await promiseIcon1;
+ await promiseIcon2;
+
+ // Let's explicitly check that we can get the favicon
+ // from nsINavHistoryService now.
+ info = getNavHistoryEntry(makeURI(fragmenturl));
+ ok(info, "There must be a history entry for the fragment.");
+ ok(info.icon, "The history entry must have an associated favicon.");
+ gBrowser.removeCurrentTab();
+});
diff --git a/docshell/test/browser/browser_bug422543.js b/docshell/test/browser/browser_bug422543.js
new file mode 100644
index 0000000000..d3b772619e
--- /dev/null
+++ b/docshell/test/browser/browser_bug422543.js
@@ -0,0 +1,253 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const ACTOR = "Bug422543";
+
+let getActor = browser => {
+ return browser.browsingContext.currentWindowGlobal.getActor(ACTOR);
+};
+
+add_task(async function runTests() {
+ if (!SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ await setupAsync();
+ let browser = gBrowser.selectedBrowser;
+ // Now that we're set up, initialize our frame script.
+ await checkListenersAsync("initial", "listeners initialized");
+
+ // Check if all history listeners are always notified.
+ info("# part 1");
+ await whenPageShown(browser, () =>
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ BrowserTestUtils.loadURIString(browser, "http://www.example.com/")
+ );
+ await checkListenersAsync("newentry", "shistory has a new entry");
+ ok(browser.canGoBack, "we can go back");
+
+ await whenPageShown(browser, () => browser.goBack());
+ await checkListenersAsync("gotoindex", "back to the first shentry");
+ ok(browser.canGoForward, "we can go forward");
+
+ await whenPageShown(browser, () => browser.goForward());
+ await checkListenersAsync("gotoindex", "forward to the second shentry");
+
+ await whenPageShown(browser, () => browser.reload());
+ await checkListenersAsync("reload", "current shentry reloaded");
+
+ await whenPageShown(browser, () => browser.gotoIndex(0));
+ await checkListenersAsync("gotoindex", "back to the first index");
+
+ // Check nsISHistory.notifyOnHistoryReload
+ info("# part 2");
+ ok(await notifyReloadAsync(), "reloading has not been canceled");
+ await checkListenersAsync("reload", "saw the reload notification");
+
+ // Let the first listener cancel the reload action.
+ info("# part 3");
+ await resetListenersAsync();
+ await setListenerRetvalAsync(0, false);
+ ok(!(await notifyReloadAsync()), "reloading has been canceled");
+ await checkListenersAsync("reload", "saw the reload notification");
+
+ // Let both listeners cancel the reload action.
+ info("# part 4");
+ await resetListenersAsync();
+ await setListenerRetvalAsync(1, false);
+ ok(!(await notifyReloadAsync()), "reloading has been canceled");
+ await checkListenersAsync("reload", "saw the reload notification");
+
+ // Let the second listener cancel the reload action.
+ info("# part 5");
+ await resetListenersAsync();
+ await setListenerRetvalAsync(0, true);
+ ok(!(await notifyReloadAsync()), "reloading has been canceled");
+ await checkListenersAsync("reload", "saw the reload notification");
+
+ function sendQuery(message, arg = {}) {
+ return getActor(gBrowser.selectedBrowser).sendQuery(message, arg);
+ }
+
+ function checkListenersAsync(aLast, aMessage) {
+ return sendQuery("getListenerStatus").then(listenerStatuses => {
+ is(listenerStatuses[0], aLast, aMessage);
+ is(listenerStatuses[1], aLast, aMessage);
+ });
+ }
+
+ function resetListenersAsync() {
+ return sendQuery("resetListeners");
+ }
+
+ function notifyReloadAsync() {
+ return sendQuery("notifyReload").then(({ rval }) => {
+ return rval;
+ });
+ }
+
+ function setListenerRetvalAsync(num, val) {
+ return sendQuery("setRetval", { num, val });
+ }
+
+ async function setupAsync() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "http://mochi.test:8888"
+ );
+
+ let base = getRootDirectory(gTestPath).slice(0, -1);
+ ChromeUtils.registerWindowActor(ACTOR, {
+ child: {
+ esModuleURI: `${base}/Bug422543Child.sys.mjs`,
+ },
+ });
+
+ registerCleanupFunction(async () => {
+ await sendQuery("cleanup");
+ gBrowser.removeTab(tab);
+
+ ChromeUtils.unregisterWindowActor(ACTOR);
+ });
+
+ await sendQuery("init");
+ }
+ return;
+ }
+
+ await setup();
+ let browser = gBrowser.selectedBrowser;
+ // Now that we're set up, initialize our frame script.
+ checkListeners("initial", "listeners initialized");
+
+ // Check if all history listeners are always notified.
+ info("# part 1");
+ await whenPageShown(browser, () =>
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ BrowserTestUtils.loadURIString(browser, "http://www.example.com/")
+ );
+ checkListeners("newentry", "shistory has a new entry");
+ ok(browser.canGoBack, "we can go back");
+
+ await whenPageShown(browser, () => browser.goBack());
+ checkListeners("gotoindex", "back to the first shentry");
+ ok(browser.canGoForward, "we can go forward");
+
+ await whenPageShown(browser, () => browser.goForward());
+ checkListeners("gotoindex", "forward to the second shentry");
+
+ await whenPageShown(browser, () => browser.reload());
+ checkListeners("reload", "current shentry reloaded");
+
+ await whenPageShown(browser, () => browser.gotoIndex(0));
+ checkListeners("gotoindex", "back to the first index");
+
+ // Check nsISHistory.notifyOnHistoryReload
+ info("# part 2");
+ ok(notifyReload(browser), "reloading has not been canceled");
+ checkListeners("reload", "saw the reload notification");
+
+ // Let the first listener cancel the reload action.
+ info("# part 3");
+ resetListeners();
+ setListenerRetval(0, false);
+ ok(!notifyReload(browser), "reloading has been canceled");
+ checkListeners("reload", "saw the reload notification");
+
+ // Let both listeners cancel the reload action.
+ info("# part 4");
+ resetListeners();
+ setListenerRetval(1, false);
+ ok(!notifyReload(browser), "reloading has been canceled");
+ checkListeners("reload", "saw the reload notification");
+
+ // Let the second listener cancel the reload action.
+ info("# part 5");
+ resetListeners();
+ setListenerRetval(0, true);
+ ok(!notifyReload(browser), "reloading has been canceled");
+ checkListeners("reload", "saw the reload notification");
+});
+
+class SHistoryListener {
+ constructor() {
+ this.retval = true;
+ this.last = "initial";
+ }
+
+ OnHistoryNewEntry(aNewURI) {
+ this.last = "newentry";
+ }
+
+ OnHistoryGotoIndex() {
+ this.last = "gotoindex";
+ }
+
+ OnHistoryPurge() {
+ this.last = "purge";
+ }
+
+ OnHistoryReload() {
+ this.last = "reload";
+ return this.retval;
+ }
+
+ OnHistoryReplaceEntry() {}
+}
+SHistoryListener.prototype.QueryInterface = ChromeUtils.generateQI([
+ "nsISHistoryListener",
+ "nsISupportsWeakReference",
+]);
+
+let listeners = [new SHistoryListener(), new SHistoryListener()];
+
+function checkListeners(aLast, aMessage) {
+ is(listeners[0].last, aLast, aMessage);
+ is(listeners[1].last, aLast, aMessage);
+}
+
+function resetListeners() {
+ for (let listener of listeners) {
+ listener.last = "initial";
+ }
+}
+
+function notifyReload(browser) {
+ return browser.browsingContext.sessionHistory.notifyOnHistoryReload();
+}
+
+function setListenerRetval(num, val) {
+ listeners[num].retval = val;
+}
+
+async function setup() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "http://mochi.test:8888"
+ );
+
+ let browser = tab.linkedBrowser;
+ registerCleanupFunction(async function () {
+ for (let listener of listeners) {
+ browser.browsingContext.sessionHistory.removeSHistoryListener(listener);
+ }
+ gBrowser.removeTab(tab);
+ });
+ for (let listener of listeners) {
+ browser.browsingContext.sessionHistory.addSHistoryListener(listener);
+ }
+}
+
+function whenPageShown(aBrowser, aNavigation) {
+ let promise = new Promise(resolve => {
+ let unregister = BrowserTestUtils.addContentEventListener(
+ aBrowser,
+ "pageshow",
+ () => {
+ unregister();
+ resolve();
+ },
+ { capture: true }
+ );
+ });
+
+ aNavigation();
+ return promise;
+}
diff --git a/docshell/test/browser/browser_bug441169.js b/docshell/test/browser/browser_bug441169.js
new file mode 100644
index 0000000000..09e83b040c
--- /dev/null
+++ b/docshell/test/browser/browser_bug441169.js
@@ -0,0 +1,44 @@
+/* Make sure that netError won't allow HTML injection through badcert parameters. See bug 441169. */
+var newBrowser;
+
+function task() {
+ let resolve;
+ let promise = new Promise(r => {
+ resolve = r;
+ });
+
+ addEventListener("DOMContentLoaded", checkPage, false);
+
+ function checkPage(event) {
+ if (event.target != content.document) {
+ return;
+ }
+ removeEventListener("DOMContentLoaded", checkPage, false);
+
+ is(
+ content.document.getElementById("test_span"),
+ null,
+ "Error message should not be parsed as HTML, and hence shouldn't include the 'test_span' element."
+ );
+ resolve();
+ }
+
+ var chromeURL =
+ "about:neterror?e=nssBadCert&u=https%3A//test.kuix.de/&c=UTF-8&d=This%20sentence%20should%20not%20be%20parsed%20to%20include%20a%20%3Cspan%20id=%22test_span%22%3Enamed%3C/span%3E%20span%20tag.%0A%0AThe%20certificate%20is%20only%20valid%20for%20%3Ca%20id=%22cert_domain_link%22%20title=%22kuix.de%22%3Ekuix.de%3C/a%3E%0A%0A(Error%20code%3A%20ssl_error_bad_cert_domain)";
+ content.location = chromeURL;
+
+ return promise;
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ var newTab = BrowserTestUtils.addTab(gBrowser);
+ gBrowser.selectedTab = newTab;
+ newBrowser = gBrowser.getBrowserForTab(newTab);
+
+ ContentTask.spawn(newBrowser, null, task).then(() => {
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+}
diff --git a/docshell/test/browser/browser_bug503832.js b/docshell/test/browser/browser_bug503832.js
new file mode 100644
index 0000000000..8699eb01fc
--- /dev/null
+++ b/docshell/test/browser/browser_bug503832.js
@@ -0,0 +1,76 @@
+/* Test for Bug 503832
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=503832
+ */
+
+add_task(async function () {
+ var pagetitle = "Page Title for Bug 503832";
+ var pageurl =
+ "http://mochi.test:8888/browser/docshell/test/browser/file_bug503832.html";
+ var fragmenturl =
+ "http://mochi.test:8888/browser/docshell/test/browser/file_bug503832.html#firefox";
+
+ var historyService = Cc[
+ "@mozilla.org/browser/nav-history-service;1"
+ ].getService(Ci.nsINavHistoryService);
+
+ let fragmentPromise = new Promise(resolve => {
+ const listener = events => {
+ const { url, title } = events[0];
+
+ switch (url) {
+ case pageurl:
+ is(title, pagetitle, "Correct page title for " + url);
+ return;
+ case fragmenturl:
+ is(title, pagetitle, "Correct page title for " + url);
+ // If titles for fragment URLs aren't set, this code
+ // branch won't be called and the test will timeout,
+ // resulting in a failure
+ PlacesObservers.removeListener(["page-title-changed"], listener);
+ resolve();
+ }
+ };
+
+ PlacesObservers.addListener(["page-title-changed"], listener);
+ });
+
+ /* Queries nsINavHistoryService and returns a single history entry
+ * for a given URI */
+ function getNavHistoryEntry(aURI) {
+ var options = historyService.getNewQueryOptions();
+ options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY;
+ options.maxResults = 1;
+
+ var query = historyService.getNewQuery();
+ query.uri = aURI;
+
+ var result = historyService.executeQuery(query, options);
+ result.root.containerOpen = true;
+
+ if (!result.root.childCount) {
+ return null;
+ }
+ var node = result.root.getChild(0);
+ result.root.containerOpen = false;
+ return node;
+ }
+
+ // Make sure neither of the test pages haven't been loaded before.
+ var info = getNavHistoryEntry(makeURI(pageurl));
+ ok(!info, "The test page must not have been visited already.");
+ info = getNavHistoryEntry(makeURI(fragmenturl));
+ ok(!info, "The fragment test page must not have been visited already.");
+
+ // Now open the test page in a new tab
+ await BrowserTestUtils.openNewForegroundTab(gBrowser, pageurl);
+
+ // Now that the page is loaded, click on fragment link
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#firefox-link",
+ {},
+ gBrowser.selectedBrowser
+ );
+ await fragmentPromise;
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/docshell/test/browser/browser_bug554155.js b/docshell/test/browser/browser_bug554155.js
new file mode 100644
index 0000000000..223f6241de
--- /dev/null
+++ b/docshell/test/browser/browser_bug554155.js
@@ -0,0 +1,33 @@
+add_task(async function test() {
+ await BrowserTestUtils.withNewTab(
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ { gBrowser, url: "http://example.com" },
+ async function (browser) {
+ let numLocationChanges = 0;
+
+ let listener = {
+ onLocationChange(browser, webProgress, request, uri, flags) {
+ info("location change: " + (uri && uri.spec));
+ numLocationChanges++;
+ },
+ };
+
+ gBrowser.addTabsProgressListener(listener);
+
+ await SpecialPowers.spawn(browser, [], function () {
+ // pushState to a new URL (http://example.com/foo"). This should trigger
+ // exactly one LocationChange event.
+ content.history.pushState(null, null, "foo");
+ });
+
+ await Promise.resolve();
+
+ gBrowser.removeTabsProgressListener(listener);
+ is(
+ numLocationChanges,
+ 1,
+ "pushState should cause exactly one LocationChange event."
+ );
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_bug655270.js b/docshell/test/browser/browser_bug655270.js
new file mode 100644
index 0000000000..3120abb269
--- /dev/null
+++ b/docshell/test/browser/browser_bug655270.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test for Bug 655273
+ *
+ * Call pushState and then make sure that the favicon service associates our
+ * old favicon with the new URI.
+ */
+
+const { PlacesTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PlacesTestUtils.sys.mjs"
+);
+
+add_task(async function test() {
+ const testDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ const origURL = testDir + "file_bug655270.html";
+ const newURL = origURL + "?new_page";
+
+ const faviconURL = testDir + "favicon_bug655270.ico";
+
+ let icon1;
+ let promiseIcon1 = PlacesTestUtils.waitForNotification(
+ "favicon-changed",
+ events =>
+ events.some(e => {
+ if (e.url == origURL) {
+ icon1 = e.faviconUrl;
+ return true;
+ }
+ return false;
+ })
+ );
+ let icon2;
+ let promiseIcon2 = PlacesTestUtils.waitForNotification(
+ "favicon-changed",
+ events =>
+ events.some(e => {
+ if (e.url == newURL) {
+ icon2 = e.faviconUrl;
+ return true;
+ }
+ return false;
+ })
+ );
+
+ // The page at origURL has a <link rel='icon'>, so we should get a call into
+ // our observer below when it loads. Once we verify that we have the right
+ // favicon URI, we call pushState, which should trigger another favicon change
+ // event, this time for the URI after pushState.
+ let tab = BrowserTestUtils.addTab(gBrowser, origURL);
+ await promiseIcon1;
+ is(icon1, faviconURL, "FaviconURL for original URI");
+ // Ignore the promise returned here and wait for the next
+ // onPageChanged notification.
+ SpecialPowers.spawn(tab.linkedBrowser, [], function () {
+ content.history.pushState("", "", "?new_page");
+ });
+ await promiseIcon2;
+ is(icon2, faviconURL, "FaviconURL for new URI");
+ gBrowser.removeTab(tab);
+});
diff --git a/docshell/test/browser/browser_bug655273.js b/docshell/test/browser/browser_bug655273.js
new file mode 100644
index 0000000000..e564eb2f6c
--- /dev/null
+++ b/docshell/test/browser/browser_bug655273.js
@@ -0,0 +1,56 @@
+/* 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/. */
+
+/**
+ * Test for Bug 655273. Make sure that after changing the URI via
+ * history.pushState, the resulting SHEntry has the same title as our old
+ * SHEntry.
+ */
+
+add_task(async function test() {
+ waitForExplicitFinish();
+
+ await BrowserTestUtils.withNewTab(
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ { gBrowser, url: "http://example.com" },
+ async function (browser) {
+ if (!SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ await SpecialPowers.spawn(browser, [], async function () {
+ let cw = content;
+ let oldTitle = cw.document.title;
+ ok(oldTitle, "Content window should initially have a title.");
+ cw.history.pushState("", "", "new_page");
+
+ let shistory = cw.docShell.QueryInterface(
+ Ci.nsIWebNavigation
+ ).sessionHistory;
+
+ is(
+ shistory.legacySHistory.getEntryAtIndex(shistory.index).title,
+ oldTitle,
+ "SHEntry title after pushstate."
+ );
+ });
+
+ return;
+ }
+
+ let bc = browser.browsingContext;
+ let oldTitle = browser.browsingContext.currentWindowGlobal.documentTitle;
+ ok(oldTitle, "Content window should initially have a title.");
+ SpecialPowers.spawn(browser, [], async function () {
+ content.history.pushState("", "", "new_page");
+ });
+
+ let shistory = bc.sessionHistory;
+ await SHListener.waitForHistory(shistory, SHListener.NewEntry);
+
+ is(
+ shistory.getEntryAtIndex(shistory.index).title,
+ oldTitle,
+ "SHEntry title after pushstate."
+ );
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_bug670318.js b/docshell/test/browser/browser_bug670318.js
new file mode 100644
index 0000000000..06c2a10f79
--- /dev/null
+++ b/docshell/test/browser/browser_bug670318.js
@@ -0,0 +1,146 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test for Bug 670318
+ *
+ * When LoadEntry() is called on a browser that has multiple duplicate history
+ * entries, history.index can end up out of range (>= history.count).
+ */
+
+const URL =
+ "http://mochi.test:8888/browser/docshell/test/browser/file_bug670318.html";
+
+add_task(async function test() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:blank" },
+ async function (browser) {
+ if (!SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ await ContentTask.spawn(browser, URL, async function (URL) {
+ let history = docShell.QueryInterface(
+ Ci.nsIWebNavigation
+ ).sessionHistory;
+ let count = 0;
+
+ let testDone = {};
+ testDone.promise = new Promise(resolve => {
+ testDone.resolve = resolve;
+ });
+
+ // Since listener implements nsISupportsWeakReference, we are
+ // responsible for keeping it alive so that the GC doesn't clear
+ // it before the test completes. We do this by anchoring the listener
+ // to the message manager, and clearing it just before the test
+ // completes.
+ this._testListener = {
+ owner: this,
+ OnHistoryNewEntry(aNewURI) {
+ info("OnHistoryNewEntry " + aNewURI.spec + ", " + count);
+ if (aNewURI.spec == URL && 5 == ++count) {
+ addEventListener(
+ "load",
+ function onLoad() {
+ Assert.ok(
+ history.index < history.count,
+ "history.index is valid"
+ );
+ testDone.resolve();
+ },
+ { capture: true, once: true }
+ );
+
+ history.legacySHistory.removeSHistoryListener(
+ this.owner._testListener
+ );
+ delete this.owner._testListener;
+ this.owner = null;
+ content.setTimeout(() => {
+ content.location.reload();
+ }, 0);
+ }
+ },
+
+ OnHistoryReload: () => true,
+ OnHistoryGotoIndex: () => {},
+ OnHistoryPurge: () => {},
+ OnHistoryReplaceEntry: () => {
+ // The initial load of about:blank causes a transient entry to be
+ // created, so our first navigation to a real page is a replace
+ // instead of a new entry.
+ ++count;
+ },
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsISHistoryListener",
+ "nsISupportsWeakReference",
+ ]),
+ };
+
+ history.legacySHistory.addSHistoryListener(this._testListener);
+ content.location = URL;
+
+ await testDone.promise;
+ });
+
+ return;
+ }
+
+ let history = browser.browsingContext.sessionHistory;
+ let count = 0;
+
+ let testDone = {};
+ testDone.promise = new Promise(resolve => {
+ testDone.resolve = resolve;
+ });
+
+ let listener = {
+ async OnHistoryNewEntry(aNewURI) {
+ if (aNewURI.spec == URL && 5 == ++count) {
+ history.removeSHistoryListener(listener);
+ await ContentTask.spawn(browser, null, () => {
+ return new Promise(resolve => {
+ addEventListener(
+ "load",
+ evt => {
+ let history = docShell.QueryInterface(
+ Ci.nsIWebNavigation
+ ).sessionHistory;
+ Assert.ok(
+ history.index < history.count,
+ "history.index is valid"
+ );
+ resolve();
+ },
+ { capture: true, once: true }
+ );
+
+ content.location.reload();
+ });
+ });
+ testDone.resolve();
+ }
+ },
+
+ OnHistoryReload: () => true,
+ OnHistoryGotoIndex: () => {},
+ OnHistoryPurge: () => {},
+ OnHistoryReplaceEntry: () => {
+ // The initial load of about:blank causes a transient entry to be
+ // created, so our first navigation to a real page is a replace
+ // instead of a new entry.
+ ++count;
+ },
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsISHistoryListener",
+ "nsISupportsWeakReference",
+ ]),
+ };
+
+ history.addSHistoryListener(listener);
+ BrowserTestUtils.loadURIString(browser, URL);
+
+ await testDone.promise;
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_bug673087-1.js b/docshell/test/browser/browser_bug673087-1.js
new file mode 100644
index 0000000000..427b246d76
--- /dev/null
+++ b/docshell/test/browser/browser_bug673087-1.js
@@ -0,0 +1,46 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug673087-1.html",
+ afterOpen,
+ afterChangeCharset
+ );
+}
+
+function afterOpen() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u00A4"),
+ 151,
+ "Parent doc should be windows-1252 initially"
+ );
+
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u00A4"),
+ 95,
+ "Child doc should be windows-1252 initially"
+ );
+}
+
+function afterChangeCharset() {
+ is(
+ content.document.documentElement.textContent.indexOf("\u3042"),
+ 151,
+ "Parent doc should decode as EUC-JP subsequently"
+ );
+ is(
+ content.frames[0].document.documentElement.textContent.indexOf("\u3042"),
+ 95,
+ "Child doc should decode as EUC-JP subsequently"
+ );
+
+ is(
+ content.document.characterSet,
+ "EUC-JP",
+ "Parent doc should report EUC-JP subsequently"
+ );
+ is(
+ content.frames[0].document.characterSet,
+ "EUC-JP",
+ "Child doc should report EUC-JP subsequently"
+ );
+}
diff --git a/docshell/test/browser/browser_bug673087-2.js b/docshell/test/browser/browser_bug673087-2.js
new file mode 100644
index 0000000000..13a7a2a82c
--- /dev/null
+++ b/docshell/test/browser/browser_bug673087-2.js
@@ -0,0 +1,36 @@
+function test() {
+ var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+ runCharsetTest(
+ rootDir + "file_bug673087-2.html",
+ afterOpen,
+ afterChangeCharset
+ );
+}
+
+function afterOpen() {
+ is(
+ content.document.documentElement.textContent.indexOf("\uFFFD"),
+ 0,
+ "Doc should decode as replacement initially"
+ );
+
+ is(
+ content.document.characterSet,
+ "replacement",
+ "Doc should report replacement initially"
+ );
+}
+
+function afterChangeCharset() {
+ is(
+ content.document.documentElement.textContent.indexOf("\uFFFD"),
+ 0,
+ "Doc should decode as replacement subsequently"
+ );
+
+ is(
+ content.document.characterSet,
+ "replacement",
+ "Doc should report replacement subsequently"
+ );
+}
diff --git a/docshell/test/browser/browser_bug673467.js b/docshell/test/browser/browser_bug673467.js
new file mode 100644
index 0000000000..507410254c
--- /dev/null
+++ b/docshell/test/browser/browser_bug673467.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test for bug 673467. In a new tab, load a page which inserts a new iframe
+// before the load and then sets its location during the load. This should
+// create just one SHEntry.
+
+var doc =
+ "data:text/html,<html><body onload='load()'>" +
+ "<script>" +
+ " var iframe = document.createElement('iframe');" +
+ " iframe.id = 'iframe';" +
+ " document.documentElement.appendChild(iframe);" +
+ " function load() {" +
+ " iframe.src = 'data:text/html,Hello!';" +
+ " }" +
+ "</script>" +
+ "</body></html>";
+
+function test() {
+ waitForExplicitFinish();
+
+ let taskFinished;
+
+ let tab = BrowserTestUtils.addTab(gBrowser, doc, {}, tab => {
+ taskFinished = ContentTask.spawn(tab.linkedBrowser, null, () => {
+ return new Promise(resolve => {
+ addEventListener(
+ "load",
+ function () {
+ // The main page has loaded. Now wait for the iframe to load.
+ let iframe = content.document.getElementById("iframe");
+ iframe.addEventListener(
+ "load",
+ function listener(aEvent) {
+ // Wait for the iframe to load the new document, not about:blank.
+ if (!iframe.src) {
+ return;
+ }
+
+ iframe.removeEventListener("load", listener, true);
+ let shistory = content.docShell.QueryInterface(
+ Ci.nsIWebNavigation
+ ).sessionHistory;
+
+ Assert.equal(shistory.count, 1, "shistory count should be 1.");
+ resolve();
+ },
+ true
+ );
+ },
+ true
+ );
+ });
+ });
+ });
+
+ taskFinished.then(() => {
+ gBrowser.removeTab(tab);
+ finish();
+ });
+}
diff --git a/docshell/test/browser/browser_bug852909.js b/docshell/test/browser/browser_bug852909.js
new file mode 100644
index 0000000000..108baa0626
--- /dev/null
+++ b/docshell/test/browser/browser_bug852909.js
@@ -0,0 +1,35 @@
+var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
+
+function test() {
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ rootDir + "file_bug852909.png"
+ );
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(image);
+}
+
+function image(event) {
+ ok(
+ !gBrowser.selectedTab.mayEnableCharacterEncodingMenu,
+ "Docshell should say the menu should be disabled for images."
+ );
+
+ gBrowser.removeCurrentTab();
+ gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ rootDir + "file_bug852909.pdf"
+ );
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(pdf);
+}
+
+function pdf(event) {
+ ok(
+ !gBrowser.selectedTab.mayEnableCharacterEncodingMenu,
+ "Docshell should say the menu should be disabled for PDF.js."
+ );
+
+ gBrowser.removeCurrentTab();
+ finish();
+}
diff --git a/docshell/test/browser/browser_bug92473.js b/docshell/test/browser/browser_bug92473.js
new file mode 100644
index 0000000000..7e386f5ee9
--- /dev/null
+++ b/docshell/test/browser/browser_bug92473.js
@@ -0,0 +1,70 @@
+/* The test text as octets for reference
+ * %83%86%83%6a%83%52%81%5b%83%68%82%cd%81%41%82%b7%82%d7%82%c4%82%cc%95%b6%8e%9a%82%c9%8c%c5%97%4c%82%cc%94%d4%8d%86%82%f0%95%74%97%5e%82%b5%82%dc%82%b7
+ */
+
+function testContent(text) {
+ return SpecialPowers.spawn(gBrowser.selectedBrowser, [text], text => {
+ Assert.equal(
+ content.document.getElementById("testpar").innerHTML,
+ text,
+ "<p> contains expected text"
+ );
+ Assert.equal(
+ content.document.getElementById("testtextarea").innerHTML,
+ text,
+ "<textarea> contains expected text"
+ );
+ Assert.equal(
+ content.document.getElementById("testinput").value,
+ text,
+ "<input> contains expected text"
+ );
+ });
+}
+
+function afterOpen() {
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(
+ afterChangeCharset
+ );
+
+ /* The test text decoded incorrectly as Windows-1251. This is the "right" wrong
+ text; anything else is unexpected. */
+ const wrongText =
+ "\u0453\u2020\u0453\u006A\u0453\u0052\u0403\u005B\u0453\u0068\u201A\u041D\u0403\u0041\u201A\u00B7\u201A\u0427\u201A\u0414\u201A\u041C\u2022\u00B6\u040B\u0459\u201A\u0419\u040A\u0415\u2014\u004C\u201A\u041C\u201D\u0424\u040C\u2020\u201A\u0440\u2022\u0074\u2014\u005E\u201A\u00B5\u201A\u042C\u201A\u00B7";
+
+ /* Test that the content on load is the expected wrong decoding */
+ testContent(wrongText).then(() => {
+ BrowserForceEncodingDetection();
+ });
+}
+
+function afterChangeCharset() {
+ /* The test text decoded correctly as Shift_JIS */
+ const rightText =
+ "\u30E6\u30CB\u30B3\u30FC\u30C9\u306F\u3001\u3059\u3079\u3066\u306E\u6587\u5B57\u306B\u56FA\u6709\u306E\u756A\u53F7\u3092\u4ED8\u4E0E\u3057\u307E\u3059";
+
+ /* test that the content is decoded correctly */
+ testContent(rightText).then(() => {
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ // Get the local directory. This needs to be a file: URI because chrome: URIs
+ // are always UTF-8 (bug 617339) and we are testing decoding from other
+ // charsets.
+ var jar = getJar(getRootDirectory(gTestPath));
+ var dir = jar
+ ? extractJarToTmp(jar)
+ : getChromeDir(getResolvedURI(gTestPath));
+ var rootDir = Services.io.newFileURI(dir).spec;
+
+ gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ rootDir + "test-form_sjis.html"
+ );
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(afterOpen);
+}
diff --git a/docshell/test/browser/browser_click_link_within_view_source.js b/docshell/test/browser/browser_click_link_within_view_source.js
new file mode 100644
index 0000000000..73542f977a
--- /dev/null
+++ b/docshell/test/browser/browser_click_link_within_view_source.js
@@ -0,0 +1,78 @@
+"use strict";
+
+/**
+ * Test for Bug 1359204
+ *
+ * Loading a local file, then view-source on that file. Make sure that
+ * clicking a link within that view-source page is not blocked by security checks.
+ */
+
+add_task(async function test_click_link_within_view_source() {
+ let TEST_FILE = "file_click_link_within_view_source.html";
+ let TEST_FILE_URI = getChromeDir(getResolvedURI(gTestPath));
+ TEST_FILE_URI.append(TEST_FILE);
+ TEST_FILE_URI = Services.io.newFileURI(TEST_FILE_URI).spec;
+
+ let DUMMY_FILE = "dummy_page.html";
+ let DUMMY_FILE_URI = getChromeDir(getResolvedURI(gTestPath));
+ DUMMY_FILE_URI.append(DUMMY_FILE);
+ DUMMY_FILE_URI = Services.io.newFileURI(DUMMY_FILE_URI).spec;
+
+ await BrowserTestUtils.withNewTab(TEST_FILE_URI, async function (aBrowser) {
+ let tabSpec = gBrowser.selectedBrowser.currentURI.spec;
+ info("loading: " + tabSpec);
+ ok(
+ tabSpec.startsWith("file://") && tabSpec.endsWith(TEST_FILE),
+ "sanity check to make sure html loaded"
+ );
+
+ info("click view-source of html");
+ let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser);
+ document.getElementById("View:PageSource").doCommand();
+
+ let tab = await tabPromise;
+ tabSpec = gBrowser.selectedBrowser.currentURI.spec;
+ info("loading: " + tabSpec);
+ ok(
+ tabSpec.startsWith("view-source:file://") && tabSpec.endsWith(TEST_FILE),
+ "loading view-source of html succeeded"
+ );
+
+ info("click testlink within view-source page");
+ let loadPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ url => url.endsWith("dummy_page.html")
+ );
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () {
+ if (content.document.readyState != "complete") {
+ await ContentTaskUtils.waitForEvent(
+ content.document,
+ "readystatechange",
+ false,
+ () => content.document.readyState == "complete"
+ );
+ }
+ // document.getElementById() does not work on a view-source page, hence we use document.links
+ let linksOnPage = content.document.links;
+ is(
+ linksOnPage.length,
+ 1,
+ "sanity check: make sure only one link is present on page"
+ );
+ let myLink = content.document.links[0];
+ myLink.click();
+ });
+
+ await loadPromise;
+
+ tabSpec = gBrowser.selectedBrowser.currentURI.spec;
+ info("loading: " + tabSpec);
+ ok(
+ tabSpec.startsWith("view-source:file://") && tabSpec.endsWith(DUMMY_FILE),
+ "loading view-source of html succeeded"
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ });
+});
diff --git a/docshell/test/browser/browser_cross_process_csp_inheritance.js b/docshell/test/browser/browser_cross_process_csp_inheritance.js
new file mode 100644
index 0000000000..76be61d0f9
--- /dev/null
+++ b/docshell/test/browser/browser_cross_process_csp_inheritance.js
@@ -0,0 +1,125 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com"
+);
+const TEST_URI = TEST_PATH + "file_cross_process_csp_inheritance.html";
+const DATA_URI =
+ "data:text/html,<html>test-same-diff-process-csp-inhertiance</html>";
+
+const FISSION_ENABLED = SpecialPowers.useRemoteSubframes;
+
+function getCurrentPID(aBrowser) {
+ return SpecialPowers.spawn(aBrowser, [], () => {
+ return Services.appinfo.processID;
+ });
+}
+
+function getCurrentURI(aBrowser) {
+ return SpecialPowers.spawn(aBrowser, [], () => {
+ let channel = content.docShell.currentDocumentChannel;
+ return channel.URI.asciiSpec;
+ });
+}
+
+function verifyResult(
+ aTestName,
+ aBrowser,
+ aDataURI,
+ aPID,
+ aSamePID,
+ aFissionEnabled
+) {
+ return SpecialPowers.spawn(
+ aBrowser,
+ [{ aTestName, aDataURI, aPID, aSamePID, aFissionEnabled }],
+ async function ({ aTestName, aDataURI, aPID, aSamePID, aFissionEnabled }) {
+ // sanity, to make sure the correct URI was loaded
+ let channel = content.docShell.currentDocumentChannel;
+ is(
+ channel.URI.asciiSpec,
+ aDataURI,
+ aTestName + ": correct data uri loaded"
+ );
+
+ // check that the process ID is the same/different when opening the new tab
+ let pid = Services.appinfo.processID;
+ if (aSamePID) {
+ is(pid, aPID, aTestName + ": process ID needs to be identical");
+ } else if (aFissionEnabled) {
+ // TODO: Fission discards dom.noopener.newprocess.enabled and puts
+ // data: URIs in the same process. Unfortunately todo_isnot is not
+ // defined in that scope, hence we have to use a workaround.
+ todo(
+ false,
+ pid == aPID,
+ ": process ID needs to be different in fission"
+ );
+ } else {
+ isnot(pid, aPID, aTestName + ": process ID needs to be different");
+ }
+
+ // finally, evaluate that the CSP was set.
+ let cspOBJ = JSON.parse(content.document.cspJSON);
+ let policies = cspOBJ["csp-policies"];
+ is(policies.length, 1, "should be one policy");
+ let policy = policies[0];
+ is(
+ policy["script-src"],
+ "'none'",
+ aTestName + ": script-src directive matches"
+ );
+ }
+ );
+}
+
+async function simulateCspInheritanceForNewTab(aTestName, aSamePID) {
+ await BrowserTestUtils.withNewTab(TEST_URI, async function (browser) {
+ // do some sanity checks
+ let currentURI = await getCurrentURI(gBrowser.selectedBrowser);
+ is(currentURI, TEST_URI, aTestName + ": correct test uri loaded");
+
+ let pid = await getCurrentPID(gBrowser.selectedBrowser);
+ let loadPromise = BrowserTestUtils.waitForNewTab(gBrowser, DATA_URI, true);
+ // simulate click
+ BrowserTestUtils.synthesizeMouseAtCenter(
+ "#testLink",
+ {},
+ gBrowser.selectedBrowser
+ );
+ let tab = await loadPromise;
+ gBrowser.selectTabAtIndex(2);
+ await verifyResult(
+ aTestName,
+ gBrowser.selectedBrowser,
+ DATA_URI,
+ pid,
+ aSamePID,
+ FISSION_ENABLED
+ );
+ await BrowserTestUtils.removeTab(tab);
+ });
+}
+
+add_task(async function test_csp_inheritance_diff_process() {
+ // forcing the new data: URI load to happen in a *new* process by flipping the pref
+ // to force <a rel="noopener" ...> to be loaded in a new process.
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.noopener.newprocess.enabled", true]],
+ });
+ await simulateCspInheritanceForNewTab("diff-process-inheritance", false);
+});
+
+add_task(async function test_csp_inheritance_same_process() {
+ // forcing the new data: URI load to happen in a *same* process by resetting the pref
+ // and loaded <a rel="noopener" ...> in the *same* process.
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.noopener.newprocess.enabled", false]],
+ });
+ await simulateCspInheritanceForNewTab("same-process-inheritance", true);
+});
diff --git a/docshell/test/browser/browser_csp_sandbox_no_script_js_uri.js b/docshell/test/browser/browser_csp_sandbox_no_script_js_uri.js
new file mode 100644
index 0000000000..d0b92084ec
--- /dev/null
+++ b/docshell/test/browser/browser_csp_sandbox_no_script_js_uri.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+
+/**
+ * Test that javascript URIs in CSP-sandboxed contexts can't be used to bypass
+ * script restrictions.
+ */
+add_task(async function test_csp_sandbox_no_script_js_uri() {
+ await BrowserTestUtils.withNewTab(
+ TEST_PATH + "dummy_page.html",
+ async browser => {
+ info("Register observer and wait for javascript-uri-blocked message.");
+ let observerPromise = SpecialPowers.spawn(browser, [], () => {
+ return new Promise(resolve => {
+ SpecialPowers.addObserver(function obs(subject) {
+ ok(
+ subject == content,
+ "Should block script spawned via javascript uri"
+ );
+ SpecialPowers.removeObserver(
+ obs,
+ "javascript-uri-blocked-by-sandbox"
+ );
+ resolve();
+ }, "javascript-uri-blocked-by-sandbox");
+ });
+ });
+
+ info("Spawn csp-sandboxed iframe with javascript URI");
+ let frameBC = await SpecialPowers.spawn(
+ browser,
+ [TEST_PATH + "file_csp_sandbox_no_script_js_uri.html"],
+ async url => {
+ let frame = content.document.createElement("iframe");
+ let loadPromise = ContentTaskUtils.waitForEvent(frame, "load", true);
+ frame.src = url;
+ content.document.body.appendChild(frame);
+ await loadPromise;
+ return frame.browsingContext;
+ }
+ );
+
+ info("Click javascript URI link in iframe");
+ BrowserTestUtils.synthesizeMouseAtCenter("a", {}, frameBC);
+ await observerPromise;
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_csp_uir.js b/docshell/test/browser/browser_csp_uir.js
new file mode 100644
index 0000000000..2eb36d3d4d
--- /dev/null
+++ b/docshell/test/browser/browser_csp_uir.js
@@ -0,0 +1,89 @@
+"use strict";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com"
+);
+const TEST_URI = TEST_PATH + "file_csp_uir.html"; // important to be http: to test upgrade-insecure-requests
+const RESULT_URI =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ TEST_PATH.replace("http://", "https://") + "file_csp_uir_dummy.html";
+
+function verifyCSP(aTestName, aBrowser, aResultURI) {
+ return SpecialPowers.spawn(
+ aBrowser,
+ [{ aTestName, aResultURI }],
+ async function ({ aTestName, aResultURI }) {
+ let channel = content.docShell.currentDocumentChannel;
+ is(channel.URI.asciiSpec, aResultURI, "testing CSP for " + aTestName);
+ }
+ );
+}
+
+add_task(async function test_csp_inheritance_regular_click() {
+ await BrowserTestUtils.withNewTab(TEST_URI, async function (browser) {
+ let loadPromise = BrowserTestUtils.browserLoaded(
+ browser,
+ false,
+ RESULT_URI
+ );
+ // set the data href + simulate click
+ BrowserTestUtils.synthesizeMouseAtCenter(
+ "#testlink",
+ {},
+ gBrowser.selectedBrowser
+ );
+ await loadPromise;
+ await verifyCSP("click()", gBrowser.selectedBrowser, RESULT_URI);
+ });
+});
+
+add_task(async function test_csp_inheritance_ctrl_click() {
+ await BrowserTestUtils.withNewTab(TEST_URI, async function (browser) {
+ let loadPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ RESULT_URI,
+ true
+ );
+ // set the data href + simulate ctrl+click
+ BrowserTestUtils.synthesizeMouseAtCenter(
+ "#testlink",
+ { ctrlKey: true, metaKey: true },
+ gBrowser.selectedBrowser
+ );
+ let tab = await loadPromise;
+ gBrowser.selectTabAtIndex(2);
+ await verifyCSP("ctrl-click()", gBrowser.selectedBrowser, RESULT_URI);
+ await BrowserTestUtils.removeTab(tab);
+ });
+});
+
+add_task(
+ async function test_csp_inheritance_right_click_open_link_in_new_tab() {
+ await BrowserTestUtils.withNewTab(TEST_URI, async function (browser) {
+ let loadPromise = BrowserTestUtils.waitForNewTab(gBrowser, RESULT_URI);
+ // set the data href + simulate right-click open link in tab
+ BrowserTestUtils.waitForEvent(document, "popupshown", false, event => {
+ // These are operations that must be executed synchronously with the event.
+ document.getElementById("context-openlinkintab").doCommand();
+ event.target.hidePopup();
+ return true;
+ });
+ BrowserTestUtils.synthesizeMouseAtCenter(
+ "#testlink",
+ { type: "contextmenu", button: 2 },
+ gBrowser.selectedBrowser
+ );
+
+ let tab = await loadPromise;
+ gBrowser.selectTabAtIndex(2);
+ await verifyCSP(
+ "right-click-open-in-new-tab()",
+ gBrowser.selectedBrowser,
+ RESULT_URI
+ );
+ await BrowserTestUtils.removeTab(tab);
+ });
+ }
+);
diff --git a/docshell/test/browser/browser_dataURI_unique_opaque_origin.js b/docshell/test/browser/browser_dataURI_unique_opaque_origin.js
new file mode 100644
index 0000000000..c46012fa82
--- /dev/null
+++ b/docshell/test/browser/browser_dataURI_unique_opaque_origin.js
@@ -0,0 +1,30 @@
+add_task(async function test_dataURI_unique_opaque_origin() {
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ let tab = BrowserTestUtils.addTab(gBrowser, "http://example.com");
+ let browser = tab.linkedBrowser;
+ await BrowserTestUtils.browserLoaded(browser);
+
+ let pagePrincipal = browser.contentPrincipal;
+ info("pagePrincial " + pagePrincipal.origin);
+
+ BrowserTestUtils.loadURIString(browser, "data:text/html,hi");
+ await BrowserTestUtils.browserLoaded(browser);
+
+ await SpecialPowers.spawn(
+ browser,
+ [{ principal: pagePrincipal }],
+ async function (args) {
+ info("data URI principal: " + content.document.nodePrincipal.origin);
+ Assert.ok(
+ content.document.nodePrincipal.isNullPrincipal,
+ "data: URI should have NullPrincipal."
+ );
+ Assert.ok(
+ !content.document.nodePrincipal.equals(args.principal),
+ "data: URI should have unique opaque origin."
+ );
+ }
+ );
+
+ gBrowser.removeTab(tab);
+});
diff --git a/docshell/test/browser/browser_data_load_inherit_csp.js b/docshell/test/browser/browser_data_load_inherit_csp.js
new file mode 100644
index 0000000000..b2bc86e0ea
--- /dev/null
+++ b/docshell/test/browser/browser_data_load_inherit_csp.js
@@ -0,0 +1,110 @@
+"use strict";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com"
+);
+const HTML_URI = TEST_PATH + "file_data_load_inherit_csp.html";
+const DATA_URI = "data:text/html;html,<html><body>foo</body></html>";
+
+function setDataHrefOnLink(aBrowser, aDataURI) {
+ return SpecialPowers.spawn(aBrowser, [aDataURI], function (uri) {
+ let link = content.document.getElementById("testlink");
+ link.href = uri;
+ });
+}
+
+function verifyCSP(aTestName, aBrowser, aDataURI) {
+ return SpecialPowers.spawn(
+ aBrowser,
+ [{ aTestName, aDataURI }],
+ async function ({ aTestName, aDataURI }) {
+ let channel = content.docShell.currentDocumentChannel;
+ is(channel.URI.spec, aDataURI, "testing CSP for " + aTestName);
+ let cspJSON = content.document.cspJSON;
+ let cspOBJ = JSON.parse(cspJSON);
+ let policies = cspOBJ["csp-policies"];
+ is(policies.length, 1, "should be one policy");
+ let policy = policies[0];
+ is(
+ policy["script-src"],
+ "'unsafe-inline'",
+ "script-src directive matches"
+ );
+ }
+ );
+}
+
+add_setup(async function () {
+ // allow top level data: URI navigations, otherwise clicking data: link fails
+ await SpecialPowers.pushPrefEnv({
+ set: [["security.data_uri.block_toplevel_data_uri_navigations", false]],
+ });
+});
+
+add_task(async function test_data_csp_inheritance_regular_click() {
+ await BrowserTestUtils.withNewTab(HTML_URI, async function (browser) {
+ let loadPromise = BrowserTestUtils.browserLoaded(browser, false, DATA_URI);
+ // set the data href + simulate click
+ await setDataHrefOnLink(gBrowser.selectedBrowser, DATA_URI);
+ BrowserTestUtils.synthesizeMouseAtCenter(
+ "#testlink",
+ {},
+ gBrowser.selectedBrowser
+ );
+ await loadPromise;
+ await verifyCSP("click()", gBrowser.selectedBrowser, DATA_URI);
+ });
+});
+
+add_task(async function test_data_csp_inheritance_ctrl_click() {
+ await BrowserTestUtils.withNewTab(HTML_URI, async function (browser) {
+ let loadPromise = BrowserTestUtils.waitForNewTab(gBrowser, DATA_URI, true);
+ // set the data href + simulate ctrl+click
+ await setDataHrefOnLink(gBrowser.selectedBrowser, DATA_URI);
+ BrowserTestUtils.synthesizeMouseAtCenter(
+ "#testlink",
+ { ctrlKey: true, metaKey: true },
+ gBrowser.selectedBrowser
+ );
+ let tab = await loadPromise;
+ gBrowser.selectTabAtIndex(2);
+ await verifyCSP("ctrl-click()", gBrowser.selectedBrowser, DATA_URI);
+ await BrowserTestUtils.removeTab(tab);
+ });
+});
+
+add_task(
+ async function test_data_csp_inheritance_right_click_open_link_in_new_tab() {
+ await BrowserTestUtils.withNewTab(HTML_URI, async function (browser) {
+ let loadPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ DATA_URI,
+ true
+ );
+ // set the data href + simulate right-click open link in tab
+ await setDataHrefOnLink(gBrowser.selectedBrowser, DATA_URI);
+ BrowserTestUtils.waitForEvent(document, "popupshown", false, event => {
+ // These are operations that must be executed synchronously with the event.
+ document.getElementById("context-openlinkintab").doCommand();
+ event.target.hidePopup();
+ return true;
+ });
+ BrowserTestUtils.synthesizeMouseAtCenter(
+ "#testlink",
+ { type: "contextmenu", button: 2 },
+ gBrowser.selectedBrowser
+ );
+
+ let tab = await loadPromise;
+ gBrowser.selectTabAtIndex(2);
+ await verifyCSP(
+ "right-click-open-in-new-tab()",
+ gBrowser.selectedBrowser,
+ DATA_URI
+ );
+ await BrowserTestUtils.removeTab(tab);
+ });
+ }
+);
diff --git a/docshell/test/browser/browser_fall_back_to_https.js b/docshell/test/browser/browser_fall_back_to_https.js
new file mode 100644
index 0000000000..b84780eec1
--- /dev/null
+++ b/docshell/test/browser/browser_fall_back_to_https.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/*
+ * This test is for bug 1002724.
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1002724
+ *
+ * When a user enters a host name or IP address in the URL bar, "http" is
+ * assumed. If the host rejects connections on port 80, we try HTTPS as a
+ * fall-back and only fail if HTTPS connection fails.
+ *
+ * This tests that when a user enters "example.com", it attempts to load
+ * http://example.com:80 (not rejected), and when trying secureonly.example.com
+ * (which rejects connections on port 80), it fails then loads
+ * https://secureonly.example.com:443 instead.
+ */
+
+const { UrlbarTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/UrlbarTestUtils.sys.mjs"
+);
+
+const bug1002724_tests = [
+ {
+ original: "example.com",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ expected: "http://example.com",
+ explanation: "Should load HTTP version of example.com",
+ },
+ {
+ original: "secureonly.example.com",
+ expected: "https://secureonly.example.com",
+ explanation:
+ "Should reject secureonly.example.com on HTTP but load the HTTPS version",
+ },
+];
+
+async function test_one(test_obj) {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+ gURLBar.focus();
+ gURLBar.value = test_obj.original;
+
+ let loadPromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await loadPromise;
+
+ ok(
+ tab.linkedBrowser.currentURI.spec.startsWith(test_obj.expected),
+ test_obj.explanation
+ );
+
+ BrowserTestUtils.removeTab(tab);
+}
+
+add_task(async function test_bug1002724() {
+ await SpecialPowers.pushPrefEnv(
+ // Disable HSTS preload just in case.
+ {
+ set: [
+ ["network.stricttransportsecurity.preloadlist", false],
+ ["network.dns.native-is-localhost", true],
+ ],
+ }
+ );
+
+ for (let test of bug1002724_tests) {
+ await test_one(test);
+ }
+});
diff --git a/docshell/test/browser/browser_frameloader_swap_with_bfcache.js b/docshell/test/browser/browser_frameloader_swap_with_bfcache.js
new file mode 100644
index 0000000000..2f7d494845
--- /dev/null
+++ b/docshell/test/browser/browser_frameloader_swap_with_bfcache.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+add_task(async function test() {
+ if (
+ !SpecialPowers.Services.appinfo.sessionHistoryInParent ||
+ !SpecialPowers.Services.prefs.getBoolPref("fission.bfcacheInParent")
+ ) {
+ ok(
+ true,
+ "This test is currently only for the bfcache in the parent process."
+ );
+ return;
+ }
+ const uri =
+ "data:text/html,<script>onpageshow = function(e) { document.documentElement.setAttribute('persisted', e.persisted); }; </script>";
+ let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, uri, true);
+ let browser1 = tab1.linkedBrowser;
+
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, uri, true);
+ let browser2 = tab2.linkedBrowser;
+ BrowserTestUtils.loadURIString(browser2, uri + "nextpage");
+ await BrowserTestUtils.browserLoaded(browser2, false);
+
+ gBrowser.swapBrowsersAndCloseOther(tab1, tab2);
+ is(tab1.linkedBrowser, browser1, "Tab's browser should stay the same.");
+ browser1.goBack(false);
+ await BrowserTestUtils.browserLoaded(browser1, false);
+ let persisted = await SpecialPowers.spawn(browser1, [], async function () {
+ return content.document.documentElement.getAttribute("persisted");
+ });
+
+ is(persisted, "false", "BFCache should be evicted when swapping browsers.");
+
+ BrowserTestUtils.removeTab(tab1);
+});
diff --git a/docshell/test/browser/browser_history_triggeringprincipal_viewsource.js b/docshell/test/browser/browser_history_triggeringprincipal_viewsource.js
new file mode 100644
index 0000000000..d542ef892c
--- /dev/null
+++ b/docshell/test/browser/browser_history_triggeringprincipal_viewsource.js
@@ -0,0 +1,88 @@
+"use strict";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com"
+);
+const HTML_URI = TEST_PATH + "dummy_page.html";
+const VIEW_SRC_URI = "view-source:" + HTML_URI;
+
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.navigation.requireUserInteraction", false]],
+ });
+
+ info("load baseline html in new tab");
+ await BrowserTestUtils.withNewTab(HTML_URI, async function (aBrowser) {
+ is(
+ gBrowser.selectedBrowser.currentURI.spec,
+ HTML_URI,
+ "sanity check to make sure html loaded"
+ );
+
+ info("right-click -> view-source of html");
+ let vSrcCtxtMenu = document.getElementById("contentAreaContextMenu");
+ let popupPromise = BrowserTestUtils.waitForEvent(
+ vSrcCtxtMenu,
+ "popupshown"
+ );
+ BrowserTestUtils.synthesizeMouseAtCenter(
+ "body",
+ { type: "contextmenu", button: 2 },
+ aBrowser
+ );
+ await popupPromise;
+ let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, VIEW_SRC_URI);
+ let vSrcItem = vSrcCtxtMenu.querySelector("#context-viewsource");
+ vSrcCtxtMenu.activateItem(vSrcItem);
+ let tab = await tabPromise;
+ is(
+ gBrowser.selectedBrowser.currentURI.spec,
+ VIEW_SRC_URI,
+ "loading view-source of html succeeded"
+ );
+
+ info("load html file again before going .back()");
+ let loadPromise = BrowserTestUtils.browserLoaded(
+ tab.linkedBrowser,
+ false,
+ HTML_URI
+ );
+ await SpecialPowers.spawn(tab.linkedBrowser, [HTML_URI], HTML_URI => {
+ content.document.location = HTML_URI;
+ });
+ await loadPromise;
+ is(
+ gBrowser.selectedBrowser.currentURI.spec,
+ HTML_URI,
+ "loading html another time succeeded"
+ );
+
+ info(
+ "click .back() to view-source of html again and make sure the history entry has a triggeringPrincipal"
+ );
+ let backCtxtMenu = document.getElementById("contentAreaContextMenu");
+ popupPromise = BrowserTestUtils.waitForEvent(backCtxtMenu, "popupshown");
+ BrowserTestUtils.synthesizeMouseAtCenter(
+ "body",
+ { type: "contextmenu", button: 2 },
+ aBrowser
+ );
+ await popupPromise;
+ loadPromise = BrowserTestUtils.waitForContentEvent(
+ tab.linkedBrowser,
+ "pageshow"
+ );
+ let backItem = backCtxtMenu.querySelector("#context-back");
+ backCtxtMenu.activateItem(backItem);
+ await loadPromise;
+ is(
+ gBrowser.selectedBrowser.currentURI.spec,
+ VIEW_SRC_URI,
+ "clicking .back() to view-source of html succeeded"
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ });
+});
diff --git a/docshell/test/browser/browser_isInitialDocument.js b/docshell/test/browser/browser_isInitialDocument.js
new file mode 100644
index 0000000000..7a047c61d0
--- /dev/null
+++ b/docshell/test/browser/browser_isInitialDocument.js
@@ -0,0 +1,319 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tag every new WindowGlobalParent with an expando indicating whether or not
+// they were an initial document when they were created for the duration of this
+// test.
+function wasInitialDocumentObserver(subject) {
+ subject._test_wasInitialDocument = subject.isInitialDocument;
+}
+Services.obs.addObserver(wasInitialDocumentObserver, "window-global-created");
+SimpleTest.registerCleanupFunction(function () {
+ Services.obs.removeObserver(
+ wasInitialDocumentObserver,
+ "window-global-created"
+ );
+});
+
+add_task(async function new_about_blank_tab() {
+ await BrowserTestUtils.withNewTab("about:blank", async browser => {
+ is(
+ browser.browsingContext.currentWindowGlobal.isInitialDocument,
+ false,
+ "After loading an actual, final about:blank in the tab, the field is false"
+ );
+ });
+});
+
+add_task(async function iframe_initial_about_blank() {
+ await BrowserTestUtils.withNewTab(
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/document-builder.sjs?html=com",
+ async browser => {
+ info("Create an iframe without any explicit location");
+ await SpecialPowers.spawn(browser, [], async () => {
+ const iframe = content.document.createElement("iframe");
+ // Add the iframe to the DOM tree in order to be able to have its browsingContext
+ content.document.body.appendChild(iframe);
+ const { browsingContext } = iframe;
+
+ is(
+ iframe.contentDocument.isInitialDocument,
+ true,
+ "The field is true on just-created iframes"
+ );
+ let beforeLoadPromise = SpecialPowers.spawnChrome(
+ [browsingContext],
+ bc => [
+ bc.currentWindowGlobal.isInitialDocument,
+ bc.currentWindowGlobal._test_wasInitialDocument,
+ ]
+ );
+
+ await new Promise(resolve => {
+ iframe.addEventListener("load", resolve, { once: true });
+ });
+ is(
+ iframe.contentDocument.isInitialDocument,
+ false,
+ "The field is false after having loaded the final about:blank document"
+ );
+ let afterLoadPromise = SpecialPowers.spawnChrome(
+ [browsingContext],
+ bc => [
+ bc.currentWindowGlobal.isInitialDocument,
+ bc.currentWindowGlobal._test_wasInitialDocument,
+ ]
+ );
+
+ // Wait to await the parent process promises, so we can't miss the "load" event.
+ let [beforeIsInitial, beforeWasInitial] = await beforeLoadPromise;
+ is(beforeIsInitial, true, "before load is initial in parent");
+ is(beforeWasInitial, true, "before load was initial in parent");
+ let [afterIsInitial, afterWasInitial] = await afterLoadPromise;
+ is(afterIsInitial, false, "after load is not initial in parent");
+ is(afterWasInitial, true, "after load was initial in parent");
+ iframe.remove();
+ });
+
+ info("Create an iframe with a cross origin location");
+ const iframeBC = await SpecialPowers.spawn(browser, [], async () => {
+ const iframe = content.document.createElement("iframe");
+ await new Promise(resolve => {
+ iframe.addEventListener("load", resolve, { once: true });
+ iframe.src =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.org/document-builder.sjs?html=org-iframe";
+ content.document.body.appendChild(iframe);
+ });
+
+ return iframe.browsingContext;
+ });
+
+ is(
+ iframeBC.currentWindowGlobal.isInitialDocument,
+ false,
+ "The field is true after having loaded the final document"
+ );
+ }
+ );
+});
+
+add_task(async function window_open() {
+ async function testWindowOpen({ browser, args, isCrossOrigin, willLoad }) {
+ info(`Open popup with ${JSON.stringify(args)}`);
+ const onNewTab = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ args[0] || "about:blank"
+ );
+ await SpecialPowers.spawn(
+ browser,
+ [args, isCrossOrigin, willLoad],
+ async (args, crossOrigin, willLoad) => {
+ const win = content.window.open(...args);
+ is(
+ win.document.isInitialDocument,
+ true,
+ "The field is true right after calling window.open()"
+ );
+ let beforeLoadPromise = SpecialPowers.spawnChrome(
+ [win.browsingContext],
+ bc => [
+ bc.currentWindowGlobal.isInitialDocument,
+ bc.currentWindowGlobal._test_wasInitialDocument,
+ ]
+ );
+
+ // In cross origin, it is harder to watch for new document load, and if
+ // no argument is passed no load will happen.
+ if (!crossOrigin && willLoad) {
+ await new Promise(r =>
+ win.addEventListener("load", r, { once: true })
+ );
+ is(
+ win.document.isInitialDocument,
+ false,
+ "The field becomes false right after the popup document is loaded"
+ );
+ }
+
+ // Perform the await after the load to avoid missing it.
+ let [beforeIsInitial, beforeWasInitial] = await beforeLoadPromise;
+ is(beforeIsInitial, true, "before load is initial in parent");
+ is(beforeWasInitial, true, "before load was initial in parent");
+ }
+ );
+ const newTab = await onNewTab;
+ const windowGlobal =
+ newTab.linkedBrowser.browsingContext.currentWindowGlobal;
+ if (willLoad) {
+ is(
+ windowGlobal.isInitialDocument,
+ false,
+ "The field is false in the parent process after having loaded the final document"
+ );
+ } else {
+ is(
+ windowGlobal.isInitialDocument,
+ true,
+ "The field remains true in the parent process as nothing will be loaded"
+ );
+ }
+ BrowserTestUtils.removeTab(newTab);
+ }
+
+ await BrowserTestUtils.withNewTab(
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/document-builder.sjs?html=com",
+ async browser => {
+ info("Use window.open() with cross-origin document");
+ await testWindowOpen({
+ browser,
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ args: ["http://example.org/document-builder.sjs?html=org-popup"],
+ isCrossOrigin: true,
+ willLoad: true,
+ });
+
+ info("Use window.open() with same-origin document");
+ await testWindowOpen({
+ browser,
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ args: ["http://example.com/document-builder.sjs?html=com-popup"],
+ isCrossOrigin: false,
+ willLoad: true,
+ });
+
+ info("Use window.open() with final about:blank document");
+ await testWindowOpen({
+ browser,
+ args: ["about:blank"],
+ isCrossOrigin: false,
+ willLoad: true,
+ });
+
+ info("Use window.open() with no argument");
+ await testWindowOpen({
+ browser,
+ args: [],
+ isCrossOrigin: false,
+ willLoad: false,
+ });
+ }
+ );
+});
+
+add_task(async function document_open() {
+ await BrowserTestUtils.withNewTab(
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/document-builder.sjs?html=com",
+ async browser => {
+ is(browser.browsingContext.currentWindowGlobal.isInitialDocument, false);
+ await SpecialPowers.spawn(browser, [], async () => {
+ const iframe = content.document.createElement("iframe");
+ // Add the iframe to the DOM tree in order to be able to have its browsingContext
+ content.document.body.appendChild(iframe);
+ const { browsingContext } = iframe;
+
+ // Check the state before the call in both parent and content.
+ is(
+ iframe.contentDocument.isInitialDocument,
+ true,
+ "Is an initial document before calling document.open"
+ );
+ let beforeOpenParentPromise = SpecialPowers.spawnChrome(
+ [browsingContext],
+ bc => [
+ bc.currentWindowGlobal.isInitialDocument,
+ bc.currentWindowGlobal._test_wasInitialDocument,
+ bc.currentWindowGlobal.innerWindowId,
+ ]
+ );
+
+ // Run the `document.open` call with reduced permissions.
+ iframe.contentWindow.eval(`
+ document.open();
+ document.write("new document");
+ document.close();
+ `);
+
+ is(
+ iframe.contentDocument.isInitialDocument,
+ false,
+ "Is no longer an initial document after calling document.open"
+ );
+ let [afterIsInitial, afterWasInitial, afterID] =
+ await SpecialPowers.spawnChrome([browsingContext], bc => [
+ bc.currentWindowGlobal.isInitialDocument,
+ bc.currentWindowGlobal._test_wasInitialDocument,
+ bc.currentWindowGlobal.innerWindowId,
+ ]);
+ let [beforeIsInitial, beforeWasInitial, beforeID] =
+ await beforeOpenParentPromise;
+ is(beforeIsInitial, true, "Should be initial before in the parent");
+ is(beforeWasInitial, true, "Was initial before in the parent");
+ is(afterIsInitial, false, "Should not be initial after in the parent");
+ is(afterWasInitial, true, "Was initial after in the parent");
+ is(beforeID, afterID, "Should be the same WindowGlobalParent");
+ });
+ }
+ );
+});
+
+add_task(async function windowless_browser() {
+ info("Create a Windowless browser");
+ const browser = Services.appShell.createWindowlessBrowser(false);
+ const { browsingContext } = browser;
+ is(
+ browsingContext.currentWindowGlobal.isInitialDocument,
+ true,
+ "The field is true for a freshly created WindowlessBrowser"
+ );
+ is(
+ browser.currentURI.spec,
+ "about:blank",
+ "The location is immediately set to about:blank"
+ );
+
+ const principal = Services.scriptSecurityManager.getSystemPrincipal();
+ browser.docShell.createAboutBlankContentViewer(principal, principal);
+ is(
+ browsingContext.currentWindowGlobal.isInitialDocument,
+ false,
+ "The field becomes false when creating an artificial blank document"
+ );
+
+ info("Load a final about:blank document in it");
+ const onLocationChange = new Promise(resolve => {
+ let wpl = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+ onLocationChange() {
+ browsingContext.webProgress.removeProgressListener(
+ wpl,
+ Ci.nsIWebProgress.NOTIFY_ALL
+ );
+ resolve();
+ },
+ };
+ browsingContext.webProgress.addProgressListener(
+ wpl,
+ Ci.nsIWebProgress.NOTIFY_ALL
+ );
+ });
+ browser.loadURI(Services.io.newURI("about:blank"), {
+ triggeringPrincipal: principal,
+ });
+ info("Wait for the location change");
+ await onLocationChange;
+ is(
+ browsingContext.currentWindowGlobal.isInitialDocument,
+ false,
+ "The field is false after the location change event"
+ );
+ browser.close();
+});
diff --git a/docshell/test/browser/browser_loadURI_postdata.js b/docshell/test/browser/browser_loadURI_postdata.js
new file mode 100644
index 0000000000..b2dbb7a641
--- /dev/null
+++ b/docshell/test/browser/browser_loadURI_postdata.js
@@ -0,0 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const gPostData = "postdata=true";
+const gUrl =
+ "http://mochi.test:8888/browser/docshell/test/browser/print_postdata.sjs";
+
+add_task(async function test_loadURI_persists_postData() {
+ waitForExplicitFinish();
+
+ let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser));
+ registerCleanupFunction(function () {
+ gBrowser.removeTab(tab);
+ });
+
+ var dataStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
+ Ci.nsIStringInputStream
+ );
+ dataStream.data = gPostData;
+
+ var postStream = Cc[
+ "@mozilla.org/network/mime-input-stream;1"
+ ].createInstance(Ci.nsIMIMEInputStream);
+ postStream.addHeader("Content-Type", "application/x-www-form-urlencoded");
+ postStream.setData(dataStream);
+ var systemPrincipal = Cc["@mozilla.org/systemprincipal;1"].getService(
+ Ci.nsIPrincipal
+ );
+
+ tab.linkedBrowser.loadURI(Services.io.newURI(gUrl), {
+ triggeringPrincipal: systemPrincipal,
+ postData: postStream,
+ });
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, gUrl);
+ let body = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ () => content.document.body.textContent
+ );
+ is(body, gPostData, "post data was submitted correctly");
+ finish();
+});
diff --git a/docshell/test/browser/browser_multiple_pushState.js b/docshell/test/browser/browser_multiple_pushState.js
new file mode 100644
index 0000000000..5d07747543
--- /dev/null
+++ b/docshell/test/browser/browser_multiple_pushState.js
@@ -0,0 +1,25 @@
+add_task(async function test_multiple_pushState() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url:
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.org/browser/docshell/test/browser/file_multiple_pushState.html",
+ },
+ async function (browser) {
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ const kExpected = "http://example.org/bar/ABC/DEF?key=baz";
+
+ let contentLocation = await SpecialPowers.spawn(
+ browser,
+ [],
+ async function () {
+ return content.document.location.href;
+ }
+ );
+
+ is(contentLocation, kExpected);
+ is(browser.documentURI.spec, kExpected);
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_onbeforeunload_frame.js b/docshell/test/browser/browser_onbeforeunload_frame.js
new file mode 100644
index 0000000000..d697af718d
--- /dev/null
+++ b/docshell/test/browser/browser_onbeforeunload_frame.js
@@ -0,0 +1,45 @@
+"use strict";
+
+// We need to test a lot of permutations here, and there isn't any sensible way
+// to split them up or run them faster.
+requestLongerTimeout(12);
+
+Services.scriptloader.loadSubScript(
+ getRootDirectory(gTestPath) + "head_browser_onbeforeunload.js",
+ this
+);
+
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.require_user_interaction_for_beforeunload", false]],
+ });
+
+ for (let actions of PERMUTATIONS) {
+ info(
+ `Testing frame actions: [${actions.map(action =>
+ ACTION_NAMES.get(action)
+ )}]`
+ );
+
+ for (let startIdx = 0; startIdx < FRAMES.length; startIdx++) {
+ info(`Testing content reload from frame ${startIdx}`);
+
+ await doTest(actions, startIdx, (tab, frames) => {
+ return SpecialPowers.spawn(frames[startIdx], [], () => {
+ let eventLoopSpun = false;
+ SpecialPowers.Services.tm.dispatchToMainThread(() => {
+ eventLoopSpun = true;
+ });
+
+ content.location.reload();
+
+ return { eventLoopSpun };
+ });
+ });
+ }
+ }
+});
+
+add_task(async function cleanup() {
+ await TabPool.cleanup();
+});
diff --git a/docshell/test/browser/browser_onbeforeunload_navigation.js b/docshell/test/browser/browser_onbeforeunload_navigation.js
new file mode 100644
index 0000000000..23dc3b528e
--- /dev/null
+++ b/docshell/test/browser/browser_onbeforeunload_navigation.js
@@ -0,0 +1,165 @@
+"use strict";
+
+const TEST_PAGE =
+ "http://mochi.test:8888/browser/docshell/test/browser/file_bug1046022.html";
+const TARGETED_PAGE =
+ "data:text/html," +
+ encodeURIComponent("<body>Shouldn't be seeing this</body>");
+
+const { PromptTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromptTestUtils.sys.mjs"
+);
+
+var loadStarted = false;
+var tabStateListener = {
+ resolveLoad: null,
+ expectLoad: null,
+
+ onStateChange(webprogress, request, flags, status) {
+ const WPL = Ci.nsIWebProgressListener;
+ if (flags & WPL.STATE_IS_WINDOW) {
+ if (flags & WPL.STATE_START) {
+ loadStarted = true;
+ } else if (flags & WPL.STATE_STOP) {
+ let url = request.QueryInterface(Ci.nsIChannel).URI.spec;
+ is(url, this.expectLoad, "Should only see expected document loads");
+ if (url == this.expectLoad) {
+ this.resolveLoad();
+ }
+ }
+ }
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+};
+
+function promiseLoaded(url, callback) {
+ if (tabStateListener.expectLoad) {
+ throw new Error("Can't wait for multiple loads at once");
+ }
+ tabStateListener.expectLoad = url;
+ return new Promise(resolve => {
+ tabStateListener.resolveLoad = resolve;
+ if (callback) {
+ callback();
+ }
+ }).then(() => {
+ tabStateListener.expectLoad = null;
+ tabStateListener.resolveLoad = null;
+ });
+}
+
+function promiseStayOnPagePrompt(browser, acceptNavigation) {
+ return PromptTestUtils.handleNextPrompt(
+ browser,
+ { modalType: Services.prompt.MODAL_TYPE_CONTENT, promptType: "confirmEx" },
+ { buttonNumClick: acceptNavigation ? 0 : 1 }
+ );
+}
+
+add_task(async function test() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.require_user_interaction_for_beforeunload", false]],
+ });
+
+ let testTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_PAGE,
+ false,
+ true
+ );
+ let browser = testTab.linkedBrowser;
+ browser.addProgressListener(
+ tabStateListener,
+ Ci.nsIWebProgress.NOTIFY_STATE_WINDOW
+ );
+
+ const NUM_TESTS = 7;
+ await SpecialPowers.spawn(browser, [NUM_TESTS], testCount => {
+ let { testFns } = this.content.wrappedJSObject;
+ Assert.equal(
+ testFns.length,
+ testCount,
+ "Should have the correct number of test functions"
+ );
+ });
+
+ for (let allowNavigation of [false, true]) {
+ for (let i = 0; i < NUM_TESTS; i++) {
+ info(
+ `Running test ${i} with navigation ${
+ allowNavigation ? "allowed" : "forbidden"
+ }`
+ );
+
+ if (allowNavigation) {
+ // If we're allowing navigations, we need to re-load the test
+ // page after each test, since the tests will each navigate away
+ // from it.
+ await promiseLoaded(TEST_PAGE, () => {
+ browser.loadURI(Services.io.newURI(TEST_PAGE), {
+ triggeringPrincipal: document.nodePrincipal,
+ });
+ });
+ }
+
+ let promptPromise = promiseStayOnPagePrompt(browser, allowNavigation);
+ let loadPromise;
+ if (allowNavigation) {
+ loadPromise = promiseLoaded(TARGETED_PAGE);
+ }
+
+ let winID = await SpecialPowers.spawn(
+ browser,
+ [i, TARGETED_PAGE],
+ (testIdx, url) => {
+ let { testFns } = this.content.wrappedJSObject;
+ this.content.onbeforeunload = testFns[testIdx];
+ this.content.location = url;
+ return this.content.windowGlobalChild.innerWindowId;
+ }
+ );
+
+ await promptPromise;
+ await loadPromise;
+
+ if (allowNavigation) {
+ await SpecialPowers.spawn(
+ browser,
+ [TARGETED_PAGE, winID],
+ (url, winID) => {
+ this.content.onbeforeunload = null;
+ Assert.equal(
+ this.content.location.href,
+ url,
+ "Page should have navigated to the correct URL"
+ );
+ Assert.notEqual(
+ this.content.windowGlobalChild.innerWindowId,
+ winID,
+ "Page should have a new inner window"
+ );
+ }
+ );
+ } else {
+ await SpecialPowers.spawn(browser, [TEST_PAGE, winID], (url, winID) => {
+ this.content.onbeforeunload = null;
+ Assert.equal(
+ this.content.location.href,
+ url,
+ "Page should have the same URL"
+ );
+ Assert.equal(
+ this.content.windowGlobalChild.innerWindowId,
+ winID,
+ "Page should have the same inner window"
+ );
+ });
+ }
+ }
+ }
+
+ gBrowser.removeTab(testTab);
+});
diff --git a/docshell/test/browser/browser_onbeforeunload_parent.js b/docshell/test/browser/browser_onbeforeunload_parent.js
new file mode 100644
index 0000000000..72c5004334
--- /dev/null
+++ b/docshell/test/browser/browser_onbeforeunload_parent.js
@@ -0,0 +1,48 @@
+"use strict";
+
+// We need to test a lot of permutations here, and there isn't any sensible way
+// to split them up or run them faster.
+requestLongerTimeout(6);
+
+Services.scriptloader.loadSubScript(
+ getRootDirectory(gTestPath) + "head_browser_onbeforeunload.js",
+ this
+);
+
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.require_user_interaction_for_beforeunload", false]],
+ });
+
+ for (let actions of PERMUTATIONS) {
+ info(
+ `Testing frame actions: [${actions.map(action =>
+ ACTION_NAMES.get(action)
+ )}]`
+ );
+
+ info(`Testing tab close from parent process`);
+ await doTest(actions, -1, (tab, frames) => {
+ let eventLoopSpun = false;
+ Services.tm.dispatchToMainThread(() => {
+ eventLoopSpun = true;
+ });
+
+ BrowserTestUtils.removeTab(tab);
+
+ let result = { eventLoopSpun };
+
+ // Make an extra couple of trips through the event loop to give us time
+ // to process SpecialPowers.spawn responses before resolving.
+ return new Promise(resolve => {
+ executeSoon(() => {
+ executeSoon(() => resolve(result));
+ });
+ });
+ });
+ }
+});
+
+add_task(async function cleanup() {
+ await TabPool.cleanup();
+});
diff --git a/docshell/test/browser/browser_onunload_stop.js b/docshell/test/browser/browser_onunload_stop.js
new file mode 100644
index 0000000000..24b2c6aab7
--- /dev/null
+++ b/docshell/test/browser/browser_onunload_stop.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PAGE_1 =
+ "http://mochi.test:8888/browser/docshell/test/browser/dummy_page.html";
+
+const TEST_PAGE_2 =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/browser/docshell/test/browser/dummy_page.html";
+
+add_task(async function test() {
+ await BrowserTestUtils.withNewTab(TEST_PAGE_1, async function (browser) {
+ let loaded = BrowserTestUtils.browserLoaded(browser, false, TEST_PAGE_2);
+ await SpecialPowers.spawn(browser, [], () => {
+ content.addEventListener("unload", e => e.currentTarget.stop(), true);
+ });
+ BrowserTestUtils.loadURIString(browser, TEST_PAGE_2);
+ await loaded;
+ ok(true, "Page loaded successfully");
+ });
+});
diff --git a/docshell/test/browser/browser_overlink.js b/docshell/test/browser/browser_overlink.js
new file mode 100644
index 0000000000..0bd88f697a
--- /dev/null
+++ b/docshell/test/browser/browser_overlink.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+
+add_task(async function test_stripAuthCredentials() {
+ await BrowserTestUtils.withNewTab(
+ TEST_PATH + "overlink_test.html",
+ async function (browser) {
+ await SpecialPowers.spawn(browser, [], function () {
+ content.document.getElementById("link").focus();
+ });
+
+ await TestUtils.waitForCondition(
+ () => XULBrowserWindow.overLink == "https://example.com",
+ "Overlink should be missing auth credentials"
+ );
+
+ ok(true, "Test successful");
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_platform_emulation.js b/docshell/test/browser/browser_platform_emulation.js
new file mode 100644
index 0000000000..017b615e15
--- /dev/null
+++ b/docshell/test/browser/browser_platform_emulation.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const URL = "data:text/html;charset=utf-8,<iframe id='test-iframe'></iframe>";
+
+async function contentTaskNoOverride() {
+ let docshell = docShell;
+ is(
+ docshell.browsingContext.customPlatform,
+ "",
+ "There should initially be no customPlatform"
+ );
+}
+
+async function contentTaskOverride() {
+ let docshell = docShell;
+ is(
+ docshell.browsingContext.customPlatform,
+ "foo",
+ "The platform should be changed to foo"
+ );
+
+ is(
+ content.navigator.platform,
+ "foo",
+ "The platform should be changed to foo"
+ );
+
+ let frameWin = content.document.querySelector("#test-iframe").contentWindow;
+ is(
+ frameWin.navigator.platform,
+ "foo",
+ "The platform should be passed on to frames."
+ );
+
+ let newFrame = content.document.createElement("iframe");
+ content.document.body.appendChild(newFrame);
+
+ let newFrameWin = newFrame.contentWindow;
+ is(
+ newFrameWin.navigator.platform,
+ "foo",
+ "Newly created frames should use the new platform"
+ );
+
+ newFrameWin.location.reload();
+ await ContentTaskUtils.waitForEvent(newFrame, "load");
+
+ is(
+ newFrameWin.navigator.platform,
+ "foo",
+ "New platform should persist across reloads"
+ );
+}
+
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: URL },
+ async function (browser) {
+ await SpecialPowers.spawn(browser, [], contentTaskNoOverride);
+
+ let browsingContext = BrowserTestUtils.getBrowsingContextFrom(browser);
+ browsingContext.customPlatform = "foo";
+
+ await SpecialPowers.spawn(browser, [], contentTaskOverride);
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_search_notification.js b/docshell/test/browser/browser_search_notification.js
new file mode 100644
index 0000000000..f98ad22a40
--- /dev/null
+++ b/docshell/test/browser/browser_search_notification.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { SearchTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/SearchTestUtils.sys.mjs"
+);
+
+SearchTestUtils.init(this);
+
+add_task(async function () {
+ // Our search would be handled by the urlbar normally and not by the docshell,
+ // thus we must force going through dns first, so that the urlbar thinks
+ // the value may be a url, and asks the docshell to visit it.
+ // On NS_ERROR_UNKNOWN_HOST the docshell will fix it up.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.fixup.dns_first_for_single_words", true]],
+ });
+ const kSearchEngineID = "test_urifixup_search_engine";
+ await SearchTestUtils.installSearchExtension(
+ {
+ name: kSearchEngineID,
+ search_url: "http://localhost/",
+ search_url_get_params: "search={searchTerms}",
+ },
+ { setAsDefault: true }
+ );
+
+ let selectedName = (await Services.search.getDefault()).name;
+ Assert.equal(
+ selectedName,
+ kSearchEngineID,
+ "Check fake search engine is selected"
+ );
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ gBrowser.selectedTab = tab;
+
+ gURLBar.value = "firefox";
+ gURLBar.handleCommand();
+
+ let [subject, data] = await TestUtils.topicObserved("keyword-search");
+
+ let engine = subject.QueryInterface(Ci.nsISupportsString).data;
+
+ Assert.equal(engine, kSearchEngineID, "Should be the search engine id");
+ Assert.equal(data, "firefox", "Notification data is search term.");
+
+ gBrowser.removeTab(tab);
+});
diff --git a/docshell/test/browser/browser_tab_replace_while_loading.js b/docshell/test/browser/browser_tab_replace_while_loading.js
new file mode 100644
index 0000000000..3c85f72192
--- /dev/null
+++ b/docshell/test/browser/browser_tab_replace_while_loading.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* Test for bug 1578379. */
+
+add_task(async function test_window_open_about_blank() {
+ const URL =
+ "http://mochi.test:8888/browser/docshell/test/browser/file_open_about_blank.html";
+ let firstTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL);
+ let promiseTabOpened = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ "about:blank"
+ );
+
+ info("Opening about:blank using a click");
+ await SpecialPowers.spawn(firstTab.linkedBrowser, [""], async function () {
+ content.document.querySelector("#open").click();
+ });
+
+ info("Waiting for the second tab to be opened");
+ let secondTab = await promiseTabOpened;
+
+ info("Detaching tab");
+ let windowOpenedPromise = BrowserTestUtils.waitForNewWindow();
+ gBrowser.replaceTabWithWindow(secondTab);
+ let win = await windowOpenedPromise;
+
+ info("Asserting document is visible");
+ let tab = win.gBrowser.selectedTab;
+ await SpecialPowers.spawn(tab.linkedBrowser, [""], async function () {
+ is(
+ content.document.visibilityState,
+ "visible",
+ "Document should be visible"
+ );
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+ await BrowserTestUtils.removeTab(firstTab);
+});
+
+add_task(async function test_detach_loading_page() {
+ const URL =
+ "http://mochi.test:8888/browser/docshell/test/browser/file_slow_load.sjs";
+ // Open a dummy tab so that detaching the second tab works.
+ let dummyTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+ let slowLoadingTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ URL,
+ /* waitForLoad = */ false
+ );
+
+ info("Wait for content document to be created");
+ await BrowserTestUtils.waitForCondition(async function () {
+ return SpecialPowers.spawn(
+ slowLoadingTab.linkedBrowser,
+ [URL],
+ async function (url) {
+ return content.document.documentURI == url;
+ }
+ );
+ });
+
+ info("Detaching tab");
+ let windowOpenedPromise = BrowserTestUtils.waitForNewWindow();
+ gBrowser.replaceTabWithWindow(slowLoadingTab);
+ let win = await windowOpenedPromise;
+
+ info("Asserting document is visible");
+ let tab = win.gBrowser.selectedTab;
+ await SpecialPowers.spawn(tab.linkedBrowser, [""], async function () {
+ is(content.document.readyState, "loading");
+ is(content.document.visibilityState, "visible");
+ });
+
+ await BrowserTestUtils.closeWindow(win);
+ await BrowserTestUtils.removeTab(dummyTab);
+});
diff --git a/docshell/test/browser/browser_tab_touch_events.js b/docshell/test/browser/browser_tab_touch_events.js
new file mode 100644
index 0000000000..c73b01e45c
--- /dev/null
+++ b/docshell/test/browser/browser_tab_touch_events.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function () {
+ const URI = "data:text/html;charset=utf-8,<iframe id='test-iframe'></iframe>";
+
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: URI },
+ async function (browser) {
+ await SpecialPowers.spawn(browser, [], test_init);
+
+ browser.browsingContext.touchEventsOverride = "disabled";
+
+ await SpecialPowers.spawn(browser, [], test_body);
+ }
+ );
+});
+
+async function test_init() {
+ is(
+ content.browsingContext.touchEventsOverride,
+ "none",
+ "touchEventsOverride flag should be initially set to NONE"
+ );
+}
+
+async function test_body() {
+ let bc = content.browsingContext;
+ is(
+ bc.touchEventsOverride,
+ "disabled",
+ "touchEventsOverride flag should be changed to DISABLED"
+ );
+
+ let frameWin = content.document.querySelector("#test-iframe").contentWindow;
+ bc = frameWin.browsingContext;
+ is(
+ bc.touchEventsOverride,
+ "disabled",
+ "touchEventsOverride flag should be passed on to frames."
+ );
+
+ let newFrame = content.document.createElement("iframe");
+ content.document.body.appendChild(newFrame);
+
+ let newFrameWin = newFrame.contentWindow;
+ bc = newFrameWin.browsingContext;
+ is(
+ bc.touchEventsOverride,
+ "disabled",
+ "Newly created frames should use the new touchEventsOverride flag"
+ );
+
+ // Wait for the non-transient about:blank to load.
+ await ContentTaskUtils.waitForEvent(newFrame, "load");
+ newFrameWin = newFrame.contentWindow;
+ bc = newFrameWin.browsingContext;
+ is(
+ bc.touchEventsOverride,
+ "disabled",
+ "Newly created frames should use the new touchEventsOverride flag"
+ );
+
+ newFrameWin.location.reload();
+ await ContentTaskUtils.waitForEvent(newFrame, "load");
+ newFrameWin = newFrame.contentWindow;
+ bc = newFrameWin.browsingContext;
+ is(
+ bc.touchEventsOverride,
+ "disabled",
+ "New touchEventsOverride flag should persist across reloads"
+ );
+}
diff --git a/docshell/test/browser/browser_targetTopLevelLinkClicksToBlank.js b/docshell/test/browser/browser_targetTopLevelLinkClicksToBlank.js
new file mode 100644
index 0000000000..9035750b93
--- /dev/null
+++ b/docshell/test/browser/browser_targetTopLevelLinkClicksToBlank.js
@@ -0,0 +1,285 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This test exercises the behaviour where user-initiated link clicks on
+ * the top-level document result in pageloads in a _blank target in a new
+ * browser window.
+ */
+
+const TEST_PAGE = "https://example.com/browser/";
+const TEST_PAGE_2 = "https://example.com/browser/components/";
+const TEST_IFRAME_PAGE =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "dummy_iframe_page.html";
+
+// There is an <a> element with this href=".." in the TEST_PAGE
+// that we will click, which should take us up a level.
+const LINK_URL = "https://example.com/";
+
+/**
+ * Test that a user-initiated link click results in targeting to a new
+ * <browser> element, and that this properly sets the referrer on the newly
+ * loaded document.
+ */
+add_task(async function target_to_new_blank_browser() {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ let originalTab = win.gBrowser.selectedTab;
+ let originalBrowser = originalTab.linkedBrowser;
+ BrowserTestUtils.loadURIString(originalBrowser, TEST_PAGE);
+ await BrowserTestUtils.browserLoaded(originalBrowser, false, TEST_PAGE);
+
+ // Now set the targetTopLevelLinkClicksToBlank property to true, since it
+ // defaults to false.
+ originalBrowser.browsingContext.targetTopLevelLinkClicksToBlank = true;
+
+ let newTabPromise = BrowserTestUtils.waitForNewTab(win.gBrowser, LINK_URL);
+ await SpecialPowers.spawn(originalBrowser, [], async () => {
+ let anchor = content.document.querySelector(`a[href=".."]`);
+ let userInput = content.windowUtils.setHandlingUserInput(true);
+ try {
+ anchor.click();
+ } finally {
+ userInput.destruct();
+ }
+ });
+ let newTab = await newTabPromise;
+ let newBrowser = newTab.linkedBrowser;
+
+ Assert.ok(
+ originalBrowser !== newBrowser,
+ "A new browser should have been created."
+ );
+ await SpecialPowers.spawn(newBrowser, [TEST_PAGE], async referrer => {
+ Assert.equal(
+ content.document.referrer,
+ referrer,
+ "Should have gotten the right referrer set"
+ );
+ });
+ await BrowserTestUtils.switchTab(win.gBrowser, originalTab);
+ BrowserTestUtils.removeTab(newTab);
+
+ // Now do the same thing with a subframe targeting "_top". This should also
+ // get redirected to "_blank".
+ BrowserTestUtils.loadURIString(originalBrowser, TEST_IFRAME_PAGE);
+ await BrowserTestUtils.browserLoaded(
+ originalBrowser,
+ false,
+ TEST_IFRAME_PAGE
+ );
+
+ newTabPromise = BrowserTestUtils.waitForNewTab(win.gBrowser, LINK_URL);
+ let frameBC1 = originalBrowser.browsingContext.children[0];
+ Assert.ok(frameBC1, "Should have found a subframe BrowsingContext");
+
+ await SpecialPowers.spawn(frameBC1, [LINK_URL], async linkUrl => {
+ let anchor = content.document.createElement("a");
+ anchor.setAttribute("href", linkUrl);
+ anchor.setAttribute("target", "_top");
+ content.document.body.appendChild(anchor);
+ let userInput = content.windowUtils.setHandlingUserInput(true);
+ try {
+ anchor.click();
+ } finally {
+ userInput.destruct();
+ }
+ });
+ newTab = await newTabPromise;
+ newBrowser = newTab.linkedBrowser;
+
+ Assert.ok(
+ originalBrowser !== newBrowser,
+ "A new browser should have been created."
+ );
+ await SpecialPowers.spawn(
+ newBrowser,
+ [frameBC1.currentURI.spec],
+ async referrer => {
+ Assert.equal(
+ content.document.referrer,
+ referrer,
+ "Should have gotten the right referrer set"
+ );
+ }
+ );
+ await BrowserTestUtils.switchTab(win.gBrowser, originalTab);
+ BrowserTestUtils.removeTab(newTab);
+
+ await BrowserTestUtils.closeWindow(win);
+});
+
+/**
+ * Test that we don't target to _blank loads caused by:
+ * 1. POST requests
+ * 2. Any load that isn't "normal" (in the nsIDocShell.LOAD_CMD_NORMAL sense)
+ * 3. Any loads that are caused by location.replace
+ * 4. Any loads that were caused by setting location.href
+ * 5. Link clicks fired without user interaction.
+ */
+add_task(async function skip_blank_target_for_some_loads() {
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ let currentBrowser = win.gBrowser.selectedBrowser;
+ BrowserTestUtils.loadURIString(currentBrowser, TEST_PAGE);
+ await BrowserTestUtils.browserLoaded(currentBrowser, false, TEST_PAGE);
+
+ // Now set the targetTopLevelLinkClicksToBlank property to true, since it
+ // defaults to false.
+ currentBrowser.browsingContext.targetTopLevelLinkClicksToBlank = true;
+
+ let ensureSingleBrowser = () => {
+ Assert.equal(
+ win.gBrowser.browsers.length,
+ 1,
+ "There should only be 1 browser."
+ );
+
+ Assert.ok(
+ currentBrowser.browsingContext.targetTopLevelLinkClicksToBlank,
+ "Should still be targeting top-level clicks to _blank"
+ );
+ };
+
+ // First we'll test a POST request
+ let sameBrowserLoad = BrowserTestUtils.browserLoaded(
+ currentBrowser,
+ false,
+ TEST_PAGE
+ );
+ await SpecialPowers.spawn(currentBrowser, [], async () => {
+ let doc = content.document;
+ let form = doc.createElement("form");
+ form.setAttribute("method", "post");
+ doc.body.appendChild(form);
+ let userInput = content.windowUtils.setHandlingUserInput(true);
+ try {
+ form.submit();
+ } finally {
+ userInput.destruct();
+ }
+ });
+ await sameBrowserLoad;
+ ensureSingleBrowser();
+
+ // Next, we'll try a non-normal load - specifically, we'll try a reload.
+ // Since we've got a page loaded via a POST request, an attempt to reload
+ // will cause the "repost" dialog to appear, so we temporarily allow the
+ // repost to go through with the always_accept testing pref.
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.confirm_repost.testing.always_accept", true]],
+ });
+ sameBrowserLoad = BrowserTestUtils.browserLoaded(
+ currentBrowser,
+ false,
+ TEST_PAGE
+ );
+ await SpecialPowers.spawn(currentBrowser, [], async () => {
+ let userInput = content.windowUtils.setHandlingUserInput(true);
+ try {
+ content.location.reload();
+ } finally {
+ userInput.destruct();
+ }
+ });
+ await sameBrowserLoad;
+ ensureSingleBrowser();
+ await SpecialPowers.popPrefEnv();
+
+ // Next, we'll try a location.replace
+ sameBrowserLoad = BrowserTestUtils.browserLoaded(
+ currentBrowser,
+ false,
+ TEST_PAGE_2
+ );
+ await SpecialPowers.spawn(currentBrowser, [TEST_PAGE_2], async page2 => {
+ let userInput = content.windowUtils.setHandlingUserInput(true);
+ try {
+ content.location.replace(page2);
+ } finally {
+ userInput.destruct();
+ }
+ });
+ await sameBrowserLoad;
+ ensureSingleBrowser();
+
+ // Finally we'll try setting location.href
+ sameBrowserLoad = BrowserTestUtils.browserLoaded(
+ currentBrowser,
+ false,
+ TEST_PAGE
+ );
+ await SpecialPowers.spawn(currentBrowser, [TEST_PAGE], async page1 => {
+ let userInput = content.windowUtils.setHandlingUserInput(true);
+ try {
+ content.location.href = page1;
+ } finally {
+ userInput.destruct();
+ }
+ });
+ await sameBrowserLoad;
+ ensureSingleBrowser();
+
+ // Now that we're back at TEST_PAGE, let's try a scripted link click. This
+ // shouldn't target to _blank.
+ sameBrowserLoad = BrowserTestUtils.browserLoaded(
+ currentBrowser,
+ false,
+ LINK_URL
+ );
+ await SpecialPowers.spawn(currentBrowser, [], async () => {
+ let anchor = content.document.querySelector(`a[href=".."]`);
+ anchor.click();
+ });
+ await sameBrowserLoad;
+ ensureSingleBrowser();
+
+ // A javascript:void(0); link should also not target to _blank.
+ sameBrowserLoad = BrowserTestUtils.browserLoaded(
+ currentBrowser,
+ false,
+ TEST_PAGE
+ );
+ await SpecialPowers.spawn(currentBrowser, [TEST_PAGE], async newPageURL => {
+ let anchor = content.document.querySelector(`a[href=".."]`);
+ anchor.href = "javascript:void(0);";
+ anchor.addEventListener("click", e => {
+ content.location.href = newPageURL;
+ });
+ let userInput = content.windowUtils.setHandlingUserInput(true);
+ try {
+ anchor.click();
+ } finally {
+ userInput.destruct();
+ }
+ });
+ await sameBrowserLoad;
+ ensureSingleBrowser();
+
+ // Let's also try a non-void javascript: location.
+ sameBrowserLoad = BrowserTestUtils.browserLoaded(
+ currentBrowser,
+ false,
+ TEST_PAGE
+ );
+ await SpecialPowers.spawn(currentBrowser, [TEST_PAGE], async newPageURL => {
+ let anchor = content.document.querySelector(`a[href=".."]`);
+ anchor.href = `javascript:"string-to-navigate-to"`;
+ anchor.addEventListener("click", e => {
+ content.location.href = newPageURL;
+ });
+ let userInput = content.windowUtils.setHandlingUserInput(true);
+ try {
+ anchor.click();
+ } finally {
+ userInput.destruct();
+ }
+ });
+ await sameBrowserLoad;
+ ensureSingleBrowser();
+
+ await BrowserTestUtils.closeWindow(win);
+});
diff --git a/docshell/test/browser/browser_timelineMarkers-01.js b/docshell/test/browser/browser_timelineMarkers-01.js
new file mode 100644
index 0000000000..7dd51a14fc
--- /dev/null
+++ b/docshell/test/browser/browser_timelineMarkers-01.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the docShell has the right profile timeline API
+
+const URL = "data:text/html;charset=utf-8,Test page";
+
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: URL },
+ async function (browser) {
+ await SpecialPowers.spawn(browser, [], function () {
+ ok(
+ "recordProfileTimelineMarkers" in docShell,
+ "The recordProfileTimelineMarkers attribute exists"
+ );
+ ok(
+ "popProfileTimelineMarkers" in docShell,
+ "The popProfileTimelineMarkers function exists"
+ );
+ ok(
+ docShell.recordProfileTimelineMarkers === false,
+ "recordProfileTimelineMarkers is false by default"
+ );
+ ok(
+ docShell.popProfileTimelineMarkers().length === 0,
+ "There are no markers by default"
+ );
+
+ docShell.recordProfileTimelineMarkers = true;
+ ok(
+ docShell.recordProfileTimelineMarkers === true,
+ "recordProfileTimelineMarkers can be set to true"
+ );
+
+ docShell.recordProfileTimelineMarkers = false;
+ ok(
+ docShell.recordProfileTimelineMarkers === false,
+ "recordProfileTimelineMarkers can be set to false"
+ );
+ });
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_timelineMarkers-02.js b/docshell/test/browser/browser_timelineMarkers-02.js
new file mode 100644
index 0000000000..a2b569d9d6
--- /dev/null
+++ b/docshell/test/browser/browser_timelineMarkers-02.js
@@ -0,0 +1,16 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var TEST_URL =
+ "<!DOCTYPE html><style>" +
+ "body {margin:0; padding: 0;} " +
+ "div {width:100px;height:100px;background:red;} " +
+ ".resize-change-color {width:50px;height:50px;background:blue;} " +
+ ".change-color {width:50px;height:50px;background:yellow;} " +
+ ".add-class {}" +
+ "</style><div></div>";
+TEST_URL = "data:text/html;charset=utf8," + encodeURIComponent(TEST_URL);
+
+var test = makeTimelineTest("browser_timelineMarkers-frame-02.js", TEST_URL);
diff --git a/docshell/test/browser/browser_timelineMarkers-frame-02.js b/docshell/test/browser/browser_timelineMarkers-frame-02.js
new file mode 100644
index 0000000000..52d1e43782
--- /dev/null
+++ b/docshell/test/browser/browser_timelineMarkers-frame-02.js
@@ -0,0 +1,185 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint-env mozilla/frame-script */
+
+// This file expects frame-head.js to be loaded in the environment.
+/* import-globals-from frame-head.js */
+
+"use strict";
+
+// Test that the docShell profile timeline API returns the right markers when
+// restyles, reflows and paints occur
+
+function rectangleContains(rect, x, y, width, height) {
+ return (
+ rect.x <= x && rect.y <= y && rect.width >= width && rect.height >= height
+ );
+}
+
+function sanitizeMarkers(list) {
+ // These markers are currently gathered from all docshells, which may
+ // interfere with this test.
+ return list.filter(e => e.name != "Worker" && e.name != "MinorGC");
+}
+
+var TESTS = [
+ {
+ desc: "Changing the width of the test element",
+ searchFor: "Paint",
+ setup(docShell) {
+ let div = content.document.querySelector("div");
+ div.setAttribute("class", "resize-change-color");
+ },
+ check(markers) {
+ markers = sanitizeMarkers(markers);
+ ok(!!markers.length, "markers were returned");
+ console.log(markers);
+ info(JSON.stringify(markers.filter(m => m.name == "Paint")));
+ ok(
+ markers.some(m => m.name == "Reflow"),
+ "markers includes Reflow"
+ );
+ ok(
+ markers.some(m => m.name == "Paint"),
+ "markers includes Paint"
+ );
+ for (let marker of markers.filter(m => m.name == "Paint")) {
+ // This change should generate at least one rectangle.
+ ok(marker.rectangles.length >= 1, "marker has one rectangle");
+ // One of the rectangles should contain the div.
+ ok(marker.rectangles.some(r => rectangleContains(r, 0, 0, 100, 100)));
+ }
+ ok(
+ markers.some(m => m.name == "Styles"),
+ "markers includes Restyle"
+ );
+ },
+ },
+ {
+ desc: "Changing the test element's background color",
+ searchFor: "Paint",
+ setup(docShell) {
+ let div = content.document.querySelector("div");
+ div.setAttribute("class", "change-color");
+ },
+ check(markers) {
+ markers = sanitizeMarkers(markers);
+ ok(!!markers.length, "markers were returned");
+ ok(
+ !markers.some(m => m.name == "Reflow"),
+ "markers doesn't include Reflow"
+ );
+ ok(
+ markers.some(m => m.name == "Paint"),
+ "markers includes Paint"
+ );
+ for (let marker of markers.filter(m => m.name == "Paint")) {
+ // This change should generate at least one rectangle.
+ ok(marker.rectangles.length >= 1, "marker has one rectangle");
+ // One of the rectangles should contain the div.
+ ok(marker.rectangles.some(r => rectangleContains(r, 0, 0, 50, 50)));
+ }
+ ok(
+ markers.some(m => m.name == "Styles"),
+ "markers includes Restyle"
+ );
+ },
+ },
+ {
+ desc: "Changing the test element's classname",
+ searchFor: "Paint",
+ setup(docShell) {
+ let div = content.document.querySelector("div");
+ div.setAttribute("class", "change-color add-class");
+ },
+ check(markers) {
+ markers = sanitizeMarkers(markers);
+ ok(!!markers.length, "markers were returned");
+ ok(
+ !markers.some(m => m.name == "Reflow"),
+ "markers doesn't include Reflow"
+ );
+ ok(
+ !markers.some(m => m.name == "Paint"),
+ "markers doesn't include Paint"
+ );
+ ok(
+ markers.some(m => m.name == "Styles"),
+ "markers includes Restyle"
+ );
+ },
+ },
+ {
+ desc: "sync console.time/timeEnd",
+ searchFor: "ConsoleTime",
+ setup(docShell) {
+ content.console.time("FOOBAR");
+ content.console.timeEnd("FOOBAR");
+ let markers = docShell.popProfileTimelineMarkers();
+ is(markers.length, 1, "Got one marker");
+ is(markers[0].name, "ConsoleTime", "Got ConsoleTime marker");
+ is(markers[0].causeName, "FOOBAR", "Got ConsoleTime FOOBAR detail");
+ content.console.time("FOO");
+ content.setTimeout(() => {
+ content.console.time("BAR");
+ content.setTimeout(() => {
+ content.console.timeEnd("FOO");
+ content.console.timeEnd("BAR");
+ }, 100);
+ }, 100);
+ },
+ check(markers) {
+ markers = sanitizeMarkers(markers);
+ is(markers.length, 2, "Got 2 markers");
+ is(markers[0].name, "ConsoleTime", "Got first ConsoleTime marker");
+ is(markers[0].causeName, "FOO", "Got ConsoleTime FOO detail");
+ is(markers[1].name, "ConsoleTime", "Got second ConsoleTime marker");
+ is(markers[1].causeName, "BAR", "Got ConsoleTime BAR detail");
+ },
+ },
+ {
+ desc: "Timestamps created by console.timeStamp()",
+ searchFor: "Timestamp",
+ setup(docShell) {
+ content.console.timeStamp("rock");
+ let markers = docShell.popProfileTimelineMarkers();
+ is(markers.length, 1, "Got one marker");
+ is(markers[0].name, "TimeStamp", "Got Timestamp marker");
+ is(markers[0].causeName, "rock", "Got Timestamp label value");
+ content.console.timeStamp("paper");
+ content.console.timeStamp("scissors");
+ content.console.timeStamp();
+ content.console.timeStamp(undefined);
+ },
+ check(markers) {
+ markers = sanitizeMarkers(markers);
+ is(markers.length, 4, "Got 4 markers");
+ is(markers[0].name, "TimeStamp", "Got Timestamp marker");
+ is(markers[0].causeName, "paper", "Got Timestamp label value");
+ is(markers[1].name, "TimeStamp", "Got Timestamp marker");
+ is(markers[1].causeName, "scissors", "Got Timestamp label value");
+ is(
+ markers[2].name,
+ "TimeStamp",
+ "Got empty Timestamp marker when no argument given"
+ );
+ is(markers[2].causeName, void 0, "Got empty Timestamp label value");
+ is(
+ markers[3].name,
+ "TimeStamp",
+ "Got empty Timestamp marker when argument is undefined"
+ );
+ is(markers[3].causeName, void 0, "Got empty Timestamp label value");
+ markers.forEach(m =>
+ is(
+ m.end,
+ m.start,
+ "All Timestamp markers should have identical start/end times"
+ )
+ );
+ },
+ },
+];
+
+timelineContentTest(TESTS);
diff --git a/docshell/test/browser/browser_title_in_session_history.js b/docshell/test/browser/browser_title_in_session_history.js
new file mode 100644
index 0000000000..bdcbbb7dfe
--- /dev/null
+++ b/docshell/test/browser/browser_title_in_session_history.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test() {
+ const TEST_PAGE =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ ) + "dummy_page.html";
+ await BrowserTestUtils.withNewTab(TEST_PAGE, async browser => {
+ let titles = await ContentTask.spawn(browser, null, () => {
+ return new Promise(resolve => {
+ let titles = [];
+ content.document.body.innerHTML = "<div id='foo'>foo</div>";
+ content.document.title = "Initial";
+ content.history.pushState("1", "1", "1");
+ content.document.title = "1";
+ content.history.pushState("2", "2", "2");
+ content.document.title = "2";
+ content.location.hash = "hash";
+ content.document.title = "3-hash";
+ content.addEventListener(
+ "popstate",
+ () => {
+ content.addEventListener(
+ "popstate",
+ () => {
+ titles.push(content.document.title);
+ resolve(titles);
+ },
+ { once: true }
+ );
+
+ titles.push(content.document.title);
+ // Test going forward a few steps.
+ content.history.go(2);
+ },
+ { once: true }
+ );
+ // Test going back a few steps.
+ content.history.go(-3);
+ });
+ });
+ is(
+ titles[0],
+ "3-hash",
+ "Document.title should have the value to which it was last time set."
+ );
+ is(
+ titles[1],
+ "3-hash",
+ "Document.title should have the value to which it was last time set."
+ );
+ let sh = browser.browsingContext.sessionHistory;
+ let count = sh.count;
+ is(sh.getEntryAtIndex(count - 1).title, "3-hash");
+ is(sh.getEntryAtIndex(count - 2).title, "2");
+ is(sh.getEntryAtIndex(count - 3).title, "1");
+ is(sh.getEntryAtIndex(count - 4).title, "Initial");
+ });
+});
diff --git a/docshell/test/browser/browser_ua_emulation.js b/docshell/test/browser/browser_ua_emulation.js
new file mode 100644
index 0000000000..f1e9657cd3
--- /dev/null
+++ b/docshell/test/browser/browser_ua_emulation.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const URL = "data:text/html;charset=utf-8,<iframe id='test-iframe'></iframe>";
+
+// Test that the docShell UA emulation works
+async function contentTaskNoOverride() {
+ let docshell = docShell;
+ is(
+ docshell.browsingContext.customUserAgent,
+ "",
+ "There should initially be no customUserAgent"
+ );
+}
+
+async function contentTaskOverride() {
+ let docshell = docShell;
+ is(
+ docshell.browsingContext.customUserAgent,
+ "foo",
+ "The user agent should be changed to foo"
+ );
+
+ is(
+ content.navigator.userAgent,
+ "foo",
+ "The user agent should be changed to foo"
+ );
+
+ let frameWin = content.document.querySelector("#test-iframe").contentWindow;
+ is(
+ frameWin.navigator.userAgent,
+ "foo",
+ "The UA should be passed on to frames."
+ );
+
+ let newFrame = content.document.createElement("iframe");
+ content.document.body.appendChild(newFrame);
+
+ let newFrameWin = newFrame.contentWindow;
+ is(
+ newFrameWin.navigator.userAgent,
+ "foo",
+ "Newly created frames should use the new UA"
+ );
+
+ newFrameWin.location.reload();
+ await ContentTaskUtils.waitForEvent(newFrame, "load");
+
+ is(
+ newFrameWin.navigator.userAgent,
+ "foo",
+ "New UA should persist across reloads"
+ );
+}
+
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: URL },
+ async function (browser) {
+ await SpecialPowers.spawn(browser, [], contentTaskNoOverride);
+
+ let browsingContext = BrowserTestUtils.getBrowsingContextFrom(browser);
+ browsingContext.customUserAgent = "foo";
+
+ await SpecialPowers.spawn(browser, [], contentTaskOverride);
+ }
+ );
+});
diff --git a/docshell/test/browser/browser_uriFixupAlternateRedirects.js b/docshell/test/browser/browser_uriFixupAlternateRedirects.js
new file mode 100644
index 0000000000..193e8cf489
--- /dev/null
+++ b/docshell/test/browser/browser_uriFixupAlternateRedirects.js
@@ -0,0 +1,66 @@
+"use strict";
+
+const { UrlbarTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/UrlbarTestUtils.sys.mjs"
+);
+
+const REDIRECTURL =
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://www.example.com/browser/docshell/test/browser/redirect_to_example.sjs";
+
+add_task(async function () {
+ // Test both directly setting a value and pressing enter, or setting the
+ // value through input events, like the user would do.
+ const setValueFns = [
+ value => {
+ gURLBar.value = value;
+ },
+ value => {
+ return UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window,
+ waitForFocus,
+ value,
+ });
+ },
+ ];
+ for (let setValueFn of setValueFns) {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank"
+ );
+ // Enter search terms and start a search.
+ gURLBar.focus();
+ await setValueFn(REDIRECTURL);
+ let errorPageLoaded = BrowserTestUtils.waitForErrorPage(tab.linkedBrowser);
+ EventUtils.synthesizeKey("KEY_Enter");
+ await errorPageLoaded;
+ let [contentURL, originalURL] = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ () => {
+ return [
+ content.document.documentURI,
+ content.document.mozDocumentURIIfNotForErrorPages.spec,
+ ];
+ }
+ );
+ info("Page that loaded: " + contentURL);
+ const errorURI = "about:neterror?";
+ ok(contentURL.startsWith(errorURI), "Should be on an error page");
+
+ const contentPrincipal = tab.linkedBrowser.contentPrincipal;
+ ok(
+ contentPrincipal.spec.startsWith(errorURI),
+ "Principal should be for the error page"
+ );
+
+ originalURL = new URL(originalURL);
+ is(
+ originalURL.host,
+ "example",
+ "Should be an error for http://example, not http://www.example.com/"
+ );
+
+ BrowserTestUtils.removeTab(tab);
+ }
+});
diff --git a/docshell/test/browser/browser_uriFixupIntegration.js b/docshell/test/browser/browser_uriFixupIntegration.js
new file mode 100644
index 0000000000..619fd9f61f
--- /dev/null
+++ b/docshell/test/browser/browser_uriFixupIntegration.js
@@ -0,0 +1,104 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { UrlbarTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/UrlbarTestUtils.sys.mjs"
+);
+const { SearchTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/SearchTestUtils.sys.mjs"
+);
+
+SearchTestUtils.init(this);
+
+const kSearchEngineID = "browser_urifixup_search_engine";
+const kSearchEngineURL = "https://example.com/?search={searchTerms}";
+const kPrivateSearchEngineID = "browser_urifixup_search_engine_private";
+const kPrivateSearchEngineURL = "https://example.com/?private={searchTerms}";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.search.separatePrivateDefault.ui.enabled", true],
+ ["browser.search.separatePrivateDefault", true],
+ ],
+ });
+
+ // Add new fake search engines.
+ await SearchTestUtils.installSearchExtension(
+ {
+ name: kSearchEngineID,
+ search_url: "https://example.com/",
+ search_url_get_params: "search={searchTerms}",
+ },
+ { setAsDefault: true }
+ );
+
+ await SearchTestUtils.installSearchExtension(
+ {
+ name: kPrivateSearchEngineID,
+ search_url: "https://example.com/",
+ search_url_get_params: "private={searchTerms}",
+ },
+ { setAsDefaultPrivate: true }
+ );
+});
+
+add_task(async function test() {
+ // Test both directly setting a value and pressing enter, or setting the
+ // value through input events, like the user would do.
+ const setValueFns = [
+ (value, win) => {
+ win.gURLBar.value = value;
+ },
+ (value, win) => {
+ return UrlbarTestUtils.promiseAutocompleteResultPopup({
+ window: win,
+ waitForFocus: SimpleTest.waitForFocus,
+ value,
+ });
+ },
+ ];
+
+ for (let value of ["foo bar", "brokenprotocol:somethingelse"]) {
+ for (let setValueFn of setValueFns) {
+ for (let inPrivateWindow of [false, true]) {
+ await do_test(value, setValueFn, inPrivateWindow);
+ }
+ }
+ }
+});
+
+async function do_test(value, setValueFn, inPrivateWindow) {
+ info(`Search ${value} in a ${inPrivateWindow ? "private" : "normal"} window`);
+ let win = await BrowserTestUtils.openNewBrowserWindow({
+ private: inPrivateWindow,
+ });
+ // Enter search terms and start a search.
+ win.gURLBar.focus();
+ await setValueFn(value, win);
+
+ EventUtils.synthesizeKey("KEY_Enter", {}, win);
+
+ // Check that we load the correct URL.
+ let escapedValue = encodeURIComponent(value).replace("%20", "+");
+ let searchEngineUrl = inPrivateWindow
+ ? kPrivateSearchEngineURL
+ : kSearchEngineURL;
+ let expectedURL = searchEngineUrl.replace("{searchTerms}", escapedValue);
+ await BrowserTestUtils.browserLoaded(
+ win.gBrowser.selectedBrowser,
+ false,
+ expectedURL
+ );
+ // There should be at least one test.
+ Assert.equal(
+ win.gBrowser.selectedBrowser.currentURI.spec,
+ expectedURL,
+ "New tab should have loaded with expected url."
+ );
+
+ // Cleanup.
+ await BrowserTestUtils.closeWindow(win);
+}
diff --git a/docshell/test/browser/browser_viewsource_chrome_to_content.js b/docshell/test/browser/browser_viewsource_chrome_to_content.js
new file mode 100644
index 0000000000..680041d704
--- /dev/null
+++ b/docshell/test/browser/browser_viewsource_chrome_to_content.js
@@ -0,0 +1,20 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+const TEST_URI = `view-source:${TEST_PATH}dummy_page.html`;
+
+add_task(async function chrome_to_content_view_source() {
+ await BrowserTestUtils.withNewTab("about:mozilla", async browser => {
+ is(browser.documentURI.spec, "about:mozilla");
+
+ // This process switch would previously crash in debug builds due to assertion failures.
+ BrowserTestUtils.loadURIString(browser, TEST_URI);
+ await BrowserTestUtils.browserLoaded(browser);
+ is(browser.documentURI.spec, TEST_URI);
+ });
+});
diff --git a/docshell/test/browser/browser_viewsource_multipart.js b/docshell/test/browser/browser_viewsource_multipart.js
new file mode 100644
index 0000000000..0a902a71ec
--- /dev/null
+++ b/docshell/test/browser/browser_viewsource_multipart.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+const MULTIPART_URI = `${TEST_PATH}file_basic_multipart.sjs`;
+
+add_task(async function viewsource_multipart_uri() {
+ await BrowserTestUtils.withNewTab("about:blank", async browser => {
+ BrowserTestUtils.loadURIString(browser, MULTIPART_URI);
+ await BrowserTestUtils.browserLoaded(browser);
+ is(browser.currentURI.spec, MULTIPART_URI);
+
+ // Continue probing the URL until we find the h1 we're expecting. This
+ // should handle cases where we somehow beat the second document having
+ // loaded.
+ await TestUtils.waitForCondition(async () => {
+ let value = await SpecialPowers.spawn(browser, [], async () => {
+ let headers = content.document.querySelectorAll("h1");
+ is(headers.length, 1, "only one h1 should be present");
+ return headers[0].textContent;
+ });
+
+ ok(value == "First" || value == "Second", "some other value was found?");
+ return value == "Second";
+ });
+
+ // Load a view-source version of the page, which should show the full
+ // content, not handling multipart.
+ BrowserTestUtils.loadURIString(browser, `view-source:${MULTIPART_URI}`);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ let viewSourceContent = await SpecialPowers.spawn(browser, [], async () => {
+ return content.document.body.textContent;
+ });
+
+ ok(viewSourceContent.includes("<h1>First</h1>"), "first header");
+ ok(viewSourceContent.includes("<h1>Second</h1>"), "second header");
+ ok(viewSourceContent.includes("BOUNDARY"), "boundary");
+ });
+});
diff --git a/docshell/test/browser/dummy_iframe_page.html b/docshell/test/browser/dummy_iframe_page.html
new file mode 100644
index 0000000000..12ce921856
--- /dev/null
+++ b/docshell/test/browser/dummy_iframe_page.html
@@ -0,0 +1,8 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+ <body>
+ just a dummy html file with an iframe
+ <iframe id="frame1" src="dummy_page.html?sub_entry=0"></iframe>
+ <iframe id="frame2" src="dummy_page.html?sub_entry=0"></iframe>
+ </body>
+</html>
diff --git a/docshell/test/browser/dummy_page.html b/docshell/test/browser/dummy_page.html
new file mode 100644
index 0000000000..59bf2a5f8f
--- /dev/null
+++ b/docshell/test/browser/dummy_page.html
@@ -0,0 +1,6 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+ <body>
+ just a dummy html file
+ </body>
+</html>
diff --git a/docshell/test/browser/favicon_bug655270.ico b/docshell/test/browser/favicon_bug655270.ico
new file mode 100644
index 0000000000..d44438903b
--- /dev/null
+++ b/docshell/test/browser/favicon_bug655270.ico
Binary files differ
diff --git a/docshell/test/browser/file_backforward_restore_scroll.html b/docshell/test/browser/file_backforward_restore_scroll.html
new file mode 100644
index 0000000000..5a40b36c10
--- /dev/null
+++ b/docshell/test/browser/file_backforward_restore_scroll.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+</head>
+<body>
+ <iframe src="http://mochi.test:8888/"></iframe>
+ <iframe src="http://example.com/"></iframe>
+</body>
+</html>
diff --git a/docshell/test/browser/file_backforward_restore_scroll.html^headers^ b/docshell/test/browser/file_backforward_restore_scroll.html^headers^
new file mode 100644
index 0000000000..59ba296103
--- /dev/null
+++ b/docshell/test/browser/file_backforward_restore_scroll.html^headers^
@@ -0,0 +1 @@
+Cache-control: no-store
diff --git a/docshell/test/browser/file_basic_multipart.sjs b/docshell/test/browser/file_basic_multipart.sjs
new file mode 100644
index 0000000000..5e89b93948
--- /dev/null
+++ b/docshell/test/browser/file_basic_multipart.sjs
@@ -0,0 +1,24 @@
+"use strict";
+
+function handleRequest(request, response) {
+ response.setHeader(
+ "Content-Type",
+ "multipart/x-mixed-replace;boundary=BOUNDARY",
+ false
+ );
+ response.setStatusLine(request.httpVersion, 200, "OK");
+
+ response.write(`--BOUNDARY
+Content-Type: text/html
+
+<h1>First</h1>
+Will be replaced
+--BOUNDARY
+Content-Type: text/html
+
+<h1>Second</h1>
+This will stick around
+--BOUNDARY
+--BOUNDARY--
+`);
+}
diff --git a/docshell/test/browser/file_bug1046022.html b/docshell/test/browser/file_bug1046022.html
new file mode 100644
index 0000000000..27a1e1f079
--- /dev/null
+++ b/docshell/test/browser/file_bug1046022.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Bug 1046022 - test navigating inside onbeforeunload</title>
+ </head>
+ <body>
+ Waiting for onbeforeunload to hit...
+ </body>
+
+ <script>
+var testFns = [
+ function(e) {
+ e.target.location.href = "otherpage-href-set.html";
+ return "stop";
+ },
+ function(e) {
+ e.target.location.reload();
+ return "stop";
+ },
+ function(e) {
+ e.currentTarget.stop();
+ return "stop";
+ },
+ function(e) {
+ e.target.location.replace("otherpage-location-replaced.html");
+ return "stop";
+ },
+ function(e) {
+ var link = e.target.createElement("a");
+ link.href = "otherpage.html";
+ e.target.body.appendChild(link);
+ link.click();
+ return "stop";
+ },
+ function(e) {
+ var link = e.target.createElement("a");
+ link.href = "otherpage.html";
+ link.setAttribute("target", "_blank");
+ e.target.body.appendChild(link);
+ link.click();
+ return "stop";
+ },
+ function(e) {
+ var link = e.target.createElement("a");
+ link.href = e.target.location.href;
+ e.target.body.appendChild(link);
+ link.setAttribute("target", "somearbitrarywindow");
+ link.click();
+ return "stop";
+ },
+];
+ </script>
+</html>
diff --git a/docshell/test/browser/file_bug1206879.html b/docshell/test/browser/file_bug1206879.html
new file mode 100644
index 0000000000..5313902a9b
--- /dev/null
+++ b/docshell/test/browser/file_bug1206879.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test page for bug 1206879</title>
+ </head>
+ <body>
+ <iframe src="http://example.com/"></iframe>
+ </body>
+</html>
diff --git a/docshell/test/browser/file_bug1328501.html b/docshell/test/browser/file_bug1328501.html
new file mode 100644
index 0000000000..517ef53e02
--- /dev/null
+++ b/docshell/test/browser/file_bug1328501.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Page with iframes</title>
+ <script type="application/javascript">
+ let promiseResolvers = {
+ "testFrame1": {},
+ "testFrame2": {},
+ };
+ let promises = [
+ new Promise(r => promiseResolvers.testFrame1.resolve = r),
+ new Promise(r => promiseResolvers.testFrame2.resolve = r),
+ ];
+ function frameLoaded(frame) {
+ promiseResolvers[frame].resolve();
+ }
+ Promise.all(promises).then(() => window.dispatchEvent(new Event("frames-loaded")));
+ </script>
+ </head>
+ <body onunload="">
+ <div>
+ <iframe id="testFrame1" src="dummy_page.html" onload="frameLoaded(this.id);" ></iframe>
+ <iframe id="testFrame2" src="dummy_page.html" onload="frameLoaded(this.id);" ></iframe>
+ </div>
+ </body>
+</html>
diff --git a/docshell/test/browser/file_bug1328501_frame.html b/docshell/test/browser/file_bug1328501_frame.html
new file mode 100644
index 0000000000..156dd41eaa
--- /dev/null
+++ b/docshell/test/browser/file_bug1328501_frame.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html lang="en">
+ <body>Subframe page for testing</body>
+</html>
diff --git a/docshell/test/browser/file_bug1328501_framescript.js b/docshell/test/browser/file_bug1328501_framescript.js
new file mode 100644
index 0000000000..19c86c75e7
--- /dev/null
+++ b/docshell/test/browser/file_bug1328501_framescript.js
@@ -0,0 +1,38 @@
+// Forward iframe loaded event.
+
+/* eslint-env mozilla/frame-script */
+
+addEventListener(
+ "frames-loaded",
+ e => sendAsyncMessage("test:frames-loaded"),
+ true,
+ true
+);
+
+let requestObserver = {
+ observe(subject, topic, data) {
+ if (topic == "http-on-opening-request") {
+ // Get DOMWindow on all child docshells to force about:blank
+ // content viewers being created.
+ getChildDocShells().map(ds => {
+ ds
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsILoadContext).associatedWindow;
+ });
+ }
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+};
+Services.obs.addObserver(requestObserver, "http-on-opening-request");
+addEventListener("unload", e => {
+ if (e.target == this) {
+ Services.obs.removeObserver(requestObserver, "http-on-opening-request");
+ }
+});
+
+function getChildDocShells() {
+ return docShell.getAllDocShellsInSubtree(
+ Ci.nsIDocShellTreeItem.typeAll,
+ Ci.nsIDocShell.ENUMERATE_FORWARDS
+ );
+}
diff --git a/docshell/test/browser/file_bug1543077-3-child.html b/docshell/test/browser/file_bug1543077-3-child.html
new file mode 100644
index 0000000000..858a4623ed
--- /dev/null
+++ b/docshell/test/browser/file_bug1543077-3-child.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>No encoding declaration in parent or child</title>
+</head>
+<body>
+<p>Hiragana letter a if decoded as ISO-2022-JP: $B$"(B</p>
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug1543077-3.html b/docshell/test/browser/file_bug1543077-3.html
new file mode 100644
index 0000000000..c4f467dd3f
--- /dev/null
+++ b/docshell/test/browser/file_bug1543077-3.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>No encoding declaration in parent or child</title>
+</head>
+<body>
+<h1>No encoding declaration in parent or child</h1>
+
+<p>Hiragana letter a if decoded as ISO-2022-JP: $B$"(B</p>
+
+<iframe src="file_bug1543077-3-child.html"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug1622420.html b/docshell/test/browser/file_bug1622420.html
new file mode 100644
index 0000000000..63beb38302
--- /dev/null
+++ b/docshell/test/browser/file_bug1622420.html
@@ -0,0 +1 @@
+<iframe src="http://example.com/"></iframe>
diff --git a/docshell/test/browser/file_bug1648464-1-child.html b/docshell/test/browser/file_bug1648464-1-child.html
new file mode 100644
index 0000000000..7bb1ad965b
--- /dev/null
+++ b/docshell/test/browser/file_bug1648464-1-child.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset=windows-1252>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>windows-1252 in parent and child, actually EUC-JP</title>
+</head>
+<body>
+<p>Hiragana letter a if decoded as EUC-JP: </p>
+<p>ʸ¸Ǥ</p>
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug1648464-1.html b/docshell/test/browser/file_bug1648464-1.html
new file mode 100644
index 0000000000..2051cf61ed
--- /dev/null
+++ b/docshell/test/browser/file_bug1648464-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset=windows-1252>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>windows-1252 in parent and child, actually EUC-JP</title>
+</head>
+<body>
+<h1>windows-1252 in parent and child, actually EUC-JP</h1>
+
+<p>Hiragana letter a if decoded as EUC-JP: </p>
+<p>ʸ¸Ǥ</p>
+
+<iframe src="file_bug1648464-1-child.html"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug1673702.json b/docshell/test/browser/file_bug1673702.json
new file mode 100644
index 0000000000..3f562dd675
--- /dev/null
+++ b/docshell/test/browser/file_bug1673702.json
@@ -0,0 +1 @@
+{ "version": 1 }
diff --git a/docshell/test/browser/file_bug1673702.json^headers^ b/docshell/test/browser/file_bug1673702.json^headers^
new file mode 100644
index 0000000000..6010bfd188
--- /dev/null
+++ b/docshell/test/browser/file_bug1673702.json^headers^
@@ -0,0 +1 @@
+Content-Type: application/json; charset=utf-8
diff --git a/docshell/test/browser/file_bug1688368-1.sjs b/docshell/test/browser/file_bug1688368-1.sjs
new file mode 100644
index 0000000000..0693b7970c
--- /dev/null
+++ b/docshell/test/browser/file_bug1688368-1.sjs
@@ -0,0 +1,44 @@
+"use strict";
+
+const DELAY = 1 * 1000; // Delay one second before completing the request.
+
+let nsTimer = Components.Constructor(
+ "@mozilla.org/timer;1",
+ "nsITimer",
+ "initWithCallback"
+);
+
+let timer;
+
+function handleRequest(request, response) {
+ response.processAsync();
+
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.write(`<!DOCTYPE html>
+<html>
+<head>
+ <title>UTF-8 file, 1024 bytes long!</title>
+</head>
+<body>`);
+
+ // Note: We need to store a reference to the timer to prevent it from being
+ // canceled when it's GCed.
+ timer = new nsTimer(
+ () => {
+ var snowmen =
+ "\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083\u00E2\u0098\u0083";
+ response.write(
+ snowmen +
+ `
+</body>
+</html>
+
+`
+ );
+ response.finish();
+ },
+ DELAY,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+}
diff --git a/docshell/test/browser/file_bug1691153.html b/docshell/test/browser/file_bug1691153.html
new file mode 100644
index 0000000000..dea144eb41
--- /dev/null
+++ b/docshell/test/browser/file_bug1691153.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>bug 1691153</title>
+</head>
+<body>
+<h1>bug 1691153</h1>
+<script>
+function toBlobURL(data, mimeType) {
+ return URL.createObjectURL(
+ new Blob([data], {
+ type: mimeType,
+ })
+ );
+}
+// closing script element literal split up to not end the parent script element
+let testurl = toBlobURL("<body></body>", "text/html");
+addEventListener("message", event => {
+ if (event.data == "getblob") {
+ postMessage({ bloburl: testurl }, "*");
+ }
+});
+// the blob URL should have a content principal
+</script>
+</body>
+</html>
diff --git a/docshell/test/browser/file_bug1716290-1.sjs b/docshell/test/browser/file_bug1716290-1.sjs
new file mode 100644
index 0000000000..83e6eede3d
--- /dev/null
+++ b/docshell/test/browser/file_bug1716290-1.sjs
@@ -0,0 +1,21 @@
+function handleRequest(request, response) {
+ if (getState("reloaded") == "reloaded") {
+ response.setHeader(
+ "Content-Type",
+ "text/html; charset=windows-1254",
+ false
+ );
+ response.write("\u00E4");
+ } else {
+ response.setHeader("Content-Type", "text/html; charset=Shift_JIS", false);
+ if (getState("loaded") == "loaded") {
+ setState("reloaded", "reloaded");
+ } else {
+ setState("loaded", "loaded");
+ }
+ // kilobyte to force late-detection reload
+ response.write("a".repeat(1024));
+ response.write("<body>");
+ response.write("\u00E4");
+ }
+}
diff --git a/docshell/test/browser/file_bug1716290-2.sjs b/docshell/test/browser/file_bug1716290-2.sjs
new file mode 100644
index 0000000000..e695259e30
--- /dev/null
+++ b/docshell/test/browser/file_bug1716290-2.sjs
@@ -0,0 +1,18 @@
+function handleRequest(request, response) {
+ if (getState("reloaded") == "reloaded") {
+ response.setHeader("Content-Type", "text/html", false);
+ response.write("<meta charset=iso-2022-kr>\u00E4");
+ } else {
+ response.setHeader("Content-Type", "text/html", false);
+ if (getState("loaded") == "loaded") {
+ setState("reloaded", "reloaded");
+ } else {
+ setState("loaded", "loaded");
+ }
+ response.write("<meta charset=Shift_JIS>");
+ // kilobyte to force late-detection reload
+ response.write("a".repeat(1024));
+ response.write("<body>");
+ response.write("\u00E4");
+ }
+}
diff --git a/docshell/test/browser/file_bug1716290-3.sjs b/docshell/test/browser/file_bug1716290-3.sjs
new file mode 100644
index 0000000000..7a302e05e4
--- /dev/null
+++ b/docshell/test/browser/file_bug1716290-3.sjs
@@ -0,0 +1,17 @@
+function handleRequest(request, response) {
+ if (getState("reloaded") == "reloaded") {
+ response.setHeader("Content-Type", "text/html; charset=iso-2022-kr", false);
+ response.write("\u00E4");
+ } else {
+ response.setHeader("Content-Type", "text/html; charset=Shift_JIS", false);
+ if (getState("loaded") == "loaded") {
+ setState("reloaded", "reloaded");
+ } else {
+ setState("loaded", "loaded");
+ }
+ // kilobyte to force late-detection reload
+ response.write("a".repeat(1024));
+ response.write("<body>");
+ response.write("\u00E4");
+ }
+}
diff --git a/docshell/test/browser/file_bug1716290-4.sjs b/docshell/test/browser/file_bug1716290-4.sjs
new file mode 100644
index 0000000000..36753ef532
--- /dev/null
+++ b/docshell/test/browser/file_bug1716290-4.sjs
@@ -0,0 +1,17 @@
+function handleRequest(request, response) {
+ if (getState("reloaded") == "reloaded") {
+ response.setHeader("Content-Type", "text/html", false);
+ response.write("\u00FE\u00FF\u00E4");
+ } else {
+ response.setHeader("Content-Type", "text/html; charset=Shift_JIS", false);
+ if (getState("loaded") == "loaded") {
+ setState("reloaded", "reloaded");
+ } else {
+ setState("loaded", "loaded");
+ }
+ // kilobyte to force late-detection reload
+ response.write("a".repeat(1024));
+ response.write("<body>");
+ response.write("\u00E4");
+ }
+}
diff --git a/docshell/test/browser/file_bug1736248-1.html b/docshell/test/browser/file_bug1736248-1.html
new file mode 100644
index 0000000000..177acb8f77
--- /dev/null
+++ b/docshell/test/browser/file_bug1736248-1.html
@@ -0,0 +1,4 @@
+Kilobyte of ASCII followed by UTF-8.
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+
+Hej världen!
diff --git a/docshell/test/browser/file_bug234628-1-child.html b/docshell/test/browser/file_bug234628-1-child.html
new file mode 100644
index 0000000000..c36197ac4f
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-1-child.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>No encoding declaration in parent or child</title>
+</head>
+<body>
+<p>Euro sign if decoded as Windows-1252: </p>
+<p>a with diaeresis if decoded as Windows-1252: </p>
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-1.html b/docshell/test/browser/file_bug234628-1.html
new file mode 100644
index 0000000000..11c523ccd9
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>No encoding declaration in parent or child</title>
+</head>
+<body>
+<h1>No encoding declaration in parent or child</h1>
+
+<p>Euro sign if decoded as Windows-1252: </p>
+<p>a with diaeresis if decoded as Windows-1252: </p>
+
+<iframe src="file_bug234628-1-child.html"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-10-child.xhtml b/docshell/test/browser/file_bug234628-10-child.xhtml
new file mode 100644
index 0000000000..cccf6f2bc0
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-10-child.xhtml
@@ -0,0 +1,4 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head><title>XML child with no encoding declaration</title></head>
+<body><p>Euro sign if decoded as UTF-8: €</p></body>
+</html>
diff --git a/docshell/test/browser/file_bug234628-10.html b/docshell/test/browser/file_bug234628-10.html
new file mode 100644
index 0000000000..78b8f0035d
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-10.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>No encoding declaration in HTML parent or XHTML child</title>
+</head>
+<body>
+<h1>No encoding declaration in HTML parent or XHTML child</h1>
+
+<p>Euro sign if decoded as Windows-1252: </p>
+<p>a with diaeresis if decoded as Windows-1252: </p>
+
+<iframe src="file_bug234628-10-child.xhtml"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-11-child.xhtml b/docshell/test/browser/file_bug234628-11-child.xhtml
new file mode 100644
index 0000000000..11ef668b0c
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-11-child.xhtml
@@ -0,0 +1,4 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head><title>No encoding declaration in HTML parent and HTTP declaration in XHTML child</title></head>
+<body><p>Euro sign if decoded as UTF-8: €</p></body>
+</html>
diff --git a/docshell/test/browser/file_bug234628-11-child.xhtml^headers^ b/docshell/test/browser/file_bug234628-11-child.xhtml^headers^
new file mode 100644
index 0000000000..30fb304056
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-11-child.xhtml^headers^
@@ -0,0 +1 @@
+Content-Type: application/xhtml+xml; charset=utf-8
diff --git a/docshell/test/browser/file_bug234628-11.html b/docshell/test/browser/file_bug234628-11.html
new file mode 100644
index 0000000000..21c5b733e0
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-11.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>No encoding declaration in HTML parent and HTTP declaration in XHTML child</title>
+</head>
+<body>
+<h1>No encoding declaration in HTML parent and HTTP declaration in XHTML child</h1>
+
+<p>Euro sign if decoded as Windows-1252: </p>
+<p>a with diaeresis if decoded as Windows-1252: </p>
+
+<iframe src="file_bug234628-11-child.xhtml"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-2-child.html b/docshell/test/browser/file_bug234628-2-child.html
new file mode 100644
index 0000000000..0acd2e0b27
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-2-child.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>No encoding declaration in parent or child</title>
+</head>
+<body>
+<p>Euro sign if decoded as UTF-8: €</p>
+<p>a with diaeresis if decoded as UTF-8: ä</p>
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-2.html b/docshell/test/browser/file_bug234628-2.html
new file mode 100644
index 0000000000..a87d29e126
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-2.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>No encoding declaration in parent or child</title>
+</head>
+<body>
+<h1>No encoding declaration in parent or child</h1>
+
+<p>Euro sign if decoded as Windows-1252: </p>
+<p>a with diaeresis if decoded as Windows-1252: </p>
+
+<iframe src="file_bug234628-2-child.html"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-3-child.html b/docshell/test/browser/file_bug234628-3-child.html
new file mode 100644
index 0000000000..a6ad832310
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-3-child.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>meta declaration in parent and child</title>
+</head>
+<body>
+<p>Euro sign if decoded as UTF-8: €</p>
+<p>a with diaeresis if decoded as UTF-8: ä</p>
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-3.html b/docshell/test/browser/file_bug234628-3.html
new file mode 100644
index 0000000000..8caab60402
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-3.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="windows-1252">
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>meta declaration in parent and child</title>
+</head>
+<body>
+<h1>meta declaration in parent and child</h1>
+
+<p>Euro sign if decoded as Windows-1252: </p>
+<p>a with diaeresis if decoded as Windows-1252: </p>
+
+<iframe src="file_bug234628-3-child.html"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-4-child.html b/docshell/test/browser/file_bug234628-4-child.html
new file mode 100644
index 0000000000..f0e7c2c058
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-4-child.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>meta declaration in parent and BOM in child</title>
+</head>
+<body>
+<p>Euro sign if decoded as UTF-8: €</p>
+<p>a with diaeresis if decoded as UTF-8: ä</p>
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-4.html b/docshell/test/browser/file_bug234628-4.html
new file mode 100644
index 0000000000..0137579010
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-4.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="windows-1252">
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>meta declaration in parent and BOM in child</title>
+</head>
+<body>
+<h1>meta declaration in parent and BOM in child</h1>
+
+<p>Euro sign if decoded as Windows-1252: </p>
+<p>a with diaeresis if decoded as Windows-1252: </p>
+
+<iframe src="file_bug234628-4-child.html"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-5-child.html b/docshell/test/browser/file_bug234628-5-child.html
new file mode 100644
index 0000000000..a650552f63
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-5-child.html
Binary files differ
diff --git a/docshell/test/browser/file_bug234628-5.html b/docshell/test/browser/file_bug234628-5.html
new file mode 100644
index 0000000000..987e6420be
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-5.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="windows-1252">
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>meta declaration in parent and UTF-16 BOM in child</title>
+</head>
+<body>
+<h1>meta declaration in parent and UTF-16 BOM in child</h1>
+
+<p>Euro sign if decoded as Windows-1252: </p>
+<p>a with diaeresis if decoded as Windows-1252: </p>
+
+<iframe src="file_bug234628-5-child.html"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-6-child.html b/docshell/test/browser/file_bug234628-6-child.html
new file mode 100644
index 0000000000..52c37f2596
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-6-child.html
Binary files differ
diff --git a/docshell/test/browser/file_bug234628-6-child.html^headers^ b/docshell/test/browser/file_bug234628-6-child.html^headers^
new file mode 100644
index 0000000000..bfdcf487fb
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-6-child.html^headers^
@@ -0,0 +1 @@
+Content-Type: text/html; charset=utf-16be
diff --git a/docshell/test/browser/file_bug234628-6.html b/docshell/test/browser/file_bug234628-6.html
new file mode 100644
index 0000000000..9d7fc580c3
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-6.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="windows-1252">
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>meta declaration in parent and BOMless UTF-16 with HTTP charset in child</title>
+</head>
+<body>
+<h1>meta declaration in parent and BOMless UTF-16 with HTTP charset in child</h1>
+
+<p>Euro sign if decoded as Windows-1252: </p>
+<p>a with diaeresis if decoded as Windows-1252: </p>
+
+<iframe src="file_bug234628-6-child.html"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-8-child.html b/docshell/test/browser/file_bug234628-8-child.html
new file mode 100644
index 0000000000..254e0fb2b3
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-8-child.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>meta declaration in parent and no declaration in child</title>
+</head>
+<body>
+<p>Capital dje if decoded as Windows-1251: </p>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-8.html b/docshell/test/browser/file_bug234628-8.html
new file mode 100644
index 0000000000..b44e91801c
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-8.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="windows-1251">
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>meta declaration in parent and no declaration in child</title>
+</head>
+<body>
+<h1>meta declaration in parent and no declaration in child</h1>
+
+<p>Capital dje if decoded as Windows-1251: </p>
+
+<iframe src="file_bug234628-8-child.html"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-9-child.html b/docshell/test/browser/file_bug234628-9-child.html
new file mode 100644
index 0000000000..a86b14d7ee
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-9-child.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>UTF-16 with BOM in parent and no declaration in child</title>
+</head>
+<body>
+<p>Euro sign if decoded as Windows-1251: </p>
+
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug234628-9.html b/docshell/test/browser/file_bug234628-9.html
new file mode 100644
index 0000000000..8a469da3aa
--- /dev/null
+++ b/docshell/test/browser/file_bug234628-9.html
Binary files differ
diff --git a/docshell/test/browser/file_bug420605.html b/docshell/test/browser/file_bug420605.html
new file mode 100644
index 0000000000..8424b92f8f
--- /dev/null
+++ b/docshell/test/browser/file_bug420605.html
@@ -0,0 +1,31 @@
+<head>
+<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg=="/>
+ <title>Page Title for Bug 420605</title>
+</head>
+<body>
+ <h1>Fragment links</h1>
+
+ <p>This page has a bunch of fragment links to sections below:</p>
+
+ <ul>
+ <li><a id="firefox-link" href="#firefox">Firefox</a></li>
+ <li><a id="thunderbird-link" href="#thunderbird">Thunderbird</a></li>
+ <li><a id="seamonkey-link" href="#seamonkey">Seamonkey</a></li>
+ </ul>
+
+ <p>And here are the sections:</p>
+
+ <h2 id="firefox">Firefox</h2>
+
+ <p>Firefox is a browser.</p>
+
+ <h2 id="thunderbird">Thunderbird</h2>
+
+ <p>Thunderbird is an email client</p>
+
+ <h2 id="seamonkey">Seamonkey</h2>
+
+ <p>Seamonkey is the all-in-one application.</p>
+
+</body>
+</html>
diff --git a/docshell/test/browser/file_bug503832.html b/docshell/test/browser/file_bug503832.html
new file mode 100644
index 0000000000..338631c8a0
--- /dev/null
+++ b/docshell/test/browser/file_bug503832.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<!--
+Test page for https://bugzilla.mozilla.org/show_bug.cgi?id=503832
+-->
+<head>
+ <title>Page Title for Bug 503832</title>
+</head>
+<body>
+ <h1>Fragment links</h1>
+
+ <p>This page has a bunch of fragment links to sections below:</p>
+
+ <ul>
+ <li><a id="firefox-link" href="#firefox">Firefox</a></li>
+ <li><a id="thunderbird-link" href="#thunderbird">Thunderbird</a></li>
+ <li><a id="seamonkey-link" href="#seamonkey">Seamonkey</a></li>
+ </ul>
+
+ <p>And here are the sections:</p>
+
+ <h2 id="firefox">Firefox</h2>
+
+ <p>Firefox is a browser.</p>
+
+ <h2 id="thunderbird">Thunderbird</h2>
+
+ <p>Thunderbird is an email client</p>
+
+ <h2 id="seamonkey">Seamonkey</h2>
+
+ <p>Seamonkey is the all-in-one application.</p>
+
+</body>
+</html>
diff --git a/docshell/test/browser/file_bug655270.html b/docshell/test/browser/file_bug655270.html
new file mode 100644
index 0000000000..0c08d982b1
--- /dev/null
+++ b/docshell/test/browser/file_bug655270.html
@@ -0,0 +1,11 @@
+<html>
+
+<head>
+ <link rel='icon' href='favicon_bug655270.ico'>
+</head>
+
+<body>
+Nothing to see here...
+</body>
+
+</html>
diff --git a/docshell/test/browser/file_bug670318.html b/docshell/test/browser/file_bug670318.html
new file mode 100644
index 0000000000..a78e8fcb19
--- /dev/null
+++ b/docshell/test/browser/file_bug670318.html
@@ -0,0 +1,23 @@
+<html><head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<script>
+function load() {
+ function next() {
+ if (count < 5)
+ iframe.src = "data:text/html;charset=utf-8,iframe " + (++count);
+ }
+
+ var count = 0;
+ var iframe = document.createElement("iframe");
+ iframe.onload = function() { setTimeout(next, 0); };
+ document.body.appendChild(iframe);
+
+ setTimeout(next, 0);
+}
+</script>
+</head>
+
+<body onload="load()">
+Testcase
+</body>
+</html>
diff --git a/docshell/test/browser/file_bug673087-1-child.html b/docshell/test/browser/file_bug673087-1-child.html
new file mode 100644
index 0000000000..7bb1ad965b
--- /dev/null
+++ b/docshell/test/browser/file_bug673087-1-child.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset=windows-1252>
+<meta content="width=device-width, initial-scale=1" name="viewport">
+<title>windows-1252 in parent and child, actually EUC-JP</title>
+</head>
+<body>
+<p>Hiragana letter a if decoded as EUC-JP: </p>
+<p>ʸ¸Ǥ</p>
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_bug673087-1.html b/docshell/test/browser/file_bug673087-1.html
new file mode 100644
index 0000000000..3dbea43d66
--- /dev/null
+++ b/docshell/test/browser/file_bug673087-1.html
Binary files differ
diff --git a/docshell/test/browser/file_bug673087-1.html^headers^ b/docshell/test/browser/file_bug673087-1.html^headers^
new file mode 100644
index 0000000000..2340a89c93
--- /dev/null
+++ b/docshell/test/browser/file_bug673087-1.html^headers^
@@ -0,0 +1 @@
+Content-Type: text/html; charset=windows-1252
diff --git a/docshell/test/browser/file_bug673087-2.html b/docshell/test/browser/file_bug673087-2.html
new file mode 100644
index 0000000000..ccbf896ca6
--- /dev/null
+++ b/docshell/test/browser/file_bug673087-2.html
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="ISO-2022-KR">
+FAIL \ No newline at end of file
diff --git a/docshell/test/browser/file_bug852909.pdf b/docshell/test/browser/file_bug852909.pdf
new file mode 100644
index 0000000000..89066463f1
--- /dev/null
+++ b/docshell/test/browser/file_bug852909.pdf
Binary files differ
diff --git a/docshell/test/browser/file_bug852909.png b/docshell/test/browser/file_bug852909.png
new file mode 100644
index 0000000000..c7510d388f
--- /dev/null
+++ b/docshell/test/browser/file_bug852909.png
Binary files differ
diff --git a/docshell/test/browser/file_click_link_within_view_source.html b/docshell/test/browser/file_click_link_within_view_source.html
new file mode 100644
index 0000000000..d78e4ba0ff
--- /dev/null
+++ b/docshell/test/browser/file_click_link_within_view_source.html
@@ -0,0 +1,6 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+ <body>
+ <a id="testlink" href="dummy_page.html">clickme</a>
+ </body>
+</html>
diff --git a/docshell/test/browser/file_cross_process_csp_inheritance.html b/docshell/test/browser/file_cross_process_csp_inheritance.html
new file mode 100644
index 0000000000..d87761a609
--- /dev/null
+++ b/docshell/test/browser/file_cross_process_csp_inheritance.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Test CSP inheritance if load happens in same and different process</title>
+ <meta http-equiv="Content-Security-Policy" content="script-src 'none'">
+</head>
+<body>
+ <a href="data:text/html,<html>test-same-diff-process-csp-inhertiance</html>" id="testLink" target="_blank" rel="noopener">click to test same/diff process CSP inheritance</a>
+</body>
+</html>
diff --git a/docshell/test/browser/file_csp_sandbox_no_script_js_uri.html b/docshell/test/browser/file_csp_sandbox_no_script_js_uri.html
new file mode 100644
index 0000000000..49341f7481
--- /dev/null
+++ b/docshell/test/browser/file_csp_sandbox_no_script_js_uri.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test Javascript URI with no script</title>
+</head>
+<body>
+<noscript>no scripts allowed here</noscript>
+<a href="javascript:alert(`origin=${origin} location=${location}`)" target="_parent">click me</a>
+</body>
+</html>
diff --git a/docshell/test/browser/file_csp_sandbox_no_script_js_uri.html^headers^ b/docshell/test/browser/file_csp_sandbox_no_script_js_uri.html^headers^
new file mode 100644
index 0000000000..461f7f99ce
--- /dev/null
+++ b/docshell/test/browser/file_csp_sandbox_no_script_js_uri.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: sandbox allow-same-origin allow-top-navigation;
diff --git a/docshell/test/browser/file_csp_uir.html b/docshell/test/browser/file_csp_uir.html
new file mode 100644
index 0000000000..be60f41a80
--- /dev/null
+++ b/docshell/test/browser/file_csp_uir.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1542858 - Test CSP upgrade-insecure-requests</title>
+ <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
+</head>
+<body>
+ <a id="testlink" href="file_csp_uir_dummy.html">testlink</a>
+</body>
+</html>
diff --git a/docshell/test/browser/file_csp_uir_dummy.html b/docshell/test/browser/file_csp_uir_dummy.html
new file mode 100644
index 0000000000..f0ab6775c0
--- /dev/null
+++ b/docshell/test/browser/file_csp_uir_dummy.html
@@ -0,0 +1 @@
+<html><body>foo</body></html>
diff --git a/docshell/test/browser/file_data_load_inherit_csp.html b/docshell/test/browser/file_data_load_inherit_csp.html
new file mode 100644
index 0000000000..1efe738e4c
--- /dev/null
+++ b/docshell/test/browser/file_data_load_inherit_csp.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1358009 - Inherit CSP into data URI</title>
+ <meta http-equiv="Content-Security-Policy" content="script-src 'unsafe-inline'">
+</head>
+<body>
+ <a id="testlink">testlink</a>
+</body>
+</html>
diff --git a/docshell/test/browser/file_multiple_pushState.html b/docshell/test/browser/file_multiple_pushState.html
new file mode 100644
index 0000000000..6592f3f53f
--- /dev/null
+++ b/docshell/test/browser/file_multiple_pushState.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Test multiple calls to history.pushState</title>
+ </head>
+ <body>
+ <h1>Ohai</h1>
+ </body>
+ <script type="text/javascript">
+ window.history.pushState({}, "", "/bar/ABC?key=baz");
+ let data = new Array(100000).join("a");
+ window.history.pushState({ data }, "", "/bar/ABC/DEF?key=baz");
+ // Test also Gecko specific state object size limit.
+ try {
+ let largeData = new Array(20000000).join("a");
+ window.history.pushState({ largeData }, "", "/bar/ABC/DEF/GHI?key=baz");
+ } catch (ex) {}
+ </script>
+</html>
diff --git a/docshell/test/browser/file_onbeforeunload_0.html b/docshell/test/browser/file_onbeforeunload_0.html
new file mode 100644
index 0000000000..7d9acf057d
--- /dev/null
+++ b/docshell/test/browser/file_onbeforeunload_0.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+</head>
+<body>
+ <iframe src="http://example.com/browser/docshell/test/browser/file_onbeforeunload_1.html"></iframe>
+</body>
+</html>
diff --git a/docshell/test/browser/file_onbeforeunload_1.html b/docshell/test/browser/file_onbeforeunload_1.html
new file mode 100644
index 0000000000..edd27783e4
--- /dev/null
+++ b/docshell/test/browser/file_onbeforeunload_1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+</head>
+<body>
+ <iframe src="http://mochi.test:8888/browser/docshell/test/browser/file_onbeforeunload_2.html"></iframe>
+</body>
+</html>
diff --git a/docshell/test/browser/file_onbeforeunload_2.html b/docshell/test/browser/file_onbeforeunload_2.html
new file mode 100644
index 0000000000..a52a4ace5c
--- /dev/null
+++ b/docshell/test/browser/file_onbeforeunload_2.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+</head>
+<body>
+ <iframe src="http://example.com/browser/docshell/test/browser/file_onbeforeunload_3.html"></iframe>
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_onbeforeunload_3.html b/docshell/test/browser/file_onbeforeunload_3.html
new file mode 100644
index 0000000000..9914f0cd85
--- /dev/null
+++ b/docshell/test/browser/file_onbeforeunload_3.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+</head>
+<body>
+</body>
+</html>
+
diff --git a/docshell/test/browser/file_open_about_blank.html b/docshell/test/browser/file_open_about_blank.html
new file mode 100644
index 0000000000..134384e2f7
--- /dev/null
+++ b/docshell/test/browser/file_open_about_blank.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<button id="open" onclick="window.open('')">Open child window</button>
diff --git a/docshell/test/browser/file_slow_load.sjs b/docshell/test/browser/file_slow_load.sjs
new file mode 100644
index 0000000000..4c6dd6d5b9
--- /dev/null
+++ b/docshell/test/browser/file_slow_load.sjs
@@ -0,0 +1,8 @@
+"use strict";
+
+function handleRequest(request, response) {
+ response.processAsync();
+ response.setHeader("Content-Type", "text/html");
+ response.write("<!doctype html>Loading... ");
+ // We don't block on this, so it's fine to never finish the response.
+}
diff --git a/docshell/test/browser/frame-head.js b/docshell/test/browser/frame-head.js
new file mode 100644
index 0000000000..4cebb32165
--- /dev/null
+++ b/docshell/test/browser/frame-head.js
@@ -0,0 +1,114 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint-env mozilla/frame-script */
+
+// Functions that are automatically loaded as frame scripts for
+// timeline tests.
+
+// eslint assumes we inherit browser window stuff, but this
+// framescript doesn't.
+// eslint-disable-next-line mozilla/no-redeclare-with-import-autofix
+const { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+// Functions that look like mochitest functions but forward to the
+// browser process.
+
+this.ok = function (value, message) {
+ sendAsyncMessage("browser:test:ok", {
+ value: !!value,
+ message,
+ });
+};
+
+this.is = function (v1, v2, message) {
+ ok(v1 == v2, message);
+};
+
+this.info = function (message) {
+ sendAsyncMessage("browser:test:info", { message });
+};
+
+this.finish = function () {
+ sendAsyncMessage("browser:test:finish");
+};
+
+/* Start a task that runs some timeline tests in the ordinary way.
+ *
+ * @param array tests
+ * The tests to run. This is an array where each element
+ * is of the form { desc, searchFor, setup, check }.
+ *
+ * desc is the test description, a string.
+ * searchFor is a string or a function
+ * If a string, then when a marker with this name is
+ * found, marker-reading is stopped.
+ * If a function, then the accumulated marker array is
+ * passed to it, and marker reading stops when it returns
+ * true.
+ * setup is a function that takes the docshell as an argument.
+ * It should start the test.
+ * check is a function that takes an array of markers
+ * as an argument and checks the results of the test.
+ */
+this.timelineContentTest = function (tests) {
+ (async function () {
+ let docShell = content.docShell;
+
+ info("Start recording");
+ docShell.recordProfileTimelineMarkers = true;
+
+ for (let { desc, searchFor, setup, check } of tests) {
+ info("Running test: " + desc);
+
+ info("Flushing the previous markers if any");
+ docShell.popProfileTimelineMarkers();
+
+ info("Running the test setup function");
+ let onMarkers = timelineWaitForMarkers(docShell, searchFor);
+ setup(docShell);
+ info("Waiting for new markers on the docShell");
+ let markers = await onMarkers;
+
+ // Cycle collection markers are non-deterministic, and none of these tests
+ // expect them to show up.
+ markers = markers.filter(m => !m.name.includes("nsCycleCollector"));
+
+ info("Running the test check function");
+ check(markers);
+ }
+
+ info("Stop recording");
+ docShell.recordProfileTimelineMarkers = false;
+ finish();
+ })();
+};
+
+function timelineWaitForMarkers(docshell, searchFor) {
+ if (typeof searchFor == "string") {
+ let searchForString = searchFor;
+ let f = function (markers) {
+ return markers.some(m => m.name == searchForString);
+ };
+ searchFor = f;
+ }
+
+ return new Promise(function (resolve, reject) {
+ let waitIterationCount = 0;
+ let maxWaitIterationCount = 10; // Wait for 2sec maximum
+ let markers = [];
+
+ setTimeout(function timeoutHandler() {
+ let newMarkers = docshell.popProfileTimelineMarkers();
+ markers = [...markers, ...newMarkers];
+ if (searchFor(markers) || waitIterationCount > maxWaitIterationCount) {
+ resolve(markers);
+ } else {
+ setTimeout(timeoutHandler, 200);
+ waitIterationCount++;
+ }
+ }, 200);
+ });
+}
diff --git a/docshell/test/browser/head.js b/docshell/test/browser/head.js
new file mode 100644
index 0000000000..e4943bcaf1
--- /dev/null
+++ b/docshell/test/browser/head.js
@@ -0,0 +1,258 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Helper function for timeline tests. Returns an async task that is
+ * suitable for use as a particular timeline test.
+ * @param string frameScriptName
+ * Base name of the frame script file.
+ * @param string url
+ * URL to load.
+ */
+function makeTimelineTest(frameScriptName, url) {
+ info("in timelineTest");
+ return async function () {
+ info("in in timelineTest");
+ waitForExplicitFinish();
+
+ await timelineTestOpenUrl(url);
+
+ const here = "chrome://mochitests/content/browser/docshell/test/browser/";
+
+ let mm = gBrowser.selectedBrowser.messageManager;
+ mm.loadFrameScript(here + "frame-head.js", false);
+ mm.loadFrameScript(here + frameScriptName, false);
+
+ // Set up some listeners so that timeline tests running in the
+ // content process can forward their results to the main process.
+ mm.addMessageListener("browser:test:ok", function (message) {
+ ok(message.data.value, message.data.message);
+ });
+ mm.addMessageListener("browser:test:info", function (message) {
+ info(message.data.message);
+ });
+ mm.addMessageListener("browser:test:finish", function (ignore) {
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+ };
+}
+
+/* Open a URL for a timeline test. */
+function timelineTestOpenUrl(url) {
+ window.focus();
+
+ let tabSwitchPromise = new Promise((resolve, reject) => {
+ window.gBrowser.addEventListener(
+ "TabSwitchDone",
+ function () {
+ resolve();
+ },
+ { once: true }
+ );
+ });
+
+ let loadPromise = new Promise(function (resolve, reject) {
+ let browser = window.gBrowser;
+ let tab = (browser.selectedTab = BrowserTestUtils.addTab(browser, url));
+ let linkedBrowser = tab.linkedBrowser;
+
+ BrowserTestUtils.browserLoaded(linkedBrowser).then(() => resolve(tab));
+ });
+
+ return Promise.all([tabSwitchPromise, loadPromise]).then(([_, tab]) => tab);
+}
+
+/**
+ * Helper function for encoding override tests, loads URL, runs check1,
+ * forces encoding detection, runs check2.
+ */
+function runCharsetTest(url, check1, check2) {
+ waitForExplicitFinish();
+
+ BrowserTestUtils.openNewForegroundTab(gBrowser, url, true).then(afterOpen);
+
+ function afterOpen() {
+ BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(
+ afterChangeCharset
+ );
+
+ SpecialPowers.spawn(gBrowser.selectedBrowser, [], check1).then(() => {
+ BrowserForceEncodingDetection();
+ });
+ }
+
+ function afterChangeCharset() {
+ SpecialPowers.spawn(gBrowser.selectedBrowser, [], check2).then(() => {
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+ }
+}
+
+/**
+ * Helper function for charset tests. It loads |url| in a new tab,
+ * runs |check|.
+ */
+function runCharsetCheck(url, check) {
+ waitForExplicitFinish();
+
+ BrowserTestUtils.openNewForegroundTab(gBrowser, url, true).then(afterOpen);
+
+ function afterOpen() {
+ SpecialPowers.spawn(gBrowser.selectedBrowser, [], check).then(() => {
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+ }
+}
+
+async function pushState(url, frameId) {
+ info(
+ `Doing a pushState, expecting to load ${url} ${
+ frameId ? "in an iframe" : ""
+ }`
+ );
+ let browser = gBrowser.selectedBrowser;
+ let bc = browser.browsingContext;
+ if (frameId) {
+ bc = await SpecialPowers.spawn(bc, [frameId], function (id) {
+ return content.document.getElementById(id).browsingContext;
+ });
+ }
+ let loaded = BrowserTestUtils.waitForLocationChange(gBrowser, url);
+ await SpecialPowers.spawn(bc, [url], function (url) {
+ content.history.pushState({}, "", url);
+ });
+ await loaded;
+ info(`Loaded ${url} ${frameId ? "in an iframe" : ""}`);
+}
+
+async function loadURI(url) {
+ info(`Doing a top-level loadURI, expecting to load ${url}`);
+ let browser = gBrowser.selectedBrowser;
+ let loaded = BrowserTestUtils.browserLoaded(browser, false, url);
+ BrowserTestUtils.loadURIString(browser, url);
+ await loaded;
+ info(`Loaded ${url}`);
+}
+
+async function followLink(url, frameId) {
+ info(
+ `Creating and following a link to ${url} ${frameId ? "in an iframe" : ""}`
+ );
+ let browser = gBrowser.selectedBrowser;
+ let bc = browser.browsingContext;
+ if (frameId) {
+ bc = await SpecialPowers.spawn(bc, [frameId], function (id) {
+ return content.document.getElementById(id).browsingContext;
+ });
+ }
+ let loaded = BrowserTestUtils.browserLoaded(browser, !!frameId, url);
+ await SpecialPowers.spawn(bc, [url], function (url) {
+ let a = content.document.createElement("a");
+ a.href = url;
+ content.document.body.appendChild(a);
+ a.click();
+ });
+ await loaded;
+ info(`Loaded ${url} ${frameId ? "in an iframe" : ""}`);
+}
+
+async function goForward(url, useFrame = false) {
+ info(
+ `Clicking the forward button, expecting to load ${url} ${
+ useFrame ? "in an iframe" : ""
+ }`
+ );
+ let loaded = BrowserTestUtils.waitForLocationChange(gBrowser, url);
+ let forwardButton = document.getElementById("forward-button");
+ forwardButton.click();
+ await loaded;
+ info(`Loaded ${url} ${useFrame ? "in an iframe" : ""}`);
+}
+
+async function goBack(url, useFrame = false) {
+ info(
+ `Clicking the back button, expecting to load ${url} ${
+ useFrame ? "in an iframe" : ""
+ }`
+ );
+ let loaded = BrowserTestUtils.waitForLocationChange(gBrowser, url);
+ let backButton = document.getElementById("back-button");
+ backButton.click();
+ await loaded;
+ info(`Loaded ${url} ${useFrame ? "in an iframe" : ""}`);
+}
+
+function assertBackForwardState(canGoBack, canGoForward) {
+ let backButton = document.getElementById("back-button");
+ let forwardButton = document.getElementById("forward-button");
+
+ is(
+ backButton.disabled,
+ !canGoBack,
+ `${gBrowser.currentURI.spec}: back button is ${
+ canGoBack ? "not" : ""
+ } disabled`
+ );
+ is(
+ forwardButton.disabled,
+ !canGoForward,
+ `${gBrowser.currentURI.spec}: forward button is ${
+ canGoForward ? "not" : ""
+ } disabled`
+ );
+}
+
+class SHListener {
+ static NewEntry = 0;
+ static Reload = 1;
+ static GotoIndex = 2;
+ static Purge = 3;
+ static ReplaceEntry = 4;
+ static async waitForHistory(history, event) {
+ return new Promise(resolve => {
+ let listener = {
+ OnHistoryNewEntry: () => {},
+ OnHistoryReload: () => {
+ return true;
+ },
+ OnHistoryGotoIndex: () => {},
+ OnHistoryPurge: () => {},
+ OnHistoryReplaceEntry: () => {},
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsISHistoryListener",
+ "nsISupportsWeakReference",
+ ]),
+ };
+
+ function finish() {
+ history.removeSHistoryListener(listener);
+ resolve();
+ }
+ switch (event) {
+ case this.NewEntry:
+ listener.OnHistoryNewEntry = finish;
+ break;
+ case this.Reload:
+ listener.OnHistoryReload = () => {
+ finish();
+ return true;
+ };
+ break;
+ case this.GotoIndex:
+ listener.OnHistoryGotoIndex = finish;
+ break;
+ case this.Purge:
+ listener.OnHistoryPurge = finish;
+ break;
+ case this.ReplaceEntry:
+ listener.OnHistoryReplaceEntry = finish;
+ break;
+ }
+
+ history.addSHistoryListener(listener);
+ });
+ }
+}
diff --git a/docshell/test/browser/head_browser_onbeforeunload.js b/docshell/test/browser/head_browser_onbeforeunload.js
new file mode 100644
index 0000000000..6bb334b793
--- /dev/null
+++ b/docshell/test/browser/head_browser_onbeforeunload.js
@@ -0,0 +1,271 @@
+"use strict";
+
+const BASE_URL = "http://mochi.test:8888/browser/docshell/test/browser/";
+
+const TEST_PAGE = BASE_URL + "file_onbeforeunload_0.html";
+
+const CONTENT_PROMPT_SUBDIALOG = Services.prefs.getBoolPref(
+ "prompts.contentPromptSubDialog",
+ false
+);
+
+const { PromptTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromptTestUtils.sys.mjs"
+);
+
+async function withTabModalPromptCount(expected, task) {
+ const DIALOG_TOPIC = CONTENT_PROMPT_SUBDIALOG
+ ? "common-dialog-loaded"
+ : "tabmodal-dialog-loaded";
+
+ let count = 0;
+ function observer() {
+ count++;
+ }
+
+ Services.obs.addObserver(observer, DIALOG_TOPIC);
+ try {
+ return await task();
+ } finally {
+ Services.obs.removeObserver(observer, DIALOG_TOPIC);
+ is(count, expected, "Should see expected number of tab modal prompts");
+ }
+}
+
+function promiseAllowUnloadPrompt(browser, allowNavigation) {
+ return PromptTestUtils.handleNextPrompt(
+ browser,
+ { modalType: Services.prompt.MODAL_TYPE_CONTENT, promptType: "confirmEx" },
+ { buttonNumClick: allowNavigation ? 0 : 1 }
+ );
+}
+
+// Maintain a pool of background tabs with our test document loaded so
+// we don't have to wait for a load prior to each test step (potentially
+// tearing down and recreating content processes in the process).
+const TabPool = {
+ poolSize: 5,
+
+ pendingCount: 0,
+
+ readyTabs: [],
+
+ readyPromise: null,
+ resolveReadyPromise: null,
+
+ spawnTabs() {
+ while (this.pendingCount + this.readyTabs.length < this.poolSize) {
+ this.pendingCount++;
+ let tab = BrowserTestUtils.addTab(gBrowser, TEST_PAGE);
+ BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(() => {
+ this.readyTabs.push(tab);
+ this.pendingCount--;
+
+ if (this.resolveReadyPromise) {
+ this.readyPromise = null;
+ this.resolveReadyPromise();
+ this.resolveReadyPromise = null;
+ }
+
+ this.spawnTabs();
+ });
+ }
+ },
+
+ getReadyPromise() {
+ if (!this.readyPromise) {
+ this.readyPromise = new Promise(resolve => {
+ this.resolveReadyPromise = resolve;
+ });
+ }
+ return this.readyPromise;
+ },
+
+ async getTab() {
+ while (!this.readyTabs.length) {
+ this.spawnTabs();
+ await this.getReadyPromise();
+ }
+
+ let tab = this.readyTabs.shift();
+ this.spawnTabs();
+
+ gBrowser.selectedTab = tab;
+ return tab;
+ },
+
+ async cleanup() {
+ this.poolSize = 0;
+
+ while (this.pendingCount) {
+ await this.getReadyPromise();
+ }
+
+ while (this.readyTabs.length) {
+ await BrowserTestUtils.removeTab(this.readyTabs.shift());
+ }
+ },
+};
+
+const ACTIONS = {
+ NONE: 0,
+ LISTEN_AND_ALLOW: 1,
+ LISTEN_AND_BLOCK: 2,
+};
+
+const ACTION_NAMES = new Map(Object.entries(ACTIONS).map(([k, v]) => [v, k]));
+
+function* generatePermutations(depth) {
+ if (depth == 0) {
+ yield [];
+ return;
+ }
+ for (let subActions of generatePermutations(depth - 1)) {
+ for (let action of Object.values(ACTIONS)) {
+ yield [action, ...subActions];
+ }
+ }
+}
+
+const PERMUTATIONS = Array.from(generatePermutations(4));
+
+const FRAMES = [
+ { process: 0 },
+ { process: SpecialPowers.useRemoteSubframes ? 1 : 0 },
+ { process: 0 },
+ { process: SpecialPowers.useRemoteSubframes ? 1 : 0 },
+];
+
+function addListener(bc, block) {
+ return SpecialPowers.spawn(bc, [block], block => {
+ return new Promise(resolve => {
+ function onbeforeunload(event) {
+ if (block) {
+ event.preventDefault();
+ }
+ resolve({ event: "beforeunload" });
+ }
+ content.addEventListener("beforeunload", onbeforeunload, { once: true });
+ content.unlisten = () => {
+ content.removeEventListener("beforeunload", onbeforeunload);
+ };
+
+ content.addEventListener(
+ "unload",
+ () => {
+ resolve({ event: "unload" });
+ },
+ { once: true }
+ );
+ });
+ });
+}
+
+function descendants(bc) {
+ if (bc) {
+ return [bc, ...descendants(bc.children[0])];
+ }
+ return [];
+}
+
+async function addListeners(frames, actions, startIdx) {
+ let process = startIdx >= 0 ? FRAMES[startIdx].process : -1;
+
+ let roundTripPromises = [];
+
+ let expectNestedEventLoop = false;
+ let numBlockers = 0;
+ let unloadPromises = [];
+ let beforeUnloadPromises = [];
+
+ for (let [i, frame] of frames.entries()) {
+ let action = actions[i];
+ if (action === ACTIONS.NONE) {
+ continue;
+ }
+
+ let block = action === ACTIONS.LISTEN_AND_BLOCK;
+ let promise = addListener(frame, block);
+ if (startIdx <= i) {
+ if (block || FRAMES[i].process !== process) {
+ expectNestedEventLoop = true;
+ }
+ beforeUnloadPromises.push(promise);
+ numBlockers += block;
+ } else {
+ unloadPromises.push(promise);
+ }
+
+ roundTripPromises.push(SpecialPowers.spawn(frame, [], () => {}));
+ }
+
+ // Wait for round trip messages to any processes with event listeners to
+ // return so we're sure that all listeners are registered and their state
+ // flags are propagated before we continue.
+ await Promise.all(roundTripPromises);
+
+ return {
+ expectNestedEventLoop,
+ expectPrompt: !!numBlockers,
+ unloadPromises,
+ beforeUnloadPromises,
+ };
+}
+
+async function doTest(actions, startIdx, navigate) {
+ let tab = await TabPool.getTab();
+ let browser = tab.linkedBrowser;
+
+ let frames = descendants(browser.browsingContext);
+ let expected = await addListeners(frames, actions, startIdx);
+
+ let awaitingPrompt = false;
+ let promptPromise;
+ if (expected.expectPrompt) {
+ awaitingPrompt = true;
+ promptPromise = promiseAllowUnloadPrompt(browser, false).then(() => {
+ awaitingPrompt = false;
+ });
+ }
+
+ let promptCount = expected.expectPrompt ? 1 : 0;
+ await withTabModalPromptCount(promptCount, async () => {
+ await navigate(tab, frames).then(result => {
+ ok(
+ !awaitingPrompt,
+ "Navigation should not complete while we're still expecting a prompt"
+ );
+
+ is(
+ result.eventLoopSpun,
+ expected.expectNestedEventLoop,
+ "Should have nested event loop?"
+ );
+ });
+
+ for (let result of await Promise.all(expected.beforeUnloadPromises)) {
+ is(
+ result.event,
+ "beforeunload",
+ "Should have seen beforeunload event before unload"
+ );
+ }
+ await promptPromise;
+
+ await Promise.all(
+ frames.map(frame =>
+ SpecialPowers.spawn(frame, [], () => {
+ if (content.unlisten) {
+ content.unlisten();
+ }
+ }).catch(() => {})
+ )
+ );
+
+ await BrowserTestUtils.removeTab(tab);
+ });
+
+ for (let result of await Promise.all(expected.unloadPromises)) {
+ is(result.event, "unload", "Should have seen unload event");
+ }
+}
diff --git a/docshell/test/browser/onload_message.html b/docshell/test/browser/onload_message.html
new file mode 100644
index 0000000000..23f6e37396
--- /dev/null
+++ b/docshell/test/browser/onload_message.html
@@ -0,0 +1,25 @@
+<html>
+ <head>
+ <meta charset="utf-8">
+ <script>
+ addEventListener("load", function() {
+ // This file is used in couple of different tests.
+ if (opener) {
+ opener.postMessage("load", "*");
+ } else {
+ var bc = new BroadcastChannel("browser_browsingContext");
+ bc.onmessage = function(event) {
+ if (event.data == "back") {
+ bc.close();
+ history.back();
+ }
+ };
+ bc.postMessage({event: "load", framesLength: frames.length });
+ }
+ });
+ </script>
+ </head>
+ <body>
+ This file posts a message containing "load" to opener or BroadcastChannel on load completion.
+ </body>
+</html>
diff --git a/docshell/test/browser/onpageshow_message.html b/docshell/test/browser/onpageshow_message.html
new file mode 100644
index 0000000000..105c06f8db
--- /dev/null
+++ b/docshell/test/browser/onpageshow_message.html
@@ -0,0 +1,41 @@
+<html>
+ <head>
+ <meta charset="utf-8">
+ <script>
+ addEventListener("pageshow", function() {
+ var bc = new BroadcastChannel("browser_browsingContext");
+ function frameData() {
+ var win = SpecialPowers.wrap(frames[0]);
+ bc.postMessage(
+ { framesLength: frames.length,
+ browsingContextId: win.docShell.browsingContext.id,
+ outerWindowId: win.docShell.outerWindowID
+ });
+ }
+
+ bc.onmessage = function(event) {
+ if (event.data == "createiframe") {
+ let frame = document.createElement("iframe");
+ frame.src = "dummy_page.html";
+ document.body.appendChild(frame);
+ frame.onload = frameData;
+ } else if (event.data == "nextpage") {
+ bc.close();
+ location.href = "onload_message.html";
+ } else if (event.data == "queryframes") {
+ frameData();
+ } else if (event.data == "close") {
+ bc.postMessage("closed");
+ bc.close();
+ window.close();
+ }
+ }
+ bc.postMessage("pageshow");
+ });
+ </script>
+ </head>
+ <body>
+ This file posts a message containing "pageshow" to a BroadcastChannel and
+ keep the channel open for commands.
+ </body>
+</html>
diff --git a/docshell/test/browser/overlink_test.html b/docshell/test/browser/overlink_test.html
new file mode 100644
index 0000000000..5efd689311
--- /dev/null
+++ b/docshell/test/browser/overlink_test.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+ <head> <meta charset="utf-8"> </head>
+ <body>
+ <a id="link" href="https://user:password@example.com">Link</a>
+ </body>
+</html>
diff --git a/docshell/test/browser/print_postdata.sjs b/docshell/test/browser/print_postdata.sjs
new file mode 100644
index 0000000000..0e3ef38419
--- /dev/null
+++ b/docshell/test/browser/print_postdata.sjs
@@ -0,0 +1,25 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ if (request.method == "GET") {
+ response.write(request.queryString);
+ } else {
+ var body = new BinaryInputStream(request.bodyInputStream);
+
+ var avail;
+ var bytes = [];
+
+ while ((avail = body.available()) > 0) {
+ Array.prototype.push.apply(bytes, body.readByteArray(avail));
+ }
+
+ var data = String.fromCharCode.apply(null, bytes);
+ response.bodyOutputStream.write(data, data.length);
+ }
+}
diff --git a/docshell/test/browser/redirect_to_example.sjs b/docshell/test/browser/redirect_to_example.sjs
new file mode 100644
index 0000000000..283e4793db
--- /dev/null
+++ b/docshell/test/browser/redirect_to_example.sjs
@@ -0,0 +1,5 @@
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 302, "Moved Permanently");
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ response.setHeader("Location", "http://example");
+}
diff --git a/docshell/test/browser/test-form_sjis.html b/docshell/test/browser/test-form_sjis.html
new file mode 100644
index 0000000000..91c375deef
--- /dev/null
+++ b/docshell/test/browser/test-form_sjis.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/REC-html401-19991224/strict.dtd">
+<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=windows-1251">
+ <title>Shift_JIS in body and text area</title>
+ </head>
+ <body>
+ <h1>Incorrect meta charset</h1>
+ <h2>This page is encoded in Shift_JIS, but has an incorrect meta charset
+ claiming that it is windows-1251</h2>
+ <p id="testpar">jR[h́AׂĂ̕ɌŗL̔ԍt^܂</p>
+ <form>
+ <p>
+ <textarea id="testtextarea" rows=6 cols=60>jR[h́AׂĂ̕ɌŗL̔ԍt^܂</textarea>
+ <input id="testinput" type="text" size=60 value="jR[h́AׂĂ̕ɌŗL̔ԍt^܂">
+ </p>
+ </form>
+ <h2>Expected text on load:</h2>
+ <p>&#x453;&#x2020;&#x453;&#x6A;&#x453;&#x52;&#x403;&#x5B;&#x453;&#x68;&#x201A;&#x41D;&#x403;&#x41;&#x201A;&#xB7;&#x201A;&#x427;&#x201A;&#x414;&#x201A;&#x41C;&#x2022;&#xB6;&#x40B;&#x459;&#x201A;&#x419;&#x40A;&#x415;&#x2014;&#x4C;&#x201A;&#x41C;&#x201D;&#x424;&#x40C;&#x2020;&#x201A;&#x440;&#x2022;&#x74;&#x2014;&#x5E;&#x201A;&#xB5;&#x201A;&#x42C;&#x201A;&#xB7;</p>
+ <h2>Expected text on resetting the encoding to Shift_JIS:</h2>
+ <p>&#x30E6;&#x30CB;&#x30B3;&#x30FC;&#x30C9;&#x306F;&#x3001;&#x3059;&#x3079;&#x3066;&#x306E;&#x6587;&#x5B57;&#x306B;&#x56FA;&#x6709;&#x306E;&#x756A;&#x53F7;&#x3092;&#x4ED8;&#x4E0E;&#x3057;&#x307E;&#x3059;</p>
+ </body>
+</html>
diff --git a/docshell/test/chrome/112564_nocache.html b/docshell/test/chrome/112564_nocache.html
new file mode 100644
index 0000000000..29fb990b86
--- /dev/null
+++ b/docshell/test/chrome/112564_nocache.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<title>test1</title>
+</head>
+<body>
+<p>
+This document will be sent with a no-cache cache-control header. When sent over a secure connection, it should not be stored in bfcache.
+</p>
+</body>
+</html>
diff --git a/docshell/test/chrome/112564_nocache.html^headers^ b/docshell/test/chrome/112564_nocache.html^headers^
new file mode 100644
index 0000000000..c829a41ae9
--- /dev/null
+++ b/docshell/test/chrome/112564_nocache.html^headers^
@@ -0,0 +1 @@
+Cache-control: no-cache
diff --git a/docshell/test/chrome/215405_nocache.html b/docshell/test/chrome/215405_nocache.html
new file mode 100644
index 0000000000..c7d48c4eba
--- /dev/null
+++ b/docshell/test/chrome/215405_nocache.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html style="height: 100%">
+<head>
+ <title>test1</title>
+</head>
+<body style="height: 100%">
+ <input type="text" id="inp" value="">
+ </input>
+ <div style="height: 50%">Some text</div>
+ <div style="height: 50%">Some text</div>
+ <div style="height: 50%">Some text</div>
+ <div style="height: 50%; width: 300%">Some more text</div>
+</body>
+</html>
diff --git a/docshell/test/chrome/215405_nocache.html^headers^ b/docshell/test/chrome/215405_nocache.html^headers^
new file mode 100644
index 0000000000..c829a41ae9
--- /dev/null
+++ b/docshell/test/chrome/215405_nocache.html^headers^
@@ -0,0 +1 @@
+Cache-control: no-cache
diff --git a/docshell/test/chrome/215405_nostore.html b/docshell/test/chrome/215405_nostore.html
new file mode 100644
index 0000000000..4f5bd0f4f0
--- /dev/null
+++ b/docshell/test/chrome/215405_nostore.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html style="height: 100%">
+<head>
+ <title>test1</title>
+</head>
+<body style="height: 100%">
+ <input type="text" id="inp" value="">
+ </input>
+ <div style="height: 50%">Some text</div>
+ <div style="height: 50%">Some text</div>
+ <div style="height: 50%">Some text</div>
+ <div style="height: 50%; width: 350%">Some more text</div>
+</body>
+</html>
diff --git a/docshell/test/chrome/215405_nostore.html^headers^ b/docshell/test/chrome/215405_nostore.html^headers^
new file mode 100644
index 0000000000..59ba296103
--- /dev/null
+++ b/docshell/test/chrome/215405_nostore.html^headers^
@@ -0,0 +1 @@
+Cache-control: no-store
diff --git a/docshell/test/chrome/582176_dummy.html b/docshell/test/chrome/582176_dummy.html
new file mode 100644
index 0000000000..3b18e512db
--- /dev/null
+++ b/docshell/test/chrome/582176_dummy.html
@@ -0,0 +1 @@
+hello world
diff --git a/docshell/test/chrome/582176_xml.xml b/docshell/test/chrome/582176_xml.xml
new file mode 100644
index 0000000000..d3dd576dfe
--- /dev/null
+++ b/docshell/test/chrome/582176_xml.xml
@@ -0,0 +1,2 @@
+<?xml-stylesheet type="text/xsl" href="582176_xslt.xsl"?>
+<out/>
diff --git a/docshell/test/chrome/582176_xslt.xsl b/docshell/test/chrome/582176_xslt.xsl
new file mode 100644
index 0000000000..5957416899
--- /dev/null
+++ b/docshell/test/chrome/582176_xslt.xsl
@@ -0,0 +1,8 @@
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:template match="out">
+ <html>
+ <head><title>XSLT result doc</title></head>
+ <body><p>xslt result</p></body>
+ </html>
+ </xsl:template>
+</xsl:stylesheet>
diff --git a/docshell/test/chrome/662200a.html b/docshell/test/chrome/662200a.html
new file mode 100644
index 0000000000..0b9ead6f3e
--- /dev/null
+++ b/docshell/test/chrome/662200a.html
@@ -0,0 +1,8 @@
+<html>
+ <head>
+ <title>A</title>
+ </head>
+ <body>
+ <a id="link" href="662200b.html">Next</a>
+ </body>
+</html>
diff --git a/docshell/test/chrome/662200b.html b/docshell/test/chrome/662200b.html
new file mode 100644
index 0000000000..91e6b971d6
--- /dev/null
+++ b/docshell/test/chrome/662200b.html
@@ -0,0 +1,8 @@
+<html>
+ <head>
+ <title>B</title>
+ </head>
+ <body>
+ <a id="link" href="662200c.html">Next</a>
+ </body>
+</html>
diff --git a/docshell/test/chrome/662200c.html b/docshell/test/chrome/662200c.html
new file mode 100644
index 0000000000..bc00e6b14b
--- /dev/null
+++ b/docshell/test/chrome/662200c.html
@@ -0,0 +1,7 @@
+<html>
+ <head>
+ <title>C</title>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/docshell/test/chrome/89419.html b/docshell/test/chrome/89419.html
new file mode 100644
index 0000000000..b36b8d788c
--- /dev/null
+++ b/docshell/test/chrome/89419.html
@@ -0,0 +1,7 @@
+<html>
+<head>
+<title>Bug 89419</title>
+</head>
+<body>
+<img src="http://mochi.test:8888/tests/docshell/test/chrome/bug89419.sjs">
+</body>
diff --git a/docshell/test/chrome/92598_nostore.html b/docshell/test/chrome/92598_nostore.html
new file mode 100644
index 0000000000..47bb90441e
--- /dev/null
+++ b/docshell/test/chrome/92598_nostore.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<title>test1</title>
+</head>
+<body>
+<p>
+This document will be sent with a no-store cache-control header. It should not be stored in bfcache.
+</p>
+</body>
+</html>
diff --git a/docshell/test/chrome/92598_nostore.html^headers^ b/docshell/test/chrome/92598_nostore.html^headers^
new file mode 100644
index 0000000000..59ba296103
--- /dev/null
+++ b/docshell/test/chrome/92598_nostore.html^headers^
@@ -0,0 +1 @@
+Cache-control: no-store
diff --git a/docshell/test/chrome/DocShellHelpers.sys.mjs b/docshell/test/chrome/DocShellHelpers.sys.mjs
new file mode 100644
index 0000000000..5f4eee8724
--- /dev/null
+++ b/docshell/test/chrome/DocShellHelpers.sys.mjs
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+export class DocShellHelpersParent extends JSWindowActorParent {
+ static eventListener;
+
+ // These static variables should be set when registering the actor
+ // (currently doPageNavigation in docshell_helpers.js).
+ static eventsToListenFor;
+ static observers;
+
+ constructor() {
+ super();
+ }
+ receiveMessage({ name, data }) {
+ if (name == "docshell_helpers:event") {
+ let { event, originalTargetIsHTMLDocument } = data;
+
+ if (this.constructor.eventsToListenFor.includes(event.type)) {
+ this.constructor.eventListener(event, originalTargetIsHTMLDocument);
+ }
+ } else if (name == "docshell_helpers:observe") {
+ let { topic } = data;
+
+ this.constructor.observers.get(topic).call();
+ }
+ }
+}
+
+export class DocShellHelpersChild extends JSWindowActorChild {
+ constructor() {
+ super();
+ }
+ receiveMessage({ name, data }) {
+ if (name == "docshell_helpers:preventBFCache") {
+ // Add an RTCPeerConnection to prevent the page from being bfcached.
+ let win = this.contentWindow;
+ win.blockBFCache = new win.RTCPeerConnection();
+ }
+ }
+ handleEvent(event) {
+ if (
+ Document.isInstance(event.originalTarget) &&
+ event.originalTarget.isInitialDocument
+ ) {
+ dump(`TEST: ignoring a ${event.type} event for an initial about:blank\n`);
+ return;
+ }
+
+ this.sendAsyncMessage("docshell_helpers:event", {
+ event: {
+ type: event.type,
+ persisted: event.persisted,
+ originalTarget: {
+ title: event.originalTarget.title,
+ location: event.originalTarget.location.href,
+ visibilityState: event.originalTarget.visibilityState,
+ hidden: event.originalTarget.hidden,
+ },
+ },
+ originalTargetIsHTMLDocument: HTMLDocument.isInstance(
+ event.originalTarget
+ ),
+ });
+ }
+ observe(subject, topic) {
+ if (Window.isInstance(subject) && subject.document.isInitialDocument) {
+ dump(`TEST: ignoring a topic notification for an initial about:blank\n`);
+ return;
+ }
+
+ this.sendAsyncMessage("docshell_helpers:observe", { topic });
+ }
+}
diff --git a/docshell/test/chrome/allowContentRetargeting.sjs b/docshell/test/chrome/allowContentRetargeting.sjs
new file mode 100644
index 0000000000..96e467ef68
--- /dev/null
+++ b/docshell/test/chrome/allowContentRetargeting.sjs
@@ -0,0 +1,7 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function handleRequest(req, resp) {
+ resp.setHeader("Content-Type", "application/octet-stream", false);
+ resp.write("hi");
+}
diff --git a/docshell/test/chrome/blue.png b/docshell/test/chrome/blue.png
new file mode 100644
index 0000000000..8df58f3a5f
--- /dev/null
+++ b/docshell/test/chrome/blue.png
Binary files differ
diff --git a/docshell/test/chrome/bug112564_window.xhtml b/docshell/test/chrome/bug112564_window.xhtml
new file mode 100644
index 0000000000..04c25763b3
--- /dev/null
+++ b/docshell/test/chrome/bug112564_window.xhtml
@@ -0,0 +1,86 @@
+<?xml version="1.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/. -->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="112564Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="onLoad();"
+ title="112564 test">
+
+ <script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src="docshell_helpers.js" />
+ <script type="application/javascript"><![CDATA[
+ var gTestsIterator;
+
+ function onLoad() {
+ gTestsIterator = testsIterator();
+ nextTest();
+ }
+
+ function nextTest() {
+ gTestsIterator.next();
+ }
+
+ function* testsIterator() {
+ // Load a secure page with a no-cache header, followed by a simple page.
+ // no-cache should not interfere with the bfcache in the way no-store
+ // does.
+ var test1DocURI = "https://example.com:443/chrome/docshell/test/chrome/112564_nocache.html";
+
+ doPageNavigation({
+ uri: test1DocURI,
+ eventsToListenFor: ["load", "pageshow"],
+ expectedEvents: [ { type: "load",
+ title: "test1" },
+ { type: "pageshow",
+ title: "test1",
+ persisted: false } ],
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ var test2DocURI = "data:text/html,<html><head><title>test2</title></head>" +
+ "<body>test2</body></html>";
+
+ doPageNavigation({
+ uri: test2DocURI,
+ eventsToListenFor: ["load", "pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "test1",
+ persisted: true },
+ { type: "load",
+ title: "test2" },
+ { type: "pageshow",
+ title: "test2",
+ persisted: false } ],
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ // Now go back in history. First page has been cached.
+ // Check persisted property to confirm
+ doPageNavigation({
+ back: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "test2",
+ persisted: true },
+ { type: "pageshow",
+ title: "test1",
+ persisted: true } ],
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ finish();
+ }
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" remote="true" maychangeremoteness="true" />
+</window>
diff --git a/docshell/test/chrome/bug113934_window.xhtml b/docshell/test/chrome/bug113934_window.xhtml
new file mode 100644
index 0000000000..794b8b2836
--- /dev/null
+++ b/docshell/test/chrome/bug113934_window.xhtml
@@ -0,0 +1,165 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window title="Mozilla Bug 113934" onload="doTheTest()"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <hbox>
+ <vbox id="box1">
+ </vbox>
+ <vbox id="box2">
+ </vbox>
+ <spacer flex="1"/>
+ </hbox>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ /* globals SimpleTest, is, isnot, ok, snapshotWindow, compareSnapshots,
+ onerror */
+ var imports = [ "SimpleTest", "is", "isnot", "ok", "snapshotWindow",
+ "compareSnapshots", "onerror" ];
+ for (var name of imports) {
+ window[name] = window.arguments[0][name];
+ }
+
+ function $(id) {
+ return document.getElementById(id);
+ }
+
+ function addBrowser(parent, id, width, height) {
+ var b =
+ document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "browser");
+ var type = window.location.search.slice(1);
+ is(type == "chrome" || type == "content", true, "Unexpected type");
+ b.setAttribute("type", type);
+ b.setAttribute("id", id);
+ b.style.width = width + "px";
+ b.style.height = height + "px";
+ $(parent).appendChild(b);
+ }
+ addBrowser("box1", "f1", 300, 200);
+ addBrowser("box1", "f2", 300, 200);
+ addBrowser("box2", "f3", 30, 200);
+
+ /** Test for Bug 113934 */
+ var doc1 =
+ "data:text/html,<html><body onbeforeunload='document.documentElement.textContent = \"\"' onunload='document.documentElement.textContent = \"\"' onpagehide='document.documentElement.textContent = \"\"'>This is a test</body></html>";
+ var doc2 = "data:text/html,<html><head></head><body>This is a second test</body></html>";
+
+
+ $("f1").setAttribute("src", doc1);
+ $("f2").setAttribute("src", doc2);
+ $("f3").setAttribute("src", doc2);
+
+ async function doTheTest() {
+ var s2 = await snapshotWindow($("f2").contentWindow);
+ // var s3 = await snapshotWindow($("f3").contentWindow);
+
+ // This test is broken - see bug 1090274
+ //ok(!compareSnapshots(s2, s3, true)[0],
+ // "Should look different due to different sizing");
+
+ function getDOM(id) {
+ return $(id).contentDocument.documentElement.innerHTML;
+ }
+
+ var dom1 = getDOM("f1");
+
+ var dom2 = getDOM("f2");
+ $("f2").contentDocument.body.textContent = "Modified the text";
+ var dom2star = getDOM("f2");
+ isnot(dom2, dom2star, "We changed the DOM!");
+
+ $("f1").swapDocShells($("f2"));
+ // now we have doms 2*, 1, 2 in the frames
+
+ is(getDOM("f1"), dom2star, "Shouldn't have changed the DOM on swap");
+ is(getDOM("f2"), dom1, "Shouldn't have fired event handlers");
+
+ // Test for bug 480149
+ // The DOMLink* events are dispatched asynchronously, thus I cannot
+ // just include the <link> element in the initial DOM and swap the
+ // docshells. Instead, the link element is added now. Then, when the
+ // first DOMLinkAdded event (which is a result of the actual addition)
+ // is dispatched, the docshells are swapped and the pageshow and pagehide
+ // events are tested. Only then, we wait for the DOMLink* events,
+ // which are a result of swapping the docshells.
+ var DOMLinkListener = {
+ _afterFirst: false,
+ _removedDispatched: false,
+ _addedDispatched: false,
+ async handleEvent(aEvent) {
+ if (!this._afterFirst) {
+ is(aEvent.type, "DOMLinkAdded");
+
+ var strs = { "f1": "", "f3" : "" };
+ function attachListener(node, type) {
+ var listener = function(e) {
+ if (strs[node.id]) strs[node.id] += " ";
+ strs[node.id] += node.id + ".page" + type;
+ }
+ node.addEventListener("page" + type, listener);
+
+ listener.detach = function() {
+ node.removeEventListener("page" + type, listener);
+ }
+ return listener;
+ }
+
+ var l1 = attachListener($("f1"), "show");
+ var l2 = attachListener($("f1"), "hide");
+ var l3 = attachListener($("f3"), "show");
+ var l4 = attachListener($("f3"), "hide");
+
+ $("f1").swapDocShells($("f3"));
+ // now we have DOMs 2, 1, 2* in the frames
+
+ l1.detach();
+ l2.detach();
+ l3.detach();
+ l4.detach();
+
+ // swapDocShells reflows asynchronously, ensure layout is
+ // clean so that the viewport of f1 is the right size.
+ $("f1").getBoundingClientRect();
+ var s1_new = await snapshotWindow($("f1").contentWindow);
+ var [same, first, second] = compareSnapshots(s1_new, s2, true);
+ ok(same, "Should reflow on swap. Expected " + second + " but got " + first);
+
+ is(strs.f1, "f1.pagehide f1.pageshow");
+ is(strs.f3, "f3.pagehide f3.pageshow");
+ this._afterFirst = true;
+ return;
+ }
+ if (aEvent.type == "DOMLinkAdded") {
+ is(this._addedDispatched, false);
+ this._addedDispatched = true;
+ }
+ else {
+ is(this._removedDispatched, false);
+ this._removedDispatched = true;
+ }
+
+ if (this._addedDispatched && this._removedDispatched) {
+ $("f1").removeEventListener("DOMLinkAdded", this);
+ $("f1").removeEventListener("DOMLinkRemoved", this);
+ $("f3").removeEventListener("DOMLinkAdded", this);
+ $("f3").removeEventListener("DOMLinkRemoved", this);
+ window.close();
+ SimpleTest.finish();
+ }
+ }
+ };
+
+ $("f1").addEventListener("DOMLinkAdded", DOMLinkListener);
+ $("f1").addEventListener("DOMLinkRemoved", DOMLinkListener);
+ $("f3").addEventListener("DOMLinkAdded", DOMLinkListener);
+ $("f3").addEventListener("DOMLinkRemoved", DOMLinkListener);
+
+ var linkElement = $("f1").contentDocument.createElement("link");
+ linkElement.setAttribute("rel", "alternate");
+ linkElement.setAttribute("href", "about:blank");
+ $("f1").contentDocument.documentElement.firstChild.appendChild(linkElement);
+ }
+
+ ]]></script>
+</window>
diff --git a/docshell/test/chrome/bug215405_window.xhtml b/docshell/test/chrome/bug215405_window.xhtml
new file mode 100644
index 0000000000..e6dddd99ba
--- /dev/null
+++ b/docshell/test/chrome/bug215405_window.xhtml
@@ -0,0 +1,177 @@
+<?xml version="1.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/. -->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="215405Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="onLoad();"
+ title="215405 test">
+
+ <script type="application/javascript"><![CDATA[
+ const {BrowserTestUtils} = ChromeUtils.import("resource://testing-common/BrowserTestUtils.jsm");
+ Services.prefs.setBoolPref("browser.navigation.requireUserInteraction", false);
+
+ /* globals SimpleTest, is, isnot, ok */
+ var imports = [ "SimpleTest", "is", "isnot", "ok"];
+ for (var name of imports) {
+ window[name] = window.arguments[0][name];
+ }
+
+ const text="MOZILLA";
+ const nostoreURI = "http://mochi.test:8888/tests/docshell/test/chrome/" +
+ "215405_nostore.html";
+ const nocacheURI = "https://example.com:443/tests/docshell/test/chrome/" +
+ "215405_nocache.html";
+
+ var gBrowser;
+ var gTestsIterator;
+ var currScrollX = 0;
+ var currScrollY = 0;
+
+ function finish() {
+ gBrowser.removeEventListener("pageshow", eventListener, true);
+ // Work around bug 467960
+ let historyPurged;
+ if (SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ let history = gBrowser.browsingContext.sessionHistory;
+ history.purgeHistory(history.count);
+ historyPurged = Promise.resolve();
+ } else {
+ historyPurged = SpecialPowers.spawn(gBrowser, [], () => {
+ let history = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory
+ .legacySHistory;
+ history.purgeHistory(history.count);
+ });
+ }
+
+ historyPurged.then(_ => {
+ window.close();
+ window.arguments[0].SimpleTest.finish();
+ });
+ }
+
+ function onLoad(e) {
+ gBrowser = document.getElementById("content");
+ gBrowser.addEventListener("pageshow", eventListener, true);
+
+ gTestsIterator = testsIterator();
+ nextTest();
+ }
+
+ function eventListener(event) {
+ setTimeout(nextTest, 0);
+ }
+
+ function nextTest() {
+ gTestsIterator.next();
+ }
+
+ function* testsIterator() {
+ // No-store tests
+ var testName = "[nostore]";
+
+ // Load a page with a no-store header
+ BrowserTestUtils.loadURIString(gBrowser, nostoreURI);
+ yield undefined;
+
+
+ // Now that the page has loaded, amend the form contents
+ var form = gBrowser.contentDocument.getElementById("inp");
+ form.value = text;
+
+ // Attempt to scroll the page
+ var originalXPosition = gBrowser.contentWindow.scrollX;
+ var originalYPosition = gBrowser.contentWindow.scrollY;
+ var scrollToX = gBrowser.contentWindow.scrollMaxX;
+ var scrollToY = gBrowser.contentWindow.scrollMaxY;
+ gBrowser.contentWindow.scrollBy(scrollToX, scrollToY);
+
+ // Save the scroll position for future comparison
+ currScrollX = gBrowser.contentWindow.scrollX;
+ currScrollY = gBrowser.contentWindow.scrollY;
+ isnot(currScrollX, originalXPosition,
+ testName + " failed to scroll window horizontally");
+ isnot(currScrollY, originalYPosition,
+ testName + " failed to scroll window vertically");
+
+ // Load a new document into the browser
+ var simple = "data:text/html,<html><head><title>test2</title></head>" +
+ "<body>test2</body></html>";
+ BrowserTestUtils.loadURIString(gBrowser, simple);
+ yield undefined;
+
+
+ // Now go back in history. First page should not have been cached.
+ gBrowser.goBack();
+ yield undefined;
+
+
+ // First uncacheable page will now be reloaded. Check scroll position
+ // restored, and form contents not
+ is(gBrowser.contentWindow.scrollX, currScrollX, testName +
+ " horizontal axis scroll position not correctly restored");
+ is(gBrowser.contentWindow.scrollY, currScrollY, testName +
+ " vertical axis scroll position not correctly restored");
+ var formValue = gBrowser.contentDocument.getElementById("inp").value;
+ isnot(formValue, text, testName + " form value incorrectly restored");
+
+
+ // https no-cache
+ testName = "[nocache]";
+
+ // Load a page with a no-cache header. This should not be
+ // restricted like no-store (bug 567365)
+ BrowserTestUtils.loadURIString(gBrowser, nocacheURI);
+ yield undefined;
+
+
+ // Now that the page has loaded, amend the form contents
+ form = gBrowser.contentDocument.getElementById("inp");
+ form.value = text;
+
+ // Attempt to scroll the page
+ originalXPosition = gBrowser.contentWindow.scrollX;
+ originalYPosition = gBrowser.contentWindow.scrollY;
+ scrollToX = gBrowser.contentWindow.scrollMaxX;
+ scrollToY = gBrowser.contentWindow.scrollMaxY;
+ gBrowser.contentWindow.scrollBy(scrollToX, scrollToY);
+
+ // Save the scroll position for future comparison
+ currScrollX = gBrowser.contentWindow.scrollX;
+ currScrollY = gBrowser.contentWindow.scrollY;
+ isnot(currScrollX, originalXPosition,
+ testName + " failed to scroll window horizontally");
+ isnot(currScrollY, originalYPosition,
+ testName + " failed to scroll window vertically");
+
+ BrowserTestUtils.loadURIString(gBrowser, simple);
+ yield undefined;
+
+
+ // Now go back in history to the cached page.
+ gBrowser.goBack();
+ yield undefined;
+
+
+ // First page will now be reloaded. Check scroll position
+ // and form contents are restored
+ is(gBrowser.contentWindow.scrollX, currScrollX, testName +
+ " horizontal axis scroll position not correctly restored");
+ is(gBrowser.contentWindow.scrollY, currScrollY, testName +
+ " vertical axis scroll position not correctly restored");
+ formValue = gBrowser.contentDocument.getElementById("inp").value;
+ is(formValue, text, testName + " form value not correctly restored");
+
+ Services.prefs.clearUserPref("browser.navigation.requireUserInteraction");
+ finish();
+ }
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" src="about:blank"/>
+</window>
diff --git a/docshell/test/chrome/bug293235.html b/docshell/test/chrome/bug293235.html
new file mode 100644
index 0000000000..458f88431c
--- /dev/null
+++ b/docshell/test/chrome/bug293235.html
@@ -0,0 +1,13 @@
+<html>
+ <head>
+ <title>Bug 293235 page1</title>
+ <style type="text/css">
+ a:visited, a.forcevisited.forcevisited { color: rgb(128, 0, 128); }
+ a:link, a.forcelink.forcelink { color: rgb(0, 0, 128); }
+ a:focus { color: rgb(128, 0, 0); }
+ </style>
+ </head>
+ <body>
+ <a id="link1" href="bug293235_p2.html">This is a test link.</a>
+ </body>
+</html>
diff --git a/docshell/test/chrome/bug293235_p2.html b/docshell/test/chrome/bug293235_p2.html
new file mode 100644
index 0000000000..2de067b80e
--- /dev/null
+++ b/docshell/test/chrome/bug293235_p2.html
@@ -0,0 +1,8 @@
+<html>
+ <head>
+ <title>Bug 293235 page2</title>
+ </head>
+ <body>
+ Nothing to see here, move along.
+ </body>
+</html>
diff --git a/docshell/test/chrome/bug293235_window.xhtml b/docshell/test/chrome/bug293235_window.xhtml
new file mode 100644
index 0000000000..7d87517824
--- /dev/null
+++ b/docshell/test/chrome/bug293235_window.xhtml
@@ -0,0 +1,118 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="293235Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="setTimeout(runTests, 0);"
+ title="bug 293235 test">
+
+ <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src="docshell_helpers.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+
+ <script type="application/javascript"><![CDATA[
+ var {NetUtil} = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+ // Return the Element object for the specified element id
+ function $(id) { return TestWindow.getDocument().getElementById(id); }
+
+ ////
+ // Generator function for test steps for bug 293235:
+ // A visited link should have the :visited style applied
+ // to it when displayed on a page which was fetched from
+ // the bfcache.
+ //
+ async function runTests() {
+ // Register our observer to know when the link lookup is complete.
+ let testURI = NetUtil.newURI(getHttpUrl("bug293235_p2.html"));
+ let os = SpecialPowers.Services.obs;
+ // Load a test page containing a link that should be initially
+ // blue, per the :link style.
+ await new Promise(resolve => {
+ doPageNavigation({
+ uri: getHttpUrl("bug293235.html"),
+ onNavComplete: resolve,
+ });
+ });
+
+ // Now that we've been notified, we can check our link color.
+ // Since we can't use getComputedStyle() for this because
+ // getComputedStyle lies about styles that result from :visited,
+ // we have to take snapshots.
+ // First, take two reference snapshots.
+ var link1 = $("link1");
+ link1.className = "forcelink";
+ var refLink = await snapshotWindow(TestWindow.getWindow());
+ link1.className = "forcevisited";
+ var refVisited = await snapshotWindow(TestWindow.getWindow());
+ link1.className = "";
+ function snapshotsEqual(snap1, snap2) {
+ return compareSnapshots(snap1, snap2, true)[0];
+ }
+ ok(!snapshotsEqual(refLink, refVisited), "references should not match");
+ ok(snapshotsEqual(refLink, await snapshotWindow(TestWindow.getWindow())),
+ "link should initially be blue");
+
+ let observedVisit = false, observedPageShow = false;
+ await new Promise(resolve => {
+ function maybeResolve() {
+ ok(true, "maybe run next test? visited: " + observedVisit + " pageShow: " + observedPageShow);
+ if (observedVisit && observedPageShow)
+ resolve();
+ }
+
+ // Because adding visits is async, we will not be notified immediately.
+ let visitObserver = {
+ observe(aSubject, aTopic, aData)
+ {
+ if (!testURI.equals(aSubject.QueryInterface(Ci.nsIURI))) {
+ return;
+ }
+ os.removeObserver(this, aTopic);
+ observedVisit = true;
+ maybeResolve();
+ },
+ };
+ os.addObserver(visitObserver, "uri-visit-saved");
+ // Load the page that the link on the previous page points to.
+ doPageNavigation({
+ uri: getHttpUrl("bug293235_p2.html"),
+ onNavComplete() {
+ observedPageShow = true;
+ maybeResolve();
+ }
+ });
+ })
+
+ // And the nodes get notified after the "uri-visit-saved" topic, so
+ // we need to execute soon...
+ await new Promise(SimpleTest.executeSoon);
+
+ // Go back, verify the original page was loaded from the bfcache,
+ // and verify that the link is now purple, per the
+ // :visited style.
+ await new Promise(resolve => {
+ doPageNavigation({
+ back: true,
+ eventsToListenFor: ["pageshow"],
+ expectedEvents: [ { type: "pageshow",
+ persisted: true,
+ title: "Bug 293235 page1" } ],
+ onNavComplete: resolve,
+ });
+ })
+
+ // Now we can test the link color.
+ ok(snapshotsEqual(refVisited, await snapshotWindow(TestWindow.getWindow())),
+ "visited link should be purple");
+
+ // Tell the framework the test is finished.
+ finish();
+ }
+
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" src="about:blank"/>
+</window>
diff --git a/docshell/test/chrome/bug294258_testcase.html b/docshell/test/chrome/bug294258_testcase.html
new file mode 100644
index 0000000000..cd80fefd06
--- /dev/null
+++ b/docshell/test/chrome/bug294258_testcase.html
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Bug 294258 Testcase</title>
+ <meta http-equiv="Content-Type" content="application/xhtml+xml"/>
+ <style type="text/css">
+ * {
+ font-family: monospace;
+ }
+ </style>
+ </head>
+ <body>
+ <div>
+ <p>
+ input type="text": <input id="text" type="text"/>
+ </p>
+ <p>
+ input type="checkbox": <input id="checkbox" type="checkbox"/>
+ </p>
+ <p>
+ input type="file": <input id="file" type="file"/>
+ </p>
+ <p>
+ input type="radio":
+ <input type="radio" id="radio1" name="radio" value="radio1"/>
+ <input id="radio2" type="radio" name="radio" value="radio2"/>
+ </p>
+ <p>
+ textarea: <textarea id="textarea" rows="4" cols="80"></textarea>
+ </p>
+ <p>
+ select -> option: <select id="select">
+ <option>1</option>
+ <option>2</option>
+ <option>3</option>
+ <option>4</option>
+ </select>
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/docshell/test/chrome/bug294258_window.xhtml b/docshell/test/chrome/bug294258_window.xhtml
new file mode 100644
index 0000000000..fe47f3e70f
--- /dev/null
+++ b/docshell/test/chrome/bug294258_window.xhtml
@@ -0,0 +1,72 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="294258Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="setTimeout(nextTest, 0);"
+ title="bug 294258 test">
+
+ <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src="docshell_helpers.js" />
+ <script type="application/javascript"><![CDATA[
+
+ // Define the generator-iterator for the tests.
+ var tests = testIterator();
+
+ ////
+ // Execute the next test in the generator function.
+ //
+ function nextTest() {
+ tests.next();
+ }
+
+ function $(id) { return TestWindow.getDocument().getElementById(id); }
+
+ ////
+ // Generator function for test steps for bug 294258:
+ // Form values should be preserved on reload.
+ //
+ function* testIterator()
+ {
+ // Load a page containing a form.
+ doPageNavigation( {
+ uri: getHttpUrl("bug294258_testcase.html"),
+ onNavComplete: nextTest
+ } );
+ yield undefined;
+
+ // Change the data for each of the form fields, and reload.
+ $("text").value = "text value";
+ $("checkbox").checked = true;
+ var file = SpecialPowers.Services.dirsvc.get("TmpD", Ci.nsIFile);
+ file.append("294258_test.file");
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666);
+ let filePath = file.path;
+ $("file").value = filePath;
+ $("textarea").value = "textarea value";
+ $("radio1").checked = true;
+ $("select").selectedIndex = 2;
+ doPageNavigation( {
+ reload: true,
+ onNavComplete: nextTest
+ } );
+ yield undefined;
+
+ // Verify that none of the form data has changed.
+ is($("text").value, "text value", "Text value changed");
+ is($("checkbox").checked, true, "Checkbox value changed");
+ is($("file").value, filePath, "File value changed");
+ is($("textarea").value, "textarea value", "Textarea value changed");
+ is($("radio1").checked, true, "Radio value changed");
+ is($("select").selectedIndex, 2, "Select value changed");
+
+ // Tell the framework the test is finished.
+ finish();
+ }
+
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" src="about:blank"/>
+</window>
diff --git a/docshell/test/chrome/bug298622_window.xhtml b/docshell/test/chrome/bug298622_window.xhtml
new file mode 100644
index 0000000000..38abf35107
--- /dev/null
+++ b/docshell/test/chrome/bug298622_window.xhtml
@@ -0,0 +1,135 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="298622Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="setTimeout(runTest, 0);"
+ title="bug 298622 test">
+
+ <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src= "docshell_helpers.js" />
+ <script type="application/javascript"><![CDATA[
+ // Global variable that holds a reference to the find bar.
+ var gFindBar;
+
+ ////
+ // Test for bug 298622:
+ // Find should work correctly on a page loaded from the
+ // bfcache.
+ //
+ async function runTest()
+ {
+ // Make sure bfcache is on.
+ enableBFCache(true);
+
+ // Load a test page which contains some text to be found.
+ await promisePageNavigation({
+ uri: "data:text/html,<html><head><title>test1</title></head>" +
+ "<body>find this!</body></html>",
+ });
+
+ // Load a second, dummy page, verifying that the original
+ // page gets stored in the bfcache.
+ await promisePageNavigation({
+ uri: getHttpUrl("generic.html"),
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "test1",
+ persisted: true },
+ { type: "pageshow",
+ title: "generic page" } ],
+ });
+
+ // Make sure we unsuppress painting before continuing
+ await new Promise(resolve => {
+ SimpleTest.executeSoon(resolve);
+ });
+
+ // Search for some text that's on the second page (but not on
+ // the first page), and verify that it can be found.
+ gFindBar = document.getElementById("FindToolbar");
+ document.getElementById("cmd_find").doCommand();
+ ok(!gFindBar.hidden, "failed to open findbar");
+ gFindBar._findField.value = "A generic page";
+ gFindBar._find();
+ await new Promise(resolve => {
+ SimpleTest.executeSoon(resolve);
+ });
+
+ // Make sure Find bar's internal status is not 'notfound'
+ isnot(gFindBar._findField.getAttribute("status"), "notfound",
+ "Findfield status attribute should not have been 'notfound'" +
+ " after Find");
+
+ // Make sure the key events above have time to be processed
+ // before continuing
+ await promiseTrue(() =>
+ SpecialPowers.spawn(document.getElementById("content"), [], function() {
+ return content.document.getSelection().toString().toLowerCase() == "a generic page";
+ }), 20
+ );
+
+ is(gFindBar._findField.value, "A generic page",
+ "expected text not present in find input field");
+ is(await SpecialPowers.spawn(document.getElementById("content"), [], async function() {
+ return content.document.getSelection().toString().toLowerCase();
+ }),
+ "a generic page",
+ "find failed on second page loaded");
+
+ // Go back to the original page and verify it's loaded from the
+ // bfcache.
+ await promisePageNavigation({
+ back: true,
+ eventsToListenFor: ["pageshow"],
+ expectedEvents: [ { type: "pageshow",
+ title: "test1",
+ persisted: true } ],
+ });
+
+ // Search for some text that's on the original page (but not
+ // the dummy page loaded above), and verify that it can
+ // be found.
+ gFindBar = document.getElementById("FindToolbar");
+ document.getElementById("cmd_find").doCommand();
+ ok(!gFindBar.hidden, "failed to open findbar");
+ gFindBar._findField.value = "find this";
+ gFindBar._find();
+ await new Promise(resolve => {
+ SimpleTest.executeSoon(resolve);
+ });
+
+ // Make sure Find bar's internal status is not 'notfound'
+ isnot(gFindBar._findField.getAttribute("status"), "notfound",
+ "Findfield status attribute should not have been 'notfound'" +
+ " after Find");
+
+ // Make sure the key events above have time to be processed
+ // before continuing
+ await promiseTrue(() =>
+ SpecialPowers.spawn(document.getElementById("content"), [], function() {
+ return content.document.getSelection().toString().toLowerCase() == "find this";
+ }), 20
+ );
+
+ is(await SpecialPowers.spawn(document.getElementById("content"), [], async function() {
+ return content.document.getSelection().toString().toLowerCase();
+ }),
+ "find this",
+ "find failed on page loaded from bfcache");
+
+ // Tell the framework the test is finished.
+ finish();
+ }
+
+ ]]></script>
+
+ <commandset>
+ <command id="cmd_find"
+ oncommand="document.getElementById('FindToolbar').onFindCommand();"/>
+ </commandset>
+ <browser type="content" primary="true" flex="1" id="content" messagemanagergroup="test" remote="true" maychangeremoteness="true" />
+ <findbar id="FindToolbar" browserid="content"/>
+</window>
diff --git a/docshell/test/chrome/bug301397_1.html b/docshell/test/chrome/bug301397_1.html
new file mode 100644
index 0000000000..9943c2efe6
--- /dev/null
+++ b/docshell/test/chrome/bug301397_1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>iframe parent</title>
+ </head>
+<body>
+ <iframe id="iframe" src="bug301397_2.html"/>
+ </body>
+</html>
diff --git a/docshell/test/chrome/bug301397_2.html b/docshell/test/chrome/bug301397_2.html
new file mode 100644
index 0000000000..4237107060
--- /dev/null
+++ b/docshell/test/chrome/bug301397_2.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>iframe content #1</title>
+ </head>
+<body>
+ iframe page 1<br/>
+ <a id="link" href="bug301397_3.html">go to next page</a>
+ </body>
+</html>
diff --git a/docshell/test/chrome/bug301397_3.html b/docshell/test/chrome/bug301397_3.html
new file mode 100644
index 0000000000..8d36e92461
--- /dev/null
+++ b/docshell/test/chrome/bug301397_3.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>iframe content #2</title>
+ </head>
+<body>
+ iframe page 2<br/>
+ You made it!
+ </body>
+</html>
diff --git a/docshell/test/chrome/bug301397_4.html b/docshell/test/chrome/bug301397_4.html
new file mode 100644
index 0000000000..5584a4554a
--- /dev/null
+++ b/docshell/test/chrome/bug301397_4.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>dummy page, no iframe</title>
+ </head>
+<body>
+ Just a boring test page, nothing special.
+ </body>
+</html>
diff --git a/docshell/test/chrome/bug301397_window.xhtml b/docshell/test/chrome/bug301397_window.xhtml
new file mode 100644
index 0000000000..cbfa77f7fd
--- /dev/null
+++ b/docshell/test/chrome/bug301397_window.xhtml
@@ -0,0 +1,218 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="301397Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="setTimeout(runTest, 0);"
+ title="bug 301397 test">
+
+ <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src="docshell_helpers.js" />
+ <script type="application/javascript"><![CDATA[
+ Services.prefs.setBoolPref("browser.navigation.requireUserInteraction", false);
+
+ ////
+ // Verifies that the given string exists in the innerHTML of the iframe
+ // content.
+ //
+ async function verifyIframeInnerHtml(string) {
+ var iframeInnerHtml = await SpecialPowers.spawn(document.getElementById("content"), [string], (string) =>
+ content.document.getElementById("iframe").contentDocument.body.innerHTML);
+ ok(iframeInnerHtml.includes(string),
+ "iframe contains wrong document: " + iframeInnerHtml);
+ }
+
+ ////
+ // Generator function for test steps for bug 301397:
+ // The correct page should be displayed in an iframe when
+ // navigating back and forwards, when the parent page
+ // occupies multiple spots in the session history.
+ //
+ async function runTest()
+ {
+ // Make sure the bfcache is enabled.
+ enableBFCache(8);
+
+ // Load a dummy page.
+ await promisePageNavigation({
+ uri: getHttpUrl("generic.html"),
+ });
+
+ // Load a page containing an iframe.
+ await promisePageNavigation({
+ uri: getHttpUrl("bug301397_1.html"),
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "generic page",
+ persisted: true },
+ { type: "pageshow",
+ title: "iframe content #1",
+ persisted: false }, // false on initial load
+ { type: "pageshow",
+ title: "iframe parent",
+ persisted: false } ], // false on initial load
+ });
+
+ // Click a link in the iframe to cause the iframe to navigate
+ // to a new page, and wait until the related pagehide/pageshow
+ // events have occurred.
+ let waitForLinkNavigation = promisePageEvents({
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "iframe content #1",
+ persisted: false }, // false, subframe nav
+ { type: "pageshow",
+ title: "iframe content #2",
+ persisted: false } ], // false on initial load
+ });
+ SpecialPowers.spawn(document.getElementById("content"), [], function() {
+ let iframe = content.document.getElementById("iframe");
+ let link = iframe.contentDocument.getElementById("link");
+ let event = iframe.contentDocument.createEvent("MouseEvents");
+ event.initMouseEvent("click", true, true, iframe.contentWindow,
+ 0, 0, 0, 0, 0,
+ false, false, false, false,
+ 0, null);
+ link.dispatchEvent(event);
+ });
+ await waitForLinkNavigation;
+
+ // Load another dummy page. Verify that both the outgoing parent and
+ // iframe pages are stored in the bfcache.
+ await promisePageNavigation({
+ uri: getHttpUrl("bug301397_4.html"),
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "iframe parent",
+ persisted: true },
+ { type: "pagehide",
+ title: "iframe content #2",
+ persisted: true },
+ { type: "pageshow",
+ title: "dummy page, no iframe",
+ persisted: false } ], // false on initial load
+ });
+
+ // Go back. The iframe should show the second page loaded in it.
+ // Both the parent and the iframe pages should be loaded from
+ // the bfcache.
+ await promisePageNavigation({
+ back: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "dummy page, no iframe",
+ persisted: true },
+ { type: "pageshow",
+ persisted: true,
+ title: "iframe content #2" },
+ { type: "pageshow",
+ persisted: true,
+ title: "iframe parent" } ],
+ });
+
+ verifyIframeInnerHtml("You made it");
+
+ // Go gack again. The iframe should show the first page loaded in it.
+ await promisePageNavigation({
+ back: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "iframe content #2",
+ persisted: false }, // false, subframe nav
+ { type: "pageshow",
+ title: "iframe content #1",
+ // false since this page was never stored
+ // in the bfcache in the first place
+ persisted: false } ],
+ });
+
+ verifyIframeInnerHtml("go to next page");
+
+ // Go back to the generic page. Now go forward to the last page,
+ // again verifying that the iframe shows the first and second
+ // pages in order.
+ await promisePageNavigation({
+ back: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "iframe parent",
+ persisted: true },
+ { type: "pagehide",
+ title: "iframe content #1",
+ persisted: true },
+ { type: "pageshow",
+ title: "generic page",
+ persisted: true } ],
+ });
+
+ await promisePageNavigation({
+ forward: true,
+ eventsToListenFor: ["pageshow"],
+ expectedEvents: [ {type: "pageshow",
+ title: "iframe content #1",
+ persisted: true},
+ {type: "pageshow",
+ title: "iframe parent",
+ persisted: true} ],
+ });
+
+ verifyIframeInnerHtml("go to next page");
+
+ await promisePageNavigation({
+ forward: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "iframe content #1",
+ persisted: false }, // false, subframe nav
+ { type: "pageshow",
+ title: "iframe content #2",
+ // false because the page wasn't stored in
+ // bfcache last time it was unloaded
+ persisted: false } ],
+ });
+
+ verifyIframeInnerHtml("You made it");
+
+ await promisePageNavigation({
+ forward: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "iframe parent",
+ persisted: true },
+ { type: "pagehide",
+ title: "iframe content #2",
+ persisted: true },
+ { type: "pageshow",
+ title: "dummy page, no iframe",
+ persisted: true } ],
+ });
+
+ // Go back once more, and again verify that the iframe shows the
+ // second page loaded in it.
+ await promisePageNavigation({
+ back: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "dummy page, no iframe",
+ persisted: true },
+ { type: "pageshow",
+ persisted: true,
+ title: "iframe content #2" },
+ { type: "pageshow",
+ persisted: true,
+ title: "iframe parent" } ],
+ });
+
+ verifyIframeInnerHtml("You made it");
+
+ Services.prefs.clearUserPref("browser.navigation.requireUserInteraction");
+ // Tell the framework the test is finished.
+ finish();
+ }
+
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" messagemanagergroup="test" remote="true" maychangeremoteness="true" />
+</window>
diff --git a/docshell/test/chrome/bug303267.html b/docshell/test/chrome/bug303267.html
new file mode 100644
index 0000000000..21b9f30311
--- /dev/null
+++ b/docshell/test/chrome/bug303267.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+ <title>
+ bug303267.html
+ </title>
+ </head>
+<body onpageshow="showpageshowcount()">
+<script>
+var pageshowcount = 0;
+function showpageshowcount() {
+ pageshowcount++;
+ var div1 = document.getElementById("div1");
+ while (div1.firstChild) {
+ div1.firstChild.remove();
+ }
+ div1.appendChild(document.createTextNode(
+ "pageshowcount: " + pageshowcount));
+}
+</script>
+<div id="div1">
+ </div>
+</body>
+</html>
diff --git a/docshell/test/chrome/bug303267_window.xhtml b/docshell/test/chrome/bug303267_window.xhtml
new file mode 100644
index 0000000000..741fab0021
--- /dev/null
+++ b/docshell/test/chrome/bug303267_window.xhtml
@@ -0,0 +1,83 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="303267Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="setTimeout(runTests, 0);"
+ title="bug 303267 test">
+
+ <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src="docshell_helpers.js" />
+ <script type="application/javascript"><![CDATA[
+ ////
+ // Bug 303267: When a page is displayed from the bfcache, the script globals should
+ // remain intact from the page's initial load.
+ //
+ async function runTests()
+ {
+ // Load an initial test page which should be saved in the bfcache.
+ var navData = {
+ uri: getHttpUrl("bug303267.html"),
+ eventsToListenFor: ["pageshow"],
+ expectedEvents: [ {type: "pageshow", title: "bug303267.html"} ],
+ };
+ await promisePageNavigation(navData);
+
+ // Save the HTML of the test page for later comparison.
+ var originalHTML = await getInnerHTMLById("div1");
+
+ // Load a second test page. The first test page's pagehide event should
+ // have the .persisted property set to true, indicating that it was
+ // stored in the bfcache.
+ navData = {
+ uri: "data:text/html,<html><head><title>page2</title></head>" +
+ "<body>bug303267, page2</body></html>",
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ {type: "pagehide",
+ title: "bug303267.html",
+ persisted: true},
+ {type: "pageshow",
+ title: "page2"} ],
+ };
+ await promisePageNavigation(navData);
+
+ // Go back. Verify that the pageshow event for the original test page
+ // had a .persisted property of true, indicating that it came from the
+ // bfcache.
+ navData = {
+ back: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ {type: "pagehide",
+ title: "page2"},
+ {type: "pageshow",
+ title: "bug303267.html",
+ persisted: true} ],
+ };
+ await promisePageNavigation(navData);
+
+ // After going back, if showpagecount() could access a global variable
+ // and change the test div's innerHTML, then we pass. Otherwise, it
+ // threw an exception and the following test will fail.
+ var newHTML = await getInnerHTMLById("div1");
+ isnot(originalHTML,
+ newHTML, "HTML not updated on pageshow; javascript broken?");
+
+ // Tell the framework the test is finished.
+ finish();
+ }
+
+ ////
+ // Return the innerHTML of a particular element in the content document.
+ //
+ function getInnerHTMLById(id) {
+ return SpecialPowers.spawn(TestWindow.getBrowser(), [id], (id) => {
+ return content.document.getElementById(id).innerHTML;
+ });
+ }
+
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" remote="true" maychangeremoteness="true" />
+</window>
diff --git a/docshell/test/chrome/bug311007_window.xhtml b/docshell/test/chrome/bug311007_window.xhtml
new file mode 100644
index 0000000000..13ae5e16e3
--- /dev/null
+++ b/docshell/test/chrome/bug311007_window.xhtml
@@ -0,0 +1,204 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="311007Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="startup();"
+ title="bug 311007 test">
+
+ <script src="chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src="docshell_helpers.js"></script>
+ <script type="application/javascript"><![CDATA[
+ // `content` is the id of the browser element used for the test.
+ /* global content */
+/*
+ Regression test for bug 283733 and bug 307027.
+
+ Bug 283733
+ "accessing a relative anchor in a secure page removes the
+ locked icon and yellow background UI"
+
+ Bug 307027
+ "Going back from secure page to error page does not clear yellow bar"
+
+ And enhancements:
+
+ Bug 478927
+ onLocationChange should notify whether or not loading an error page.
+
+ */
+
+const kDNSErrorURI = "https://example/err.html";
+const kSecureURI =
+ "https://example.com/tests/docshell/test/navigation/blank.html";
+
+/*
+ Step 1: load a network error page. <err.html> Not Secure
+ Step 2: load a secure page. <blank.html> Secure
+ Step 3: a secure page + hashchange. <blank.html#foo> Secure (bug 283733)
+ Step 4: go back to the error page. <err.html> Not Secure (bug 307027)
+ */
+
+var gListener = null;
+
+function WebProgressListener() {
+ this._callback = null;
+}
+
+WebProgressListener.prototype = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+
+ onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+ setTimeout(this._callback, 0, aWebProgress, aRequest, aLocation, aFlags);
+ },
+
+ set callback(aVal) {
+ this._callback = aVal;
+ }
+};
+
+function startup() {
+ gListener = new WebProgressListener();
+
+ document.getElementById("content")
+ .webProgress
+ .addProgressListener(gListener,
+ Ci.nsIWebProgress
+ .NOTIFY_LOCATION);
+
+ setTimeout(step1A, 0);
+}
+
+/******************************************************************************
+ * Step 1: Load an error page, and confirm UA knows it's insecure.
+ ******************************************************************************/
+
+function step1A() {
+ gListener.callback = step1B;
+ content.location = kDNSErrorURI;
+}
+
+function step1B(aWebProgress, aRequest, aLocation, aFlags) {
+ is(aLocation.spec, kDNSErrorURI, "Error page's URI (1)");
+
+ ok(!(aFlags & Ci.nsIWebProgressListener
+ .LOCATION_CHANGE_SAME_DOCUMENT),
+ "DocShell loaded a document (1)");
+
+ ok((aFlags & Ci.nsIWebProgressListener
+ .LOCATION_CHANGE_ERROR_PAGE),
+ "This page is an error page.");
+
+ ok(!(document.getElementById("content")
+ .browsingContext
+ .secureBrowserUI.state &
+ Ci.nsIWebProgressListener.STATE_IS_SECURE),
+ "This is not a secure page (1)");
+
+ /* Go to step 2. */
+ setTimeout(step2A, 0);
+}
+
+/******************************************************************************
+ * Step 2: Load a HTTPS page, and confirm it's secure.
+ ******************************************************************************/
+
+function step2A() {
+ gListener.callback = step2B;
+ content.location = kSecureURI;
+}
+
+function step2B(aWebProgress, aRequest, aLocation, aFlags) {
+ is(aLocation.spec, kSecureURI, "A URI on HTTPS (2)");
+
+ ok(!(aFlags & Ci.nsIWebProgressListener
+ .LOCATION_CHANGE_SAME_DOCUMENT),
+ "DocShell loaded a document (2)");
+
+ ok(!(aFlags & Ci.nsIWebProgressListener
+ .LOCATION_CHANGE_ERROR_PAGE),
+ "This page is not an error page.");
+
+ ok((document.getElementById("content")
+ .browsingContext
+ .secureBrowserUI.state &
+ Ci.nsIWebProgressListener.STATE_IS_SECURE),
+ "This is a secure page (2)");
+
+ /* Go to step 3. */
+ setTimeout(step3A, 0);
+}
+
+/*****************************************************************************
+ * Step 3: Trigger hashchange within a secure page, and confirm UA knows
+ * it's secure. (Bug 283733)
+ *****************************************************************************/
+
+function step3A() {
+ gListener.callback = step3B;
+ content.location += "#foo";
+}
+
+function step3B(aWebProgress, aRequest, aLocation, aFlags) {
+ is(aLocation.spec, kSecureURI + "#foo", "hashchange on HTTPS (3)");
+
+ ok((aFlags & Ci.nsIWebProgressListener
+ .LOCATION_CHANGE_SAME_DOCUMENT),
+ "We are in the same document as before (3)");
+
+ ok(!(aFlags & Ci.nsIWebProgressListener
+ .LOCATION_CHANGE_ERROR_PAGE),
+ "This page is not an error page.");
+
+ ok((document.getElementById("content")
+ .browsingContext
+ .secureBrowserUI.state &
+ Ci.nsIWebProgressListener.STATE_IS_SECURE),
+ "This is a secure page (3)");
+
+ /* Go to step 4. */
+ setTimeout(step4A, 0);
+}
+
+/*****************************************************************************
+ * Step 4: Go back from a secure page to an error page, and confirm UA knows
+ * it's not secure. (Bug 307027)
+ *****************************************************************************/
+
+function step4A() {
+ gListener.callback = step4B;
+ content.history.go(-2);
+}
+
+function step4B(aWebProgress, aRequest, aLocation, aFlags) {
+ is(aLocation.spec, kDNSErrorURI, "Go back to the error URI (4)");
+
+ ok(!(aFlags & Ci.nsIWebProgressListener
+ .LOCATION_CHANGE_SAME_DOCUMENT),
+ "DocShell loaded a document (4)");
+
+ ok((aFlags & Ci.nsIWebProgressListener
+ .LOCATION_CHANGE_ERROR_PAGE),
+ "This page is an error page.");
+
+ ok(!(document.getElementById("content")
+ .browsingContext
+ .secureBrowserUI.state &
+ Ci.nsIWebProgressListener.STATE_IS_SECURE),
+ "This is not a secure page (4)");
+
+ /* End. */
+ document.getElementById("content")
+ .webProgress.removeProgressListener(gListener);
+ gListener = null;
+ finish();
+}
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" src="about:blank"/>
+</window>
diff --git a/docshell/test/chrome/bug321671_window.xhtml b/docshell/test/chrome/bug321671_window.xhtml
new file mode 100644
index 0000000000..60ab5e80d0
--- /dev/null
+++ b/docshell/test/chrome/bug321671_window.xhtml
@@ -0,0 +1,128 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="321671Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="setTimeout(runTest, 0);"
+ title="bug 321671 test">
+
+ <script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src="docshell_helpers.js" />
+ <script type="application/javascript"><![CDATA[
+ Services.prefs.setBoolPref("browser.navigation.requireUserInteraction", false);
+
+ // Maximum number of entries in the bfcache for this session history.
+ // This number is hardcoded in docshell code. In the test, we'll
+ // navigate through enough pages so that we hit one that's been
+ // evicted from the bfcache because it's farther from the current
+ // page than this number.
+ const MAX_BFCACHE_PAGES = 3;
+
+ ////
+ // Bug 321671: Scroll position should be retained when moving backwards and
+ // forwards through pages when bfcache is enabled.
+ //
+ async function runTest()
+ {
+ // Variable to hold the scroll positions of the test pages.
+ var scrollPositions = [];
+
+ // Make sure bfcache is on.
+ enableBFCache(true);
+
+ // Load enough test pages that so the first one is evicted from the
+ // bfcache, scroll down on each page, and save the
+ // current scroll position before continuing. Verify that each
+ // page we're navigating away from is initially put into the bfcache.
+ for (var i = 0; i <= MAX_BFCACHE_PAGES + 1; i++) {
+ let eventsToListenFor = ["pageshow"];
+ let expectedEvents = [ { type: "pageshow",
+ title: "bug321671 page" + (i + 1) } ];
+ if (i > 0) {
+ eventsToListenFor.push("pagehide");
+ expectedEvents.unshift({ type: "pagehide",
+ persisted: true,
+ title: "bug321671 page" + i });
+ }
+ await promisePageNavigation( {
+ uri: "data:text/html,<html><head><title>bug321671 page" + (i + 1) +
+ "</title></head>" +
+ "<body><table border='1' width='300' height='1000'>" +
+ "<tbody><tr><td>" +
+ " page " + (i + 1) + ": foobar foobar foobar foobar " +
+ "</td></tr></tbody></table> " +
+ "</body></html>",
+ eventsToListenFor,
+ expectedEvents,
+ } );
+
+ let { initialScrollY, scrollY } = await SpecialPowers.spawn(TestWindow.getBrowser(), [i], (i) => {
+ let initialScrollY = content.scrollY;
+ content.scrollByLines(10 + (2 * i));
+ return { initialScrollY, scrollY: content.scrollY };
+ });
+ is(initialScrollY, 0,
+ "Page initially has non-zero scrollY position");
+ ok(scrollY > 0,
+ "Page has zero scrollY position after scrolling");
+ scrollPositions[i] = scrollY;
+ }
+
+ // Go back to the first page, one page at a time. For each 'back'
+ // action, verify that its vertical scroll position is restored
+ // correctly. Verify that the last page in the sequence
+ // does not come from the bfcache. Again verify that all pages
+ // that we navigate away from are initially
+ // stored in the bfcache.
+ for (i = MAX_BFCACHE_PAGES + 1; i > 0; i--) {
+ await promisePageNavigation( {
+ back: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "bug321671 page" + (i+1),
+ persisted: true },
+ { type: "pageshow",
+ title: "bug321671 page" + i,
+ persisted: i > 1 } ],
+ } );
+
+ is(await SpecialPowers.spawn(TestWindow.getBrowser(), [], () => {
+ return content.scrollY;
+ }), scrollPositions[i-1],
+ "Scroll position not restored while going back!");
+ }
+
+ // Traverse history forward now, and verify scroll position is still
+ // restored. Similar to the backward traversal, verify that all
+ // but the last page in the sequence comes from the bfcache. Also
+ // verify that all of the pages get stored in the bfcache when we
+ // navigate away from them.
+ for (i = 1; i <= MAX_BFCACHE_PAGES + 1; i++) {
+ await promisePageNavigation( {
+ forward: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ persisted: true,
+ title: "bug321671 page" + i },
+ { type: "pageshow",
+ persisted: i < MAX_BFCACHE_PAGES + 1,
+ title: "bug321671 page" + (i + 1) } ],
+ } );
+
+ is(await SpecialPowers.spawn(TestWindow.getBrowser(), [], () => {
+ return content.scrollY;
+ }), scrollPositions[i],
+ "Scroll position not restored while going forward!");
+ }
+
+ Services.prefs.clearUserPref("browser.navigation.requireUserInteraction");
+ // Tell the framework the test is finished.
+ finish();
+ }
+
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" remote="true" maychangeremoteness="true" />
+</window>
diff --git a/docshell/test/chrome/bug360511_case1.html b/docshell/test/chrome/bug360511_case1.html
new file mode 100644
index 0000000000..cca043bb66
--- /dev/null
+++ b/docshell/test/chrome/bug360511_case1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html style="height: 100%">
+<head>
+ <title>
+ bug360511 case 1
+ </title>
+ </head>
+<body style="height: 100%">
+<a id="link1" href="#bottom">jump to bottom</a>
+<div id="div1" style="height: 200%; border: thin solid black;">
+ hello large div
+ </div>
+ <a name="bottom">here's the bottom of the page</a>
+</body>
+</html>
diff --git a/docshell/test/chrome/bug360511_case2.html b/docshell/test/chrome/bug360511_case2.html
new file mode 100644
index 0000000000..217f47724e
--- /dev/null
+++ b/docshell/test/chrome/bug360511_case2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html style="height: 100%">
+<head>
+ <title>
+ bug360511 case 2
+ </title>
+ </head>
+<body style="height: 100%">
+<a id="link1" href="#bottom">jump to bottom</a>
+<div id="div1" style="height: 200%; border: thin solid black;">
+ hello large div
+ </div>
+ <a name="bottom">here's the bottom of the page</a>
+</body>
+</html>
diff --git a/docshell/test/chrome/bug360511_window.xhtml b/docshell/test/chrome/bug360511_window.xhtml
new file mode 100644
index 0000000000..cfb0845ef7
--- /dev/null
+++ b/docshell/test/chrome/bug360511_window.xhtml
@@ -0,0 +1,127 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="360511Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="setTimeout(runTest, 0);"
+ title="bug 360511 test">
+
+ <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src="docshell_helpers.js" />
+ <script type="application/javascript"><![CDATA[
+ Services.prefs.setBoolPref("browser.navigation.requireUserInteraction", false);
+
+ function getScrollY()
+ {
+ return SpecialPowers.spawn(TestWindow.getBrowser(), [], () => {
+ return content.scrollY;
+ });
+ }
+ function getLocation()
+ {
+ return SpecialPowers.spawn(TestWindow.getBrowser(), [], () => {
+ return content.location.href;
+ });
+ }
+
+ ////
+ // Bug 360511: Fragment uri's in session history should be restored correctly
+ // upon back navigation.
+ //
+ async function runTest()
+ {
+ // Case 1: load a page containing a fragment link; the page should be
+ // stored in the bfcache.
+ // Case 2: load a page containing a fragment link; the page should NOT
+ // be stored in the bfcache.
+ for (var i = 1; i < 3; i++)
+ {
+ var url = "bug360511_case" + i + ".html";
+ await promisePageNavigation( {
+ uri: getHttpUrl(url),
+ preventBFCache: i != 1
+ } );
+
+ // Store the original url for later comparison.
+ var originalUrl = TestWindow.getBrowser().currentURI.spec;
+ var originalDocLocation = await getLocation();
+
+ // Verify we're at the top of the page.
+ is(await getScrollY(), 0, "Page initially has a non-zero scrollY property");
+
+ // Click the on the fragment link in the browser, and use setTimeout
+ // to give the event a chance to be processed.
+ await SpecialPowers.spawn(TestWindow.getBrowser(), [], () => {
+ var event = content.document.createEvent('MouseEvent');
+ event.initMouseEvent("click", true, true, content, 0,
+ 0, 0, 0, 0,
+ false, false, false, false, 0, null);
+ content.document.getElementById("link1").dispatchEvent(event);
+ });
+ await promiseNextPaint();
+
+ // Verify we're no longer at the top of the page.
+ await promiseTrue(async function() {
+ return await getScrollY() > 0;
+ }, 20);
+
+ // Store the fragment url for later comparison.
+ var fragmentUrl = TestWindow.getBrowser().currentURI.spec;
+ let fragDocLocation = await getLocation();
+
+ // Now navigate to any other page
+ var expectedPageTitle = "bug360511 case " + i;
+ await promisePageNavigation( {
+ uri: getHttpUrl("generic.html"),
+ eventsToListenFor: ["pagehide", "pageshow"],
+ expectedEvents: [ {type: "pagehide", title: expectedPageTitle,
+ persisted: i == 1},
+ {type: "pageshow"} ],
+ } );
+
+ // Go back
+ await promisePageNavigation( {
+ back: true,
+ eventsToListenFor: ["pageshow"],
+ expectedEvents: [ {type: "pageshow", title: expectedPageTitle,
+ persisted: i == 1} ],
+ } );
+
+ // Verify the current url is the fragment url
+ is(TestWindow.getBrowser().currentURI.spec, fragmentUrl,
+ "current url is not the previous fragment url");
+ is(await getLocation(), fragDocLocation,
+ "document.location is not the previous fragment url");
+
+ // Go back again. Since we're just going from a fragment url to
+ // parent url, no pageshow event is fired, so don't wait for any
+ // events. Rather, just wait for the page's scrollY property to
+ // change.
+ var originalScrollY = await getScrollY();
+ doPageNavigation( {
+ back: true,
+ eventsToListenFor: []
+ } );
+ await promiseTrue(
+ async function() {
+ return (await getScrollY() != originalScrollY);
+ }, 20);
+
+ // Verify the current url is the original url without fragment
+ is(TestWindow.getBrowser().currentURI.spec, originalUrl,
+ "current url is not the original url");
+ is(await getLocation(), originalDocLocation,
+ "document.location is not the original url");
+ }
+
+ Services.prefs.clearUserPref("browser.navigation.requireUserInteraction");
+ // Tell the framework the test is finished.
+ finish();
+ }
+
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" remote="true" maychangeremoteness="true" />
+</window>
diff --git a/docshell/test/chrome/bug364461_window.xhtml b/docshell/test/chrome/bug364461_window.xhtml
new file mode 100644
index 0000000000..938a015e73
--- /dev/null
+++ b/docshell/test/chrome/bug364461_window.xhtml
@@ -0,0 +1,253 @@
+<?xml version="1.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/. -->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="364461Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="runTest();"
+ title="364461 test">
+
+ <script src="chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src="docshell_helpers.js" />
+ <script type="application/javascript"><![CDATA[
+ Services.prefs.setBoolPref("browser.navigation.requireUserInteraction", false);
+
+ var gBrowser;
+
+ async function runTest() {
+ gBrowser = document.getElementById("content");
+
+ // Tests 1 + 2:
+ // Back/forward between two simple documents. Bfcache will be used.
+
+ var test1Doc = "data:text/html,<html><head><title>test1</title></head>" +
+ "<body>test1</body></html>";
+
+ await promisePageNavigation({
+ uri: test1Doc,
+ eventsToListenFor: ["load", "pageshow"],
+ expectedEvents: [{type: "load", title: "test1"},
+ {type: "pageshow", title: "test1", persisted: false}],
+ });
+
+ var test2Doc = "data:text/html,<html><head><title>test2</title></head>" +
+ "<body>test2</body></html>";
+
+ await promisePageNavigation({
+ uri: test2Doc,
+ eventsToListenFor: ["load", "pageshow", "pagehide"],
+ expectedEvents: [{type: "pagehide", title: "test1", persisted: true},
+ {type: "load", title: "test2"},
+ {type: "pageshow", title: "test2", persisted: false}],
+ });
+
+ await promisePageNavigation({
+ back: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [{type: "pagehide", title: "test2", persisted: true},
+ {type: "pageshow", title: "test1", persisted: true}],
+ });
+
+ await promisePageNavigation({
+ forward: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [{type: "pagehide", title: "test1", persisted: true},
+ {type: "pageshow", title: "test2", persisted: true}],
+ });
+
+ // Tests 3 + 4:
+ // Back/forward between a two-level deep iframed document and a simple
+ // document. Bfcache will be used and events should be dispatched to
+ // all frames.
+
+ var test3Doc = "data:text/html,<html><head><title>test3</title>" +
+ "</head><body>" +
+ "<iframe src='data:text/html," +
+ "<html><head><title>test3-nested1</title></head>" +
+ "<body>test3-nested1" +
+ "<iframe src=\"data:text/html," +
+ "<html><head><title>test3-nested2</title></head>" +
+ "<body>test3-nested2</body></html>\">" +
+ "</iframe>" +
+ "</body></html>'>" +
+ "</iframe>" +
+ "</body></html>";
+
+ await promisePageNavigation({
+ uri: test3Doc,
+ eventsToListenFor: ["load", "pageshow", "pagehide"],
+ expectedEvents: [{type: "pagehide", title: "test2", persisted: true},
+ {type: "load", title: "test3-nested2"},
+ {type: "pageshow", title: "test3-nested2", persisted: false},
+ {type: "load", title: "test3-nested1"},
+ {type: "pageshow", title: "test3-nested1", persisted: false},
+ {type: "load", title: "test3"},
+ {type: "pageshow", title: "test3", persisted: false}],
+ });
+
+ var test4Doc = "data:text/html,<html><head><title>test4</title></head>" +
+ "<body>test4</body></html>";
+
+ await promisePageNavigation({
+ uri: test4Doc,
+ eventsToListenFor: ["load", "pageshow", "pagehide"],
+ expectedEvents: [{type: "pagehide", title: "test3", persisted: true},
+ {type: "pagehide", title: "test3-nested1", persisted: true},
+ {type: "pagehide", title: "test3-nested2", persisted: true},
+ {type: "load", title: "test4"},
+ {type: "pageshow", title: "test4", persisted: false}],
+ });
+
+ await promisePageNavigation({
+ back: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [{type: "pagehide", title: "test4", persisted: true},
+ {type: "pageshow", title: "test3-nested2", persisted: true},
+ {type: "pageshow", title: "test3-nested1", persisted: true},
+ {type: "pageshow", title: "test3", persisted: true}],
+ });
+
+ // This is where the two nested pagehide are not dispatched in bug 364461
+ await promisePageNavigation({
+ forward: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [{type: "pagehide", title: "test3", persisted: true},
+ {type: "pagehide", title: "test3-nested1", persisted: true},
+ {type: "pagehide", title: "test3-nested2", persisted: true},
+ {type: "pageshow", title: "test4", persisted: true}],
+ });
+
+ // Tests 5 + 6:
+ // Back/forward between a document containing an unload handler and a
+ // a simple document. Bfcache won't be used for the first one (see
+ // http://developer.mozilla.org/en/docs/Using_Firefox_1.5_caching).
+
+ var test5Doc = "data:text/html,<html><head><title>test5</title></head>" +
+ "<body onunload='while(false) { /* nop */ }'>" +
+ "test5</body></html>";
+
+ await promisePageNavigation({
+ uri: test5Doc,
+ eventsToListenFor: ["load", "pageshow", "pagehide"],
+ expectedEvents: [{type: "pagehide", title: "test4", persisted: true},
+ {type: "load", title: "test5"},
+ {type: "pageshow", title: "test5", persisted: false}],
+ });
+
+ var test6Doc = "data:text/html,<html><head><title>test6</title></head>" +
+ "<body>test6</body></html>";
+
+ await promisePageNavigation({
+ uri: test6Doc,
+ eventsToListenFor: ["load", "unload", "pageshow", "pagehide"],
+ expectedEvents: [{type: "pagehide", title: "test5", persisted: false},
+ {type: "unload", title: "test5"},
+ {type: "load", title: "test6"},
+ {type: "pageshow", title: "test6", persisted: false}],
+ });
+
+ await promisePageNavigation({
+ back: true,
+ eventsToListenFor: ["load", "pageshow", "pagehide"],
+ expectedEvents: [{type: "pagehide", title: "test6", persisted: true},
+ {type: "load", title: "test5"},
+ {type: "pageshow", title: "test5", persisted: false}],
+ });
+
+ await promisePageNavigation({
+ forward: true,
+ eventsToListenFor: ["unload", "pageshow", "pagehide"],
+ expectedEvents: [{type: "pagehide", title: "test5", persisted: false},
+ {type: "unload", title: "test5"},
+ {type: "pageshow", title: "test6", persisted: true}],
+ });
+
+ // Test 7:
+ // Testcase from https://bugzilla.mozilla.org/show_bug.cgi?id=384977#c10
+ // Check that navigation is not blocked after a document is restored
+ // from bfcache
+
+ var test7Doc = "data:text/html,<html><head><title>test7</title>" +
+ "</head><body>" +
+ "<iframe src='data:text/html," +
+ "<html><head><title>test7-nested1</title></head>" +
+ "<body>test7-nested1<br/>" +
+ "<a href=\"data:text/plain,aaa\" target=\"_top\">" +
+ "Click me, hit back, click me again</a>" +
+ "</body></html>'>" +
+ "</iframe>" +
+ "</body></html>";
+
+ await promisePageNavigation({
+ uri: test7Doc,
+ eventsToListenFor: ["load", "pageshow", "pagehide"],
+ expectedEvents: [{type: "pagehide", title: "test6", persisted: true},
+ {type: "load", title: "test7-nested1"},
+ {type: "pageshow", title: "test7-nested1", persisted: false},
+ {type: "load", title: "test7"},
+ {type: "pageshow", title: "test7", persisted: false}],
+ });
+
+ // Simulates a click on the link inside the iframe
+ function clickIframeLink() {
+ SpecialPowers.spawn(TestWindow.getBrowser(), [], () => {
+ var iframe = content.document.getElementsByTagName("iframe")[0];
+ var w = iframe.contentWindow;
+ var d = iframe.contentDocument;
+
+ var evt = d.createEvent("MouseEvents");
+ evt.initMouseEvent("click", true, true, w,
+ 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ d.getElementsByTagName("a")[0].dispatchEvent(evt);
+ });
+ }
+
+ let clicked = promisePageNavigation({
+ eventsToListenFor: ["load", "pageshow", "pagehide"],
+ expectedEvents: [{type: "pagehide", title: "test7", persisted: true},
+ {type: "pagehide", title: "test7-nested1", persisted: true},
+ {type: "load"},
+ {type: "pageshow", persisted: false}],
+ waitForEventsOnly: true,
+ });
+ clickIframeLink();
+ await clicked;
+
+ is(gBrowser.currentURI.spec, "data:text/plain,aaa",
+ "Navigation is blocked when clicking link");
+
+ await promisePageNavigation({
+ back: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [{type: "pagehide", persisted: true},
+ {type: "pageshow", title: "test7-nested1", persisted: true},
+ {type: "pageshow", title: "test7", persisted: true}],
+ });
+
+ clicked = promisePageNavigation({
+ eventsToListenFor: ["load", "pageshow", "pagehide"],
+ expectedEvents: [{type: "pagehide", title: "test7", persisted: true},
+ {type: "pagehide", title: "test7-nested1", persisted: true},
+ {type: "load"},
+ {type: "pageshow", persisted: false}],
+ waitForEventsOnly: true,
+ });
+ clickIframeLink();
+ await clicked;
+
+ is(gBrowser.currentURI.spec, "data:text/plain,aaa",
+ "Navigation is blocked when clicking link");
+
+ Services.prefs.clearUserPref("browser.navigation.requireUserInteraction");
+ finish();
+ }
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" remote="true" maychangeremoteness="true" />
+</window>
diff --git a/docshell/test/chrome/bug396519_window.xhtml b/docshell/test/chrome/bug396519_window.xhtml
new file mode 100644
index 0000000000..a5ecbeb3e8
--- /dev/null
+++ b/docshell/test/chrome/bug396519_window.xhtml
@@ -0,0 +1,132 @@
+<?xml version="1.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/. -->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="396519Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="runTest();"
+ title="396519 test">
+
+ <script src="chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src="docshell_helpers.js" />
+ <script type="application/javascript"><![CDATA[
+ Services.prefs.setBoolPref("browser.navigation.requireUserInteraction", false);
+
+ var gTestCount = 0;
+
+ async function navigateAndTest(params, expected) {
+ await promisePageNavigation(params);
+ ++gTestCount;
+ await doTest(expected);
+ }
+
+ async function doTest(expected) {
+ function check(testCount, expected) {
+ let history;
+ if (SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ history = this.browsingContext.sessionHistory;
+ } else {
+ history = this.content.browsingContext.childSessionHistory.legacySHistory;
+ }
+ if (history.count == expected.length) {
+ for (let i = 0; i < history.count; i++) {
+ var shEntry = history.getEntryAtIndex(i).
+ QueryInterface(Ci.nsISHEntry);
+ is(shEntry.isInBFCache, expected[i], `BFCache for shentry[${i}], test ${testCount}`);
+ }
+
+ // Make sure none of the SHEntries share bfcache entries with one
+ // another.
+ for (let i = 0; i < history.count; i++) {
+ for (let j = 0; j < history.count; j++) {
+ if (j == i)
+ continue;
+
+ let shentry1 = history.getEntryAtIndex(i)
+ .QueryInterface(Ci.nsISHEntry);
+ let shentry2 = history.getEntryAtIndex(j)
+ .QueryInterface(Ci.nsISHEntry);
+ ok(!shentry1.sharesDocumentWith(shentry2),
+ 'Test ' + testCount + ': shentry[' + i + "] shouldn't " +
+ "share document with shentry[" + j + ']');
+ }
+ }
+ }
+ else {
+ is(history.count, expected.length, "Wrong history length in test "+testCount);
+ }
+ }
+
+ if (SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ check.call(TestWindow.getBrowser(), gTestCount, expected);
+ } else {
+ await SpecialPowers.spawn(TestWindow.getBrowser(), [gTestCount, expected], check);
+ }
+ }
+
+ async function runTest() {
+ // Tests 1 + 2:
+ // Back/forward between two simple documents. Bfcache will be used.
+
+ var test1Doc = "data:text/html,<html><head><title>test1</title></head>" +
+ "<body>test1</body></html>";
+
+ await navigateAndTest({
+ uri: test1Doc,
+ }, [false]);
+
+ var test2Doc = test1Doc.replace(/1/,"2");
+
+ await navigateAndTest({
+ uri: test2Doc,
+ }, [true, false]);
+
+ await navigateAndTest({
+ uri: test1Doc,
+ }, [true, true, false]);
+
+ await navigateAndTest({
+ uri: test2Doc,
+ }, [true, true, true, false]);
+
+ await navigateAndTest({
+ uri: test1Doc,
+ }, [false, true, true, true, false]);
+
+ await navigateAndTest({
+ uri: test2Doc,
+ }, [false, false, true, true, true, false]);
+
+ await navigateAndTest({
+ back: true,
+ }, [false, false, true, true, false, true]);
+
+ await navigateAndTest({
+ forward: true,
+ }, [false, false, true, true, true, false]);
+
+ await navigateAndTest({
+ gotoIndex: 1,
+ }, [false, false, true, true, true, false]);
+
+ await navigateAndTest({
+ back: true,
+ }, [false, true, true, true, false, false]);
+
+ await navigateAndTest({
+ gotoIndex: 5,
+ }, [false, false, true, true, false, false]);
+
+ Services.prefs.clearUserPref("browser.navigation.requireUserInteraction");
+ finish();
+ }
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" remote="true" maychangeremoteness="true" />
+</window>
diff --git a/docshell/test/chrome/bug396649_window.xhtml b/docshell/test/chrome/bug396649_window.xhtml
new file mode 100644
index 0000000000..c378f11dc3
--- /dev/null
+++ b/docshell/test/chrome/bug396649_window.xhtml
@@ -0,0 +1,119 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="396649Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="setTimeout(nextTest, 0);"
+ title="bug 396649 test">
+
+ <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src="docshell_helpers.js" />
+ <script type="application/javascript"><![CDATA[
+ Services.prefs.setBoolPref("browser.navigation.requireUserInteraction", false);
+
+ // Define the generator-iterator for the tests.
+ var tests = testIterator();
+
+ // Maximum number of entries in the bfcache for this session history.
+ // This number is hardcoded in docshell code. In the test, we'll
+ // navigate through enough pages so that we hit one that's been
+ // evicted from the bfcache because it's farther from the current
+ // page than this number.
+ const MAX_BFCACHE_PAGES = 3;
+
+ ////
+ // Execute the next test in the generator function.
+ //
+ function nextTest() {
+ tests.next();
+ }
+
+ ////
+ // Generator function for test steps for bug 396649: Content
+ // viewers should be evicted from bfcache when going back if more
+ // than MAX_BFCACHE_PAGES from the current index.
+ //
+ function* testIterator()
+ {
+ // Make sure bfcache is on.
+ enableBFCache(true);
+
+ // Load enough pages so that the first loaded is eviced from
+ // the bfcache, since it is greater the MAX_BFCACHE_PAGES from
+ // the current position in the session history. Verify all
+ // of the pages are initially stored in the bfcache when
+ // they're unloaded.
+ for (var i = 0; i <= MAX_BFCACHE_PAGES + 1; i++) {
+ let eventsToListenFor = ["pageshow"];
+ let expectedEvents = [ { type: "pageshow",
+ title: "bug396649 page" + i } ];
+ if (i > 0) {
+ eventsToListenFor.push("pagehide");
+ expectedEvents.unshift({ type: "pagehide",
+ title: "bug396649 page" + (i-1) });
+ }
+ doPageNavigation( {
+ uri: "data:text/html,<!DOCTYPE html><html>" +
+ "<head><title>bug396649 page" + i +
+ "</title></head>" +
+ "<body>" +
+ "test page " + i +
+ "</body></html>",
+ eventsToListenFor,
+ expectedEvents,
+ onNavComplete: nextTest
+ } );
+ yield undefined;
+ }
+
+ // Go back to the first page, one page at a time. The first
+ // MAX_BFCACHE_PAGES pages loaded via back should come from the bfcache,
+ // the last should not, since it should have been evicted during the
+ // previous navigation sequence. Verify all pages are initially stored
+ // in the bfcache when they're unloaded.
+ for (i = MAX_BFCACHE_PAGES + 1; i > 0; i--) {
+ doPageNavigation( {
+ back: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "bug396649 page" + i,
+ persisted: true },
+ { type: "pageshow",
+ title: "bug396649 page" + (i - 1),
+ persisted: i > 1 } ],
+ onNavComplete: nextTest
+ } );
+ yield undefined;
+ }
+
+ // Traverse history forward now. Again, the first MAX_BFCACHE_PAGES
+ // pages should come from the bfcache, the last should not,
+ // since it should have been evicted during the backwards
+ // traversal above. Verify all pages are initially stored
+ // in the bfcache when they're unloaded.
+ for (i = 1; i <= MAX_BFCACHE_PAGES + 1; i++) {
+ doPageNavigation( {
+ forward: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "bug396649 page" + (i-1),
+ persisted: true },
+ { type: "pageshow",
+ title: "bug396649 page" + i,
+ persisted: i < MAX_BFCACHE_PAGES + 1 } ],
+ onNavComplete: nextTest
+ } );
+ yield undefined;
+ }
+
+ Services.prefs.clearUserPref("browser.navigation.requireUserInteraction");
+ // Tell the framework the test is finished.
+ finish();
+ }
+
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" remote="true" maychangeremoteness="true" />
+</window>
diff --git a/docshell/test/chrome/bug449778_window.xhtml b/docshell/test/chrome/bug449778_window.xhtml
new file mode 100644
index 0000000000..3197b7acf4
--- /dev/null
+++ b/docshell/test/chrome/bug449778_window.xhtml
@@ -0,0 +1,107 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window title="Mozilla Bug 449778" onload="doTheTest()"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <hbox id="parent">
+ </hbox>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ /* globals SimpleTest, is */
+ var imports = [ "SimpleTest", "is" ];
+ for (var name of imports) {
+ window[name] = window.arguments[0][name];
+ }
+
+ function $(id) {
+ return document.getElementById(id);
+ }
+
+ function addBrowser(parent, id, width, height) {
+ var b =
+ document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "browser");
+ b.setAttribute("type", "content");
+ b.setAttribute("id", id);
+ b.setAttribute("width", width);
+ b.setAttribute("height", height);
+ $(parent).appendChild(b);
+ }
+ addBrowser("parent", "f1", 300, 200);
+ addBrowser("parent", "f2", 300, 200);
+
+ /** Test for Bug 449778 */
+ var doc1 = "data:text/html,<html><body>This is a test</body></html>";
+ var doc2 = "data:text/html,<html><body>This is a second test</body></html>";
+ var doc3 = "data:text/html,<html><body>This is a <script>var evt = document.createEvent('Events'); evt.initEvent('testEvt', true, true); document.dispatchEvent(evt);</script>third test</body></html>";
+
+
+ $("f1").setAttribute("src", doc1);
+ $("f2").setAttribute("src", doc2);
+
+ function doTheTest() {
+ var strs = { "f1": "", "f2" : "" };
+ function attachListener(node, type) {
+ var listener = function(e) {
+ if (strs[node.id]) strs[node.id] += " ";
+ strs[node.id] += node.id + ".page" + type;
+ }
+ node.addEventListener("page" + type, listener);
+
+ listener.detach = function() {
+ node.removeEventListener("page" + type, listener);
+ }
+ return listener;
+ }
+
+ var l1 = attachListener($("f1"), "show");
+ var l2 = attachListener($("f1"), "hide");
+ var l3 = attachListener($("f2"), "show");
+ var l4 = attachListener($("f2"), "hide");
+
+ $("f1").swapDocShells($("f2"));
+
+ is(strs.f1, "f1.pagehide f1.pageshow",
+ "Expected hide then show on first loaded page");
+ is(strs.f2, "f2.pagehide f2.pageshow",
+ "Expected hide then show on second loaded page");
+
+ function listener2() {
+ $("f2").removeEventListener("testEvt", listener2);
+
+ strs = { "f1": "", "f2" : "" };
+
+ $("f1").swapDocShells($("f2"));
+ is(strs.f1, "f1.pagehide",
+ "Expected hide on already-loaded page, then nothing");
+ is(strs.f2, "f2.pageshow f2.pagehide f2.pageshow",
+ "Expected show on still-loading page, then hide on it, then show " +
+ "on already-loaded page");
+
+ strs = { "f1": "", "f2" : "" };
+
+ $("f1").addEventListener("pageshow", listener3);
+ }
+
+ function listener3() {
+ $("f1").removeEventListener("pageshow", listener3);
+
+ is(strs.f1, "f1.pageshow",
+ "Expected show as our page finishes loading");
+ is(strs.f2, "", "Expected no more events here.");
+
+ l1.detach();
+ l2.detach();
+ l3.detach();
+ l4.detach();
+
+ window.close();
+ SimpleTest.finish();
+ }
+
+ $("f2").addEventListener("testEvt", listener2, false, true);
+ $("f2").setAttribute("src", doc3);
+ }
+
+ ]]></script>
+</window>
diff --git a/docshell/test/chrome/bug449780_window.xhtml b/docshell/test/chrome/bug449780_window.xhtml
new file mode 100644
index 0000000000..c37bc096b2
--- /dev/null
+++ b/docshell/test/chrome/bug449780_window.xhtml
@@ -0,0 +1,83 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window title="Mozilla Bug 449780" onload="setTimeout(doTheTest, 0);"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <hbox id="parent">
+ </hbox>
+
+ <!-- test code goes here -->
+ <script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src="docshell_helpers.js" />
+ <script type="application/javascript"><![CDATA[
+ function addBrowser(parent, width, height) {
+ var b =
+ document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "browser");
+ b.setAttribute("type", "content");
+ b.setAttribute("id", "content");
+ b.setAttribute("width", width);
+ b.setAttribute("height", height);
+ b.setAttribute("remote", SpecialPowers.Services.appinfo.sessionHistoryInParent);
+ if (SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ b.setAttribute("maychangeremoteness", "true");
+ }
+ document.getElementById("parent").appendChild(b);
+ return b;
+ }
+
+ let f1 = addBrowser("parent", 300, 200);
+
+ /** Test for Bug 449780 */
+ var doc1 = "data:text/html,<html><body>This is a test</body></html>";
+ var doc2 = "data:text/html,<html><body>This is a second test</body></html>";
+
+ async function doTheTest() {
+ await promisePageNavigation({
+ uri: doc1,
+ });
+ let { origDOM, modifiedDOM } = await SpecialPowers.spawn(f1, [], () => {
+ var origDOM = content.document.documentElement.innerHTML;
+ content.document.body.textContent = "Modified";
+ var modifiedDOM = content.document.documentElement.innerHTML;
+ isnot(origDOM, modifiedDOM, "DOM should be different");
+ return { origDOM, modifiedDOM };
+ });
+
+ await promisePageNavigation({
+ uri: doc2,
+ });
+
+ await promisePageNavigation({
+ back: true,
+ });
+
+ await SpecialPowers.spawn(f1, [modifiedDOM], (modifiedDOM) => {
+ is(content.document.documentElement.innerHTML, modifiedDOM, "Should have been bfcached");
+ });
+
+ await promisePageNavigation({
+ forward: true,
+ });
+
+ f1.removeAttribute("id");
+ let f2 = addBrowser("parent", 300, 200);
+
+ // Make sure there's a document or the swap will fail.
+ await promisePageNavigation({
+ uri: "about:blank",
+ });
+
+ f1.swapDocShells(f2);
+
+ await promisePageNavigation({
+ back: true,
+ });
+
+ await SpecialPowers.spawn(f2, [origDOM], (origDOM) => {
+ is(content.document.documentElement.innerHTML, origDOM, "Should not have been bfcached");
+ });
+
+ finish();
+ }
+ ]]></script>
+</window>
diff --git a/docshell/test/chrome/bug454235-subframe.xhtml b/docshell/test/chrome/bug454235-subframe.xhtml
new file mode 100644
index 0000000000..a8b6178e65
--- /dev/null
+++ b/docshell/test/chrome/bug454235-subframe.xhtml
@@ -0,0 +1,7 @@
+<window title="Mozilla Bug 454235 SubFrame"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <deck flex="1">
+ <browser id="topBrowser" src="about:mozilla"/>
+ <browser id="burriedBrowser" src="about:mozilla"/>
+ </deck>
+</window>
diff --git a/docshell/test/chrome/bug582176_window.xhtml b/docshell/test/chrome/bug582176_window.xhtml
new file mode 100644
index 0000000000..b43220a2fe
--- /dev/null
+++ b/docshell/test/chrome/bug582176_window.xhtml
@@ -0,0 +1,74 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="303267Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="runTest();"
+ title="bug 582176 test">
+
+ <script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src="docshell_helpers.js" />
+ <script type="application/javascript"><![CDATA[
+ ////
+ // Bug 582176.
+ //
+ async function runTest()
+ {
+ enableBFCache(true);
+
+ var notificationCount = 0;
+
+ let onGlobalCreation = () => {
+ ++notificationCount;
+ };
+
+ await promisePageNavigation({
+ uri: "http://mochi.test:8888/chrome/docshell/test/chrome/582176_dummy.html",
+ onGlobalCreation,
+ });
+ is(await SpecialPowers.spawn(TestWindow.getBrowser(), [], () => {
+ let testVar = content.testVar;
+ content.testVar = 1;
+ return testVar;
+ }), undefined,
+ "variable unexpectedly there already");
+ is(notificationCount, 1, "Should notify on first navigation");
+
+ await promisePageNavigation({
+ uri: "http://mochi.test:8888/chrome/docshell/test/chrome/582176_dummy.html?2",
+ onGlobalCreation,
+ });
+ is(await SpecialPowers.spawn(TestWindow.getBrowser(), [], () => {
+ return content.testVar;
+ }), undefined,
+ "variable should no longer be there");
+ is(notificationCount, 2, "Should notify on second navigation");
+
+ await promisePageNavigation({
+ back: true,
+ });
+ is(await SpecialPowers.spawn(TestWindow.getBrowser(), [], () => {
+ return content.testVar;
+ }), 1,
+ "variable should still be there");
+ is(notificationCount, 2, "Should not notify on back navigation");
+
+ await promisePageNavigation({
+ uri: "http://mochi.test:8888/chrome/docshell/test/chrome/582176_xml.xml",
+ onGlobalCreation,
+ });
+ is(await SpecialPowers.spawn(TestWindow.getBrowser(), [], () => {
+ return content.document.body.textContent;
+ }), "xslt result",
+ "Transform performed successfully");
+ is(notificationCount, 3, "Should notify only once on XSLT navigation");
+
+ // Tell the framework the test is finished.
+ finish();
+ }
+
+ ]]></script>
+ <browser type="content" primary="true" flex="1" id="content" remote="true" maychangeremoteness="true" />
+</window>
diff --git a/docshell/test/chrome/bug608669.xhtml b/docshell/test/chrome/bug608669.xhtml
new file mode 100644
index 0000000000..993f24051c
--- /dev/null
+++ b/docshell/test/chrome/bug608669.xhtml
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<window title="Mozilla Bug 608669 - Blank page"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="notifyOpener();">
+ <description flex="1" value="This window is intentionally left blank"/>
+ <script type="application/javascript">
+ function notifyOpener() {
+ if (opener) {
+ opener.postMessage("load", "*");
+ }
+ }
+ </script>
+</window>
diff --git a/docshell/test/chrome/bug662200_window.xhtml b/docshell/test/chrome/bug662200_window.xhtml
new file mode 100644
index 0000000000..7c6f656f26
--- /dev/null
+++ b/docshell/test/chrome/bug662200_window.xhtml
@@ -0,0 +1,119 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="303267Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="setTimeout(runTest, 0);"
+ title="bug 662200 test">
+
+ <script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src="docshell_helpers.js" />
+ <script type="application/javascript"><![CDATA[
+ Services.prefs.setBoolPref("browser.navigation.requireUserInteraction", false);
+
+ ////
+ // Bug 662200
+ //
+ async function runTest()
+ {
+ // Load the first test page
+ var navData = {
+ uri: getHttpUrl("662200a.html"),
+ eventsToListenFor: ["pageshow"],
+ expectedEvents: [ {type: "pageshow", title: "A"} ],
+ };
+ await promisePageNavigation(navData);
+
+ // Load the second test page.
+ navData = {
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ {type: "pagehide",
+ title: "A"},
+ {type: "pageshow",
+ title: "B"} ],
+ }
+ let clicked = promisePageEvents(navData);
+ SpecialPowers.spawn(TestWindow.getBrowser(), [], () => {
+ var link = content.document.getElementById("link");
+ var event = content.document.createEvent("MouseEvents");
+ event.initMouseEvent("click", true, true, content,
+ 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ link.dispatchEvent(event);
+ });
+ await clicked;
+
+ // Load the third test page.
+ navData = {
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ {type: "pagehide",
+ title: "B"},
+ {type: "pageshow",
+ title: "C"} ],
+ };
+ clicked = promisePageEvents(navData);
+ SpecialPowers.spawn(TestWindow.getBrowser(), [], () => {
+ var link = content.document.getElementById("link");
+ var event = content.document.createEvent("MouseEvents");
+ event.initMouseEvent("click", true, true, content,
+ 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ link.dispatchEvent(event);
+ });
+ await clicked;
+
+ // Go back.
+ navData = {
+ back: true,
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ {type: "pagehide",
+ title: "C"},
+ {type: "pageshow",
+ title: "B"} ],
+ };
+ await promisePageNavigation(navData);
+
+ // Reload.
+ navData = {
+ eventsToListenFor: ["pageshow", "pagehide"],
+ expectedEvents: [ {type: "pagehide",
+ title: "B"},
+ {type: "pageshow",
+ title: "B"} ],
+ };
+ // Asking the docshell harness to reload for us will call reload on
+ // nsDocShell which has different behavior than the reload on nsSHistory
+ // so we call reloadCurrentEntry() (which is equivalent to reload(0) and
+ // visible from JS) explicitly here.
+ let reloaded = promisePageEvents(navData);
+ if (SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ TestWindow.getBrowser().browsingContext.sessionHistory.reloadCurrentEntry();
+ } else {
+ SpecialPowers.spawn(TestWindow.getBrowser(), [], () => {
+ docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory.legacySHistory.reloadCurrentEntry();
+ });
+ }
+ await reloaded;
+
+ // After this sequence of events, we should be able to go back and forward
+ is(TestWindow.getBrowser().canGoBack, true, "Should be able to go back!");
+ is(TestWindow.getBrowser().canGoForward, true, "Should be able to go forward!");
+ let requestedIndex;
+ if (SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ requestedIndex = TestWindow.getBrowser().browsingContext.sessionHistory.requestedIndex;
+ } else {
+ requestedIndex = await SpecialPowers.spawn(TestWindow.getBrowser(), [], () => {
+ return docShell.sessionHistory.legacySHistory.requestedIndex;
+ })
+ }
+ is(requestedIndex, -1, "Requested index should be cleared!");
+
+ Services.prefs.clearUserPref("browser.navigation.requireUserInteraction");
+ // Tell the framework the test is finished.
+ finish();
+ }
+
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" remote="true" />
+</window>
diff --git a/docshell/test/chrome/bug690056_window.xhtml b/docshell/test/chrome/bug690056_window.xhtml
new file mode 100644
index 0000000000..fb5dfaea40
--- /dev/null
+++ b/docshell/test/chrome/bug690056_window.xhtml
@@ -0,0 +1,171 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="690056Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="setTimeout(nextTest, 0);"
+ title="bug 6500056 test">
+
+ <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src="docshell_helpers.js" />
+ <script type="application/javascript"><![CDATA[
+ var tests = testIterator();
+
+ function nextTest() {
+ tests.next();
+ }
+
+ // Makes sure that we fire the visibilitychange events
+ function* testIterator() {
+ // Enable bfcache
+ enableBFCache(8);
+
+ // Load something for a start
+ doPageNavigation({
+ uri: 'data:text/html,<title>initial load</title>',
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ // Now load a new page
+ doPageNavigation({
+ uri: 'data:text/html,<title>new load</title>',
+ eventsToListenFor: [ "pageshow", "pagehide", "visibilitychange" ],
+ expectedEvents: [ { type: "pagehide",
+ title: "initial load",
+ persisted: true },
+ { type: "visibilitychange",
+ title: "initial load",
+ visibilityState: "hidden",
+ hidden: true },
+ // No visibilitychange events fired for initial pageload
+ { type: "pageshow",
+ title: "new load",
+ persisted: false }, // false on initial load
+ ],
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ // Now go back
+ doPageNavigation({
+ back: true,
+ eventsToListenFor: [ "pageshow", "pagehide", "visibilitychange" ],
+ expectedEvents: [ { type: "pagehide",
+ title: "new load",
+ persisted: true },
+ { type: "visibilitychange",
+ title: "new load",
+ visibilityState: "hidden",
+ hidden: true },
+ { type: "visibilitychange",
+ title: "initial load",
+ visibilityState: "visible",
+ hidden: false },
+ { type: "pageshow",
+ title: "initial load",
+ persisted: true },
+ ],
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ // And forward
+ doPageNavigation({
+ forward: true,
+ eventsToListenFor: [ "pageshow", "pagehide", "visibilitychange" ],
+ expectedEvents: [ { type: "pagehide",
+ title: "initial load",
+ persisted: true },
+ { type: "visibilitychange",
+ title: "initial load",
+ visibilityState: "hidden",
+ hidden: true },
+ { type: "visibilitychange",
+ title: "new load",
+ visibilityState: "visible",
+ hidden: false },
+ { type: "pageshow",
+ title: "new load",
+ persisted: true },
+ ],
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ waitForPageEvents({
+ eventsToListenFor: [ "visibilitychange" ],
+ expectedEvents: [ { type: "visibilitychange",
+ title: "new load",
+ visibilityState: "hidden",
+ hidden: true },
+ ],
+ onNavComplete: nextTest
+ });
+
+ // Now flip our docshell to not active
+ TestWindow.getBrowser().docShellIsActive = false;
+ yield undefined;
+
+ // And navigate back; there should be no visibility state transitions
+ doPageNavigation({
+ back: true,
+ eventsToListenFor: [ "pageshow", "pagehide", "visibilitychange" ],
+ expectedEvents: [ { type: "pagehide",
+ title: "new load",
+ persisted: true },
+ { type: "pageshow",
+ title: "initial load",
+ persisted: true },
+ ],
+ unexpectedEvents: [ "visibilitychange" ],
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ waitForPageEvents({
+ eventsToListenFor: [ "visibilitychange" ],
+ expectedEvents: [ { type: "visibilitychange",
+ title: "initial load",
+ visibilityState: "visible",
+ hidden: false },
+ ],
+ onNavComplete: nextTest
+ });
+
+ // Now set the docshell active again
+ TestWindow.getBrowser().docShellIsActive = true;
+ yield undefined;
+
+ // And forward
+ doPageNavigation({
+ forward: true,
+ eventsToListenFor: [ "pageshow", "pagehide", "visibilitychange" ],
+ expectedEvents: [ { type: "pagehide",
+ title: "initial load",
+ persisted: true },
+ { type: "visibilitychange",
+ title: "initial load",
+ visibilityState: "hidden",
+ hidden: true },
+ { type: "visibilitychange",
+ title: "new load",
+ visibilityState: "visible",
+ hidden: false },
+ { type: "pageshow",
+ title: "new load",
+ persisted: true },
+ ],
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ // Tell the framework the test is finished.
+ finish();
+ }
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" remote="true" maychangeremoteness="true" />
+</window>
diff --git a/docshell/test/chrome/bug846906.html b/docshell/test/chrome/bug846906.html
new file mode 100644
index 0000000000..a289417ea8
--- /dev/null
+++ b/docshell/test/chrome/bug846906.html
@@ -0,0 +1,10 @@
+<html>
+ <head>
+ <title>
+ </title>
+ </head>
+ <body>
+ <div id="div1" style="width:1024px; height:768px; border:none;">
+ </div>
+ </body>
+</html>
diff --git a/docshell/test/chrome/bug89419.sjs b/docshell/test/chrome/bug89419.sjs
new file mode 100644
index 0000000000..7172690a9a
--- /dev/null
+++ b/docshell/test/chrome/bug89419.sjs
@@ -0,0 +1,12 @@
+function handleRequest(request, response) {
+ var redirectstate = "/docshell/test/chrome/bug89419.sjs";
+ response.setStatusLine("1.1", 302, "Found");
+ if (getState(redirectstate) == "") {
+ response.setHeader("Location", "red.png", false);
+ setState(redirectstate, "red");
+ } else {
+ response.setHeader("Location", "blue.png", false);
+ setState(redirectstate, "");
+ }
+ response.setHeader("Cache-Control", "no-cache", false);
+}
diff --git a/docshell/test/chrome/bug89419_window.xhtml b/docshell/test/chrome/bug89419_window.xhtml
new file mode 100644
index 0000000000..12b9dec650
--- /dev/null
+++ b/docshell/test/chrome/bug89419_window.xhtml
@@ -0,0 +1,69 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="89419Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="setTimeout(runTests, 0);"
+ title="bug 89419 test">
+
+ <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src="docshell_helpers.js" />
+ <script src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+
+ <script type="application/javascript"><![CDATA[
+ ////
+ // A visited link should have the :visited style applied
+ // to it when displayed on a page which was fetched from
+ // the bfcache.
+ //
+ async function runTests() {
+ // Disable rcwn to make cache behavior deterministic.
+ var {SpecialPowers} = window.arguments[0];
+ await SpecialPowers.pushPrefEnv({"set":[["network.http.rcwn.enabled", false]]});
+
+ // Load a test page containing an image referring to the sjs that returns
+ // a different redirect every time it's loaded.
+ await new Promise(resolve => {
+ doPageNavigation({
+ uri: getHttpUrl("89419.html"),
+ onNavComplete: resolve,
+ preventBFCache: true,
+ });
+ })
+
+ var first = await snapshotWindow(TestWindow.getWindow());
+
+ await new Promise(resolve => {
+ doPageNavigation({
+ uri: "about:blank",
+ onNavComplete: resolve,
+ });
+ });
+
+ var second = await snapshotWindow(TestWindow.getWindow());
+ function snapshotsEqual(snap1, snap2) {
+ return compareSnapshots(snap1, snap2, true)[0];
+ }
+ ok(!snapshotsEqual(first, second), "about:blank should not be the same as the image web page");
+
+ await new Promise(resolve => {
+ doPageNavigation({
+ back: true,
+ onNavComplete: resolve,
+ });
+ });
+
+ var third = await snapshotWindow(TestWindow.getWindow());
+ ok(!snapshotsEqual(third, second), "going back should not be the same as about:blank");
+ ok(snapshotsEqual(first, third), "going back should be the same as the initial load");
+
+ // Tell the framework the test is finished.
+ finish();
+ }
+
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" src="about:blank"/>
+</window>
diff --git a/docshell/test/chrome/bug909218.html b/docshell/test/chrome/bug909218.html
new file mode 100644
index 0000000000..a11fa6000d
--- /dev/null
+++ b/docshell/test/chrome/bug909218.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+ <link rel="stylesheet" type="text/css" href="http://mochi.test:8888/tests/SimpleTest/test.css">
+ <script src="bug909218.js"></script>
+</head>
+<body>
+ <img src="http://mochi.test:8888/tests/docshell/test/chrome/red.png">
+ <!-- an iframe so we can check these too get the correct flags -->
+ <iframe src="generic.html"/>
+</body>
+</html>
diff --git a/docshell/test/chrome/bug909218.js b/docshell/test/chrome/bug909218.js
new file mode 100644
index 0000000000..2222480cd3
--- /dev/null
+++ b/docshell/test/chrome/bug909218.js
@@ -0,0 +1,2 @@
+// This file exists just to ensure that we load it with the correct flags.
+dump("bug909218.js loaded\n");
diff --git a/docshell/test/chrome/bug92598_window.xhtml b/docshell/test/chrome/bug92598_window.xhtml
new file mode 100644
index 0000000000..bad91f3df6
--- /dev/null
+++ b/docshell/test/chrome/bug92598_window.xhtml
@@ -0,0 +1,89 @@
+<?xml version="1.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/. -->
+
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="92598Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="onLoad();"
+ title="92598 test">
+
+ <script src="chrome://mochikit/content/chrome-harness.js" />
+ <script type="application/javascript" src="docshell_helpers.js" />
+ <script type="application/javascript"><![CDATA[
+ var gBrowser;
+ var gTestsIterator;
+
+ function onLoad() {
+ gBrowser = document.getElementById("content");
+ gTestsIterator = testsIterator();
+ nextTest();
+ }
+
+ function nextTest() {
+ gTestsIterator.next();
+ }
+
+ function* testsIterator() {
+ // Load a page with a no-cache header, followed by a simple page
+ // On pagehide, first page should report it is not being persisted
+ var test1DocURI = "http://mochi.test:8888/chrome/docshell/test/chrome/92598_nostore.html";
+
+ doPageNavigation({
+ uri: test1DocURI,
+ eventsToListenFor: ["load", "pageshow"],
+ expectedEvents: [ { type: "load",
+ title: "test1" },
+ { type: "pageshow",
+ title: "test1",
+ persisted: false } ],
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ var test2Doc = "data:text/html,<html><head><title>test2</title></head>" +
+ "<body>test2</body></html>";
+
+ doPageNavigation({
+ uri: test2Doc,
+ eventsToListenFor: ["load", "pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "test1",
+ persisted: false },
+ { type: "load",
+ title: "test2" },
+ { type: "pageshow",
+ title: "test2",
+ persisted: false } ],
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ // Now go back in history. First page should not have been cached.
+ // Check persisted property to confirm
+ doPageNavigation({
+ back: true,
+ eventsToListenFor: ["load", "pageshow", "pagehide"],
+ expectedEvents: [ { type: "pagehide",
+ title: "test2",
+ persisted: true },
+ { type: "load",
+ title: "test1" },
+ { type: "pageshow",
+ title: "test1",
+ persisted: false } ],
+ onNavComplete: nextTest
+ });
+ yield undefined;
+
+ finish();
+ }
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" remote="true" maychangeremoteness="true" />
+</window>
diff --git a/docshell/test/chrome/chrome.ini b/docshell/test/chrome/chrome.ini
new file mode 100644
index 0000000000..e6294dbb8c
--- /dev/null
+++ b/docshell/test/chrome/chrome.ini
@@ -0,0 +1,107 @@
+[DEFAULT]
+skip-if = os == 'android'
+support-files =
+ 662200a.html
+ 662200b.html
+ 662200c.html
+ 89419.html
+ bug113934_window.xhtml
+ bug215405_window.xhtml
+ bug293235.html
+ bug293235_p2.html
+ bug293235_window.xhtml
+ bug294258_testcase.html
+ bug294258_window.xhtml
+ bug298622_window.xhtml
+ bug301397_1.html
+ bug301397_2.html
+ bug301397_3.html
+ bug301397_4.html
+ bug301397_window.xhtml
+ bug303267.html
+ bug303267_window.xhtml
+ bug311007_window.xhtml
+ bug321671_window.xhtml
+ bug360511_case1.html
+ bug360511_case2.html
+ bug360511_window.xhtml
+ bug364461_window.xhtml
+ bug396519_window.xhtml
+ bug396649_window.xhtml
+ bug449778_window.xhtml
+ bug449780_window.xhtml
+ bug454235-subframe.xhtml
+ bug608669.xhtml
+ bug662200_window.xhtml
+ bug690056_window.xhtml
+ bug846906.html
+ bug89419_window.xhtml
+ bug909218.html
+ bug909218.js
+ docshell_helpers.js
+ DocShellHelpers.sys.mjs
+ file_viewsource_forbidden_in_iframe.html
+ generic.html
+ mozFrameType_window.xhtml
+ test_docRedirect.sjs
+
+[test_allowContentRetargeting.html]
+[test_bug112564.xhtml]
+support-files =
+ bug112564_window.xhtml
+ 112564_nocache.html
+ 112564_nocache.html^headers^
+[test_bug113934.xhtml]
+[test_bug215405.xhtml]
+[test_bug293235.xhtml]
+skip-if = true # bug 1393441
+[test_bug294258.xhtml]
+[test_bug298622.xhtml]
+[test_bug301397.xhtml]
+skip-if = (os == 'win' && processor == 'aarch64') # bug 1533819
+[test_bug303267.xhtml]
+[test_bug311007.xhtml]
+[test_bug321671.xhtml]
+skip-if =
+ (os == "mac" && !debug) # Bug 1784831
+[test_bug360511.xhtml]
+skip-if = (os == 'win' && processor == 'x86')
+[test_bug364461.xhtml]
+skip-if = (os == 'win' && processor == 'aarch64') # bug 1533814
+[test_bug396519.xhtml]
+[test_bug396649.xhtml]
+[test_bug428288.html]
+[test_bug449778.xhtml]
+[test_bug449780.xhtml]
+[test_bug453650.xhtml]
+[test_bug454235.xhtml]
+[test_bug456980.xhtml]
+[test_bug565388.xhtml]
+skip-if = true # Bug 1026815,Bug 1546159
+[test_bug582176.xhtml]
+support-files =
+ 582176_dummy.html
+ 582176_xml.xml
+ 582176_xslt.xsl
+ bug582176_window.xhtml
+[test_bug608669.xhtml]
+[test_bug662200.xhtml]
+[test_bug690056.xhtml]
+[test_bug789773.xhtml]
+[test_bug846906.xhtml]
+[test_bug89419.xhtml]
+[test_bug909218.html]
+[test_bug92598.xhtml]
+support-files =
+ 92598_nostore.html
+ 92598_nostore.html^headers^
+ bug92598_window.xhtml
+[test_open_and_immediately_close_opener.html]
+# This bug only manifests in the non-e10s window open codepath. The test
+# should be updated to make sure it still opens a new window in the parent
+# process if and when we start running chrome mochitests with e10s enabled.
+skip-if = e10s
+[test_mozFrameType.xhtml]
+[test_viewsource_forbidden_in_iframe.xhtml]
+skip-if = true # bug 1019315
+[test_docRedirect.xhtml]
diff --git a/docshell/test/chrome/docshell_helpers.js b/docshell/test/chrome/docshell_helpers.js
new file mode 100644
index 0000000000..4af1a3f9ea
--- /dev/null
+++ b/docshell/test/chrome/docshell_helpers.js
@@ -0,0 +1,759 @@
+if (!window.opener && window.arguments) {
+ window.opener = window.arguments[0];
+}
+/**
+ * Import common SimpleTest methods so that they're usable in this window.
+ */
+/* globals SimpleTest, is, isnot, ok, onerror, todo, todo_is, todo_isnot */
+var imports = [
+ "SimpleTest",
+ "is",
+ "isnot",
+ "ok",
+ "onerror",
+ "todo",
+ "todo_is",
+ "todo_isnot",
+];
+for (var name of imports) {
+ window[name] = window.opener.wrappedJSObject[name];
+}
+const { BrowserTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/BrowserTestUtils.sys.mjs"
+);
+
+const ACTOR_MODULE_URI =
+ "chrome://mochitests/content/chrome/docshell/test/chrome/DocShellHelpers.sys.mjs";
+const { DocShellHelpersParent } = ChromeUtils.importESModule(ACTOR_MODULE_URI);
+// Some functions assume chrome-harness.js has been loaded.
+/* import-globals-from ../../../testing/mochitest/chrome-harness.js */
+
+/**
+ * Define global constants and variables.
+ */
+const NAV_NONE = 0;
+const NAV_BACK = 1;
+const NAV_FORWARD = 2;
+const NAV_GOTOINDEX = 3;
+const NAV_URI = 4;
+const NAV_RELOAD = 5;
+
+var gExpectedEvents; // an array of events which are expected to
+// be triggered by this navigation
+var gUnexpectedEvents; // an array of event names which are NOT expected
+// to be triggered by this navigation
+var gFinalEvent; // true if the last expected event has fired
+var gUrisNotInBFCache = []; // an array of uri's which shouldn't be stored
+// in the bfcache
+var gNavType = NAV_NONE; // defines the most recent navigation type
+// executed by doPageNavigation
+var gOrigMaxTotalViewers = undefined; // original value of max_total_viewers, // to be restored at end of test
+
+var gExtractedPath = null; // used to cache file path for extracting files from a .jar file
+
+/**
+ * The doPageNavigation() function performs page navigations asynchronously,
+ * listens for specified events, and compares actual events with a list of
+ * expected events. When all expected events have occurred, an optional
+ * callback can be notified. The parameter passed to this function is an
+ * object with the following properties:
+ *
+ * uri: if !undefined, the browser will navigate to this uri
+ *
+ * back: if true, the browser will execute goBack()
+ *
+ * forward: if true, the browser will execute goForward()
+ *
+ * gotoIndex: if a number, the browser will execute gotoIndex() with
+ * the number as index
+ *
+ * reload: if true, the browser will execute reload()
+ *
+ * eventsToListenFor: an array containing one or more of the following event
+ * types to listen for: "pageshow", "pagehide", "onload",
+ * "onunload". If this property is undefined, only a
+ * single "pageshow" events will be listened for. If this
+ * property is explicitly empty, [], then no events will
+ * be listened for.
+ *
+ * expectedEvents: an array of one or more expectedEvent objects,
+ * corresponding to the events which are expected to be
+ * fired for this navigation. Each object has the
+ * following properties:
+ *
+ * type: one of the event type strings
+ * title (optional): the title of the window the
+ * event belongs to
+ * persisted (optional): the event's expected
+ * .persisted attribute
+ *
+ * This function will verify that events with the
+ * specified properties are fired in the same order as
+ * specified in the array. If .title or .persisted
+ * properties for an expectedEvent are undefined, those
+ * properties will not be verified for that particular
+ * event.
+ *
+ * This property is ignored if eventsToListenFor is
+ * undefined or [].
+ *
+ * preventBFCache: if true, an RTCPeerConnection will be added to the loaded
+ * page to prevent it from being bfcached. This property
+ * has no effect when eventsToListenFor is [].
+ *
+ * onNavComplete: a callback which is notified after all expected events
+ * have occurred, or after a timeout has elapsed. This
+ * callback is not notified if eventsToListenFor is [].
+ * onGlobalCreation: a callback which is notified when a DOMWindow is created
+ * (implemented by observing
+ * "content-document-global-created")
+ *
+ * There must be an expectedEvent object for each event of the types in
+ * eventsToListenFor which is triggered by this navigation. For example, if
+ * eventsToListenFor = [ "pagehide", "pageshow" ], then expectedEvents
+ * must contain an object for each pagehide and pageshow event which occurs as
+ * a result of this navigation.
+ */
+// eslint-disable-next-line complexity
+function doPageNavigation(params) {
+ // Parse the parameters.
+ let back = params.back ? params.back : false;
+ let forward = params.forward ? params.forward : false;
+ let gotoIndex = params.gotoIndex ? params.gotoIndex : false;
+ let reload = params.reload ? params.reload : false;
+ let uri = params.uri ? params.uri : false;
+ let eventsToListenFor =
+ typeof params.eventsToListenFor != "undefined"
+ ? params.eventsToListenFor
+ : ["pageshow"];
+ gExpectedEvents =
+ typeof params.eventsToListenFor == "undefined" || !eventsToListenFor.length
+ ? undefined
+ : params.expectedEvents;
+ gUnexpectedEvents =
+ typeof params.eventsToListenFor == "undefined" || !eventsToListenFor.length
+ ? undefined
+ : params.unexpectedEvents;
+ let preventBFCache =
+ typeof [params.preventBFCache] == "undefined"
+ ? false
+ : params.preventBFCache;
+ let waitOnly =
+ typeof params.waitForEventsOnly == "boolean" && params.waitForEventsOnly;
+
+ // Do some sanity checking on arguments.
+ let navigation = ["back", "forward", "gotoIndex", "reload", "uri"].filter(k =>
+ params.hasOwnProperty(k)
+ );
+ if (navigation.length > 1) {
+ throw new Error(`Can't specify both ${navigation[0]} and ${navigation[1]}`);
+ } else if (!navigation.length && !waitOnly) {
+ throw new Error(
+ "Must specify back or forward or gotoIndex or reload or uri"
+ );
+ }
+ if (params.onNavComplete && !eventsToListenFor.length) {
+ throw new Error("Can't use onNavComplete when eventsToListenFor == []");
+ }
+ if (params.preventBFCache && !eventsToListenFor.length) {
+ throw new Error("Can't use preventBFCache when eventsToListenFor == []");
+ }
+ if (params.preventBFCache && waitOnly) {
+ throw new Error("Can't prevent bfcaching when only waiting for events");
+ }
+ if (waitOnly && typeof params.onNavComplete == "undefined") {
+ throw new Error(
+ "Must specify onNavComplete when specifying waitForEventsOnly"
+ );
+ }
+ if (waitOnly && navigation.length) {
+ throw new Error(
+ "Can't specify a navigation type when using waitForEventsOnly"
+ );
+ }
+ for (let anEventType of eventsToListenFor) {
+ let eventFound = false;
+ if (anEventType == "pageshow" && !gExpectedEvents) {
+ eventFound = true;
+ }
+ if (gExpectedEvents) {
+ for (let anExpectedEvent of gExpectedEvents) {
+ if (anExpectedEvent.type == anEventType) {
+ eventFound = true;
+ }
+ }
+ }
+ if (gUnexpectedEvents) {
+ for (let anExpectedEventType of gUnexpectedEvents) {
+ if (anExpectedEventType == anEventType) {
+ eventFound = true;
+ }
+ }
+ }
+ if (!eventFound) {
+ throw new Error(
+ `Event type ${anEventType} is specified in ` +
+ "eventsToListenFor, but not in expectedEvents"
+ );
+ }
+ }
+
+ // If the test explicitly sets .eventsToListenFor to [], don't wait for any
+ // events.
+ gFinalEvent = !eventsToListenFor.length;
+
+ // Add observers as needed.
+ let observers = new Map();
+ if (params.hasOwnProperty("onGlobalCreation")) {
+ observers.set("content-document-global-created", params.onGlobalCreation);
+ }
+
+ // Add an event listener for each type of event in the .eventsToListenFor
+ // property of the input parameters, and add an observer for all the topics
+ // in the observers map.
+ let cleanup;
+ let useActor = TestWindow.getBrowser().isRemoteBrowser;
+ if (useActor) {
+ ChromeUtils.registerWindowActor("DocShellHelpers", {
+ parent: {
+ esModuleURI: ACTOR_MODULE_URI,
+ },
+ child: {
+ esModuleURI: ACTOR_MODULE_URI,
+ events: {
+ pageshow: { createActor: true, capture: true },
+ pagehide: { createActor: true, capture: true },
+ load: { createActor: true, capture: true },
+ unload: { createActor: true, capture: true },
+ visibilitychange: { createActor: true, capture: true },
+ },
+ observers: observers.keys(),
+ },
+ allFrames: true,
+ });
+ DocShellHelpersParent.eventsToListenFor = eventsToListenFor;
+ DocShellHelpersParent.observers = observers;
+
+ cleanup = () => {
+ DocShellHelpersParent.eventsToListenFor = null;
+ DocShellHelpersParent.observers = null;
+ ChromeUtils.unregisterWindowActor("DocShellHelpers");
+ };
+ } else {
+ for (let eventType of eventsToListenFor) {
+ dump("TEST: registering a listener for " + eventType + " events\n");
+ TestWindow.getBrowser().addEventListener(
+ eventType,
+ pageEventListener,
+ true
+ );
+ }
+ if (observers.size > 0) {
+ let observer = (_, topic) => {
+ observers.get(topic).call();
+ };
+ for (let topic of observers.keys()) {
+ Services.obs.addObserver(observer, topic);
+ }
+
+ // We only need to do cleanup for the observer, the event listeners will
+ // go away with the window.
+ cleanup = () => {
+ for (let topic of observers.keys()) {
+ Services.obs.removeObserver(observer, topic);
+ }
+ };
+ }
+ }
+
+ if (cleanup) {
+ // Register a cleanup function on domwindowclosed, to avoid contaminating
+ // other tests if we bail out early because of an error.
+ Services.ww.registerNotification(function windowClosed(
+ subject,
+ topic,
+ data
+ ) {
+ if (topic == "domwindowclosed" && subject == window) {
+ Services.ww.unregisterNotification(windowClosed);
+ cleanup();
+ }
+ });
+ }
+
+ // Perform the specified navigation.
+ if (back) {
+ gNavType = NAV_BACK;
+ TestWindow.getBrowser().goBack();
+ } else if (forward) {
+ gNavType = NAV_FORWARD;
+ TestWindow.getBrowser().goForward();
+ } else if (typeof gotoIndex == "number") {
+ gNavType = NAV_GOTOINDEX;
+ TestWindow.getBrowser().gotoIndex(gotoIndex);
+ } else if (uri) {
+ gNavType = NAV_URI;
+ BrowserTestUtils.loadURIString(TestWindow.getBrowser(), uri);
+ } else if (reload) {
+ gNavType = NAV_RELOAD;
+ TestWindow.getBrowser().reload();
+ } else if (waitOnly) {
+ gNavType = NAV_NONE;
+ } else {
+ throw new Error("No valid navigation type passed to doPageNavigation!");
+ }
+
+ // If we're listening for events and there is an .onNavComplete callback,
+ // wait for all events to occur, and then call doPageNavigation_complete().
+ if (eventsToListenFor.length && params.onNavComplete) {
+ waitForTrue(
+ function () {
+ return gFinalEvent;
+ },
+ function () {
+ doPageNavigation_complete(
+ eventsToListenFor,
+ params.onNavComplete,
+ preventBFCache,
+ useActor,
+ cleanup
+ );
+ }
+ );
+ } else if (cleanup) {
+ cleanup();
+ }
+}
+
+/**
+ * Finish doPageNavigation(), by removing event listeners, adding an unload
+ * handler if appropriate, and calling the onNavComplete callback. This
+ * function is called after all the expected events for this navigation have
+ * occurred.
+ */
+function doPageNavigation_complete(
+ eventsToListenFor,
+ onNavComplete,
+ preventBFCache,
+ useActor,
+ cleanup
+) {
+ if (useActor) {
+ if (preventBFCache) {
+ let actor =
+ TestWindow.getBrowser().browsingContext.currentWindowGlobal.getActor(
+ "DocShellHelpers"
+ );
+ actor.sendAsyncMessage("docshell_helpers:preventBFCache");
+ }
+ } else {
+ // Unregister our event listeners.
+ dump("TEST: removing event listeners\n");
+ for (let eventType of eventsToListenFor) {
+ TestWindow.getBrowser().removeEventListener(
+ eventType,
+ pageEventListener,
+ true
+ );
+ }
+
+ // If the .preventBFCache property was set, add an RTCPeerConnection to
+ // prevent the page from being bfcached.
+ if (preventBFCache) {
+ let win = TestWindow.getWindow();
+ win.blockBFCache = new win.RTCPeerConnection();
+ }
+ }
+
+ if (cleanup) {
+ cleanup();
+ }
+
+ let uri = TestWindow.getBrowser().currentURI.spec;
+ if (preventBFCache) {
+ // Save the current uri in an array of uri's which shouldn't be
+ // stored in the bfcache, for later verification.
+ if (!(uri in gUrisNotInBFCache)) {
+ gUrisNotInBFCache.push(uri);
+ }
+ } else if (gNavType == NAV_URI) {
+ // If we're navigating to a uri and .preventBFCache was not
+ // specified, splice it out of gUrisNotInBFCache if it's there.
+ gUrisNotInBFCache.forEach(function (element, index, array) {
+ if (element == uri) {
+ array.splice(index, 1);
+ }
+ }, this);
+ }
+
+ // Notify the callback now that we're done.
+ onNavComplete.call();
+}
+
+function promisePageNavigation(params) {
+ if (params.hasOwnProperty("onNavComplete")) {
+ throw new Error(
+ "Can't use a onNavComplete completion callback with promisePageNavigation."
+ );
+ }
+ return new Promise(resolve => {
+ params.onNavComplete = resolve;
+ doPageNavigation(params);
+ });
+}
+
+/**
+ * Allows a test to wait for page navigation events, and notify a
+ * callback when they've all been received. This works exactly the
+ * same as doPageNavigation(), except that no navigation is initiated.
+ */
+function waitForPageEvents(params) {
+ params.waitForEventsOnly = true;
+ doPageNavigation(params);
+}
+
+function promisePageEvents(params) {
+ if (params.hasOwnProperty("onNavComplete")) {
+ throw new Error(
+ "Can't use a onNavComplete completion callback with promisePageEvents."
+ );
+ }
+ return new Promise(resolve => {
+ params.waitForEventsOnly = true;
+ params.onNavComplete = resolve;
+ doPageNavigation(params);
+ });
+}
+
+/**
+ * The event listener which listens for expectedEvents.
+ */
+function pageEventListener(
+ event,
+ originalTargetIsHTMLDocument = HTMLDocument.isInstance(event.originalTarget)
+) {
+ try {
+ dump(
+ "TEST: eventListener received a " +
+ event.type +
+ " event for page " +
+ event.originalTarget.title +
+ ", persisted=" +
+ event.persisted +
+ "\n"
+ );
+ } catch (e) {
+ // Ignore any exception.
+ }
+
+ // If this page shouldn't be in the bfcache because it was previously
+ // loaded with .preventBFCache, make sure that its pageshow event
+ // has .persisted = false, even if the test doesn't explicitly test
+ // for .persisted.
+ if (
+ event.type == "pageshow" &&
+ (gNavType == NAV_BACK ||
+ gNavType == NAV_FORWARD ||
+ gNavType == NAV_GOTOINDEX)
+ ) {
+ let uri = TestWindow.getBrowser().currentURI.spec;
+ if (uri in gUrisNotInBFCache) {
+ ok(
+ !event.persisted,
+ "pageshow event has .persisted = false, even " +
+ "though it was loaded with .preventBFCache previously\n"
+ );
+ }
+ }
+
+ if (typeof gUnexpectedEvents != "undefined") {
+ is(
+ gUnexpectedEvents.indexOf(event.type),
+ -1,
+ "Should not get unexpected event " + event.type
+ );
+ }
+
+ // If no expected events were specified, mark the final event as having been
+ // triggered when a pageshow event is fired; this will allow
+ // doPageNavigation() to return.
+ if (typeof gExpectedEvents == "undefined" && event.type == "pageshow") {
+ waitForNextPaint(function () {
+ gFinalEvent = true;
+ });
+ return;
+ }
+
+ // If there are explicitly no expected events, but we receive one, it's an
+ // error.
+ if (!gExpectedEvents.length) {
+ ok(false, "Unexpected event (" + event.type + ") occurred");
+ return;
+ }
+
+ // Grab the next expected event, and compare its attributes against the
+ // actual event.
+ let expected = gExpectedEvents.shift();
+
+ is(
+ event.type,
+ expected.type,
+ "A " +
+ expected.type +
+ " event was expected, but a " +
+ event.type +
+ " event occurred"
+ );
+
+ if (typeof expected.title != "undefined") {
+ ok(
+ originalTargetIsHTMLDocument,
+ "originalTarget for last " + event.type + " event not an HTMLDocument"
+ );
+ is(
+ event.originalTarget.title,
+ expected.title,
+ "A " +
+ event.type +
+ " event was expected for page " +
+ expected.title +
+ ", but was fired for page " +
+ event.originalTarget.title
+ );
+ }
+
+ if (typeof expected.persisted != "undefined") {
+ is(
+ event.persisted,
+ expected.persisted,
+ "The persisted property of the " +
+ event.type +
+ " event on page " +
+ event.originalTarget.location +
+ " had an unexpected value"
+ );
+ }
+
+ if ("visibilityState" in expected) {
+ is(
+ event.originalTarget.visibilityState,
+ expected.visibilityState,
+ "The visibilityState property of the document on page " +
+ event.originalTarget.location +
+ " had an unexpected value"
+ );
+ }
+
+ if ("hidden" in expected) {
+ is(
+ event.originalTarget.hidden,
+ expected.hidden,
+ "The hidden property of the document on page " +
+ event.originalTarget.location +
+ " had an unexpected value"
+ );
+ }
+
+ // If we're out of expected events, let doPageNavigation() return.
+ if (!gExpectedEvents.length) {
+ waitForNextPaint(function () {
+ gFinalEvent = true;
+ });
+ }
+}
+
+DocShellHelpersParent.eventListener = pageEventListener;
+
+/**
+ * End a test.
+ */
+function finish() {
+ // Work around bug 467960.
+ let historyPurged;
+ if (SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ let history = TestWindow.getBrowser().browsingContext?.sessionHistory;
+ history.purgeHistory(history.count);
+ historyPurged = Promise.resolve();
+ } else {
+ historyPurged = SpecialPowers.spawn(TestWindow.getBrowser(), [], () => {
+ let history = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory
+ .legacySHistory;
+ history.purgeHistory(history.count);
+ });
+ }
+
+ // If the test changed the value of max_total_viewers via a call to
+ // enableBFCache(), then restore it now.
+ if (typeof gOrigMaxTotalViewers != "undefined") {
+ Services.prefs.setIntPref(
+ "browser.sessionhistory.max_total_viewers",
+ gOrigMaxTotalViewers
+ );
+ }
+
+ // Close the test window and signal the framework that the test is done.
+ let opener = window.opener;
+ let SimpleTest = opener.wrappedJSObject.SimpleTest;
+
+ // Wait for the window to be closed before finishing the test
+ Services.ww.registerNotification(function observer(subject, topic, data) {
+ if (topic == "domwindowclosed") {
+ Services.ww.unregisterNotification(observer);
+ SimpleTest.waitForFocus(SimpleTest.finish, opener);
+ }
+ });
+
+ historyPurged.then(_ => {
+ window.close();
+ });
+}
+
+/**
+ * Helper function which waits until another function returns true, or until a
+ * timeout occurs, and then notifies a callback.
+ *
+ * Parameters:
+ *
+ * fn: a function which is evaluated repeatedly, and when it turns true,
+ * the onWaitComplete callback is notified.
+ *
+ * onWaitComplete: a callback which will be notified when fn() returns
+ * true, or when a timeout occurs.
+ *
+ * timeout: a timeout, in seconds or ms, after which waitForTrue() will
+ * fail an assertion and then return, even if the fn function never
+ * returns true. If timeout is undefined, waitForTrue() will never
+ * time out.
+ */
+function waitForTrue(fn, onWaitComplete, timeout) {
+ promiseTrue(fn, timeout).then(() => {
+ onWaitComplete.call();
+ });
+}
+
+function promiseTrue(fn, timeout) {
+ if (typeof timeout != "undefined") {
+ // If timeoutWait is less than 500, assume it represents seconds, and
+ // convert to ms.
+ if (timeout < 500) {
+ timeout *= 1000;
+ }
+ }
+
+ // Loop until the test function returns true, or until a timeout occurs,
+ // if a timeout is defined.
+ let intervalid, timeoutid;
+ let condition = new Promise(resolve => {
+ intervalid = setInterval(async () => {
+ if (await fn.call()) {
+ resolve();
+ }
+ }, 20);
+ });
+ if (typeof timeout != "undefined") {
+ condition = Promise.race([
+ condition,
+ new Promise((_, reject) => {
+ timeoutid = setTimeout(() => {
+ reject();
+ }, timeout);
+ }),
+ ]);
+ }
+ return condition
+ .finally(() => {
+ clearInterval(intervalid);
+ })
+ .then(() => {
+ clearTimeout(timeoutid);
+ });
+}
+
+function waitForNextPaint(cb) {
+ requestAnimationFrame(_ => requestAnimationFrame(cb));
+}
+
+function promiseNextPaint() {
+ return new Promise(resolve => {
+ waitForNextPaint(resolve);
+ });
+}
+
+/**
+ * Enable or disable the bfcache.
+ *
+ * Parameters:
+ *
+ * enable: if true, set max_total_viewers to -1 (the default); if false, set
+ * to 0 (disabled), if a number, set it to that specific number
+ */
+function enableBFCache(enable) {
+ // If this is the first time the test called enableBFCache(),
+ // store the original value of max_total_viewers, so it can
+ // be restored at the end of the test.
+ if (typeof gOrigMaxTotalViewers == "undefined") {
+ gOrigMaxTotalViewers = Services.prefs.getIntPref(
+ "browser.sessionhistory.max_total_viewers"
+ );
+ }
+
+ if (typeof enable == "boolean") {
+ if (enable) {
+ Services.prefs.setIntPref("browser.sessionhistory.max_total_viewers", -1);
+ } else {
+ Services.prefs.setIntPref("browser.sessionhistory.max_total_viewers", 0);
+ }
+ } else if (typeof enable == "number") {
+ Services.prefs.setIntPref(
+ "browser.sessionhistory.max_total_viewers",
+ enable
+ );
+ }
+}
+
+/*
+ * get http root for local tests. Use a single extractJarToTmp instead of
+ * extracting for each test.
+ * Returns a file://path if we have a .jar file
+ */
+function getHttpRoot() {
+ var location = window.location.href;
+ location = getRootDirectory(location);
+ var jar = getJar(location);
+ if (jar != null) {
+ if (gExtractedPath == null) {
+ var resolved = extractJarToTmp(jar);
+ gExtractedPath = resolved.path;
+ }
+ } else {
+ return null;
+ }
+ return "file://" + gExtractedPath + "/";
+}
+
+/**
+ * Returns the full HTTP url for a file in the mochitest docshell test
+ * directory.
+ */
+function getHttpUrl(filename) {
+ var root = getHttpRoot();
+ if (root == null) {
+ root = "http://mochi.test:8888/chrome/docshell/test/chrome/";
+ }
+ return root + filename;
+}
+
+/**
+ * A convenience object with methods that return the current test window,
+ * browser, and document.
+ */
+var TestWindow = {};
+TestWindow.getWindow = function () {
+ return document.getElementById("content").contentWindow;
+};
+TestWindow.getBrowser = function () {
+ return document.getElementById("content");
+};
+TestWindow.getDocument = function () {
+ return document.getElementById("content").contentDocument;
+};
diff --git a/docshell/test/chrome/file_viewsource_forbidden_in_iframe.html b/docshell/test/chrome/file_viewsource_forbidden_in_iframe.html
new file mode 100644
index 0000000000..fdecbbdfe1
--- /dev/null
+++ b/docshell/test/chrome/file_viewsource_forbidden_in_iframe.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test ifranes for view-source forbidden in iframe tests</title>
+</head>
+<body>
+ <iframe id="testIframe"></iframe>
+ <iframe id="refIframe"></iframe>
+</body>
+</html>
diff --git a/docshell/test/chrome/gen_template.pl b/docshell/test/chrome/gen_template.pl
new file mode 100644
index 0000000000..109d6161cd
--- /dev/null
+++ b/docshell/test/chrome/gen_template.pl
@@ -0,0 +1,39 @@
+#!/usr/bin/perl
+
+# This script makes docshell test case templates. It takes one argument:
+#
+# -b: a bugnumber
+#
+# For example, this command:
+#
+# perl gen_template.pl -b 303267
+#
+# Writes test case template files test_bug303267.xhtml and bug303267_window.xhtml
+# to the current directory.
+
+use FindBin;
+use Getopt::Long;
+GetOptions("b=i"=> \$bug_number);
+
+$template = "$FindBin::RealBin/test.template.txt";
+
+open(IN,$template) or die("Failed to open input file for reading.");
+open(OUT, ">>test_bug" . $bug_number . ".xhtml") or die("Failed to open output file for appending.");
+while((defined(IN)) && ($line = <IN>)) {
+ $line =~ s/{BUGNUMBER}/$bug_number/g;
+ print OUT $line;
+}
+close(IN);
+close(OUT);
+
+$template = "$FindBin::RealBin/window.template.txt";
+
+open(IN,$template) or die("Failed to open input file for reading.");
+open(OUT, ">>bug" . $bug_number . "_window.xhtml") or die("Failed to open output file for appending.");
+while((defined(IN)) && ($line = <IN>)) {
+ $line =~ s/{BUGNUMBER}/$bug_number/g;
+ print OUT $line;
+}
+close(IN);
+close(OUT);
+
diff --git a/docshell/test/chrome/generic.html b/docshell/test/chrome/generic.html
new file mode 100644
index 0000000000..569a78c05a
--- /dev/null
+++ b/docshell/test/chrome/generic.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+ <title>
+ generic page
+ </title>
+ </head>
+<body>
+<div id="div1" style="height: 1000px; border: thin solid black;">
+ A generic page which can be used any time a test needs to load an arbitrary page via http.
+ </div>
+</body>
+</html>
diff --git a/docshell/test/chrome/mozFrameType_window.xhtml b/docshell/test/chrome/mozFrameType_window.xhtml
new file mode 100644
index 0000000000..e5d0126d22
--- /dev/null
+++ b/docshell/test/chrome/mozFrameType_window.xhtml
@@ -0,0 +1,49 @@
+<?xml version="1.0"?>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<window title="Test mozFrameType attribute"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTests();">
+
+ <html:iframe id="normalFrame"/>
+ <html:iframe id="typeContentFrame" mozframetype="content"/>
+
+ <script type="application/javascript"><![CDATA[
+ function runTests() {
+ let opener = window.arguments[0];
+ let SimpleTest = opener.SimpleTest;
+
+ function getDocShellType(frame) {
+ return frame.contentWindow.docShell.itemType;
+ }
+
+ var normalFrame = document.getElementById("normalFrame");
+ var typeContentFrame = document.getElementById("typeContentFrame");
+
+ SimpleTest.is(getDocShellType(normalFrame), Ci.nsIDocShellTreeItem.typeChrome,
+ "normal iframe in chrome document is typeChrome");
+ SimpleTest.is(getDocShellType(typeContentFrame), Ci.nsIDocShellTreeItem.typeContent,
+ "iframe with mozFrameType='content' in chrome document is typeContent");
+
+ SimpleTest.executeSoon(function () {
+ // First focus the parent window and then close this one.
+ SimpleTest.waitForFocus(function() {
+ let ww = SpecialPowers.Services.ww;
+ ww.registerNotification(function windowObs(subject, topic, data) {
+ if (topic == "domwindowclosed") {
+ ww.unregisterNotification(windowObs);
+
+ // Don't start the next test synchronously!
+ SimpleTest.executeSoon(function() {
+ SimpleTest.finish();
+ });
+ }
+ });
+
+ window.close();
+ }, opener);
+ });
+ }
+ ]]></script>
+</window>
diff --git a/docshell/test/chrome/red.png b/docshell/test/chrome/red.png
new file mode 100644
index 0000000000..aa9ce25263
--- /dev/null
+++ b/docshell/test/chrome/red.png
Binary files differ
diff --git a/docshell/test/chrome/test.template.txt b/docshell/test/chrome/test.template.txt
new file mode 100644
index 0000000000..b7dd5e5c23
--- /dev/null
+++ b/docshell/test/chrome/test.template.txt
@@ -0,0 +1,41 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id={BUGNUMBER}.xul
+-->
+<window title="Mozilla Bug {BUGNUMBER}"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <title>Test for Bug {BUGNUMBER}</title>
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id={BUGNUMBER}">
+ Mozilla Bug {BUGNUMBER}</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug {BUGNUMBER} **/
+
+SimpleTest.waitForExplicitFinish();
+window.open("bug{BUGNUMBER}_window.xul", "bug{BUGNUMBER}",
+ "chrome,width=600,height=600");
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_allowContentRetargeting.html b/docshell/test/chrome/test_allowContentRetargeting.html
new file mode 100644
index 0000000000..b6b830138f
--- /dev/null
+++ b/docshell/test/chrome/test_allowContentRetargeting.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runNextTest);
+
+var TEST_URL = "http://mochi.test:8888/tests/docshell/test/chrome/allowContentRetargeting.sjs";
+
+function runNextTest() {
+ var test = tests.shift();
+ if (!test) {
+ SimpleTest.finish();
+ return;
+ }
+ test();
+}
+
+var tests = [
+
+ // Set allowContentRetargeting = false, load a downloadable URL, verify the
+ // downloadable stops loading.
+ function basic() {
+ var iframe = insertIframe();
+ iframe.contentWindow.docShell.allowContentRetargeting = false;
+ loadIframe(iframe);
+ },
+
+ // Set allowContentRetargeting = false on parent docshell, load a downloadable
+ // URL, verify the downloadable stops loading.
+ function inherit() {
+ var docshell = window.docShell;
+ docshell.allowContentRetargeting = false;
+ loadIframe(insertIframe());
+ },
+];
+
+function insertIframe() {
+ var iframe = document.createElement("iframe");
+ document.body.appendChild(iframe);
+ return iframe;
+}
+
+function loadIframe(iframe) {
+ iframe.setAttribute("src", TEST_URL);
+ iframe.contentWindow.docShell.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebProgress).
+ addProgressListener(progressListener,
+ Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
+}
+
+var progressListener = {
+ onStateChange(webProgress, req, flags, status) {
+ if (!(flags & Ci.nsIWebProgressListener.STATE_STOP))
+ return;
+ is(Components.isSuccessCode(status), false,
+ "Downloadable should have failed to load");
+ document.querySelector("iframe").remove();
+ runNextTest();
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIWebProgressListener", "nsISupportsWeakReference"]),
+};
+
+ </script>
+</head>
+<body>
+<p id="display">
+</p>
+</body>
+</html>
diff --git a/docshell/test/chrome/test_bug112564.xhtml b/docshell/test/chrome/test_bug112564.xhtml
new file mode 100644
index 0000000000..83a087b6d9
--- /dev/null
+++ b/docshell/test/chrome/test_bug112564.xhtml
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=112564
+-->
+<window title="Mozilla Bug 112564"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=112564">Mozilla Bug 112564</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 112564 */
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug112564_window.xhtml", "bug112564",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug113934.xhtml b/docshell/test/chrome/test_bug113934.xhtml
new file mode 100644
index 0000000000..99b8ae253c
--- /dev/null
+++ b/docshell/test/chrome/test_bug113934.xhtml
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=113934
+-->
+<window title="Mozilla Bug 113934"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=113934"
+ target="_blank">Mozilla Bug 396519</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ addLoadEvent(function() {
+ window.openDialog("bug113934_window.xhtml?content", "bug113934",
+ "chrome,width=800,height=800,noopener", window);
+ });
+
+ ]]></script>
+</window>
diff --git a/docshell/test/chrome/test_bug215405.xhtml b/docshell/test/chrome/test_bug215405.xhtml
new file mode 100644
index 0000000000..e9511a6ed1
--- /dev/null
+++ b/docshell/test/chrome/test_bug215405.xhtml
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=215405
+-->
+<window title="Mozilla Bug 215405"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=215405">Mozilla Bug 215405</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 215405 */
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug215405_window.xhtml", "bug215405",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug293235.xhtml b/docshell/test/chrome/test_bug293235.xhtml
new file mode 100644
index 0000000000..4696c37557
--- /dev/null
+++ b/docshell/test/chrome/test_bug293235.xhtml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=293235.xul
+-->
+<window title="Mozilla Bug 293235"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=293235">
+ Mozilla Bug 293235</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 293235 */
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug293235_window.xhtml", "bug293235",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug294258.xhtml b/docshell/test/chrome/test_bug294258.xhtml
new file mode 100644
index 0000000000..8f61a6c299
--- /dev/null
+++ b/docshell/test/chrome/test_bug294258.xhtml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=294258.xul
+-->
+<window title="Mozilla Bug 294258"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=294258">
+ Mozilla Bug 294258</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 294258 */
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug294258_window.xhtml", "bug294258",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug298622.xhtml b/docshell/test/chrome/test_bug298622.xhtml
new file mode 100644
index 0000000000..8a154b5145
--- /dev/null
+++ b/docshell/test/chrome/test_bug298622.xhtml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=298622.xul
+-->
+<window title="Mozilla Bug 298622"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=298622">
+ Mozilla Bug 298622</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 298622 */
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug298622_window.xhtml", "bug298622",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug301397.xhtml b/docshell/test/chrome/test_bug301397.xhtml
new file mode 100644
index 0000000000..3da88ecf53
--- /dev/null
+++ b/docshell/test/chrome/test_bug301397.xhtml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=301397.xul
+-->
+<window title="Mozilla Bug 301397"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=301397">
+ Mozilla Bug 301397</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 301397 */
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug301397_window.xhtml", "bug301397",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug303267.xhtml b/docshell/test/chrome/test_bug303267.xhtml
new file mode 100644
index 0000000000..03af56d344
--- /dev/null
+++ b/docshell/test/chrome/test_bug303267.xhtml
@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=303267.xul
+-->
+<window title="Mozilla Bug 303267"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=303267">Mozilla Bug 303267</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+SimpleTest.expectAssertions(0, 1);
+
+/** Test for Bug 303267 */
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug303267_window.xhtml", "bug303267",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug311007.xhtml b/docshell/test/chrome/test_bug311007.xhtml
new file mode 100644
index 0000000000..4601c6ec20
--- /dev/null
+++ b/docshell/test/chrome/test_bug311007.xhtml
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=311007.xul
+-->
+<window title="Mozilla Bug 311007"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=311007">
+ Mozilla Bug 311007</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+if (navigator.platform.startsWith("Win")) {
+ SimpleTest.expectAssertions(0, 1);
+}
+
+/** Test for Bug 311007 */
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug311007_window.xhtml", "bug311007",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug321671.xhtml b/docshell/test/chrome/test_bug321671.xhtml
new file mode 100644
index 0000000000..7855db6f13
--- /dev/null
+++ b/docshell/test/chrome/test_bug321671.xhtml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=321671.xul
+-->
+<window title="Mozilla Bug 321671"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=321671">
+ Mozilla Bug 321671</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 321671 */
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug321671_window.xhtml", "bug321671",
+ "chrome,width=600,height=600,scrollbars,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug360511.xhtml b/docshell/test/chrome/test_bug360511.xhtml
new file mode 100644
index 0000000000..99b9d180f4
--- /dev/null
+++ b/docshell/test/chrome/test_bug360511.xhtml
@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=360511.xul
+-->
+<window title="Mozilla Bug 360511"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js">
+ </script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=360511">
+ Mozilla Bug 360511</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 360511 */
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug360511_window.xhtml", "bug360511",
+ "chrome,scrollbars,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug364461.xhtml b/docshell/test/chrome/test_bug364461.xhtml
new file mode 100644
index 0000000000..9bdc016ae7
--- /dev/null
+++ b/docshell/test/chrome/test_bug364461.xhtml
@@ -0,0 +1,43 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=364461
+-->
+<window title="Mozilla Bug 364461"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=364461">Mozilla Bug 364461</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 364461 */
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv({
+ "set":[["security.data_uri.block_toplevel_data_uri_navigations", false]]
+}, runTests);
+
+function runTests() {
+ window.openDialog("bug364461_window.xhtml", "bug364461",
+ "chrome,width=600,height=600,noopener", window);
+}
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug396519.xhtml b/docshell/test/chrome/test_bug396519.xhtml
new file mode 100644
index 0000000000..7c99bf4a32
--- /dev/null
+++ b/docshell/test/chrome/test_bug396519.xhtml
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=396519
+-->
+<window title="Mozilla Bug 396519"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=396519"
+ target="_blank">Mozilla Bug 396519</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ /** Test for Bug 396519 */
+
+ SimpleTest.waitForExplicitFinish();
+ window.openDialog("bug396519_window.xhtml", "bug396519",
+ "chrome,width=600,height=600,noopener", window);
+
+ ]]></script>
+</window>
diff --git a/docshell/test/chrome/test_bug396649.xhtml b/docshell/test/chrome/test_bug396649.xhtml
new file mode 100644
index 0000000000..8ac05eee85
--- /dev/null
+++ b/docshell/test/chrome/test_bug396649.xhtml
@@ -0,0 +1,41 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=396649.xul
+-->
+<window title="Mozilla Bug 396649"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript"
+ src=
+ "chrome://mochikit/content/tests/SimpleTest/SimpleTest.js">
+ </script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=396649">
+ Mozilla Bug 396649</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 396649 */
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug396649_window.xhtml", "bug396649",
+ "chrome,width=600,height=600,scrollbars,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug428288.html b/docshell/test/chrome/test_bug428288.html
new file mode 100644
index 0000000000..4697aed515
--- /dev/null
+++ b/docshell/test/chrome/test_bug428288.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=428288
+-->
+<head>
+ <title>Test for Bug 428288</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=428288">Mozilla Bug 428288</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe name="target"></iframe>
+ <a id="crashy" target="target" href="about:blank">crash me</a>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 428288 */
+
+function makeClick() {
+ var event = document.createEvent("MouseEvents");
+ event.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0,
+ false, false, false, false, 0, null);
+ document.getElementById("crashy").dispatchEvent(event);
+ return true;
+}
+
+ok(makeClick(), "Crashes if bug 428288 is present");
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/docshell/test/chrome/test_bug449778.xhtml b/docshell/test/chrome/test_bug449778.xhtml
new file mode 100644
index 0000000000..67e17164ea
--- /dev/null
+++ b/docshell/test/chrome/test_bug449778.xhtml
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=449778
+-->
+<window title="Mozilla Bug 449778"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=449778"
+ target="_blank">Mozilla Bug 396519</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ addLoadEvent(function() {
+ window.openDialog("bug449778_window.xhtml", "bug449778",
+ "chrome,width=800,height=800,noopener", window);
+ });
+
+ ]]></script>
+</window>
diff --git a/docshell/test/chrome/test_bug449780.xhtml b/docshell/test/chrome/test_bug449780.xhtml
new file mode 100644
index 0000000000..43ed3ce25d
--- /dev/null
+++ b/docshell/test/chrome/test_bug449780.xhtml
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=449780
+-->
+<window title="Mozilla Bug 449780"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=449780"
+ target="_blank">Mozilla Bug 396519</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ addLoadEvent(function() {
+ window.openDialog("bug449780_window.xhtml", "bug449780",
+ "chrome,width=800,height=800,noopener", window);
+ });
+
+ ]]></script>
+</window>
diff --git a/docshell/test/chrome/test_bug453650.xhtml b/docshell/test/chrome/test_bug453650.xhtml
new file mode 100644
index 0000000000..2283e29206
--- /dev/null
+++ b/docshell/test/chrome/test_bug453650.xhtml
@@ -0,0 +1,120 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=453650
+-->
+<window title="Mozilla Bug 453650"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 453650 */
+ SimpleTest.waitForExplicitFinish();
+
+ var iter = runTests();
+ nextTest();
+
+ function* runTests() {
+ var iframe = document.createXULElement("iframe");
+ iframe.style.width = "300px";
+ iframe.style.height = "300px";
+ iframe.setAttribute("src", "data:text/html,<h1 id='h'>hello</h1>");
+
+ document.documentElement.appendChild(iframe);
+ yield whenLoaded(iframe);
+ info("iframe loaded");
+
+ var h1 = iframe.contentDocument.getElementById("h");
+ let myCallback = function() { h1.style.width = "400px"; };
+ info("Calling waitForInterruptibleReflow");
+ yield waitForInterruptibleReflow(iframe.docShell, myCallback);
+ info("got past top-level waitForInterruptibleReflow");
+
+ myCallback = function() { h1.style.width = "300px"; };
+ info("Calling waitForReflow");
+ waitForReflow(iframe.docShell, myCallback);
+ info("got past top-level waitForReflow");
+ yield is(300, h1.offsetWidth, "h1 has correct width");
+
+ SimpleTest.finish();
+ }
+
+ function waitForInterruptibleReflow(docShell,
+ callbackThatShouldTriggerReflow) {
+ waitForReflow(docShell, callbackThatShouldTriggerReflow, true);
+ }
+
+ function waitForReflow(docShell, callbackThatShouldTriggerReflow,
+ interruptible = false) {
+ info("Entering waitForReflow");
+ function done() {
+ info("Entering done (inside of waitForReflow)");
+
+ docShell.removeWeakReflowObserver(observer);
+ SimpleTest.executeSoon(nextTest);
+ }
+
+ var observer = {
+ reflow (start, end) {
+ info("Entering observer.reflow");
+ if (interruptible) {
+ ok(false, "expected interruptible reflow");
+ } else {
+ ok(true, "observed uninterruptible reflow");
+ }
+
+ info("times: " + start + ", " + end);
+ ok(start <= end, "reflow start time lower than end time");
+ done();
+ },
+
+ reflowInterruptible (start, end) {
+ info("Entering observer.reflowInterruptible");
+ if (!interruptible) {
+ ok(false, "expected uninterruptible reflow");
+ } else {
+ ok(true, "observed interruptible reflow");
+ }
+
+ info("times: " + start + ", " + end);
+ ok(start <= end, "reflow start time lower than end time");
+ done();
+ },
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIReflowObserver",
+ "nsISupportsWeakReference",
+ ]),
+ };
+
+ info("waitForReflow is adding a reflow observer");
+ docShell.addWeakReflowObserver(observer);
+ callbackThatShouldTriggerReflow();
+ }
+
+ function whenLoaded(iframe) {
+ info("entering whenLoaded");
+ iframe.addEventListener("load", function() {
+ SimpleTest.executeSoon(nextTest);
+ }, { once: true });
+ }
+
+ function nextTest() {
+ info("entering nextTest");
+ iter.next();
+ }
+
+ ]]>
+ </script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=453650"
+ target="_blank">Mozilla Bug 453650</a>
+ </body>
+</window>
diff --git a/docshell/test/chrome/test_bug454235.xhtml b/docshell/test/chrome/test_bug454235.xhtml
new file mode 100644
index 0000000000..d64f701f8e
--- /dev/null
+++ b/docshell/test/chrome/test_bug454235.xhtml
@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=454235
+-->
+<window title="Mozilla Bug 454235"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=454235"
+ target="_blank">Mozilla Bug 454235</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ /** Test for Bug 454235 */
+SimpleTest.waitForExplicitFinish();
+
+SimpleTest.waitForFocus(doTest);
+
+function doTest() {
+ var hiddenBrowser = document.getElementById("hiddenBrowser");
+
+ hiddenBrowser.contentWindow.focus();
+ ok(!hiddenBrowser.contentDocument.hasFocus(), "hidden browser is unfocusable");
+
+ SimpleTest.finish();
+}
+
+
+
+ ]]></script>
+ <box flex="1" style="visibility: hidden; border:5px black solid">
+ <browser style="border:5px blue solid" id="hiddenBrowser" src="bug454235-subframe.xhtml"/>
+ </box>
+</window>
diff --git a/docshell/test/chrome/test_bug456980.xhtml b/docshell/test/chrome/test_bug456980.xhtml
new file mode 100644
index 0000000000..d1741209c6
--- /dev/null
+++ b/docshell/test/chrome/test_bug456980.xhtml
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=456980
+-->
+<window title="Mozilla Bug 456980"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=456980"
+ target="_blank">Mozilla Bug 396519</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ addLoadEvent(function() {
+ window.openDialog("bug113934_window.xhtml?chrome", "bug456980",
+ "chrome,width=800,height=800,noopener", window);
+ });
+
+ ]]></script>
+</window>
diff --git a/docshell/test/chrome/test_bug565388.xhtml b/docshell/test/chrome/test_bug565388.xhtml
new file mode 100644
index 0000000000..23af2c146d
--- /dev/null
+++ b/docshell/test/chrome/test_bug565388.xhtml
@@ -0,0 +1,79 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=565388
+-->
+<window title="Mozilla Bug 565388"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 565388 */
+ SimpleTest.waitForExplicitFinish();
+
+function test() {
+ var progressListener = {
+ add(docShell, callback) {
+ this.callback = callback;
+ this.docShell = docShell;
+ docShell.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebProgress).
+ addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);
+ },
+
+ finish() {
+ this.docShell.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebProgress).
+ removeProgressListener(this);
+ this.callback();
+ },
+
+ onStateChange (webProgress, req, flags, status) {
+ if (req.name.startsWith("data:application/vnd.mozilla.xul")) {
+ if (flags & Ci.nsIWebProgressListener.STATE_STOP)
+ this.finish();
+ }
+ },
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+ }
+
+ var systemPrincipal = Cc["@mozilla.org/systemprincipal;1"]
+ .getService(Ci.nsIPrincipal);
+ var webNav = SpecialPowers.Services.appShell.createWindowlessBrowser(true);
+ var docShell = webNav.docShell;
+ docShell.createAboutBlankContentViewer(systemPrincipal, systemPrincipal);
+ var win = docShell.contentViewer.DOMDocument.defaultView;
+
+ progressListener.add(docShell, function(){
+ is(win.document.documentURI, "data:application/xhtml+xml;charset=utf-8,<window/>");
+ webNav.close();
+ SimpleTest.finish();
+ });
+
+ win.location = "data:application/xhtml+xml;charset=utf-8,<window/>";
+}
+
+addLoadEvent(function onLoad() {
+ test();
+});
+
+ ]]>
+ </script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=565388"
+ target="_blank">Mozilla Bug 565388</a>
+ </body>
+</window>
diff --git a/docshell/test/chrome/test_bug582176.xhtml b/docshell/test/chrome/test_bug582176.xhtml
new file mode 100644
index 0000000000..5f189ae87b
--- /dev/null
+++ b/docshell/test/chrome/test_bug582176.xhtml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=582176.xul
+-->
+<window title="Mozilla Bug 582176"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=582176">
+ Mozilla Bug 582176</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 582176 */
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug582176_window.xhtml", "bug582176",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug608669.xhtml b/docshell/test/chrome/test_bug608669.xhtml
new file mode 100644
index 0000000000..f6a62b0802
--- /dev/null
+++ b/docshell/test/chrome/test_bug608669.xhtml
@@ -0,0 +1,80 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=608669
+-->
+<window title="Mozilla Bug 608669"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=608669"
+ target="_blank">Mozilla Bug 608669</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+/** Test for Bug 608669 */
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(nextTest);
+
+let gen = doTest();
+
+function nextTest() {
+ gen.next();
+}
+
+let chromeWindow = window.browsingContext.topChromeWindow;
+
+function* doTest() {
+ var notificationCount = 0;
+ var observer = {
+ observe(aSubject, aTopic, aData) {
+ is(aTopic, "chrome-document-global-created",
+ "correct topic");
+ is(aData, "null",
+ "correct data");
+ notificationCount++;
+ }
+ };
+
+ var os = SpecialPowers.Services.obs;
+ os.addObserver(observer, "chrome-document-global-created");
+ os.addObserver(observer, "content-document-global-created");
+
+ is(notificationCount, 0, "initial count");
+
+ // create a new window
+ var testWin = chromeWindow.open("", "bug 608669", "chrome,width=600,height=600");
+ testWin.x = "y";
+ is(notificationCount, 1, "after created window");
+
+ // Try loading in the window
+ testWin.location = "bug608669.xhtml";
+ chromeWindow.onmessage = nextTest;
+ yield undefined;
+ is(notificationCount, 1, "after first load");
+ is(testWin.x, "y", "reused window");
+
+ // Try loading again in the window
+ testWin.location = "bug608669.xhtml?x";
+ chromeWindow.onmessage = nextTest;
+ yield undefined;
+ is(notificationCount, 2, "after second load");
+ is("x" in testWin, false, "didn't reuse window");
+
+ chromeWindow.onmessage = null;
+
+ testWin.close();
+
+ os.removeObserver(observer, "chrome-document-global-created");
+ os.removeObserver(observer, "content-document-global-created");
+ SimpleTest.finish();
+}
+
+ ]]></script>
+</window>
diff --git a/docshell/test/chrome/test_bug662200.xhtml b/docshell/test/chrome/test_bug662200.xhtml
new file mode 100644
index 0000000000..19a386ba97
--- /dev/null
+++ b/docshell/test/chrome/test_bug662200.xhtml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=662200.xul
+-->
+<window title="Mozilla Bug 662200"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=662200">
+ Mozilla Bug 662200</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 662200 */
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug662200_window.xhtml", "bug662200",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug690056.xhtml b/docshell/test/chrome/test_bug690056.xhtml
new file mode 100644
index 0000000000..5f7a2b9534
--- /dev/null
+++ b/docshell/test/chrome/test_bug690056.xhtml
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=690056
+-->
+<window title="Mozilla Bug 690056"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=690056"
+ target="_blank">Mozilla Bug 690056</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ /** Test for Bug 690056 */
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug690056_window.xhtml", "bug690056",
+ "chrome,width=600,height=600,noopener", window);
+ ]]>
+ </script>
+</window>
diff --git a/docshell/test/chrome/test_bug789773.xhtml b/docshell/test/chrome/test_bug789773.xhtml
new file mode 100644
index 0000000000..0f4a67fc10
--- /dev/null
+++ b/docshell/test/chrome/test_bug789773.xhtml
@@ -0,0 +1,67 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=789773
+-->
+<window title="Mozilla Bug 789773"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=789773"
+ target="_blank">Mozilla Bug 789773</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ /* Test for Bug 789773.
+ *
+ * See comment 50 for the situation we're testing against here.
+ *
+ * Note that the failure mode of this test is to hang, and hang the browser on quit.
+ * This is an unfortunate occurance, but that's why we're testing it.
+ */
+ SimpleTest.waitForExplicitFinish();
+
+ const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+
+ var calledListenerForBrowserChromeURL = false;
+ var testProgressListener = {
+ START_DOC: Ci.nsIWebProgressListener.STATE_START | Ci.nsIWebProgressListener.STATE_IS_DOCUMENT,
+ onStateChange(wp, req, stateFlags, status) {
+ let browserChromeFileName = AppConstants.BROWSER_CHROME_URL.split("/").reverse()[0];
+ if (req.name.includes(browserChromeFileName)) {
+ wp.DOMWindow; // Force the lazy creation of a DOM window.
+ calledListenerForBrowserChromeURL = true;
+ }
+ if (req.name.includes("mozilla.html") && (stateFlags & Ci.nsIWebProgressListener.STATE_STOP))
+ finishTest();
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+ }
+
+ // Add our progress listener
+ var webProgress = Cc['@mozilla.org/docloaderservice;1'].getService(Ci.nsIWebProgress);
+ webProgress.addProgressListener(testProgressListener, Ci.nsIWebProgress.NOTIFY_STATE_REQUEST);
+
+ // Open the window.
+ var popup = window.open("about:mozilla", "_blank", "width=640,height=400");
+
+ // Wait for the window to load.
+ function finishTest() {
+ webProgress.removeProgressListener(testProgressListener);
+ ok(true, "Loaded the popup window without spinning forever in the event loop!");
+ ok(calledListenerForBrowserChromeURL, "Should have called the progress listener for browser.xhtml");
+ popup.close();
+ SimpleTest.finish();
+ }
+
+ ]]>
+ </script>
+</window>
diff --git a/docshell/test/chrome/test_bug846906.xhtml b/docshell/test/chrome/test_bug846906.xhtml
new file mode 100644
index 0000000000..74bfdf7036
--- /dev/null
+++ b/docshell/test/chrome/test_bug846906.xhtml
@@ -0,0 +1,94 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=846906
+-->
+<window title="Mozilla Bug 846906"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 846906 */
+ SimpleTest.waitForExplicitFinish();
+
+ var appShellService = SpecialPowers.Services.appShell;
+ ok(appShellService, "Should be able to get app shell service");
+
+ var windowlessBrowser = appShellService.createWindowlessBrowser();
+ ok(windowlessBrowser, "Should be able to create windowless browser");
+
+ ok(windowlessBrowser instanceof Ci.nsIWindowlessBrowser,
+ "Windowless browser should implement nsIWindowlessBrowser");
+
+ var webNavigation = windowlessBrowser.QueryInterface(Ci.nsIWebNavigation);
+ ok(webNavigation, "Windowless browser should implement nsIWebNavigation");
+
+ var interfaceRequestor = windowlessBrowser.QueryInterface(Ci.nsIInterfaceRequestor);
+ ok(interfaceRequestor, "Should be able to query interface requestor interface");
+
+ var docShell = windowlessBrowser.docShell;
+ ok(docShell, "Should be able to get doc shell interface");
+
+ var document = webNavigation.document;
+ ok(document, "Should be able to get document");
+
+ var iframe = document.createXULElement("iframe");
+ ok(iframe, "Should be able to create iframe");
+
+ iframe.onload = function () {
+ ok(true, "Should receive initial onload event");
+
+ iframe.onload = function () {
+ ok(true, "Should receive onload event");
+
+ var contentDocument = iframe.contentDocument;
+ ok(contentDocument, "Should be able to get content document");
+
+ var div = contentDocument.getElementById("div1");
+ ok(div, "Should be able to get element by id");
+
+ var rect = div.getBoundingClientRect();
+ ok(rect, "Should be able to get bounding client rect");
+
+ // xxx: can we do better than hardcoding these values here?
+ is(rect.width, 1024);
+ is(rect.height, 768);
+
+ windowlessBrowser.close();
+
+ // Once the browser is closed, nsIWebNavigation and
+ // nsIInterfaceRequestor methods should no longer be accessible.
+ try {
+ windowlessBrowser.getInterface(Ci.nsIDocShell);
+ ok(false);
+ } catch (e) {
+ is(e.result, Cr.NS_ERROR_NULL_POINTER);
+ }
+
+ try {
+ windowlessBrowser.document;
+ ok(false);
+ } catch (e) {
+ is(e.result, Cr.NS_ERROR_NULL_POINTER);
+ }
+
+ SimpleTest.finish();
+ };
+ iframe.setAttribute("src", "http://mochi.test:8888/chrome/docshell/test/chrome/bug846906.html");
+ };
+ document.documentElement.appendChild(iframe);
+
+ ]]>
+ </script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=846906"
+ target="_blank">Mozilla Bug 846906</a>
+ </body>
+</window>
diff --git a/docshell/test/chrome/test_bug89419.xhtml b/docshell/test/chrome/test_bug89419.xhtml
new file mode 100644
index 0000000000..848982180f
--- /dev/null
+++ b/docshell/test/chrome/test_bug89419.xhtml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=89419.xul
+-->
+<window title="Mozilla Bug 89419"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=89419">
+ Mozilla Bug 89419</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 89419 */
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug89419_window.xhtml", "bug89419",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_bug909218.html b/docshell/test/chrome/test_bug909218.html
new file mode 100644
index 0000000000..bcbcc176eb
--- /dev/null
+++ b/docshell/test/chrome/test_bug909218.html
@@ -0,0 +1,117 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(test);
+
+// The default flags we will stick on the docShell - every request made by the
+// docShell should include those flags.
+const TEST_FLAGS = Ci.nsIRequest.LOAD_ANONYMOUS |
+ Ci.nsIRequest.LOAD_BYPASS_CACHE |
+ Ci.nsIRequest.INHIBIT_CACHING;
+
+var TEST_URL = "http://mochi.test:8888/chrome/docshell/test/chrome/bug909218.html";
+
+// These are the requests we expect to see loading TEST_URL into our iframe.
+
+// The test entry-point. The basic outline is:
+// * Create an iframe and set defaultLoadFlags on its docShell.
+// * Add a web progress listener to observe each request as the iframe is
+// loaded, and check that each request has the flags we specified.
+// * Load our test URL into the iframe and wait for the load to complete.
+function test() {
+ var iframe = document.createElement("iframe");
+ document.body.appendChild(iframe);
+ var docShell = iframe.contentWindow.docShell;
+ // Add our progress listener - when it notices the top-level document is
+ // complete, the test will end.
+ RequestWatcher.init(docShell, SimpleTest.finish);
+ // Set the flags we care about, then load our test URL.
+ docShell.defaultLoadFlags = TEST_FLAGS;
+ iframe.setAttribute("src", TEST_URL);
+}
+
+// an nsIWebProgressListener that checks all requests made by the docShell
+// have the flags we expect.
+var RequestWatcher = {
+ init(docShell, callback) {
+ this.callback = callback;
+ this.docShell = docShell;
+ docShell.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebProgress).
+ addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_REQUEST |
+ Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
+ // These are the requests we expect to see - initialize each to have a
+ // count of zero.
+ this.requestCounts = {};
+ for (var url of [
+ TEST_URL,
+ // content loaded by the above test html.
+ "http://mochi.test:8888/chrome/docshell/test/chrome/bug909218.js",
+ "http://mochi.test:8888/tests/SimpleTest/test.css",
+ "http://mochi.test:8888/tests/docshell/test/chrome/red.png",
+ // the content of an iframe in the test html.
+ "http://mochi.test:8888/chrome/docshell/test/chrome/generic.html",
+ ]) {
+ this.requestCounts[url] = 0;
+ }
+ },
+
+ // Finalize the test after we detect a completed load. We check we saw the
+ // correct requests and make a callback to exit.
+ finalize() {
+ ok(Object.keys(this.requestCounts).length, "we expected some requests");
+ for (var url in this.requestCounts) {
+ var count = this.requestCounts[url];
+ // As we are looking at all request states, we expect more than 1 for
+ // each URL - 0 or 1 would imply something went wrong - >1 just means
+ // multiple states for each request were recorded, which we don't care
+ // about (we do care they all have the correct flags though - but we
+ // do that in onStateChange)
+ ok(count > 1, url + " saw " + count + " requests");
+ }
+ this.docShell.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebProgress).
+ removeProgressListener(this);
+ this.callback();
+ },
+
+ onStateChange(webProgress, req, flags, status) {
+ // We are checking requests - if there isn't one, ignore it.
+ if (!req) {
+ return;
+ }
+ // We will usually see requests for 'about:document-onload-blocker' not
+ // have the flag, so we just ignore them.
+ // We also see, eg, resource://gre-resources/loading-image.png, so
+ // skip resource:// URLs too.
+ // We may also see, eg, chrome://global/skin/icons/chevron.svg, so
+ // skip chrome:// URLs too.
+ if (req.name.startsWith("about:") || req.name.startsWith("resource:") ||
+ req.name.startsWith("chrome:") || req.name.startsWith("documentchannel:")) {
+ return;
+ }
+ is(req.loadFlags & TEST_FLAGS, TEST_FLAGS, "request " + req.name + " has the expected flags");
+ this.requestCounts[req.name] += 1;
+ var stopFlags = Ci.nsIWebProgressListener.STATE_STOP |
+ Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
+ if (req.name == TEST_URL && (flags & stopFlags) == stopFlags) {
+ this.finalize();
+ }
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+};
+
+</script>
+</head>
+</html>
diff --git a/docshell/test/chrome/test_bug92598.xhtml b/docshell/test/chrome/test_bug92598.xhtml
new file mode 100644
index 0000000000..1b89e3eeb3
--- /dev/null
+++ b/docshell/test/chrome/test_bug92598.xhtml
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=92598
+-->
+<window title="Mozilla Bug 92598"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=92598">Mozilla Bug 92598</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+/** Test for Bug 92598 */
+
+SimpleTest.waitForExplicitFinish();
+window.openDialog("bug92598_window.xhtml", "bug92598",
+ "chrome,width=600,height=600,noopener", window);
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_docRedirect.sjs b/docshell/test/chrome/test_docRedirect.sjs
new file mode 100644
index 0000000000..4050eb06d7
--- /dev/null
+++ b/docshell/test/chrome/test_docRedirect.sjs
@@ -0,0 +1,6 @@
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 301, "Moved Permanently");
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ response.setHeader("Location", "http://example.org/");
+ response.write("Hello world!");
+}
diff --git a/docshell/test/chrome/test_docRedirect.xhtml b/docshell/test/chrome/test_docRedirect.xhtml
new file mode 100644
index 0000000000..1688d9823e
--- /dev/null
+++ b/docshell/test/chrome/test_docRedirect.xhtml
@@ -0,0 +1,91 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1342989
+-->
+<window title="Mozilla Bug 1342989"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <script type="application/javascript">
+ <![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ const WEB_PROGRESS_LISTENER_FLAGS =
+ Object.keys(Ci.nsIWebProgressListener).filter(
+ propName => propName.startsWith("STATE_")
+ );
+
+ function bitFlagsToNames(flags, knownNames, intf) {
+ return knownNames.map( (F) => {
+ return (flags & intf[F]) ? F : undefined;
+ }).filter( (s) => !!s );
+ }
+
+ var progressListener = {
+ add(docShell, callback) {
+ this.callback = callback;
+ this.docShell = docShell;
+ docShell.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebProgress).
+ addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_ALL);
+ },
+
+ finish(success) {
+ this.docShell.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebProgress).
+ removeProgressListener(this);
+ this.callback(success);
+ },
+
+ onStateChange (webProgress, req, flags, status) {
+ if (!(flags & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT) &&
+ !(flags & Ci.nsIWebProgressListener.STATE_IS_REDIRECTED_DOCUMENT))
+ return;
+
+ var channel = req.QueryInterface(Ci.nsIChannel);
+
+ if (flags & Ci.nsIWebProgressListener.STATE_IS_REDIRECTED_DOCUMENT) {
+ SimpleTest.is(channel.URI.host, "example.org",
+ "Should be redirected to example.org (see test_docRedirect.sjs)");
+ this.finish(true);
+ }
+
+ // Fail in case we didn't receive document redirection event.
+ if (flags & Ci.nsIWebProgressListener.STATE_STOP)
+ this.finish(false);
+ },
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+ }
+
+ var webNav = SpecialPowers.Services.appShell.createWindowlessBrowser(true);
+ let docShell = webNav.docShell;
+ let system = Cc["@mozilla.org/systemprincipal;1"].getService(Ci.nsIPrincipal);
+ docShell.createAboutBlankContentViewer(system, system);
+
+ progressListener.add(docShell, function(success) {
+ webNav.close();
+ SimpleTest.is(success, true, "Received document redirect event");
+ SimpleTest.finish();
+ });
+
+ var win = docShell.contentViewer.DOMDocument.defaultView;
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ win.location = "http://example.com/chrome/docshell/test/chrome/test_docRedirect.sjs"
+
+ ]]>
+ </script>
+
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1342989"
+ target="_blank">Mozilla Bug 1342989</a>
+ </body>
+</window>
diff --git a/docshell/test/chrome/test_mozFrameType.xhtml b/docshell/test/chrome/test_mozFrameType.xhtml
new file mode 100644
index 0000000000..f9740a761b
--- /dev/null
+++ b/docshell/test/chrome/test_mozFrameType.xhtml
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet
+ href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=769771
+-->
+<window title="Test mozFrameType attribute"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+if (navigator.platform.startsWith("Win")) {
+ SimpleTest.expectAssertions(0, 1);
+}
+
+/** Test for Bug 769771 */
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function () {
+ window.openDialog("mozFrameType_window.xhtml", "mozFrameType",
+ "chrome,width=600,height=600,noopener", window);
+});
+
+]]>
+</script>
+
+</window>
diff --git a/docshell/test/chrome/test_open_and_immediately_close_opener.html b/docshell/test/chrome/test_open_and_immediately_close_opener.html
new file mode 100644
index 0000000000..bb5f6f054d
--- /dev/null
+++ b/docshell/test/chrome/test_open_and_immediately_close_opener.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for Bug 1702678</title>
+ <meta charset="utf-8">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1702678">Mozilla Bug 1702678</a>
+
+<script type="application/javascript">
+"use strict";
+
+const HTML = `
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script>
+ // We need to queue a promise reaction job whilch will close the window
+ // during the nested event loop spun up by the window opening code.
+ Promise.resolve().then(() => {
+ window.close();
+ });
+ window.open("data:text/html,Hello");
+ <\/script>
+</head>
+</html>
+`;
+
+add_task(async function() {
+ // This bug only manifests when opening tabs in new windows.
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.link.open_newwindow", 2]],
+ });
+
+ // Create a window in a new BrowsingContextGroup so that it will be the last
+ // window in the group when it closes, and the group will be destroyed.
+ window.open(`data:text/html,${encodeURIComponent(HTML)}`, "", "noopener");
+
+ // Make a few trips through the event loop to ensure we've had a chance to
+ // open and close the relevant windows.
+ for (let i = 0; i < 10; i++) {
+ await new Promise(resolve => setTimeout(resolve, 0));
+ }
+
+ ok(true, "We didn't crash");
+});
+</script>
+
+</body>
+</html>
+
diff --git a/docshell/test/chrome/test_viewsource_forbidden_in_iframe.xhtml b/docshell/test/chrome/test_viewsource_forbidden_in_iframe.xhtml
new file mode 100644
index 0000000000..0cc45c7821
--- /dev/null
+++ b/docshell/test/chrome/test_viewsource_forbidden_in_iframe.xhtml
@@ -0,0 +1,159 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin/global.css"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=624883
+-->
+<window title="Mozilla Bug 624883"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=624883"
+ target="_blank">Mozilla Bug 624883</a>
+ </body>
+
+ <!-- test code goes here -->
+ <iframe type="content" onload="startTest()" src="file_viewsource_forbidden_in_iframe.html"></iframe>
+
+ <script type="application/javascript">
+ <![CDATA[
+
+ SimpleTest.waitForExplicitFinish();
+
+ // We create a promise that will resolve with the error message
+ // on a network error page load and reject on any other load.
+ function createNetworkErrorMessagePromise(frame) {
+ return new Promise(function(resolve, reject) {
+
+ // Error pages do not fire "load" events, so use a progressListener.
+ var originalDocumentURI = frame.contentDocument.documentURI;
+ var progressListener = {
+ onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+ // Make sure nothing other than an error page is loaded.
+ if (!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE)) {
+ reject("location change was not to an error page");
+ }
+ },
+
+ onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+ // Wait until the documentURI changes (from about:blank) this should
+ // be the error page URI.
+ var documentURI = frame.contentDocument.documentURI;
+ if (documentURI == originalDocumentURI) {
+ return;
+ }
+
+ aWebProgress.removeProgressListener(progressListener,
+ Ci.nsIWebProgress.NOTIFY_ALL);
+ var matchArray = /about:neterror\?.*&d=([^&]*)/.exec(documentURI);
+ if (!matchArray) {
+ reject("no network error message found in URI")
+ return;
+ }
+
+ var errorMsg = matchArray[1];
+ resolve(decodeURIComponent(errorMsg));
+ },
+
+ QueryInterface: ChromeUtils.generateQI(["nsIWebProgressListener",
+ "nsISupportsWeakReference"])
+ };
+
+ frame.contentWindow.docShell
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebProgress)
+ .addProgressListener(progressListener,
+ Ci.nsIWebProgress.NOTIFY_LOCATION |
+ Ci.nsIWebProgress.NOTIFY_STATE_REQUEST);
+ });
+ }
+
+ function startTest() {
+ // Get a reference message that we know will be an unknown protocol message,
+ // so we can use it for comparisons in the test cases.
+ var refIframe = window[0].document.getElementById("refIframe");
+ var refErrorPromise = createNetworkErrorMessagePromise(refIframe);
+
+ refErrorPromise.then(
+ function(msg) {
+ window.refErrorMsg = msg;
+ var testIframe = window[0].document.getElementById("testIframe");
+
+ // Run test cases on load of "about:blank", so that the URI always changes
+ // and we can detect this in our Promise.
+ testIframe.onload = runNextTestCase;
+ testIframe.src = "about:blank";
+ },
+ function(reason) {
+ ok(false, "Could not get reference error message", reason);
+ SimpleTest.finish();
+ })
+ .catch(function(e) {
+ ok(false, "Unexpected exception thrown getting reference error message", e);
+ });
+
+ refIframe.src = "wibble://example.com";
+ }
+
+ function runTestCase(testCase) {
+ var testIframe = window[0].document.getElementById("testIframe");
+ var expectedErrorMsg = window.refErrorMsg.replace("wibble", testCase.expectedProtocolList);
+
+ var testErrorPromise = createNetworkErrorMessagePromise(testIframe);
+ testErrorPromise.then(
+ function(actualErrorMsg) {
+ is(actualErrorMsg, expectedErrorMsg, testCase.desc);
+ testIframe.src = "about:blank";
+ },
+ function(reason) {
+ ok(false, testCase.desc, reason);
+ testIframe.src = "about:blank";
+ })
+ .catch(function(e) {
+ ok(false, testCase.desc + " - unexpected exception thrown", e);
+ });
+
+ testIframe.src = testCase.protocols + "://example.com/!/";
+ }
+
+ var testCaseIndex = -1;
+ let testCases = [
+ {
+ desc: "Test 1: view-source should not be allowed in an iframe",
+ protocols: "view-source:http",
+ expectedProtocolList: "view-source, http"
+ },
+ {
+ desc: "Test 2: jar:view-source should not be allowed in an iframe",
+ protocols: "jar:view-source:http",
+ expectedProtocolList: "jar, view-source, http"
+ },
+ {
+ desc: "Test 3: if invalid protocol first should report before view-source",
+ protocols: "wibble:view-source:http",
+ // Nothing after the invalid protocol gets set as a proper nested URI,
+ // so the list stops there.
+ expectedProtocolList: "wibble"
+ },
+ {
+ desc: "Test 4: if view-source first should report before invalid protocol",
+ protocols: "view-source:wibble:http",
+ expectedProtocolList: "view-source, wibble"
+ }
+ ];
+
+ function runNextTestCase() {
+ ++testCaseIndex;
+ if (testCaseIndex == testCases.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ runTestCase(testCases[testCaseIndex]);
+ }
+
+ ]]>
+ </script>
+</window>
diff --git a/docshell/test/chrome/window.template.txt b/docshell/test/chrome/window.template.txt
new file mode 100644
index 0000000000..4c520dc075
--- /dev/null
+++ b/docshell/test/chrome/window.template.txt
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window id="{BUGNUMBER}Test"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ width="600"
+ height="600"
+ onload="setTimeout(nextTest,0);"
+ title="bug {BUGNUMBER} test">
+
+ <script type="application/javascript"
+ src="docshell_helpers.js">
+ </script>
+
+ <script type="application/javascript"><![CDATA[
+
+ // Define the generator-iterator for the tests.
+ var tests = testIterator();
+
+ ////
+ // Execute the next test in the generator function.
+ //
+ function nextTest() {
+ tests.next();
+ }
+
+ ////
+ // Generator function for test steps for bug {BUGNUMBER}:
+ // Description goes here.
+ //
+ function testIterator()
+ {
+ // Test steps go here. See bug303267_window.xhtml for an example.
+
+ // Tell the framework the test is finished. Include the final 'yield'
+ // statement to prevent a StopIteration exception from being thrown.
+ finish();
+ yield undefined;
+ }
+
+ ]]></script>
+
+ <browser type="content" primary="true" flex="1" id="content" src="about:blank"/>
+</window>
diff --git a/docshell/test/iframesandbox/file_child_navigation_by_location.html b/docshell/test/iframesandbox/file_child_navigation_by_location.html
new file mode 100644
index 0000000000..7365bed81f
--- /dev/null
+++ b/docshell/test/iframesandbox/file_child_navigation_by_location.html
@@ -0,0 +1 @@
+<script>function onNav() { parent.parent.postMessage("childIframe", "*"); } window.onload = onNav; window.onhashchange = onNav;</script>
diff --git a/docshell/test/iframesandbox/file_marquee_event_handlers.html b/docshell/test/iframesandbox/file_marquee_event_handlers.html
new file mode 100644
index 0000000000..13ee31ddb7
--- /dev/null
+++ b/docshell/test/iframesandbox/file_marquee_event_handlers.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test marquee attribute event handlers in iframe sandbox</title>
+</head>
+<body>
+ <!-- Note that the width here is slightly longer than the contents, to make
+ sure we bounce and finish very quickly. -->
+ <marquee loop="2" width="145" behavior="alternate" truespeed scrolldelay="1"
+ onstart="parent.postMessage(window.name + ' marquee onstart', '*');"
+ onbounce="parent.postMessage(window.name + ' marquee onbounce', '*');"
+ onfinish="parent.postMessage(window.name + ' marquee onfinish', '*');">
+ Will bounce and finish
+ </marquee>
+</body>
+</html>
diff --git a/docshell/test/iframesandbox/file_other_auxiliary_navigation_by_location.html b/docshell/test/iframesandbox/file_other_auxiliary_navigation_by_location.html
new file mode 100644
index 0000000000..ad24c0f242
--- /dev/null
+++ b/docshell/test/iframesandbox/file_other_auxiliary_navigation_by_location.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test window for other auxiliary navigation by location tests</title>
+<script>
+ function onNav() {
+ opener.postMessage(window.name, "*");
+ }
+
+ window.onload = onNav;
+ window.onhashchange = onNav;
+</script>
+</head>
+</html>
diff --git a/docshell/test/iframesandbox/file_our_auxiliary_navigation_by_location.html b/docshell/test/iframesandbox/file_our_auxiliary_navigation_by_location.html
new file mode 100644
index 0000000000..978980df25
--- /dev/null
+++ b/docshell/test/iframesandbox/file_our_auxiliary_navigation_by_location.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test window for our auxiliary navigation by location tests</title>
+<script>
+ function onNav() {
+ opener.parent.postMessage(window.name, "*");
+ }
+
+ window.onload = onNav;
+ window.onhashchange = onNav;
+</script>
+</head>
+</html>
diff --git a/docshell/test/iframesandbox/file_parent_navigation_by_location.html b/docshell/test/iframesandbox/file_parent_navigation_by_location.html
new file mode 100644
index 0000000000..9a2e95fad0
--- /dev/null
+++ b/docshell/test/iframesandbox/file_parent_navigation_by_location.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test window for parent navigation by location tests</title>
+<script>
+ function onNav() {
+ parent.postMessage(window.name, "*");
+ }
+
+ window.onload = onNav;
+ window.onhashchange = onNav;
+</script>
+</head>
+<body>
+ <iframe name="childIframe" sandbox="allow-scripts allow-same-origin allow-top-navigation"></iframe>
+</body>
+</html>
diff --git a/docshell/test/iframesandbox/file_sibling_navigation_by_location.html b/docshell/test/iframesandbox/file_sibling_navigation_by_location.html
new file mode 100644
index 0000000000..51a52bb8eb
--- /dev/null
+++ b/docshell/test/iframesandbox/file_sibling_navigation_by_location.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test window for sibling navigation by location tests</title>
+<script>
+ function onNav() {
+ parent.postMessage(window.name, "*");
+ }
+
+ window.onload = onNav;
+ window.onhashchange = onNav;
+</script>
+</head>
+</html>
diff --git a/docshell/test/iframesandbox/file_top_navigation_by_location.html b/docshell/test/iframesandbox/file_top_navigation_by_location.html
new file mode 100644
index 0000000000..194430f38c
--- /dev/null
+++ b/docshell/test/iframesandbox/file_top_navigation_by_location.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test window for top navigation by location tests</title>
+<script>
+ function onNav() {
+ opener.postMessage(window.name, "*");
+ }
+
+ window.onload = onNav;
+ window.onhashchange = onNav;
+</script>
+</head>
+<body>
+ <iframe name="if1" sandbox="allow-scripts allow-same-origin"></iframe>
+ <iframe name="if2" sandbox="allow-scripts allow-same-origin allow-top-navigation"></iframe>
+ <iframe name="if3" sandbox="allow-scripts allow-top-navigation"></iframe>
+</body>
+</html>
diff --git a/docshell/test/iframesandbox/file_top_navigation_by_location_exotic.html b/docshell/test/iframesandbox/file_top_navigation_by_location_exotic.html
new file mode 100644
index 0000000000..9a26bc80db
--- /dev/null
+++ b/docshell/test/iframesandbox/file_top_navigation_by_location_exotic.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test window for top navigation by location tests</title>
+<script>
+ function onBlock() {
+ opener.postMessage({ name: window.name, blocked: true }, "*");
+ }
+
+ function onNav() {
+ opener.postMessage({ name: window.name, blocked: false }, "*");
+ }
+
+ function setOwnHref() {
+ // eslint-disable-next-line no-self-assign
+ location.href = location.href;
+ }
+
+ window.onload = onNav;
+</script>
+</head>
+<body>
+ <iframe name="if1" sandbox="allow-scripts allow-same-origin"></iframe>
+ <iframe name="if2" sandbox="allow-scripts allow-same-origin allow-top-navigation"></iframe>
+</body>
+</html>
diff --git a/docshell/test/iframesandbox/file_top_navigation_by_user_activation.html b/docshell/test/iframesandbox/file_top_navigation_by_user_activation.html
new file mode 100644
index 0000000000..8454f1fac4
--- /dev/null
+++ b/docshell/test/iframesandbox/file_top_navigation_by_user_activation.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test window for top navigation with user activation</title>
+<script>
+window.onload = () => {
+ opener.postMessage("READY", "*");
+};
+
+window.onhashchange = () => {
+ opener.postMessage("NAVIGATED", "*");
+};
+
+window.onmessage = (e) => {
+ if (e.data == "CLICK" || e.data == "SCRIPT") {
+ frames[0].postMessage([e.data, location.href + "#hash"], "*");
+ } else {
+ opener.postMessage(e.data, "*");
+ }
+};
+</script>
+</head>
+<body>
+ <iframe sandbox="allow-scripts allow-top-navigation-by-user-activation" src="http://example.org/tests/docshell/test/iframesandbox/file_top_navigation_by_user_activation_iframe.html"></iframe>
+</body>
+</html>
diff --git a/docshell/test/iframesandbox/file_top_navigation_by_user_activation_iframe.html b/docshell/test/iframesandbox/file_top_navigation_by_user_activation_iframe.html
new file mode 100644
index 0000000000..b775579f28
--- /dev/null
+++ b/docshell/test/iframesandbox/file_top_navigation_by_user_activation_iframe.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<title>Test window for top navigation with user activation</title>
+<script>
+function navigate(aURL) {
+ try {
+ top.location.href = aURL;
+ } catch (e) {
+ top.postMessage("BLOCKED", "*");
+ }
+}
+
+window.onmessage = (e) => {
+ SpecialPowers.wrap(document).clearUserGestureActivation();
+ let [command, url] = e.data;
+ if (command == "CLICK") {
+ let button = document.querySelector("button");
+ button.addEventListener("click", () => {
+ navigate(url);
+ }, { once: true });
+ synthesizeMouseAtCenter(button, {});
+ } else if (command == "SCRIPT") {
+ navigate(url);
+ }
+};
+</script>
+</head>
+<body><button>Click</button></body>
+</html>
diff --git a/docshell/test/iframesandbox/mochitest.ini b/docshell/test/iframesandbox/mochitest.ini
new file mode 100644
index 0000000000..ee5a668fcc
--- /dev/null
+++ b/docshell/test/iframesandbox/mochitest.ini
@@ -0,0 +1,30 @@
+[DEFAULT]
+support-files =
+ file_child_navigation_by_location.html
+ file_marquee_event_handlers.html
+ file_other_auxiliary_navigation_by_location.html
+ file_our_auxiliary_navigation_by_location.html
+ file_parent_navigation_by_location.html
+ file_sibling_navigation_by_location.html
+ file_top_navigation_by_location.html
+ file_top_navigation_by_location_exotic.html
+
+[test_child_navigation_by_location.html]
+[test_marquee_event_handlers.html]
+skip-if = true # Bug 1455996
+[test_other_auxiliary_navigation_by_location.html]
+tags = openwindow
+[test_our_auxiliary_navigation_by_location.html]
+tags = openwindow
+[test_parent_navigation_by_location.html]
+tags = openwindow
+[test_sibling_navigation_by_location.html]
+tags = openwindow
+[test_top_navigation_by_location_exotic.html]
+[test_top_navigation_by_location.html]
+[test_top_navigation_by_user_activation.html]
+support-files =
+ file_top_navigation_by_user_activation.html
+ file_top_navigation_by_user_activation_iframe.html
+skip-if =
+ http3
diff --git a/docshell/test/iframesandbox/test_child_navigation_by_location.html b/docshell/test/iframesandbox/test_child_navigation_by_location.html
new file mode 100644
index 0000000000..383320a02b
--- /dev/null
+++ b/docshell/test/iframesandbox/test_child_navigation_by_location.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=785310
+html5 sandboxed iframe should not be able to perform top navigation with scripts allowed
+-->
+<head>
+<meta charset="utf-8">
+<title>Test for Bug 785310 - iframe sandbox child navigation by location tests</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ var testDataUri = "file_child_navigation_by_location.html";
+
+ function runScriptNavigationTest(testCase) {
+ window.onmessage = function(event) {
+ if (event.data != "childIframe") {
+ ok(false, "event.data: got '" + event.data + "', expected 'childIframe'");
+ }
+ ok(!testCase.shouldBeBlocked, testCase.desc + " - child navigation was NOT blocked");
+ runNextTest();
+ };
+ try {
+ window.parentIframe.eval(testCase.script);
+ } catch (e) {
+ ok(testCase.shouldBeBlocked, testCase.desc + " - " + e.message);
+ runNextTest();
+ }
+ }
+
+ var testCaseIndex = -1;
+ var testCases = [
+ {
+ desc: "Test 1: cross origin child location.replace should NOT be blocked",
+ script: "window['crossOriginChildIframe'].location.replace(\"" + testDataUri + "\")",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 2: cross origin child location.assign should be blocked",
+ script: "window['crossOriginChildIframe'].location.assign(\"" + testDataUri + "\")",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 3: same origin child location.assign should NOT be blocked",
+ script: "window['sameOriginChildIframe'].location.assign(\"" + testDataUri + "\")",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 4: cross origin child location.href should NOT be blocked",
+ script: "window['crossOriginChildIframe'].location.href = \"" + testDataUri + "\"",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 5: cross origin child location.hash should be blocked",
+ script: "window['crossOriginChildIframe'].location.hash = 'wibble'",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 6: same origin child location.hash should NOT be blocked",
+ script: "window['sameOriginChildIframe'].location.hash = 'wibble'",
+ shouldBeBlocked: false,
+ },
+ ];
+
+ function runNextTest() {
+ ++testCaseIndex;
+ if (testCaseIndex == testCases.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ runScriptNavigationTest(testCases[testCaseIndex]);
+ }
+
+ addLoadEvent(runNextTest);
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=785310">Mozilla Bug 785310</a>
+<p id="display"></p>
+<div id="content">
+Tests for Bug 785310
+</div>
+
+<iframe name="parentIframe" sandbox="allow-scripts allow-same-origin" srcdoc="<iframe name='sameOriginChildIframe'></iframe><iframe name='crossOriginChildIframe' sandbox='allow-scripts'></iframe>"</iframe>
+
+</body>
+</html>
diff --git a/docshell/test/iframesandbox/test_marquee_event_handlers.html b/docshell/test/iframesandbox/test_marquee_event_handlers.html
new file mode 100644
index 0000000000..80added8ab
--- /dev/null
+++ b/docshell/test/iframesandbox/test_marquee_event_handlers.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1277475
+html5 sandboxed iframe should not run marquee attribute event handlers without allow-scripts
+-->
+<head>
+<meta charset="utf-8">
+<title>Test for Bug 1277475 - html5 sandboxed iframe should not run marquee attribute event handlers without allow-scripts</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1277475">Mozilla Bug 1277475</a>
+<p id="display"></p>
+<div id="content">Tests for Bug 1277475</div>
+
+<iframe id="if1" name="if1" src="file_marquee_event_handlers.html"
+ sandbox="allow-same-origin allow-forms allow-top-navigation allow-pointer-lock allow-orientation-lock allow-popups allow-modals allow-popups-to-escape-sandbox">
+</iframe>
+
+<iframe id="if2" name="if2" src="file_marquee_event_handlers.html"
+ sandbox="allow-scripts"></iframe>
+
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ var expectedMessages = new Set();
+ var numberOfMessagesExpected = 4;
+ var unexpectedMessages = new Set();
+
+ window.onmessage = function(event) {
+ info(event.data + " message received");
+ if (event.data.startsWith("if2") || event.data == "if1 chaser") {
+ expectedMessages.add(event.data);
+ if (expectedMessages.size == numberOfMessagesExpected) {
+ checkRecievedMessages();
+ }
+ } else {
+ unexpectedMessages.add(event.data);
+ }
+ };
+
+ function checkRecievedMessages() {
+ // Check the expected messages explicitly as a cross-check.
+ ok(expectedMessages.has("if1 chaser"),
+ "if1 chaser message should have been received");
+ ok(expectedMessages.has("if2 marquee onstart"),
+ "if2 marquee onstart should have run in iframe sandbox with allow-scripts");
+ ok(expectedMessages.has("if2 marquee onbounce"),
+ "if2 marquee onbounce should have run in iframe sandbox with allow-scripts");
+ ok(expectedMessages.has("if2 marquee onfinish"),
+ "if2 marquee onfinish should have run in iframe sandbox with allow-scripts");
+
+ unexpectedMessages.forEach(
+ (v) => {
+ ok(false, v + " should NOT have run in iframe sandbox without allow-scripts");
+ }
+ );
+
+ SimpleTest.finish();
+ }
+
+ // If things are working properly the attribute event handlers won't run on
+ // the marquee in if1, so add our own capturing listeners on its window, so we
+ // know when they have fired. (These will run as we are not sandboxed.)
+ var if1FiredEvents = new Set();
+ var if1NumberOfEventsExpected = 3;
+ var if1Win = document.getElementById("if1").contentWindow;
+ if1Win.addEventListener("start", () => { checkMarqueeEvent("start"); }, true);
+ if1Win.addEventListener("bounce", () => { checkMarqueeEvent("bounce"); }, true);
+ if1Win.addEventListener("finish", () => { checkMarqueeEvent("finish"); }, true);
+
+ function checkMarqueeEvent(eventType) {
+ info("if1 event " + eventType + " fired");
+ if1FiredEvents.add(eventType);
+ if (if1FiredEvents.size == if1NumberOfEventsExpected) {
+ // Only send the chasing message after a tick of the event loop to allow
+ // event handlers on the marquee to process.
+ SimpleTest.executeSoon(sendChasingMessage);
+ }
+ }
+
+ function sendChasingMessage() {
+ // Add our own message listener to if1's window and echo back a chasing
+ // message to make sure that any messages from incorrectly run marquee
+ // attribute event handlers should have arrived before it.
+ if1Win.addEventListener("message",
+ (e) => { if1Win.parent.postMessage(e.data, "*"); });
+ if1Win.postMessage("if1 chaser", "*");
+ info("if1 chaser message sent");
+ }
+</script>
+</body>
+</html>
diff --git a/docshell/test/iframesandbox/test_other_auxiliary_navigation_by_location.html b/docshell/test/iframesandbox/test_other_auxiliary_navigation_by_location.html
new file mode 100644
index 0000000000..3440878db7
--- /dev/null
+++ b/docshell/test/iframesandbox/test_other_auxiliary_navigation_by_location.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=785310
+html5 sandboxed iframe should not be able to perform top navigation with scripts allowed
+-->
+<head>
+<meta charset="utf-8">
+<title>Test for Bug 785310 - iframe sandbox other auxiliary navigation by location tests</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ function runScriptNavigationTest(testCase) {
+ window.onmessage = function(event) {
+ if (event.data != "otherWindow") {
+ ok(false, "event.data: got '" + event.data + "', expected 'otherWindow'");
+ }
+ ok(false, testCase.desc + " - auxiliary navigation was NOT blocked");
+ runNextTest();
+ };
+ try {
+ window.testIframe.eval(testCase.script);
+ } catch (e) {
+ ok(true, testCase.desc + " - " + e.message);
+ runNextTest();
+ }
+ }
+
+ var testCaseIndex = -1;
+ var testCases = [
+ {
+ desc: "Test 1: location.replace on auxiliary NOT opened by us should be blocked",
+ script: "parent.openedWindow.location.replace('file_other_auxiliary_navigation_by_location.html')",
+ },
+ {
+ desc: "Test 2: location.assign on auxiliary NOT opened by us should be blocked",
+ script: "parent.openedWindow.location.assign('file_other_auxiliary_navigation_by_location.html')",
+ },
+ {
+ desc: "Test 3: location.href on auxiliary NOT opened by us should be blocked",
+ script: "parent.openedWindow.location.href = 'file_other_auxiliary_navigation_by_location.html'",
+ },
+ {
+ desc: "Test 4: location.hash on auxiliary NOT opened by us should be blocked",
+ script: "parent.openedWindow.location.hash = 'wibble'",
+ },
+ ];
+
+ function runNextTest() {
+ ++testCaseIndex;
+ if (testCaseIndex == testCases.length) {
+ window.openedWindow.close();
+ SimpleTest.finish();
+ return;
+ }
+
+ runScriptNavigationTest(testCases[testCaseIndex]);
+ }
+
+ window.onmessage = runNextTest;
+
+ window.onload = function() {
+ window.openedWindow = window.open("file_other_auxiliary_navigation_by_location.html", "otherWindow");
+ };
+</script>
+
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=785310">Mozilla Bug 785310</a>
+<p id="display"></p>
+<div id="content">
+Tests for Bug 785310
+</div>
+
+<iframe name="testIframe" sandbox="allow-scripts allow-same-origin allow-top-navigation allow-popups"></iframe>
+</body>
+</html>
diff --git a/docshell/test/iframesandbox/test_our_auxiliary_navigation_by_location.html b/docshell/test/iframesandbox/test_our_auxiliary_navigation_by_location.html
new file mode 100644
index 0000000000..1719f566a5
--- /dev/null
+++ b/docshell/test/iframesandbox/test_our_auxiliary_navigation_by_location.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=785310
+html5 sandboxed iframe should not be able to perform top navigation with scripts allowed
+-->
+<head>
+<meta charset="utf-8">
+<title>Test for Bug 785310 - iframe sandbox our auxiliary navigation by location tests</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ function runScriptNavigationTest(testCase) {
+ window.onmessage = function(event) {
+ if (event.data != "ourWindow") {
+ ok(false, "event.data: got '" + event.data + "', expected 'ourWindow'");
+ }
+ ok(!testCase.shouldBeBlocked, testCase.desc + " - auxiliary navigation was NOT blocked");
+ runNextTest();
+ };
+ try {
+ SpecialPowers.wrap(window.testIframe).eval(testCase.script);
+ } catch (e) {
+ ok(testCase.shouldBeBlocked, testCase.desc + " - " + SpecialPowers.wrap(e).message);
+ runNextTest();
+ }
+ }
+
+ var testCaseIndex = -1;
+ var testCases = [
+ {
+ desc: "Test 1: location.replace on auxiliary opened by us should NOT be blocked",
+ script: "openedWindow.location.replace('file_our_auxiliary_navigation_by_location.html')",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 2: location.assign on auxiliary opened by us should be blocked without allow-same-origin",
+ script: "openedWindow.location.assign('file_our_auxiliary_navigation_by_location.html')",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 3: location.href on auxiliary opened by us should NOT be blocked",
+ script: "openedWindow.location.href = 'file_our_auxiliary_navigation_by_location.html'",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 4: location.hash on auxiliary opened by us should be blocked without allow-same-origin",
+ script: "openedWindow.location.hash = 'wibble'",
+ shouldBeBlocked: true,
+ },
+ ];
+
+ function runNextTest() {
+ ++testCaseIndex;
+ if (testCaseIndex == testCases.length) {
+ SpecialPowers.wrap(window.testIframe).eval("openedWindow.close()");
+ SimpleTest.finish();
+ return;
+ }
+
+ runScriptNavigationTest(testCases[testCaseIndex]);
+ }
+
+ window.onmessage = runNextTest;
+
+ window.onload = function() {
+ SpecialPowers.wrap(window.testIframe).eval("var openedWindow = window.open('file_our_auxiliary_navigation_by_location.html', 'ourWindow')");
+ };
+</script>
+
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=785310">Mozilla Bug 785310</a>
+<p id="display"></p>
+<div id="content">
+Tests for Bug 785310
+</div>
+
+<iframe name="testIframe" sandbox="allow-scripts allow-popups"></iframe>
+</body>
+</html>
diff --git a/docshell/test/iframesandbox/test_parent_navigation_by_location.html b/docshell/test/iframesandbox/test_parent_navigation_by_location.html
new file mode 100644
index 0000000000..ac6977a3f3
--- /dev/null
+++ b/docshell/test/iframesandbox/test_parent_navigation_by_location.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=785310
+html5 sandboxed iframe should not be able to perform top navigation with scripts allowed
+-->
+<head>
+<meta charset="utf-8">
+<title>Test for Bug 785310 - iframe sandbox parent navigation by location tests</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ function runScriptNavigationTest(testCase) {
+ window.onmessage = function(event) {
+ if (event.data != "parentIframe") {
+ ok(false, "event.data: got '" + event.data + "', expected 'parentIframe'");
+ }
+ ok(false, testCase.desc + " - parent navigation was NOT blocked");
+ runNextTest();
+ };
+ try {
+ window.parentIframe.childIframe.eval(testCase.script);
+ } catch (e) {
+ ok(true, testCase.desc + " - " + e.message);
+ runNextTest();
+ }
+ }
+
+ var testCaseIndex = -1;
+ var testCases = [
+ {
+ desc: "Test 1: parent.location.replace should be blocked even when sandboxed with allow-same-origin allow-top-navigation",
+ script: "parent.location.replace('file_parent_navigation_by_location.html')",
+ },
+ {
+ desc: "Test 2: parent.location.assign should be blocked even when sandboxed with allow-same-origin allow-top-navigation",
+ script: "parent.location.assign('file_parent_navigation_by_location.html')",
+ },
+ {
+ desc: "Test 3: parent.location.href should be blocked even when sandboxed with allow-same-origin allow-top-navigation",
+ script: "parent.location.href = 'file_parent_navigation_by_location.html'",
+ },
+ {
+ desc: "Test 4: parent.location.hash should be blocked even when sandboxed with allow-same-origin allow-top-navigation",
+ script: "parent.location.hash = 'wibble'",
+ },
+ ];
+
+ function runNextTest() {
+ ++testCaseIndex;
+ if (testCaseIndex == testCases.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ runScriptNavigationTest(testCases[testCaseIndex]);
+ }
+
+ window.onmessage = runNextTest;
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=785310">Mozilla Bug 785310</a>
+<p id="display"></p>
+<div id="content">
+Tests for Bug 785310
+</div>
+
+<iframe name="parentIframe" src="file_parent_navigation_by_location.html"></iframe>
+
+</body>
+</html>
diff --git a/docshell/test/iframesandbox/test_sibling_navigation_by_location.html b/docshell/test/iframesandbox/test_sibling_navigation_by_location.html
new file mode 100644
index 0000000000..d7508d5748
--- /dev/null
+++ b/docshell/test/iframesandbox/test_sibling_navigation_by_location.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=785310
+html5 sandboxed iframe should not be able to perform top navigation with scripts allowed
+-->
+<head>
+<meta charset="utf-8">
+<title>Test for Bug 785310 - iframe sandbox sibling navigation by location tests</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ function runScriptNavigationTest(testCase) {
+ window.onmessage = function(event) {
+ if (event.data != "siblingIframe") {
+ ok(false, "event.data: got '" + event.data + "', expected 'siblingIframe'");
+ }
+
+ ok(false, testCase.desc + " - sibling navigation was NOT blocked");
+ runNextTest();
+ };
+
+ try {
+ window.testIframe.eval(testCase.script);
+ } catch (e) {
+ ok(true, testCase.desc + " - " + e.message);
+ runNextTest();
+ }
+ }
+
+ var testCaseIndex = -1;
+ var testCases = [
+ {
+ desc: "Test 1: sibling location.replace should be blocked even when sandboxed with allow-same-origin allow-top-navigation",
+ script: "parent['siblingIframe'].location.replace('file_sibling_navigation_by_location.html')",
+ },
+ {
+ desc: "Test 2: sibling location.assign should be blocked even when sandboxed with allow-same-origin allow-top-navigation",
+ script: "parent['siblingIframe'].location.assign('file_sibling_navigation_by_location.html')",
+ },
+ {
+ desc: "Test 3: sibling location.href should be blocked even when sandboxed with allow-same-origin allow-top-navigation",
+ script: "parent['siblingIframe'].location.href = 'file_sibling_navigation_by_location.html'",
+ },
+ {
+ desc: "Test 4: sibling location.hash should be blocked even when sandboxed with allow-same-origin allow-top-navigation",
+ script: "parent['siblingIframe'].location.hash = 'wibble'",
+ },
+ ];
+
+ function runNextTest() {
+ ++testCaseIndex;
+ if (testCaseIndex == testCases.length) {
+ SimpleTest.finish();
+ return;
+ }
+
+ runScriptNavigationTest(testCases[testCaseIndex]);
+ }
+
+ window.onmessage = runNextTest;
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=785310">Mozilla Bug 785310</a>
+<p id="display"></p>
+<div id="content">
+Tests for Bug 785310
+</div>
+
+<iframe name="testIframe" sandbox="allow-scripts allow-same-origin allow-top-navigation"></iframe>
+<iframe name="siblingIframe" src="file_sibling_navigation_by_location.html"></iframe>
+
+</body>
+</html>
diff --git a/docshell/test/iframesandbox/test_top_navigation_by_location.html b/docshell/test/iframesandbox/test_top_navigation_by_location.html
new file mode 100644
index 0000000000..248f854bbf
--- /dev/null
+++ b/docshell/test/iframesandbox/test_top_navigation_by_location.html
@@ -0,0 +1,167 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=785310
+html5 sandboxed iframe should not be able to perform top navigation with scripts allowed
+-->
+<head>
+<meta charset="utf-8">
+<title>Test for Bug 785310 - iframe sandbox top navigation by location tests</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ var testWin;
+
+ function runScriptNavigationTest(testCase) {
+ window.onmessage = function(event) {
+ if (event.data != "newTop") {
+ ok(false, "event.data: got '" + event.data + "', expected 'newTop'");
+ }
+ ok(!testCase.shouldBeBlocked, testCase.desc + " - top navigation was NOT blocked");
+ runNextTest();
+ };
+ try {
+ SpecialPowers.wrap(testWin[testCase.iframeName]).eval(testCase.script);
+ } catch (e) {
+ ok(testCase.shouldBeBlocked, testCase.desc + " - " + SpecialPowers.wrap(e).message);
+ runNextTest();
+ }
+ }
+
+ var testCaseIndex = -1;
+ var testCases = [
+ {
+ desc: "Test 1: top.location.replace should be blocked when sandboxed without allow-top-navigation",
+ script: "top.location.replace('file_top_navigation_by_location.html')",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 2: top.location.assign should be blocked when sandboxed without allow-top-navigation",
+ script: "top.location.assign('file_top_navigation_by_location.html')",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 3: top.location.href should be blocked when sandboxed without allow-top-navigation",
+ script: "top.location.href = 'file_top_navigation_by_location.html'",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 4: top.location.pathname should be blocked when sandboxed without allow-top-navigation",
+ script: "top.location.pathname = top.location.pathname",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 5: top.location should be blocked when sandboxed without allow-top-navigation",
+ script: "top.location = 'file_top_navigation_by_location.html'",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 6: top.location.hash should be blocked when sandboxed without allow-top-navigation",
+ script: "top.location.hash = 'wibble'",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 7: top.location.replace should NOT be blocked when sandboxed with allow-same-origin allow-top-navigation",
+ script: "top.location.replace('file_top_navigation_by_location.html')",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 8: top.location.assign should NOT be blocked when sandboxed with allow-same-origin allow-top-navigation",
+ script: "top.location.assign('file_top_navigation_by_location.html')",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 9: top.location.href should NOT be blocked when sandboxed with allow-same-origin allow-top-navigation",
+ script: "top.location.href = 'file_top_navigation_by_location.html'",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 10: top.location.pathname should NOT be blocked when sandboxed with allow-same-origin allow-top-navigation",
+ script: "top.location.pathname = top.location.pathname",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 11: top.location should NOT be blocked when sandboxed with allow-same-origin allow-top-navigation",
+ script: "top.location = 'file_top_navigation_by_location.html'",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 12: top.location.hash should NOT be blocked when sandboxed with allow-same-origin allow-top-navigation",
+ script: "top.location.hash = 'wibble'",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 13: top.location.replace should NOT be blocked when sandboxed with allow-top-navigation, but without allow-same-origin",
+ script: "top.location.replace('file_top_navigation_by_location.html')",
+ iframeName: "if3",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 14: top.location.assign should be blocked when sandboxed with allow-top-navigation, but without allow-same-origin",
+ script: "top.location.assign('file_top_navigation_by_location.html')",
+ iframeName: "if3",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 15: top.location.href should NOT be blocked when sandboxed with allow-top-navigation, but without allow-same-origin",
+ script: "top.location.href = 'file_top_navigation_by_location.html'",
+ iframeName: "if3",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 16: top.location.pathname should be blocked when sandboxed with allow-top-navigation, but without allow-same-origin",
+ script: "top.location.pathname = top.location.pathname",
+ iframeName: "if3",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 17: top.location should NOT be blocked when sandboxed with allow-top-navigation, but without allow-same-origin",
+ script: "top.location = 'file_top_navigation_by_location.html'",
+ iframeName: "if3",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 18: top.location.hash should be blocked when sandboxed with allow-top-navigation, but without allow-same-origin",
+ script: "top.location.hash = 'wibble'",
+ iframeName: "if3",
+ shouldBeBlocked: true,
+ },
+ ];
+
+ function runNextTest() {
+ ++testCaseIndex;
+ if (testCaseIndex == testCases.length) {
+ testWin.close();
+ SimpleTest.finish();
+ return;
+ }
+
+ runScriptNavigationTest(testCases[testCaseIndex]);
+ }
+
+ window.onmessage = runNextTest;
+ testWin = window.open("file_top_navigation_by_location.html", "newTop");
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=785310">Mozilla Bug 785310</a>
+<p id="display"></p>
+<div id="content">
+Tests for Bug 785310
+</div>
+</body>
+</html>
diff --git a/docshell/test/iframesandbox/test_top_navigation_by_location_exotic.html b/docshell/test/iframesandbox/test_top_navigation_by_location_exotic.html
new file mode 100644
index 0000000000..11b7c78699
--- /dev/null
+++ b/docshell/test/iframesandbox/test_top_navigation_by_location_exotic.html
@@ -0,0 +1,204 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=785310
+html5 sandboxed iframe should not be able to perform top navigation with scripts allowed
+-->
+<head>
+<meta charset="utf-8">
+<title>Test for Bug 785310 - iframe sandbox top navigation by location via exotic means tests</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ var testWin;
+
+ function runScriptNavigationTest(testCase) {
+ window.onmessage = function(event) {
+ if (event.data.name != "newWindow") {
+ ok(false, "event.data.name: got '" + event.data.name + "', expected 'newWindow'");
+ }
+ var diag = "top navigation was " + (event.data.blocked ? "" : "NOT ") + "blocked";
+ ok((testCase.shouldBeBlocked == event.data.blocked), testCase.desc + " - " + diag);
+ runNextTest();
+ };
+ try {
+ testWin[testCase.iframeName].eval(testCase.script);
+ } catch (e) {
+ ok(testCase.shouldBeBlocked, testCase.desc + " - " + e.message);
+ runNextTest();
+ }
+ }
+
+ var testCaseIndex = -1;
+ var testCases = [
+ {
+ desc: "Test 1: location.replace.call(top.location, ...) should be blocked when sandboxed without allow-top-navigation",
+ script: "location.replace.call(top.location, 'file_top_navigation_by_location_exotic.html')",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 2: location.replace.bind(top.location, ...) should be blocked when sandboxed without allow-top-navigation",
+ script: "location.replace.bind(top.location, 'file_top_navigation_by_location_exotic.html')()",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 3: Function.bind.call(location.replace, top.location, ...) should be blocked when sandboxed without allow-top-navigation",
+ script: "Function.bind.call(location.replace, top.location, 'file_top_navigation_by_location_exotic.html')()",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 4: location.replace.call(top.location, ...) should NOT be blocked when sandboxed with allow-top-navigation",
+ script: "location.replace.call(top.location, 'file_top_navigation_by_location_exotic.html')",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 5: location.replace.bind(top.location, ...) should NOT be blocked when sandboxed with allow-top-navigation",
+ script: "location.replace.bind(top.location, 'file_top_navigation_by_location_exotic.html')()",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 6: Function.bind.call(location.replace, top.location, ...) should NOT be blocked when sandboxed with allow-top-navigation",
+ script: "Function.bind.call(location.replace, top.location, 'file_top_navigation_by_location_exotic.html')()",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 7: top.location.href, via setTimeout, should be blocked when sandboxed without allow-top-navigation",
+ script: "setTimeout(function() { try { top.location.href = 'file_top_navigation_by_location_exotic.html' } catch (e) { top.onBlock() } }, 0)",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 8: top.location.href, via setTimeout, should NOT be blocked when sandboxed with allow-top-navigation",
+ script: "setTimeout(function() { try { top.location.href = 'file_top_navigation_by_location_exotic.html' } catch(e) { top.onBlock() } }, 0)",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 9: top.location.href, via eval, should be blocked when sandboxed without allow-top-navigation",
+ script: "eval('top.location.href = \"file_top_navigation_by_location_exotic.html\"')",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 10: top.location.href, via eval, should NOT be blocked when sandboxed with allow-top-navigation",
+ script: "eval('top.location.href = \"file_top_navigation_by_location_exotic.html\"')",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 11: top.location.href, via anonymous function, should be blocked when sandboxed without allow-top-navigation",
+ script: "(function() { top.location.href = 'file_top_navigation_by_location_exotic.html' })()",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 12: top.location.href, via anonymous function, should NOT be blocked when sandboxed with allow-top-navigation",
+ script: "(function() { top.location.href = 'file_top_navigation_by_location_exotic.html' })()",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 13: top.location.href, via function inserted in top, should be blocked when sandboxed without allow-top-navigation",
+ script: "top.doTest = function() { top.location.href = 'file_top_navigation_by_location_exotic.html' }; top.doTest();",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 14: top.location.href, via function inserted in top, should NOT be blocked when sandboxed with allow-top-navigation",
+ script: "top.doTest = function() { top.location.href = 'file_top_navigation_by_location_exotic.html' }; top.doTest();",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 15: top.location.href, via function inserted in us by top, should NOT be blocked when sandboxed without allow-top-navigation",
+ script: "top.eval('window[\"if1\"].doTest = function() { top.location.href = \"file_top_navigation_by_location_exotic.html\" };'), doTest();",
+ iframeName: "if1",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 16: top.location.href, via function inserted in top, should NOT be blocked when sandboxed with allow-top-navigation",
+ script: "top.eval('window[\"if2\"].doTest = function() { top.location.href = \"file_top_navigation_by_location_exotic.html\" };'), doTest();",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 17: top.location.href, via function in top, should NOT be blocked when sandboxed without allow-top-navigation",
+ script: "top.setOwnHref()",
+ iframeName: "if1",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 18: top.location.href, via function in top, should NOT be blocked when sandboxed with allow-top-navigation",
+ script: "top.setOwnHref()",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 19: top.location.href, via eval in top, should NOT be blocked when sandboxed without allow-top-navigation",
+ script: "top.eval('location.href = \"file_top_navigation_by_location_exotic.html\"')",
+ iframeName: "if1",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 20: top.location.href, via eval in top, should NOT be blocked when sandboxed with allow-top-navigation",
+ script: "top.eval('location.href = \"file_top_navigation_by_location_exotic.html\"')",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 21: top.location.href, via eval in top calling us, should be blocked when sandboxed without allow-top-navigation",
+ script: "function doTest() { top.location.href = 'file_top_navigation_by_location_exotic.html' } top.eval('window[\"if1\"].doTest()');",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 22: top.location.href, via eval in top calling us, should NOT be blocked when sandboxed with allow-top-navigation",
+ script: "function doTest() { top.location.href = 'file_top_navigation_by_location_exotic.html' } top.eval('window[\"if2\"].doTest()');",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ {
+ desc: "Test 23: top.location.href, via function bound to top, should be blocked when sandboxed without allow-top-navigation",
+ script: "(function() { top.location.href = 'file_top_navigation_by_location_exotic.html' }).bind(top)();",
+ iframeName: "if1",
+ shouldBeBlocked: true,
+ },
+ {
+ desc: "Test 24: top.location.href, via function bound to top, should NOT be blocked when sandboxed with allow-top-navigation",
+ script: "(function() { top.location.href = 'file_top_navigation_by_location_exotic.html' }).bind(top)();",
+ iframeName: "if2",
+ shouldBeBlocked: false,
+ },
+ ];
+
+ function runNextTest() {
+ ++testCaseIndex;
+ if (testCaseIndex == testCases.length) {
+ testWin.close();
+ SimpleTest.finish();
+ return;
+ }
+
+ runScriptNavigationTest(testCases[testCaseIndex]);
+ }
+
+ window.onmessage = runNextTest;
+ testWin = window.open("file_top_navigation_by_location_exotic.html", "newWindow");
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=785310">Mozilla Bug 785310</a>
+<p id="display"></p>
+<div id="content">
+Tests for Bug 785310
+</div>
+</body>
+</html>
diff --git a/docshell/test/iframesandbox/test_top_navigation_by_user_activation.html b/docshell/test/iframesandbox/test_top_navigation_by_user_activation.html
new file mode 100644
index 0000000000..b462c54d7b
--- /dev/null
+++ b/docshell/test/iframesandbox/test_top_navigation_by_user_activation.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1744321
+-->
+<head>
+<meta charset="utf-8">
+<title>Iframe sandbox top navigation by user activation</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<script>
+function waitForMessage(aCallback) {
+ return new Promise((aResolve) => {
+ window.addEventListener("message", function listener(aEvent) {
+ aCallback(aEvent);
+ aResolve();
+ }, { once: true });
+ });
+}
+
+[
+ {
+ desc: "A same-origin iframe in sandbox with 'allow-top-navigation-by-user-activation' cannot navigate its top level page, if the navigation is not triggered by a user gesture",
+ sameOrigin: true,
+ userGesture: false,
+ },
+ {
+ desc: "A same-origin iframe in sandbox with 'allow-top-navigation-by-user-activation' can navigate its top level page, if the navigation is triggered by a user gesture",
+ sameOrigin: true,
+ userGesture: true,
+ },
+ {
+ desc: "A cross-origin iframe in sandbox with 'allow-top-navigation-by-user-activation' cannot navigate its top level page, if the navigation is not triggered by a user gesture",
+ sameOrigin: false,
+ userGesture: false,
+ },
+ {
+ desc: "A cross-origin iframe in sandbox with 'allow-top-navigation-by-user-activation' can navigate its top level page, if the navigation is triggered by a user gesture",
+ sameOrigin: false,
+ userGesture: true,
+ },
+].forEach(({desc, sameOrigin, userGesture}) => {
+ add_task(async function() {
+ info(`Test: ${desc}`);
+
+ let url = "file_top_navigation_by_user_activation.html";
+ if (sameOrigin) {
+ url = `${location.origin}/tests/docshell/test/iframesandbox/${url}`;
+ }
+
+ let promise = waitForMessage((e) => {
+ is(e.data, "READY", "Ready for test");
+ });
+ let testWin = window.open(url);
+ await promise;
+
+ promise = waitForMessage((e) => {
+ is(e.data, userGesture ? "NAVIGATED" : "BLOCKED", "Check the result");
+ });
+ testWin.postMessage(userGesture ? "CLICK" : "SCRIPT", "*");
+ await promise;
+ testWin.close();
+ });
+});
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1744321">Mozilla Bug 1744321</a>
+<p id="display"></p>
+<div id="content">
+Tests for Bug 1744321
+</div>
+</body>
+</html>
diff --git a/docshell/test/mochitest/bug1422334_redirect.html b/docshell/test/mochitest/bug1422334_redirect.html
new file mode 100644
index 0000000000..eec7fda2c7
--- /dev/null
+++ b/docshell/test/mochitest/bug1422334_redirect.html
@@ -0,0 +1,3 @@
+<html>
+ <body>You should never see this</body>
+</html>
diff --git a/docshell/test/mochitest/bug1422334_redirect.html^headers^ b/docshell/test/mochitest/bug1422334_redirect.html^headers^
new file mode 100644
index 0000000000..fbf2d1b745
--- /dev/null
+++ b/docshell/test/mochitest/bug1422334_redirect.html^headers^
@@ -0,0 +1,2 @@
+HTTP 302 Moved Temporarily
+Location: ../navigation/blank.html?x=y
diff --git a/docshell/test/mochitest/bug404548-subframe.html b/docshell/test/mochitest/bug404548-subframe.html
new file mode 100644
index 0000000000..9a248b40b3
--- /dev/null
+++ b/docshell/test/mochitest/bug404548-subframe.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+<body onload="setTimeout(function() { window.location = 'bug404548-subframe_window.html'; }, 10)">
+<iframe srcdoc="<body onpagehide='var p = window.parent.opener; var e = window.frameElement; e.parentNode.removeChild(e); if (e.parentNode == null && e.contentWindow == null) { p.firstRemoved = true; }'>">
+</iframe>
+<iframe srcdoc="<body onpagehide='window.parent.opener.secondHidden = true;'>">
+</iframe>
diff --git a/docshell/test/mochitest/bug404548-subframe_window.html b/docshell/test/mochitest/bug404548-subframe_window.html
new file mode 100644
index 0000000000..82ea73ea83
--- /dev/null
+++ b/docshell/test/mochitest/bug404548-subframe_window.html
@@ -0,0 +1 @@
+<body onload='window.opener.finishTest()'>
diff --git a/docshell/test/mochitest/bug413310-post.sjs b/docshell/test/mochitest/bug413310-post.sjs
new file mode 100644
index 0000000000..f87937ab56
--- /dev/null
+++ b/docshell/test/mochitest/bug413310-post.sjs
@@ -0,0 +1,10 @@
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/html");
+ response.write(
+ "<body onload='window.parent.onloadCount++'>" +
+ request.method +
+ " " +
+ Date.now() +
+ "</body>"
+ );
+}
diff --git a/docshell/test/mochitest/bug413310-subframe.html b/docshell/test/mochitest/bug413310-subframe.html
new file mode 100644
index 0000000000..bcff1886fd
--- /dev/null
+++ b/docshell/test/mochitest/bug413310-subframe.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+ <body onload="window.parent.onloadCount++">
+ <form action="bug413310-post.sjs" method="POST">
+ </form>
+ </body>
+</html>
diff --git a/docshell/test/mochitest/bug529119-window.html b/docshell/test/mochitest/bug529119-window.html
new file mode 100644
index 0000000000..f1908835a7
--- /dev/null
+++ b/docshell/test/mochitest/bug529119-window.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test bug 529119, sub-window</title>
+<body onload="window.opener.windowLoaded();">
+</body>
+</html>
diff --git a/docshell/test/mochitest/bug530396-noref.sjs b/docshell/test/mochitest/bug530396-noref.sjs
new file mode 100644
index 0000000000..6a65882160
--- /dev/null
+++ b/docshell/test/mochitest/bug530396-noref.sjs
@@ -0,0 +1,22 @@
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/html");
+ response.setHeader("Cache-Control", "no-cache");
+ response.write("<body onload='");
+
+ if (!request.hasHeader("Referer")) {
+ response.write("window.parent.onloadCount++;");
+ }
+
+ if (request.queryString == "newwindow") {
+ response.write(
+ "if (window.opener) { window.opener.parent.onloadCount++; window.opener.parent.doNextStep(); }"
+ );
+ response.write("if (!window.opener) window.close();");
+ response.write("'>");
+ } else {
+ response.write("window.parent.doNextStep();'>");
+ }
+
+ response.write(request.method + " " + Date.now());
+ response.write("</body>");
+}
diff --git a/docshell/test/mochitest/bug530396-subframe.html b/docshell/test/mochitest/bug530396-subframe.html
new file mode 100644
index 0000000000..be81b9f144
--- /dev/null
+++ b/docshell/test/mochitest/bug530396-subframe.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+ <body onload="window.parent.onloadCount++">
+ <a href="bug530396-noref.sjs" rel="noreferrer foo" id="target1">bug530396-noref.sjs</a>
+ <a href="bug530396-noref.sjs?newwindow" rel="nofollow noreferrer" id="target2" target="newwindow">bug530396-noref.sjs with new window</a>
+ </body>
+</html>
diff --git a/docshell/test/mochitest/bug570341_recordevents.html b/docshell/test/mochitest/bug570341_recordevents.html
new file mode 100644
index 0000000000..45b04866ec
--- /dev/null
+++ b/docshell/test/mochitest/bug570341_recordevents.html
@@ -0,0 +1,21 @@
+<html>
+<head>
+<script>
+ var start = Date.now();
+ window._testing_js_start = Date.now();
+ window["_testing_js_after_" + document.readyState] = start;
+ document.addEventListener("DOMContentLoaded",
+ function() {
+ window._testing_evt_DOMContentLoaded = Date.now();
+ }, true);
+ document.addEventListener("readystatechange", function() {
+ window["_testing_evt_DOM_" + document.readyState] = Date.now();
+ }, true);
+ function recordLoad() {
+ window._testing_evt_load = Date.now();
+ }
+</script>
+</head>
+<body onload="recordLoad()">This document collects time
+for events related to the page load progress.</body>
+</html>
diff --git a/docshell/test/mochitest/bug668513_redirect.html b/docshell/test/mochitest/bug668513_redirect.html
new file mode 100644
index 0000000000..1b8f66c631
--- /dev/null
+++ b/docshell/test/mochitest/bug668513_redirect.html
@@ -0,0 +1 @@
+<html><body>This document is redirected to a blank document.</body></html>
diff --git a/docshell/test/mochitest/bug668513_redirect.html^headers^ b/docshell/test/mochitest/bug668513_redirect.html^headers^
new file mode 100644
index 0000000000..0e785833c6
--- /dev/null
+++ b/docshell/test/mochitest/bug668513_redirect.html^headers^
@@ -0,0 +1,2 @@
+HTTP 302 Moved Temporarily
+Location: navigation/blank.html
diff --git a/docshell/test/mochitest/bug691547_frame.html b/docshell/test/mochitest/bug691547_frame.html
new file mode 100644
index 0000000000..00172f7119
--- /dev/null
+++ b/docshell/test/mochitest/bug691547_frame.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=691547
+-->
+<head>
+ <title>Test for Bug 691547</title>
+</head>
+<body>
+<iframe style="width:95%"></iframe>
+</body>
+</html>
diff --git a/docshell/test/mochitest/clicker.html b/docshell/test/mochitest/clicker.html
new file mode 100644
index 0000000000..b655e27ea5
--- /dev/null
+++ b/docshell/test/mochitest/clicker.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<script>
+ "use strict";
+ let target = window.opener ? window.opener : window.parent;
+
+ onmessage = ({data}) => target.postMessage({}, "*");
+</script>
diff --git a/docshell/test/mochitest/double_submit.sjs b/docshell/test/mochitest/double_submit.sjs
new file mode 100644
index 0000000000..487150d456
--- /dev/null
+++ b/docshell/test/mochitest/double_submit.sjs
@@ -0,0 +1,79 @@
+"use strict";
+
+let self = this;
+
+let { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+const CC = Components.Constructor;
+const BinaryInputStream = CC(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+
+function log(str) {
+ // dump(`LOG: ${str}\n`);
+}
+
+function readStream(inputStream) {
+ let available = 0;
+ let result = [];
+ while ((available = inputStream.available()) > 0) {
+ result.push(inputStream.readBytes(available));
+ }
+
+ return result.join("");
+}
+
+function now() {
+ return Date.now();
+}
+
+async function handleRequest(request, response) {
+ log("Get query parameters");
+ Cu.importGlobalProperties(["URLSearchParams"]);
+ let params = new URLSearchParams(request.queryString);
+
+ let start = now();
+ let delay = parseInt(params.get("delay")) || 0;
+ log(`Delay for ${delay}`);
+
+ let message = "good";
+ if (request.method !== "POST") {
+ message = "bad";
+ } else {
+ log("Read POST body");
+ let body = new URLSearchParams(
+ readStream(new BinaryInputStream(request.bodyInputStream))
+ );
+ message = body.get("token") || "bad";
+ log(`The result was ${message}`);
+ }
+
+ let body = `<!doctype html>
+ <script>
+ "use strict";
+ let target = (opener || parent);
+ target.postMessage(${JSON.stringify(message)}, '*');
+ </script>`;
+
+ // Sieze power from the response to allow manually transmitting data at any
+ // rate we want, so we can delay transmitting headers.
+ response.seizePower();
+
+ log(`Writing HTTP status line at ${now() - start}`);
+ response.write("HTTP/1.1 200 OK\r\n");
+
+ await new Promise(resolve => setTimeout(() => resolve(), delay));
+
+ log(`Delay completed at ${now() - start}`);
+ response.write("Content-Type: text/html\r\n");
+ response.write(`Content-Length: ${body.length}\r\n`);
+ response.write("\r\n");
+ response.write(body);
+ response.finish();
+
+ log("Finished");
+}
diff --git a/docshell/test/mochitest/dummy_page.html b/docshell/test/mochitest/dummy_page.html
new file mode 100644
index 0000000000..59bf2a5f8f
--- /dev/null
+++ b/docshell/test/mochitest/dummy_page.html
@@ -0,0 +1,6 @@
+<html>
+<head> <meta charset="utf-8"> </head>
+ <body>
+ just a dummy html file
+ </body>
+</html>
diff --git a/docshell/test/mochitest/file_anchor_scroll_after_document_open.html b/docshell/test/mochitest/file_anchor_scroll_after_document_open.html
new file mode 100644
index 0000000000..7903380eac
--- /dev/null
+++ b/docshell/test/mochitest/file_anchor_scroll_after_document_open.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<script>
+ if (location.hash == "#target") {
+ parent.postMessage("haveHash", "*");
+ } else {
+ document.addEventListener("DOMContentLoaded", function() {
+ document.open();
+ document.write("<!DOCTYPE html><html style='height: 100%'><body style='height: 100%'><div style='height: 200%'></div><div id='target'></div></body></html>");
+ document.close();
+ // Notify parent via postMessage, since otherwise exceptions will not get
+ // caught by its onerror handler.
+ parent.postMessage("doTest", "*");
+ });
+ }
+</script>
diff --git a/docshell/test/mochitest/file_bfcache_plus_hash_1.html b/docshell/test/mochitest/file_bfcache_plus_hash_1.html
new file mode 100644
index 0000000000..199f6003e0
--- /dev/null
+++ b/docshell/test/mochitest/file_bfcache_plus_hash_1.html
@@ -0,0 +1,24 @@
+<html><body>
+ Popup 1
+ <script type="application/javascript">
+ var bc = new BroadcastChannel("bug646641_1");
+ window.onload = () => {
+ bc.postMessage({ message: "childLoad", num: 1 })
+ }
+
+ window.onpageshow = () => {
+ bc.postMessage({ message: "childPageshow", num: 1 })
+ }
+ bc.onmessage = (msgEvent) => {
+ var msg = msgEvent.data;
+ if (msg == "pushState") {
+ history.pushState("", "", "");
+ location = "file_bfcache_plus_hash_2.html";
+ } else if (msg == "close") {
+ bc.postMessage({ message: "closed" });
+ bc.close();
+ window.close();
+ }
+ }
+ </script>
+</body></html>
diff --git a/docshell/test/mochitest/file_bfcache_plus_hash_2.html b/docshell/test/mochitest/file_bfcache_plus_hash_2.html
new file mode 100644
index 0000000000..c27d4eaa3b
--- /dev/null
+++ b/docshell/test/mochitest/file_bfcache_plus_hash_2.html
@@ -0,0 +1,17 @@
+<html><body>
+ Popup 2
+ <script type="application/javascript">
+ var bc = new BroadcastChannel("bug646641_2");
+ window.onload = () => {
+ bc.postMessage({ message: "childLoad", num: 2 })
+ requestAnimationFrame(() => bc.postMessage({message: "childPageshow", num: 2}));
+ }
+ bc.onmessage = (msgEvent) => {
+ var msg = msgEvent.data;
+ if (msg == "go-2") {
+ history.go(-2);
+ bc.close();
+ }
+ }
+ </script>
+</body></html>
diff --git a/docshell/test/mochitest/file_bug1121701_1.html b/docshell/test/mochitest/file_bug1121701_1.html
new file mode 100644
index 0000000000..62c58495f7
--- /dev/null
+++ b/docshell/test/mochitest/file_bug1121701_1.html
@@ -0,0 +1,29 @@
+<script>
+ var bc = new BroadcastChannel("file_bug1121701_1");
+ var pageHideAsserts = undefined;
+ bc.onmessage = (msgEvent) => {
+ var msg = msgEvent.data;
+ var command = msg.command;
+ if (command == "setInnerHTML") {
+ document.body.innerHTML = "modified";
+ window.onpagehide = function(event) {
+ window.onpagehide = null;
+ pageHideAsserts = {};
+ pageHideAsserts.persisted = event.persisted;
+ // eslint-disable-next-line no-unsanitized/property
+ pageHideAsserts.innerHTML = window.document.body.innerHTML;
+ };
+ window.location.href = msg.testUrl2;
+ } else if (command == "close") {
+ bc.postMessage({command: "closed"});
+ bc.close();
+ window.close();
+ }
+ }
+ window.onpageshow = function(e) {
+ var msg = {command: "child1PageShow", persisted: e.persisted, pageHideAsserts};
+ // eslint-disable-next-line no-unsanitized/property
+ msg.innerHTML = window.document.body.innerHTML;
+ bc.postMessage(msg);
+ };
+</script>
diff --git a/docshell/test/mochitest/file_bug1121701_2.html b/docshell/test/mochitest/file_bug1121701_2.html
new file mode 100644
index 0000000000..6cec88cd5d
--- /dev/null
+++ b/docshell/test/mochitest/file_bug1121701_2.html
@@ -0,0 +1,23 @@
+<script>
+ var bc = new BroadcastChannel("file_bug1121701_2");
+ bc.onmessage = (msgEvent) => {
+ var msg = msgEvent.data;
+ var command = msg.command;
+ if (command == "setInnerHTML") {
+ window.document.body.innerHTML = "<img>";
+ window.onmessage = function() {
+ bc.postMessage({command: "onmessage"});
+ window.document.body.firstChild.src = msg.location;
+ bc.close();
+ };
+ window.onbeforeunload = function() {
+ window.postMessage("foo", "*");
+ };
+
+ history.back();
+ }
+ }
+ window.onpageshow = function(e) {
+ bc.postMessage({command: "child2PageShow", persisted: e.persisted});
+ };
+</script>
diff --git a/docshell/test/mochitest/file_bug1151421.html b/docshell/test/mochitest/file_bug1151421.html
new file mode 100644
index 0000000000..7bb8c8f363
--- /dev/null
+++ b/docshell/test/mochitest/file_bug1151421.html
@@ -0,0 +1,19 @@
+<html>
+<head>
+<style>
+body, html {
+ height: 100%;
+}
+.spacer {
+ height: 80%;
+}
+</style>
+</head>
+<body onload='(parent || opener).childLoad()'>
+
+<div class="spacer"></div>
+<div id="content">content</div>
+<div class="spacer"></div>
+
+</body>
+</html>
diff --git a/docshell/test/mochitest/file_bug1186774.html b/docshell/test/mochitest/file_bug1186774.html
new file mode 100644
index 0000000000..9af95b09bd
--- /dev/null
+++ b/docshell/test/mochitest/file_bug1186774.html
@@ -0,0 +1 @@
+<div style='height: 9000px;'></div>
diff --git a/docshell/test/mochitest/file_bug1450164.html b/docshell/test/mochitest/file_bug1450164.html
new file mode 100644
index 0000000000..55e32ce93d
--- /dev/null
+++ b/docshell/test/mochitest/file_bug1450164.html
@@ -0,0 +1,16 @@
+<html>
+ <head>
+ <script>
+ function go() {
+ var a = window.history.state;
+ window.history.replaceState(a, "", "1");
+ var ok = opener.ok;
+ var SimpleTest = opener.SimpleTest;
+ ok("Addition of history in unload did not crash browser");
+ SimpleTest.finish();
+ }
+ </script>
+ </head>
+ <body onunload="go()">
+ </body>
+</html>
diff --git a/docshell/test/mochitest/file_bug1729662.html b/docshell/test/mochitest/file_bug1729662.html
new file mode 100644
index 0000000000..f5710e1c49
--- /dev/null
+++ b/docshell/test/mochitest/file_bug1729662.html
@@ -0,0 +1,8 @@
+<script>
+addEventListener("load", () => {
+ (new BroadcastChannel("bug1729662")).postMessage("load");
+ history.pushState(1, null, location.href);
+ history.back();
+ history.forward();
+});
+</script>
diff --git a/docshell/test/mochitest/file_bug1740516_1.html b/docshell/test/mochitest/file_bug1740516_1.html
new file mode 100644
index 0000000000..ac8ca71d93
--- /dev/null
+++ b/docshell/test/mochitest/file_bug1740516_1.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script>
+ window.addEventListener("pageshow", ({ persisted }) => {
+ let bc = new BroadcastChannel("bug1740516_1");
+ bc.addEventListener("message", ({ data }) => {
+ bc.close();
+ switch (data) {
+ case "block_bfcache_and_navigate":
+ window.blockBFCache = new RTCPeerConnection();
+ // Fall through
+ case "navigate":
+ document.location = "file_bug1740516_2.html";
+ break;
+ case "close":
+ window.close();
+ break;
+ }
+ });
+ bc.postMessage(persisted);
+ });
+ </script>
+</head>
+<body>
+ <iframe src="file_bug1740516_1_inner.html"></iframe>
+</body>
+</html>
diff --git a/docshell/test/mochitest/file_bug1740516_1_inner.html b/docshell/test/mochitest/file_bug1740516_1_inner.html
new file mode 100644
index 0000000000..159c6bde5a
--- /dev/null
+++ b/docshell/test/mochitest/file_bug1740516_1_inner.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script>
+ window.addEventListener("pageshow", ({ persisted }) => {
+ let bc = new BroadcastChannel("bug1740516_1_inner");
+ bc.postMessage(persisted);
+ bc.close();
+ });
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/docshell/test/mochitest/file_bug1740516_2.html b/docshell/test/mochitest/file_bug1740516_2.html
new file mode 100644
index 0000000000..2dc714feef
--- /dev/null
+++ b/docshell/test/mochitest/file_bug1740516_2.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script>
+ addEventListener("pageshow", () => { history.back(); });
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/docshell/test/mochitest/file_bug1741132.html b/docshell/test/mochitest/file_bug1741132.html
new file mode 100644
index 0000000000..d863b9f015
--- /dev/null
+++ b/docshell/test/mochitest/file_bug1741132.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script>
+ window.addEventListener("pageshow", ({ persisted }) => {
+ let bc = new BroadcastChannel("bug1741132");
+ bc.addEventListener("message", ({ data: { cmd, arg } }) => {
+ bc.close();
+ switch (cmd) {
+ case "load":
+ document.location = arg;
+ break;
+ case "go":
+ window.blockBFCache = new RTCPeerConnection();
+ history.go(arg);
+ break;
+ case "close":
+ window.close();
+ break;
+ }
+ });
+ bc.postMessage(persisted);
+ });
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/docshell/test/mochitest/file_bug1742865.sjs b/docshell/test/mochitest/file_bug1742865.sjs
new file mode 100644
index 0000000000..950c30ecd2
--- /dev/null
+++ b/docshell/test/mochitest/file_bug1742865.sjs
@@ -0,0 +1,77 @@
+Cu.importGlobalProperties(["URLSearchParams"]);
+
+function handleRequest(request, response) {
+ if (request.queryString == "reset") {
+ setState("index", "0");
+ response.setStatusLine(request.httpVersion, 200, "Ok");
+ response.write("Reset");
+ return;
+ }
+
+ let refresh = "";
+ let index = Number(getState("index"));
+ // index == 0 First load, returns first meta refresh
+ // index == 1 Second load, caused by first meta refresh, returns second meta refresh
+ // index == 2 Third load, caused by second meta refresh, doesn't return a meta refresh
+ let query = new URLSearchParams(request.queryString);
+ if (index < 2) {
+ refresh = query.get("seconds");
+ if (query.get("crossOrigin") == "true") {
+ const hosts = ["example.org", "example.com"];
+
+ let url = `${request.scheme}://${hosts[index]}${request.path}?${request.queryString}`;
+ refresh += `; url=${url}`;
+ }
+ refresh = `<meta http-equiv="Refresh" content="${refresh}">`;
+ }
+ // We want to scroll for the first load, and check that the meta refreshes keep the same
+ // scroll position.
+ let scroll = index == 0 ? `scrollTo(0, ${query.get("scrollTo")});` : "";
+
+ setState("index", String(index + 1));
+
+ response.write(
+ `<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta http-equiv="Cache-Control" content="no-cache">
+ ${refresh}
+ <script>
+ window.addEventListener("pageshow", () => {
+ ${scroll}
+ window.top.opener.postMessage({
+ commandType: "pageShow",
+ commandData: {
+ inputValue: document.getElementById("input").value,
+ scrollPosition: window.scrollY,
+ },
+ }, "*");
+ });
+ window.addEventListener("message", ({ data }) => {
+ if (data == "changeInputValue") {
+ document.getElementById("input").value = "1234";
+ window.top.opener.postMessage({
+ commandType: "onChangedInputValue",
+ commandData: {
+ historyLength: history.length,
+ inputValue: document.getElementById("input").value,
+ },
+ }, "*");
+ } else if (data == "loadNext") {
+ location.href += "&loadnext=1";
+ } else if (data == "back") {
+ history.back();
+ }
+ });
+ </script>
+</head>
+<body>
+<input type="text" id="input" value="initial"></input>
+<div style='height: 9000px;'></div>
+<p>
+</p>
+</body>
+</html>`
+ );
+}
diff --git a/docshell/test/mochitest/file_bug1742865_outer.sjs b/docshell/test/mochitest/file_bug1742865_outer.sjs
new file mode 100644
index 0000000000..bad8b23f00
--- /dev/null
+++ b/docshell/test/mochitest/file_bug1742865_outer.sjs
@@ -0,0 +1,25 @@
+Cu.importGlobalProperties(["URLSearchParams"]);
+
+function handleRequest(request, response) {
+ response.write(
+ `<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script>
+ window.addEventListener("message", ({ data }) => {
+ if (data == "loadNext") {
+ location.href += "&loadnext=1";
+ return;
+ }
+ // Forward other messages to the frame.
+ document.getElementById("frame").contentWindow.postMessage(data, "*");
+ });
+ </script>
+</head>
+<body>
+ <iframe src="file_bug1742865.sjs?${request.queryString}" id="frame"></iframe>
+</body>
+</html>`
+ );
+}
diff --git a/docshell/test/mochitest/file_bug1743353.html b/docshell/test/mochitest/file_bug1743353.html
new file mode 100644
index 0000000000..c08f8d143f
--- /dev/null
+++ b/docshell/test/mochitest/file_bug1743353.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script>
+ window.addEventListener("pageshow", () => {
+ let bc = new BroadcastChannel("bug1743353");
+ bc.addEventListener("message", ({ data: cmd }) => {
+ switch (cmd) {
+ case "load":
+ bc.close();
+ document.location += "?1";
+ break;
+ case "back":
+ window.blockBFCache = new RTCPeerConnection();
+ window.addEventListener("pagehide", () => {
+ bc.postMessage("pagehide");
+ });
+ window.addEventListener("unload", () => {
+ bc.postMessage("unload");
+ bc.close();
+ });
+ history.back();
+ break;
+ case "close":
+ bc.close();
+ window.close();
+ break;
+ }
+ });
+ bc.postMessage("pageshow");
+ });
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/docshell/test/mochitest/file_bug1747033.sjs b/docshell/test/mochitest/file_bug1747033.sjs
new file mode 100644
index 0000000000..14401101b2
--- /dev/null
+++ b/docshell/test/mochitest/file_bug1747033.sjs
@@ -0,0 +1,110 @@
+"use strict";
+
+const BOUNDARY = "BOUNDARY";
+
+// waitForPageShow should be false if this is for multipart/x-mixed-replace
+// and it's not the last part, Gecko doesn't fire pageshow for those parts.
+function documentString(waitForPageShow = true) {
+ return `<html>
+ <head>
+ <script>
+ let bc = new BroadcastChannel("bug1747033");
+ bc.addEventListener("message", ({ data: { cmd, arg = undefined } }) => {
+ switch (cmd) {
+ case "load":
+ location.href = arg;
+ break;
+ case "replaceState":
+ history.replaceState({}, "Replaced state", arg);
+ bc.postMessage({ "historyLength": history.length, "location": location.href });
+ break;
+ case "back":
+ history.back();
+ break;
+ case "close":
+ close();
+ break;
+ }
+ });
+
+ function reply() {
+ bc.postMessage({ "historyLength": history.length, "location": location.href });
+ }
+
+ ${waitForPageShow ? `addEventListener("pageshow", reply);` : "reply();"}
+ </script>
+ </head>
+ <body></body>
+</html>
+`;
+}
+
+function boundary(last = false) {
+ let b = `--${BOUNDARY}`;
+ if (last) {
+ b += "--";
+ }
+ return b + "\n";
+}
+
+function sendMultipart(response, last = false) {
+ setState("sendMore", "");
+
+ response.write(`Content-Type: text/html
+
+${documentString(last)}
+`);
+ response.write(boundary(last));
+}
+
+function shouldSendMore() {
+ return new Promise(resolve => {
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(
+ () => {
+ let sendMore = getState("sendMore");
+ if (sendMore !== "") {
+ timer.cancel();
+ resolve(sendMore);
+ }
+ },
+ 100,
+ Ci.nsITimer.TYPE_REPEATING_SLACK
+ );
+ });
+}
+
+async function handleRequest(request, response) {
+ if (request.queryString == "") {
+ // This is for non-multipart/x-mixed-replace loads.
+ response.write(documentString());
+ return;
+ }
+
+ if (request.queryString == "sendNextPart") {
+ setState("sendMore", "next");
+ return;
+ }
+
+ if (request.queryString == "sendLastPart") {
+ setState("sendMore", "last");
+ return;
+ }
+
+ response.processAsync();
+
+ response.setHeader(
+ "Content-Type",
+ `multipart/x-mixed-replace; boundary=${BOUNDARY}`,
+ false
+ );
+ response.setStatusLine(request.httpVersion, 200, "OK");
+
+ response.write(boundary());
+ sendMultipart(response);
+ while ((await shouldSendMore("sendMore")) !== "last") {
+ sendMultipart(response);
+ }
+ sendMultipart(response, true);
+ response.finish();
+}
diff --git a/docshell/test/mochitest/file_bug1773192_1.html b/docshell/test/mochitest/file_bug1773192_1.html
new file mode 100644
index 0000000000..42d5b3fced
--- /dev/null
+++ b/docshell/test/mochitest/file_bug1773192_1.html
@@ -0,0 +1,13 @@
+<script>
+ let bc = new BroadcastChannel("bug1743353");
+ bc.addEventListener("message", ({ data }) => {
+ if (data == "next") {
+ location = "file_bug1773192_2.html";
+ } else if (data == "close") {
+ window.close();
+ }
+ });
+ window.addEventListener("pageshow", () => {
+ bc.postMessage({ location: location.href, referrer: document.referrer });
+ });
+</script>
diff --git a/docshell/test/mochitest/file_bug1773192_2.html b/docshell/test/mochitest/file_bug1773192_2.html
new file mode 100644
index 0000000000..3b1e5bcb28
--- /dev/null
+++ b/docshell/test/mochitest/file_bug1773192_2.html
@@ -0,0 +1,13 @@
+<html>
+ <head>
+ <meta http-equiv="Cache-Control" content="no-cache, no-store">
+ <meta name="referrer" content="same-origin">
+ </head>
+ <body>
+ <form method="POST" action="file_bug1773192_3.sjs"></form>
+ <script>
+ history.replaceState({}, "", document.referrer);
+ document.forms[0].submit();
+ </script>
+ </body>
+</html>
diff --git a/docshell/test/mochitest/file_bug1773192_3.sjs b/docshell/test/mochitest/file_bug1773192_3.sjs
new file mode 100644
index 0000000000..ce889c7035
--- /dev/null
+++ b/docshell/test/mochitest/file_bug1773192_3.sjs
@@ -0,0 +1,3 @@
+function handleRequest(request, response) {
+ response.write("<script>history.back();</script>");
+}
diff --git a/docshell/test/mochitest/file_bug385434_1.html b/docshell/test/mochitest/file_bug385434_1.html
new file mode 100644
index 0000000000..5c951f1fa6
--- /dev/null
+++ b/docshell/test/mochitest/file_bug385434_1.html
@@ -0,0 +1,29 @@
+<!--
+Inner frame for test of bug 385434.
+https://bugzilla.mozilla.org/show_bug.cgi?id=385434
+-->
+<html>
+<head>
+ <script type="application/javascript">
+ function hashchange() {
+ parent.onIframeHashchange();
+ }
+
+ function load() {
+ parent.onIframeLoad();
+ }
+
+ function scroll() {
+ parent.onIframeScroll();
+ }
+ </script>
+</head>
+
+<body onscroll="scroll()" onload="load()" onhashchange="hashchange()">
+<a href="#link1" id="link1">link1</a>
+<!-- Our parent loads us in an iframe with height 100px, so this spacer ensures
+ that switching between #link1 and #link2 causes us to scroll -->
+<div style="height:200px;"></div>
+<a href="#link2" id="link2">link2</a>
+</body>
+</html>
diff --git a/docshell/test/mochitest/file_bug385434_2.html b/docshell/test/mochitest/file_bug385434_2.html
new file mode 100644
index 0000000000..4aa5ef82b8
--- /dev/null
+++ b/docshell/test/mochitest/file_bug385434_2.html
@@ -0,0 +1,26 @@
+<!--
+Inner frame for test of bug 385434.
+https://bugzilla.mozilla.org/show_bug.cgi?id=385434
+-->
+<html>
+<head>
+ <script type="application/javascript">
+ function hashchange(e) {
+ // pass the event back to the parent so it can check its properties.
+ parent.gSampleEvent = e;
+
+ parent.statusMsg("Hashchange in 2.");
+ parent.onIframeHashchange();
+ }
+
+ function load() {
+ parent.statusMsg("Loading 2.");
+ parent.onIframeLoad();
+ }
+ </script>
+</head>
+
+<frameset onload="load()" onhashchange="hashchange(event)">
+ <frame src="about:blank" />
+</frameset>
+</html>
diff --git a/docshell/test/mochitest/file_bug385434_3.html b/docshell/test/mochitest/file_bug385434_3.html
new file mode 100644
index 0000000000..34dd68ef45
--- /dev/null
+++ b/docshell/test/mochitest/file_bug385434_3.html
@@ -0,0 +1,22 @@
+<!--
+Inner frame for test of bug 385434.
+https://bugzilla.mozilla.org/show_bug.cgi?id=385434
+-->
+<html>
+<head>
+ <script type="application/javascript">
+ // Notify our parent if we have a hashchange and once we're done loading.
+ window.addEventListener("hashchange", parent.onIframeHashchange);
+
+ window.addEventListener("DOMContentLoaded", function() {
+ // This also should trigger a hashchange, becuase the readystate is
+ // "interactive", not "complete" during DOMContentLoaded.
+ window.location.hash = "2";
+ });
+
+ </script>
+</head>
+
+<body>
+</body>
+</html>
diff --git a/docshell/test/mochitest/file_bug475636.sjs b/docshell/test/mochitest/file_bug475636.sjs
new file mode 100644
index 0000000000..cba9a968d6
--- /dev/null
+++ b/docshell/test/mochitest/file_bug475636.sjs
@@ -0,0 +1,97 @@
+let jsURL =
+ "javascript:" +
+ escape(
+ 'window.parent.postMessage("JS uri ran", "*");\
+return \'\
+<script>\
+window.parent.postMessage("Able to access private: " +\
+ window.parent.private, "*");\
+</script>\''
+ );
+let dataURL =
+ "data:text/html," +
+ escape(
+ '<!DOCTYPE HTML>\
+<script>\
+try {\
+ window.parent.postMessage("Able to access private: " +\
+ window.parent.private, "*");\
+}\
+catch (e) {\
+ window.parent.postMessage("pass", "*");\
+}\
+</script>'
+ );
+
+let tests = [
+ // Plain document should work as normal
+ '<!DOCTYPE HTML>\
+<script>\
+try {\
+ window.parent.private;\
+ window.parent.postMessage("pass", "*");\
+}\
+catch (e) {\
+ window.parent.postMessage("Unble to access private", "*");\
+}\
+</script>',
+
+ // refresh to plain doc
+ { refresh: "file_bug475636.sjs?1", doc: "<!DOCTYPE HTML>" },
+
+ // meta-refresh to plain doc
+ '<!DOCTYPE HTML>\
+<head>\
+ <meta http-equiv="refresh" content="0; url=file_bug475636.sjs?1">\
+</head>',
+
+ // refresh to data url
+ { refresh: dataURL, doc: "<!DOCTYPE HTML>" },
+
+ // meta-refresh to data url
+ '<!DOCTYPE HTML>\
+<head>\
+ <meta http-equiv="refresh" content="0; url=' +
+ dataURL +
+ '">\
+</head>',
+
+ // refresh to js url should not be followed
+ {
+ refresh: jsURL,
+ doc: '<!DOCTYPE HTML>\
+<script>\
+setTimeout(function() {\
+ window.parent.postMessage("pass", "*");\
+}, 2000);\
+</script>',
+ },
+
+ // meta refresh to js url should not be followed
+ '<!DOCTYPE HTML>\
+<head>\
+ <meta http-equiv="refresh" content="0; url=' +
+ jsURL +
+ '">\
+</head>\
+<script>\
+setTimeout(function() {\
+ window.parent.postMessage("pass", "*");\
+}, 2000);\
+</script>',
+];
+
+function handleRequest(request, response) {
+ dump("@@@@@@@@@hi there: " + request.queryString + "\n");
+ let test = tests[parseInt(request.queryString, 10) - 1];
+ response.setHeader("Content-Type", "text/html");
+
+ if (!test) {
+ response.write('<script>parent.postMessage("done", "*");</script>');
+ } else if (typeof test == "string") {
+ response.write(test);
+ } else if (test.refresh) {
+ response.setHeader("Refresh", "0; url=" + test.refresh);
+ response.write(test.doc);
+ }
+}
diff --git a/docshell/test/mochitest/file_bug509055.html b/docshell/test/mochitest/file_bug509055.html
new file mode 100644
index 0000000000..ac30876bbf
--- /dev/null
+++ b/docshell/test/mochitest/file_bug509055.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test inner frame for bug 509055</title>
+</head>
+<body onhashchange="hashchangeCallback()">
+ file_bug509055.html
+</body>
+</html>
diff --git a/docshell/test/mochitest/file_bug511449.html b/docshell/test/mochitest/file_bug511449.html
new file mode 100644
index 0000000000..637732dbbf
--- /dev/null
+++ b/docshell/test/mochitest/file_bug511449.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<title>Used in test for bug 511449</title>
+<input type="text" id="input">
+<script type="text/javascript">
+ document.getElementById("input").focus();
+</script>
diff --git a/docshell/test/mochitest/file_bug540462.html b/docshell/test/mochitest/file_bug540462.html
new file mode 100644
index 0000000000..ab8c07eba5
--- /dev/null
+++ b/docshell/test/mochitest/file_bug540462.html
@@ -0,0 +1,25 @@
+<html>
+ <head>
+ <script>
+ // <!--
+ function test() {
+ document.open();
+ document.write(
+ `<html>
+ <body onload='opener.documentWriteLoad(); rel();'>
+ <a href='foo.html'>foo</a>
+ <script>
+ function rel() { setTimeout('location.reload()', 0); }
+ <\/script>
+ </body>
+ </html>`
+ );
+ document.close();
+ }
+ // -->
+ </script>
+ </head>
+ <body onload="setTimeout('test()', 0)">
+ Test for bug 540462
+ </body>
+</html>
diff --git a/docshell/test/mochitest/file_bug580069_1.html b/docshell/test/mochitest/file_bug580069_1.html
new file mode 100644
index 0000000000..7ab4610334
--- /dev/null
+++ b/docshell/test/mochitest/file_bug580069_1.html
@@ -0,0 +1,8 @@
+<html>
+<body onload='parent.page1Load();'>
+file_bug580069_1.html
+
+<form id='form' action='file_bug580069_2.sjs' method='POST'></form>
+
+</body>
+</html>
diff --git a/docshell/test/mochitest/file_bug580069_2.sjs b/docshell/test/mochitest/file_bug580069_2.sjs
new file mode 100644
index 0000000000..ff03c74e68
--- /dev/null
+++ b/docshell/test/mochitest/file_bug580069_2.sjs
@@ -0,0 +1,8 @@
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.write(
+ "<html><body onload='parent.page2Load(\"" +
+ request.method +
+ "\")'>file_bug580069_2.sjs</body></html>"
+ );
+}
diff --git a/docshell/test/mochitest/file_bug590573_1.html b/docshell/test/mochitest/file_bug590573_1.html
new file mode 100644
index 0000000000..b859ca7469
--- /dev/null
+++ b/docshell/test/mochitest/file_bug590573_1.html
@@ -0,0 +1,7 @@
+<html>
+<body onload='opener.page1Load();' onpageshow='opener.page1PageShow();'>
+
+<div style='height:10000px' id='div1'>This is a very tall div.</div>
+
+</body>
+</html>
diff --git a/docshell/test/mochitest/file_bug590573_2.html b/docshell/test/mochitest/file_bug590573_2.html
new file mode 100644
index 0000000000..5f9ca22be4
--- /dev/null
+++ b/docshell/test/mochitest/file_bug590573_2.html
@@ -0,0 +1,8 @@
+<html>
+<body onpopstate='opener.page2Popstate();' onload='opener.page2Load();'
+ onpageshow='opener.page2PageShow();'>
+
+<div style='height:300%' id='div2'>The second page also has a big div.</div>
+
+</body>
+</html>
diff --git a/docshell/test/mochitest/file_bug598895_1.html b/docshell/test/mochitest/file_bug598895_1.html
new file mode 100644
index 0000000000..d21f2b4a5d
--- /dev/null
+++ b/docshell/test/mochitest/file_bug598895_1.html
@@ -0,0 +1 @@
+<script>window.onload = function() { opener.postMessage("loaded", "*"); };</script><body>Should show</body>
diff --git a/docshell/test/mochitest/file_bug598895_2.html b/docshell/test/mochitest/file_bug598895_2.html
new file mode 100644
index 0000000000..680c9bf22b
--- /dev/null
+++ b/docshell/test/mochitest/file_bug598895_2.html
@@ -0,0 +1 @@
+<script>window.onload = function() { opener.postMessage("loaded", "*"); };</script><body></body>
diff --git a/docshell/test/mochitest/file_bug634834.html b/docshell/test/mochitest/file_bug634834.html
new file mode 100644
index 0000000000..3ff0897451
--- /dev/null
+++ b/docshell/test/mochitest/file_bug634834.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Nothing to see here; just an empty page.
+</body>
+</html>
diff --git a/docshell/test/mochitest/file_bug637644_1.html b/docshell/test/mochitest/file_bug637644_1.html
new file mode 100644
index 0000000000..d21f2b4a5d
--- /dev/null
+++ b/docshell/test/mochitest/file_bug637644_1.html
@@ -0,0 +1 @@
+<script>window.onload = function() { opener.postMessage("loaded", "*"); };</script><body>Should show</body>
diff --git a/docshell/test/mochitest/file_bug637644_2.html b/docshell/test/mochitest/file_bug637644_2.html
new file mode 100644
index 0000000000..680c9bf22b
--- /dev/null
+++ b/docshell/test/mochitest/file_bug637644_2.html
@@ -0,0 +1 @@
+<script>window.onload = function() { opener.postMessage("loaded", "*"); };</script><body></body>
diff --git a/docshell/test/mochitest/file_bug640387.html b/docshell/test/mochitest/file_bug640387.html
new file mode 100644
index 0000000000..3a939fb41e
--- /dev/null
+++ b/docshell/test/mochitest/file_bug640387.html
@@ -0,0 +1,26 @@
+<html>
+<body onhashchange='hashchange()' onload='load()' onpopstate='popstate()'>
+
+<script>
+function hashchange() {
+ var f = (opener || parent).childHashchange;
+ if (f)
+ f();
+}
+
+function load() {
+ var f = (opener || parent).childLoad;
+ if (f)
+ f();
+}
+
+function popstate() {
+ var f = (opener || parent).childPopstate;
+ if (f)
+ f();
+}
+</script>
+
+Not much to see here...
+</body>
+</html>
diff --git a/docshell/test/mochitest/file_bug653741.html b/docshell/test/mochitest/file_bug653741.html
new file mode 100644
index 0000000000..3202b52573
--- /dev/null
+++ b/docshell/test/mochitest/file_bug653741.html
@@ -0,0 +1,13 @@
+<html>
+<body onload='(parent || opener).childLoad()'>
+
+<div style='height:500px; background:yellow'>
+<a id='#top'>Top of the page</a>
+</div>
+
+<div id='bottom'>
+<a id='#bottom'>Bottom of the page</a>
+</div>
+
+</body>
+</html>
diff --git a/docshell/test/mochitest/file_bug660404 b/docshell/test/mochitest/file_bug660404
new file mode 100644
index 0000000000..ed773420ef
--- /dev/null
+++ b/docshell/test/mochitest/file_bug660404
@@ -0,0 +1,13 @@
+--testingtesting
+Content-Type: text/html
+
+<script>
+ var bc = new BroadcastChannel("bug660404_multipart");
+ bc.postMessage({command: "finishTest",
+ textContent: window.document.documentElement.textContent,
+ innerHTML: window.document.documentElement.innerHTML
+ });
+ bc.close();
+ window.close();
+</script>
+--testingtesting--
diff --git a/docshell/test/mochitest/file_bug660404-1.html b/docshell/test/mochitest/file_bug660404-1.html
new file mode 100644
index 0000000000..878bd80426
--- /dev/null
+++ b/docshell/test/mochitest/file_bug660404-1.html
@@ -0,0 +1,12 @@
+<script>
+ var bc = new BroadcastChannel("bug660404");
+ window.onload = function() {
+ setTimeout(() => {
+ window.onpagehide = function(ev) {
+ bc.postMessage({command: "pagehide", persisted: ev.persisted});
+ bc.close();
+ };
+ window.location.href = "file_bug660404";
+ }, 0);
+ };
+</script>
diff --git a/docshell/test/mochitest/file_bug660404^headers^ b/docshell/test/mochitest/file_bug660404^headers^
new file mode 100644
index 0000000000..5c821f3f48
--- /dev/null
+++ b/docshell/test/mochitest/file_bug660404^headers^
@@ -0,0 +1 @@
+Content-Type: multipart/x-mixed-replace; boundary="testingtesting"
diff --git a/docshell/test/mochitest/file_bug662170.html b/docshell/test/mochitest/file_bug662170.html
new file mode 100644
index 0000000000..3202b52573
--- /dev/null
+++ b/docshell/test/mochitest/file_bug662170.html
@@ -0,0 +1,13 @@
+<html>
+<body onload='(parent || opener).childLoad()'>
+
+<div style='height:500px; background:yellow'>
+<a id='#top'>Top of the page</a>
+</div>
+
+<div id='bottom'>
+<a id='#bottom'>Bottom of the page</a>
+</div>
+
+</body>
+</html>
diff --git a/docshell/test/mochitest/file_bug668513.html b/docshell/test/mochitest/file_bug668513.html
new file mode 100644
index 0000000000..ae417a35bd
--- /dev/null
+++ b/docshell/test/mochitest/file_bug668513.html
@@ -0,0 +1,101 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test file for Bug 668513</title>
+<script>
+ var SimpleTest = opener.SimpleTest;
+ var ok = opener.ok;
+ var is = opener.is;
+
+ function finish() {
+ SimpleTest.finish();
+ close();
+ }
+
+ function onload_test() {
+ var win = frames[0];
+ ok(win.performance, "Window.performance should be defined");
+ ok(win.performance.navigation, "Window.performance.navigation should be defined");
+ var navigation = win.performance && win.performance.navigation;
+ if (navigation === undefined) {
+ // avoid script errors
+ finish();
+ return;
+ }
+
+ // do this with a timeout to see the visuals of the navigations.
+ setTimeout(nav_frame, 100);
+ }
+
+ var step = 1;
+ function nav_frame() {
+ var navigation_frame = frames[0];
+ var navigation = navigation_frame.performance.navigation;
+ switch (step) {
+ case 1:
+ {
+ navigation_frame.location.href = "bug570341_recordevents.html";
+ step++;
+ break;
+ }
+ case 2:
+ {
+ is(navigation.type, navigation.TYPE_NAVIGATE,
+ "Expected window.performance.navigation.type == TYPE_NAVIGATE");
+ navigation_frame.history.back();
+ step++;
+ break;
+ }
+ case 3:
+ {
+ is(navigation.type, navigation.TYPE_BACK_FORWARD,
+ "Expected window.performance.navigation.type == TYPE_BACK_FORWARD");
+ step++;
+ navigation_frame.history.forward();
+ break;
+ }
+ case 4:
+ {
+ is(navigation.type, navigation.TYPE_BACK_FORWARD,
+ "Expected window.performance.navigation.type == TYPE_BACK_FORWARD");
+ navigation_frame.location.href = "bug668513_redirect.html";
+ step++;
+ break;
+ }
+ case 5:
+ {
+ is(navigation.type, navigation.TYPE_NAVIGATE,
+ "Expected timing.navigation.type as TYPE_NAVIGATE");
+ is(navigation.redirectCount, 1,
+ "Expected navigation.redirectCount == 1 on an server redirected navigation");
+
+ var timing = navigation_frame.performance && navigation_frame.performance.timing;
+ if (timing === undefined) {
+ // avoid script errors
+ finish();
+ break;
+ }
+ ok(timing.navigationStart > 0, "navigationStart should be > 0");
+ var sequence = ["navigationStart", "redirectStart", "redirectEnd", "fetchStart"];
+ for (var j = 1; j < sequence.length; ++j) {
+ var prop = sequence[j];
+ var prevProp = sequence[j - 1];
+ ok(timing[prevProp] <= timing[prop],
+ ["Expected ", prevProp, " to happen before ", prop,
+ ", got ", prevProp, " = ", timing[prevProp],
+ ", ", prop, " = ", timing[prop]].join(""));
+ }
+ step++;
+ finish();
+ break;
+ }
+ }
+ }
+</script>
+</head>
+<body>
+<div id="frames">
+<iframe name="child0" onload="onload_test();" src="navigation/blank.html"></iframe>
+</div>
+</body>
+</html>
diff --git a/docshell/test/mochitest/file_bug669671.sjs b/docshell/test/mochitest/file_bug669671.sjs
new file mode 100644
index 0000000000..5871419de8
--- /dev/null
+++ b/docshell/test/mochitest/file_bug669671.sjs
@@ -0,0 +1,17 @@
+function handleRequest(request, response) {
+ var count = parseInt(getState("count"));
+ if (!count || request.queryString == "countreset") {
+ count = 0;
+ }
+
+ setState("count", count + 1 + "");
+
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Cache-Control", "max-age=0");
+ response.write(
+ '<html><body onload="opener.onChildLoad()" ' +
+ "onunload=\"parseInt('0')\">" +
+ count +
+ "</body></html>"
+ );
+}
diff --git a/docshell/test/mochitest/file_bug675587.html b/docshell/test/mochitest/file_bug675587.html
new file mode 100644
index 0000000000..842ab9ae79
--- /dev/null
+++ b/docshell/test/mochitest/file_bug675587.html
@@ -0,0 +1 @@
+<script>location.hash = "";</script>
diff --git a/docshell/test/mochitest/file_bug680257.html b/docshell/test/mochitest/file_bug680257.html
new file mode 100644
index 0000000000..ff480e96a5
--- /dev/null
+++ b/docshell/test/mochitest/file_bug680257.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <style type='text/css'>
+ a { color: black; }
+ a:target { color: red; }
+ </style>
+</head>
+
+<body onload='(opener || parent).popupLoaded()'>
+
+<a id='a' href='#a'>link</a>
+<a id='b' href='#b'>link2</a>
+
+</body>
+</html>
diff --git a/docshell/test/mochitest/file_bug703855.html b/docshell/test/mochitest/file_bug703855.html
new file mode 100644
index 0000000000..fe15b6e3df
--- /dev/null
+++ b/docshell/test/mochitest/file_bug703855.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<!-- Just need an empty file here, as long as it's served over HTTP -->
diff --git a/docshell/test/mochitest/file_bug728939.html b/docshell/test/mochitest/file_bug728939.html
new file mode 100644
index 0000000000..1cd52a44e1
--- /dev/null
+++ b/docshell/test/mochitest/file_bug728939.html
@@ -0,0 +1,3 @@
+<html>
+<body onload="opener.popupLoaded()">file_bug728939</body>
+</html>
diff --git a/docshell/test/mochitest/file_close_onpagehide1.html b/docshell/test/mochitest/file_close_onpagehide1.html
new file mode 100644
index 0000000000..ccf3b625a1
--- /dev/null
+++ b/docshell/test/mochitest/file_close_onpagehide1.html
@@ -0,0 +1,5 @@
+<script>
+ window.onload = () => {
+ opener.postMessage("initial", "*");
+ };
+</script>
diff --git a/docshell/test/mochitest/file_close_onpagehide2.html b/docshell/test/mochitest/file_close_onpagehide2.html
new file mode 100644
index 0000000000..a8e9479f47
--- /dev/null
+++ b/docshell/test/mochitest/file_close_onpagehide2.html
@@ -0,0 +1,5 @@
+<script>
+ window.onload = () => {
+ opener.postMessage("second", "*");
+ };
+</script>;
diff --git a/docshell/test/mochitest/file_compressed_multipart b/docshell/test/mochitest/file_compressed_multipart
new file mode 100644
index 0000000000..3c56226951
--- /dev/null
+++ b/docshell/test/mochitest/file_compressed_multipart
Binary files differ
diff --git a/docshell/test/mochitest/file_compressed_multipart^headers^ b/docshell/test/mochitest/file_compressed_multipart^headers^
new file mode 100644
index 0000000000..9376927812
--- /dev/null
+++ b/docshell/test/mochitest/file_compressed_multipart^headers^
@@ -0,0 +1,2 @@
+Content-Type: multipart/x-mixed-replace; boundary="testingtesting"
+Content-Encoding: gzip
diff --git a/docshell/test/mochitest/file_content_javascript_loads_frame.html b/docshell/test/mochitest/file_content_javascript_loads_frame.html
new file mode 100644
index 0000000000..9e2851aa8b
--- /dev/null
+++ b/docshell/test/mochitest/file_content_javascript_loads_frame.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<script type="application/javascript">
+"use strict";
+
+addEventListener("message", event => {
+ if ("ping" in event.data) {
+ event.source.postMessage({ pong: event.data.ping }, event.origin);
+ }
+});
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/docshell/test/mochitest/file_content_javascript_loads_root.html b/docshell/test/mochitest/file_content_javascript_loads_root.html
new file mode 100644
index 0000000000..b9f2c1faa7
--- /dev/null
+++ b/docshell/test/mochitest/file_content_javascript_loads_root.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<script type="application/javascript">
+"use strict";
+
+window.onload = () => {
+ opener.postMessage("ready", "*");
+};
+
+// eslint-disable-next-line no-shadow
+function promiseMessage(source, filter = event => true) {
+ return new Promise(resolve => {
+ function listener(event) {
+ if (event.source == source && filter(event)) {
+ removeEventListener("message", listener);
+ resolve(event);
+ }
+ }
+ addEventListener("message", listener);
+ });
+}
+
+// Sends a message to the given target window and waits for the response.
+function ping(target) {
+ let msg = { ping: Math.random() };
+ target.postMessage(msg, "*");
+ return promiseMessage(
+ target,
+ event => event.data && event.data.pong == msg.ping
+ );
+}
+
+function setFrameLocation(name, uri) {
+ window[name].location = uri;
+}
+</script>
+</head>
+<body>
+</body>
+</html>
diff --git a/docshell/test/mochitest/file_form_restoration_no_store.html b/docshell/test/mochitest/file_form_restoration_no_store.html
new file mode 100644
index 0000000000..6e8756a693
--- /dev/null
+++ b/docshell/test/mochitest/file_form_restoration_no_store.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script>
+ window.addEventListener("pageshow", ({ persisted }) => {
+ let bc = new BroadcastChannel("form_restoration");
+ bc.addEventListener("message", ({ data }) => {
+ switch (data) {
+ case "enter_data":
+ document.getElementById("formElement").value = "test";
+ break;
+ case "reload":
+ bc.close();
+ location.reload();
+ break;
+ case "navigate":
+ bc.close();
+ document.location = "file_form_restoration_no_store.html?1";
+ break;
+ case "back":
+ bc.close();
+ history.back();
+ break;
+ case "close":
+ bc.close();
+ window.close();
+ break;
+ }
+ });
+ bc.postMessage({ persisted, formData: document.getElementById("formElement").value });
+ });
+ </script>
+</head>
+<body>
+ <input id="formElement" type="text" value="initial">
+</body>
+</html>
diff --git a/docshell/test/mochitest/file_form_restoration_no_store.html^headers^ b/docshell/test/mochitest/file_form_restoration_no_store.html^headers^
new file mode 100644
index 0000000000..4030ea1d3d
--- /dev/null
+++ b/docshell/test/mochitest/file_form_restoration_no_store.html^headers^
@@ -0,0 +1 @@
+Cache-Control: no-store
diff --git a/docshell/test/mochitest/file_framedhistoryframes.html b/docshell/test/mochitest/file_framedhistoryframes.html
new file mode 100644
index 0000000000..314f9c72d8
--- /dev/null
+++ b/docshell/test/mochitest/file_framedhistoryframes.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<iframe id="iframe" src="historyframes.html"></iframe>
+<script type="application/javascript">
+
+var SimpleTest = window.opener.SimpleTest;
+var is = window.opener.is;
+
+function done() {
+ window.opener.done();
+}
+
+</script>
+</body>
+</html>
diff --git a/docshell/test/mochitest/file_load_during_reload.html b/docshell/test/mochitest/file_load_during_reload.html
new file mode 100644
index 0000000000..600d5c1728
--- /dev/null
+++ b/docshell/test/mochitest/file_load_during_reload.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <script>
+ function notifyOpener() {
+ opener.postMessage("loaded", "*");
+ }
+ </script>
+ </head>
+ <body onload="notifyOpener()">
+ </body>
+</html>
diff --git a/docshell/test/mochitest/file_pushState_after_document_open.html b/docshell/test/mochitest/file_pushState_after_document_open.html
new file mode 100644
index 0000000000..97a6954f2e
--- /dev/null
+++ b/docshell/test/mochitest/file_pushState_after_document_open.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<script>
+ document.addEventListener("DOMContentLoaded", function() {
+ document.open();
+ document.write("<!DOCTYPE html>New Document here");
+ document.close();
+ // Notify parent via postMessage, since otherwise exceptions will not get
+ // caught by its onerror handler.
+ parent.postMessage("doTest", "*");
+ });
+</script>
diff --git a/docshell/test/mochitest/file_redirect_history.html b/docshell/test/mochitest/file_redirect_history.html
new file mode 100644
index 0000000000..3971faf4fd
--- /dev/null
+++ b/docshell/test/mochitest/file_redirect_history.html
@@ -0,0 +1,18 @@
+<html>
+ <head>
+ <script>
+ function loaded() {
+ addEventListener("message", ({ data }) => {
+ document.getElementById("form").action = data;
+ document.getElementById("button").click();
+ }, { once: true });
+ opener.postMessage("loaded", "*");
+ }
+ </script>
+ </head>
+ <body onload="loaded();">
+ <form id="form" method="POST">
+ <input id="button" type="submit" />
+ </form>
+ </body>
+</html>
diff --git a/docshell/test/mochitest/form_submit.sjs b/docshell/test/mochitest/form_submit.sjs
new file mode 100644
index 0000000000..1a1fa5d89c
--- /dev/null
+++ b/docshell/test/mochitest/form_submit.sjs
@@ -0,0 +1,40 @@
+"use strict";
+
+const CC = Components.Constructor;
+const BinaryInputStream = CC(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+
+const BinaryOutputStream = CC(
+ "@mozilla.org/binaryoutputstream;1",
+ "nsIBinaryOutputStream",
+ "setOutputStream"
+);
+
+function log(str) {
+ // dump(`LOG: ${str}\n`);
+}
+
+async function handleRequest(request, response) {
+ if (request.method !== "POST") {
+ throw new Error("Expected a post request");
+ } else {
+ log("Reading request");
+ let available = 0;
+ let inputStream = new BinaryInputStream(request.bodyInputStream);
+ while ((available = inputStream.available()) > 0) {
+ log(inputStream.readBytes(available));
+ }
+ }
+
+ log("Setting Headers");
+ response.setHeader("Content-Type", "text/html", false);
+ response.setStatusLine(request.httpVersion, "200", "OK");
+ log("Writing body");
+ response.write(
+ '<script>"use strict"; let target = opener ? opener : parent; target.postMessage("done", "*");</script>'
+ );
+ log("Done");
+}
diff --git a/docshell/test/mochitest/form_submit_redirect.sjs b/docshell/test/mochitest/form_submit_redirect.sjs
new file mode 100644
index 0000000000..dbc32b9643
--- /dev/null
+++ b/docshell/test/mochitest/form_submit_redirect.sjs
@@ -0,0 +1,15 @@
+"use strict";
+
+Cu.importGlobalProperties(["URLSearchParams"]);
+
+async function handleRequest(request, response) {
+ if (request.method !== "POST") {
+ throw new Error("Expected a post request");
+ } else {
+ let params = new URLSearchParams(request.queryString);
+ let redirect = params.get("redirectTo");
+
+ response.setStatusLine(request.httpVersion, 302, "Moved Temporarily");
+ response.setHeader("Location", redirect);
+ }
+}
diff --git a/docshell/test/mochitest/historyframes.html b/docshell/test/mochitest/historyframes.html
new file mode 100644
index 0000000000..31f46a5071
--- /dev/null
+++ b/docshell/test/mochitest/historyframes.html
@@ -0,0 +1,176 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=602256
+-->
+<head>
+ <title>Test for Bug 602256</title>
+</head>
+<body onload="SimpleTest.executeSoon(run_test)">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=602256">Mozilla Bug 602256</a>
+<div id="content">
+ <iframe id="iframe" src="start_historyframe.html"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 602256 */
+
+var testWin = window.opener ? window.opener : window.parent;
+
+var SimpleTest = testWin.SimpleTest;
+function is() { testWin.is.apply(testWin, arguments); }
+
+var gFrame = null;
+
+function gState() {
+ return location.hash.replace(/^#/, "");
+}
+
+function waitForLoad(aCallback) {
+ function listener() {
+ gFrame.removeEventListener("load", listener);
+ SimpleTest.executeSoon(aCallback);
+ }
+
+ gFrame.addEventListener("load", listener);
+}
+
+function loadContent(aURL, aCallback) {
+ waitForLoad(aCallback);
+
+ gFrame.src = aURL;
+}
+
+function getURL() {
+ return gFrame.contentDocument.documentURI;
+}
+
+function getContent() {
+ return gFrame.contentDocument.getElementById("text").textContent;
+}
+
+var BASE_URI = "http://mochi.test:8888/tests/docshell/test/mochitest/";
+var START = BASE_URI + "start_historyframe.html";
+var URL1 = BASE_URI + "url1_historyframe.html";
+var URL2 = BASE_URI + "url2_historyframe.html";
+
+function run_test() {
+ window.location.hash = "START";
+
+ gFrame = document.getElementById("iframe");
+
+ test_basic_inner_navigation();
+}
+
+function end_test() {
+ testWin.done();
+}
+
+var gTestContinuation = null;
+function continueAsync() {
+ setTimeout(function() { gTestContinuation.next(); })
+}
+
+function test_basic_inner_navigation() {
+ // Navigate the inner frame a few times
+ loadContent(URL1, function() {
+ is(getURL(), URL1, "URL should be correct");
+ is(getContent(), "Test1", "Page should be correct");
+
+ loadContent(URL2, function() {
+ is(getURL(), URL2, "URL should be correct");
+ is(getContent(), "Test2", "Page should be correct");
+
+ // Test that history is working
+ waitForLoad(function() {
+ is(getURL(), URL1, "URL should be correct");
+ is(getContent(), "Test1", "Page should be correct");
+
+ waitForLoad(function() {
+ is(getURL(), URL2, "URL should be correct");
+ is(getContent(), "Test2", "Page should be correct");
+
+ gTestContinuation = test_state_navigation();
+ gTestContinuation.next();
+ });
+ window.history.forward();
+ });
+ window.history.back();
+ });
+ });
+}
+
+function* test_state_navigation() {
+ window.location.hash = "STATE1";
+
+ is(getURL(), URL2, "URL should be correct");
+ is(getContent(), "Test2", "Page should be correct");
+
+ window.location.hash = "STATE2";
+
+ is(getURL(), URL2, "URL should be correct");
+ is(getContent(), "Test2", "Page should be correct");
+
+ window.addEventListener("popstate", (e) => {
+ continueAsync();
+ }, {once: true});
+ window.history.back();
+ yield;
+
+ is(gState(), "STATE1", "State should be correct after going back");
+ is(getURL(), URL2, "URL should be correct");
+ is(getContent(), "Test2", "Page should be correct");
+
+ window.addEventListener("popstate", (e) => {
+ continueAsync();
+ }, {once: true});
+ window.history.forward();
+ yield;
+
+ is(gState(), "STATE2", "State should be correct after going forward");
+ is(getURL(), URL2, "URL should be correct");
+ is(getContent(), "Test2", "Page should be correct");
+
+ window.addEventListener("popstate", (e) => {
+ continueAsync();
+ }, {once: true});
+ window.history.back();
+ yield;
+
+ window.addEventListener("popstate", (e) => {
+ continueAsync();
+ }, {once: true});
+ window.history.back();
+ yield;
+
+ is(gState(), "START", "State should be correct");
+ is(getURL(), URL2, "URL should be correct");
+ is(getContent(), "Test2", "Page should be correct");
+
+ waitForLoad(function() {
+ is(getURL(), URL1, "URL should be correct");
+ is(getContent(), "Test1", "Page should be correct");
+
+ waitForLoad(function() {
+ is(gState(), "START", "State should be correct");
+ is(getURL(), START, "URL should be correct");
+ is(getContent(), "Start", "Page should be correct");
+
+ end_test();
+ });
+
+ window.history.back();
+
+ is(gState(), "START", "State should be correct after going back twice");
+ });
+
+ window.history.back();
+ continueAsync();
+ yield;
+ is(gState(), "START", "State should be correct");
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/mochitest.ini b/docshell/test/mochitest/mochitest.ini
new file mode 100644
index 0000000000..598d629673
--- /dev/null
+++ b/docshell/test/mochitest/mochitest.ini
@@ -0,0 +1,209 @@
+[DEFAULT]
+support-files =
+ bug404548-subframe.html
+ bug404548-subframe_window.html
+ bug413310-post.sjs
+ bug413310-subframe.html
+ bug529119-window.html
+ bug570341_recordevents.html
+ bug668513_redirect.html
+ bug668513_redirect.html^headers^
+ bug691547_frame.html
+ dummy_page.html
+ file_anchor_scroll_after_document_open.html
+ file_bfcache_plus_hash_1.html
+ file_bfcache_plus_hash_2.html
+ file_bug385434_1.html
+ file_bug385434_2.html
+ file_bug385434_3.html
+ file_bug475636.sjs
+ file_bug509055.html
+ file_bug540462.html
+ file_bug580069_1.html
+ file_bug580069_2.sjs
+ file_bug598895_1.html
+ file_bug598895_2.html
+ file_bug590573_1.html
+ file_bug590573_2.html
+ file_bug634834.html
+ file_bug637644_1.html
+ file_bug637644_2.html
+ file_bug640387.html
+ file_bug653741.html
+ file_bug660404
+ file_bug660404^headers^
+ file_bug660404-1.html
+ file_bug662170.html
+ file_bug669671.sjs
+ file_bug680257.html
+ file_bug703855.html
+ file_bug728939.html
+ file_bug1121701_1.html
+ file_bug1121701_2.html
+ file_bug1186774.html
+ file_bug1151421.html
+ file_bug1450164.html
+ file_close_onpagehide1.html
+ file_close_onpagehide2.html
+ file_compressed_multipart
+ file_compressed_multipart^headers^
+ file_pushState_after_document_open.html
+ historyframes.html
+ ping.html
+ start_historyframe.html
+ url1_historyframe.html
+ url2_historyframe.html
+
+[test_anchor_scroll_after_document_open.html]
+[test_bfcache_plus_hash.html]
+[test_bug1422334.html]
+support-files =
+ bug1422334_redirect.html
+ bug1422334_redirect.html^headers^
+ !/docshell/test/navigation/blank.html
+[test_bug385434.html]
+[test_bug387979.html]
+[test_bug402210.html]
+skip-if =
+ http3
+[test_bug404548.html]
+[test_bug413310.html]
+skip-if = true
+# Disabled for too many intermittent failures (bug 719186)
+[test_bug475636.html]
+[test_bug509055.html]
+[test_bug511449.html]
+skip-if = toolkit != "cocoa" || headless # Headless: bug 1410525
+support-files = file_bug511449.html
+[test_bug529119-1.html]
+skip-if =
+ fission && os == "android" # Bug 1827321
+[test_bug529119-2.html]
+skip-if =
+ http3
+ fission && os == "android" # Bug 1827321
+[test_bug530396.html]
+support-files = bug530396-noref.sjs bug530396-subframe.html
+skip-if =
+ http3
+[test_bug540462.html]
+skip-if = toolkit == 'android' && debug
+[test_bug551225.html]
+[test_bug570341.html]
+skip-if = (verify && !debug && (os == 'win'))
+[test_bug580069.html]
+skip-if = (verify && !debug && (os == 'win'))
+[test_bug590573.html]
+[test_bug598895.html]
+[test_bug634834.html]
+skip-if =
+ http3
+[test_bug637644.html]
+[test_bug640387_1.html]
+[test_bug640387_2.html]
+[test_bug653741.html]
+[test_bug660404.html]
+[test_bug662170.html]
+[test_bug668513.html]
+support-files = file_bug668513.html
+[test_bug669671.html]
+[test_bug675587.html]
+support-files = file_bug675587.html
+[test_bug680257.html]
+[test_bug691547.html]
+[test_bug694612.html]
+[test_bug703855.html]
+[test_bug728939.html]
+[test_bug797909.html]
+[test_bug1045096.html]
+[test_bug1121701.html]
+[test_bug1151421.html]
+[test_bug1186774.html]
+[test_bug1450164.html]
+[test_bug1507702.html]
+[test_bug1645781.html]
+support-files =
+ form_submit.sjs
+skip-if =
+ http3
+[test_bug1729662.html]
+support-files =
+ file_bug1729662.html
+[test_bug1740516.html]
+support-files =
+ file_bug1740516_1.html
+ file_bug1740516_1_inner.html
+ file_bug1740516_2.html
+[test_form_restoration.html]
+support-files =
+ file_form_restoration_no_store.html
+ file_form_restoration_no_store.html^headers^
+[test_bug1741132.html]
+support-files =
+ file_bug1741132.html
+skip-if = toolkit == "android" && !sessionHistoryInParent
+[test_bug1742865.html]
+support-files =
+ file_bug1742865.sjs
+ file_bug1742865_outer.sjs
+skip-if =
+ toolkit == "android" && debug && fission && verify # Bug 1745937
+ http3
+[test_bug1743353.html]
+support-files =
+ file_bug1743353.html
+[test_bug1747033.html]
+support-files =
+ file_bug1747033.sjs
+skip-if =
+ http3
+[test_bug1773192.html]
+support-files =
+ file_bug1773192_1.html
+ file_bug1773192_2.html
+ file_bug1773192_3.sjs
+[test_close_onpagehide_by_history_back.html]
+[test_close_onpagehide_by_window_close.html]
+[test_compressed_multipart.html]
+[test_content_javascript_loads.html]
+support-files =
+ file_content_javascript_loads_root.html
+ file_content_javascript_loads_frame.html
+skip-if =
+ http3
+[test_forceinheritprincipal_overrule_owner.html]
+skip-if =
+ http3
+[test_framedhistoryframes.html]
+support-files = file_framedhistoryframes.html
+skip-if =
+ http3
+[test_load_during_reload.html]
+support-files = file_load_during_reload.html
+[test_pushState_after_document_open.html]
+[test_navigate_after_pagehide.html]
+skip-if =
+ http3
+[test_redirect_history.html]
+support-files =
+ file_redirect_history.html
+ form_submit_redirect.sjs
+skip-if =
+ http3
+[test_windowedhistoryframes.html]
+skip-if =
+ (!debug && os == 'android') # Bug 1573892
+ http3
+[test_triggeringprincipal_location_seturi.html]
+skip-if =
+ http3
+[test_double_submit.html]
+support-files =
+ clicker.html
+ double_submit.sjs
+skip-if =
+ http3
+[test_iframe_srcdoc_to_remote.html]
+skip-if =
+ http3
+[test_javascript_sandboxed_popup.html]
diff --git a/docshell/test/mochitest/ping.html b/docshell/test/mochitest/ping.html
new file mode 100644
index 0000000000..7d84560dd1
--- /dev/null
+++ b/docshell/test/mochitest/ping.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<script>
+ "use strict";
+ let target = (window.opener || window.parent);
+ target.postMessage("ping", "*");
+</script>
diff --git a/docshell/test/mochitest/start_historyframe.html b/docshell/test/mochitest/start_historyframe.html
new file mode 100644
index 0000000000..a791af4e64
--- /dev/null
+++ b/docshell/test/mochitest/start_historyframe.html
@@ -0,0 +1 @@
+<p id='text'>Start</p>
diff --git a/docshell/test/mochitest/test_anchor_scroll_after_document_open.html b/docshell/test/mochitest/test_anchor_scroll_after_document_open.html
new file mode 100644
index 0000000000..5309f6cf19
--- /dev/null
+++ b/docshell/test/mochitest/test_anchor_scroll_after_document_open.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=881487
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 881487</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 881487 */
+ SimpleTest.waitForExplicitFinish();
+ // Child needs to invoke us, otherwise our onload will fire before the child
+ // has done the write/close bit.
+ var gotOnload = false;
+ addLoadEvent(function() {
+ gotOnload = true;
+ });
+ onmessage = function handleMessage(msg) {
+ if (msg.data == "doTest") {
+ if (!gotOnload) {
+ addLoadEvent(function() { handleMessage(msg); });
+ return;
+ }
+ frames[0].onscroll = function() {
+ ok(true, "Got a scroll event");
+ SimpleTest.finish();
+ };
+ frames[0].location.hash = "#target";
+ return;
+ }
+ if (msg.data == "haveHash") {
+ ok(false, "Child got reloaded");
+ } else {
+ ok(false, "Unexpected message");
+ }
+ SimpleTest.finish();
+ };
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=881487">Mozilla Bug 881487</a>
+<p id="display">
+ <!-- iframe goes here so it can scroll -->
+<iframe src="file_anchor_scroll_after_document_open.html"></iframe>
+</p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bfcache_plus_hash.html b/docshell/test/mochitest/test_bfcache_plus_hash.html
new file mode 100644
index 0000000000..cb5bc06f21
--- /dev/null
+++ b/docshell/test/mochitest/test_bfcache_plus_hash.html
@@ -0,0 +1,153 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=646641
+-->
+<head>
+ <title>Test for Bug 646641</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=646641">Mozilla Bug 646641</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 646641 */
+
+/**
+ * Steps:
+ * - Main page (this one) opens file_bfcache_plus_hash_1.html (subpage 1)
+ * - subpage 1 sends msg { "childLoad", 1 }
+ * - subpage 1 sends msg { "childPageshow", 1 }
+ * - main page sends message "pushState"
+ * - subpage 1 does pushState()
+ * - subpage 1 navigates to file_bfcache_plus_hash_2.html (subpage 2)
+ * - subpage 2 sends msg { "childLoad", 2 }
+ * - subpage 2 sends msg { "childPageshow", 2 }
+ * - main page sends msg "go-2"
+ * - subpage 2 goes back two history entries
+ * - subpage 1 sends msg { "childPageshow", 1 }
+ * - Receiving only this msg shows we have retrieved the document from bfcache
+ * - main page sends msg "close"
+ * - subpage 1 sends msg "closed"
+ */
+SimpleTest.waitForExplicitFinish();
+
+function debug(msg) {
+ // Wrap dump so we can turn debug messages on and off easily.
+ dump(msg + "\n");
+}
+
+var expectedLoadNum = -1;
+var expectedPageshowNum = -1;
+
+function waitForLoad(n) {
+ debug("Waiting for load " + n);
+ expectedLoadNum = n;
+}
+
+function waitForShow(n) {
+ debug("Waiting for show " + n);
+ expectedPageshowNum = n;
+}
+
+
+
+function executeTest() {
+ function* test() {
+ window.open("file_bfcache_plus_hash_1.html", "", "noopener");
+ waitForLoad(1);
+ waitForShow(1);
+ yield undefined;
+ yield undefined;
+
+ bc1.postMessage("pushState");
+
+ waitForLoad(2);
+ waitForShow(2);
+ yield undefined;
+ yield undefined;
+
+ // Now go back 2. The first page should be retrieved from bfcache.
+ bc2.postMessage("go-2");
+ waitForShow(1);
+ yield undefined;
+
+ bc1.postMessage("close");
+ }
+
+ var bc1 = new BroadcastChannel("bug646641_1");
+ var bc2 = new BroadcastChannel("bug646641_2");
+ bc1.onmessage = (msgEvent) => {
+ var msg = msgEvent.data.message;
+ var n = msgEvent.data.num;
+ if (msg == "childLoad") {
+ if (n == expectedLoadNum) {
+ debug("Got load " + n);
+ expectedLoadNum = -1;
+
+ // Spin the event loop before calling gGen.next() so the generator runs
+ // outside the onload handler. This prevents us from encountering all
+ // sorts of docshell quirks.
+ setTimeout(function() { gGen.next(); }, 0);
+ } else {
+ debug("Got unexpected load " + n);
+ ok(false, "Got unexpected load " + n);
+ }
+ } else if (msg == "childPageshow") {
+ if (n == expectedPageshowNum) {
+ debug("Got expected pageshow " + n);
+ expectedPageshowNum = -1;
+ ok(true, "Got expected pageshow " + n);
+ setTimeout(function() { gGen.next(); }, 0);
+ } else {
+ debug("Got unexpected pageshow " + n);
+ ok(false, "Got unexpected pageshow " + n);
+ }
+ } else if (msg == "closed") {
+ bc1.close();
+ bc2.close();
+ SimpleTest.finish();
+ }
+ }
+
+ bc2.onmessage = bc1.onmessage;
+
+ var gGen = test();
+
+ // If Fission is disabled, the pref is no-op.
+ SpecialPowers.pushPrefEnv({set: [["fission.bfcacheInParent", true]]}, () => {
+ gGen.next();
+ });
+}
+if (isXOrigin) {
+ // Bug 1746646: Make mochitests work with TCP enabled (cookieBehavior = 5)
+ // Acquire storage access permission here so that the BroadcastChannel used to
+ // communicate with the opened windows works in xorigin tests. Otherwise,
+ // the iframe containing this page is isolated from first-party storage access,
+ // which isolates BroadcastChannel communication.
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ SpecialPowers.addPermission("storageAccessAPI", true, window.location.href).then(() => {
+ SpecialPowers.wrap(document).requestStorageAccess().then(() => {
+ SpecialPowers.pushPrefEnv({
+ set: [["privacy.partition.always_partition_third_party_non_cookie_storage", false]],
+ }).then(() => {
+ executeTest();
+ });
+ });
+ });
+} else {
+ executeTest();
+}
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug1045096.html b/docshell/test/mochitest/test_bug1045096.html
new file mode 100644
index 0000000000..2df2232b7e
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1045096.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1045096
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1045096</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1045096">Mozilla Bug 1045096</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+ <script type="application/javascript">
+
+ /** Test for Bug 1045096 */
+ var i = document.createElement("iframe");
+ i.src = "javascript:false"; // This is required!
+ $("content").appendChild(i);
+ ok(i.contentWindow.performance, "Should have a performance object");
+ </script>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug1121701.html b/docshell/test/mochitest/test_bug1121701.html
new file mode 100644
index 0000000000..cd0b2529d4
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1121701.html
@@ -0,0 +1,108 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1121701
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1121701</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1121701 */
+ SimpleTest.waitForExplicitFinish();
+
+ var testUrl1 = "file_bug1121701_1.html";
+ var testUrl2 = "file_bug1121701_2.html";
+
+ var page1LoadCount = 0;
+ let page1Done = {};
+ page1Done.promise = new Promise(resolve => {
+ page1Done.resolve = resolve;
+ });
+ let page2Done = {};
+ page2Done.promise = new Promise(resolve => {
+ page2Done.resolve = resolve;
+ });
+
+ addLoadEvent(async function() {
+
+ // Bug 1746646: Make mochitests work with TCP enabled (cookieBehavior = 5)
+ // Acquire storage access permission here so that the BroadcastChannel used to
+ // communicate with the opened windows works in xorigin tests. Otherwise,
+ // the iframe containing this page is isolated from first-party storage access,
+ // which isolates BroadcastChannel communication.
+ if (isXOrigin) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.partition.always_partition_third_party_non_cookie_storage", false]],
+ });
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ await SpecialPowers.addPermission("storageAccessAPI", true, window.location.href);
+ await SpecialPowers.wrap(document).requestStorageAccess();
+ }
+
+ var bc = new BroadcastChannel("file_bug1121701_1");
+ var bc2 = new BroadcastChannel("file_bug1121701_2");
+
+ async function scheduleFinish() {
+ await Promise.all([page1Done.promise, page2Done.promise]);
+ bc2.close();
+ bc.close();
+ SimpleTest.finish();
+ }
+
+ bc.onmessage = (msgEvent) => {
+ var msg = msgEvent.data;
+ var command = msg.command;
+ if (command == "child1PageShow") {
+ ++page1LoadCount;
+ var persisted = msg.persisted;
+ var pageHideAsserts = msg.pageHideAsserts;
+ if (pageHideAsserts) {
+ ok(pageHideAsserts.persisted, "onpagehide: test page 1 should get persisted");
+ is(pageHideAsserts.innerHTML, "modified", "onpagehide: innerHTML text is 'modified'");
+ }
+ if (page1LoadCount == 1) {
+ SimpleTest.executeSoon(function() {
+ is(persisted, false, "Initial page load shouldn't be persisted.");
+ bc.postMessage({command: "setInnerHTML", testUrl2});
+ });
+ } else if (page1LoadCount == 2) {
+ is(persisted, true, "Page load from bfcache should be persisted.");
+ is(msg.innerHTML, "modified", "innerHTML text is 'modified'");
+ bc.postMessage({command: "close"});
+ }
+ } else if (command == "closed") {
+ page1Done.resolve();
+ }
+ }
+ bc2.onmessage = (msgEvent) => {
+ var msg = msgEvent.data;
+ var command = msg.command;
+ if (command == "child2PageShow") {
+ bc2.postMessage({command: "setInnerHTML", location: location.href});
+ } else if (command == "onmessage") {
+ page2Done.resolve();
+ }
+ }
+
+ scheduleFinish();
+ // If Fission is disabled, the pref is no-op.
+ SpecialPowers.pushPrefEnv({set: [["fission.bfcacheInParent", true]]}, () => {
+ window.open(testUrl1, "", "noopener");
+ });
+ });
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1121701">Mozilla Bug 1121701</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug1151421.html b/docshell/test/mochitest/test_bug1151421.html
new file mode 100644
index 0000000000..0738ee783e
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1151421.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1151421
+-->
+<head>
+ <title>Test for Bug 1151421</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1151421">Mozilla Bug 1151421</a>
+
+<script type="application/javascript">
+
+/** Test for Bug 1151421 */
+SimpleTest.waitForExplicitFinish();
+
+function childLoad() {
+ // Spin the event loop so we leave the onload handler.
+ SimpleTest.executeSoon(childLoad2);
+}
+
+function childLoad2() {
+ let iframe = document.getElementById("iframe");
+ let cw = iframe.contentWindow;
+ let content = cw.document.getElementById("content");
+
+ // Create a function to calculate an invariant.
+ let topPlusOffset = function() {
+ return Math.round(content.getBoundingClientRect().top + cw.pageYOffset);
+ };
+
+ let initialTPO = topPlusOffset();
+
+ // Scroll the iframe to various positions, and check the TPO.
+ // Scrolling down to the bottom will adjust the page offset by a fractional amount.
+ let positions = [-100, 0.17, 0, 1.5, 10.41, 1e6, 12.1];
+
+ // Run some tests with scrollTo() and ensure we have the same invariant after scrolling.
+ positions.forEach(function(pos) {
+ cw.scrollTo(0, pos);
+ is(topPlusOffset(), initialTPO, "Top plus offset should remain invariant across scrolling.");
+ });
+
+ positions.reverse().forEach(function(pos) {
+ cw.scrollTo(0, pos);
+ is(topPlusOffset(), initialTPO, "(reverse) Top plus offset should remain invariant across scrolling.");
+ });
+
+ SimpleTest.finish();
+}
+
+</script>
+
+<!-- When the iframe loads, it calls childLoad(). -->
+<br>
+<iframe height='100px' id='iframe' src='file_bug1151421.html'></iframe>
+
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug1186774.html b/docshell/test/mochitest/test_bug1186774.html
new file mode 100644
index 0000000000..9ec56baf11
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1186774.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1186774
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1186774</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1186774 */
+
+var child;
+
+function runTest() {
+ child = window.open("file_bug1186774.html", "", "width=100,height=100");
+ child.onload = function() {
+ setTimeout(function() {
+ child.scrollTo(0, 0);
+ child.history.pushState({}, "initial");
+ child.scrollTo(0, 3000);
+ child.history.pushState({}, "scrolled");
+ child.scrollTo(0, 6000);
+ child.history.back();
+ });
+ };
+
+ child.onpopstate = function() {
+ is(Math.round(child.scrollY), 6000, "Shouldn't have scrolled before popstate");
+ child.close();
+ SimpleTest.finish();
+ };
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(runTest);
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1186774">Mozilla Bug 1186774</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug1422334.html b/docshell/test/mochitest/test_bug1422334.html
new file mode 100644
index 0000000000..b525ae1d9c
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1422334.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Ensure that reload after replaceState after 3xx redirect does the right thing.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(function() {
+ var ifr = document.querySelector("iframe");
+ var win = ifr.contentWindow;
+ is(win.location.href, location.href.replace(location.search, "")
+ .replace("mochitest/test_bug1422334.html",
+ "navigation/blank.html?x=y"),
+ "Should have the right location on initial load");
+
+ win.history.replaceState(null, '', win.location.pathname);
+ is(win.location.href, location.href.replace(location.search, "")
+ .replace("mochitest/test_bug1422334.html",
+ "navigation/blank.html"),
+ "Should have the right location after replaceState call");
+
+ ifr.onload = function() {
+ is(win.location.href, location.href.replace(location.search, "")
+ .replace("mochitest/test_bug1422334.html",
+ "navigation/blank.html"),
+ "Should have the right location after reload");
+ SimpleTest.finish();
+ }
+ win.location.reload();
+ });
+ </script>
+</head>
+<body>
+<p id="display"><iframe src="bug1422334_redirect.html"></iframe></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug1450164.html b/docshell/test/mochitest/test_bug1450164.html
new file mode 100644
index 0000000000..546a988394
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1450164.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1450164
+ -->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1450164</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1450164 */
+
+ function runTest() {
+ var child = window.open("file_bug1450164.html", "", "width=100,height=100");
+ child.onload = function() {
+ // After the window loads, close it. If we don't crash in debug, consider that a pass.
+ child.close();
+ };
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ addLoadEvent(runTest);
+
+ </script>
+ </head>
+ <body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1450164">Mozilla Bug 1450164</a>
+ </body>
+</html>
diff --git a/docshell/test/mochitest/test_bug1507702.html b/docshell/test/mochitest/test_bug1507702.html
new file mode 100644
index 0000000000..fd88ee60a5
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1507702.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1507702
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1507702</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <link rel="icon" href="about:crashparent"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1507702">Mozilla Bug 1507702</a>
+<img src="about:crashparent">
+<img src="about:crashcontent">
+<iframe src="about:crashparent"></iframe>
+<iframe src="about:crashcontent"></iframe>
+<script>
+ let urls = ["about:crashparent", "about:crashcontent"];
+ async function testFetch() {
+ const url = urls.shift();
+ if (!url) {
+ return Promise.resolve();
+ }
+
+ let threw;
+ try {
+ await fetch(url);
+ threw = false;
+ } catch (e) {
+ threw = true;
+ }
+
+ ok(threw === true, "fetch should reject");
+ return testFetch();
+ }
+
+ document.body.onload = async () => {
+ for (const url of ["about:crashparent", "about:crashcontent"]) {
+ SimpleTest.doesThrow(() => {
+ top.location.href = url;
+ }, "navigation should throw");
+
+ SimpleTest.doesThrow(() => {
+ location.href = url;
+ }, "navigation should throw");
+ }
+
+ await testFetch();
+ SimpleTest.finish();
+ };
+
+ SimpleTest.waitForExplicitFinish();
+</script>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug1645781.html b/docshell/test/mochitest/test_bug1645781.html
new file mode 100644
index 0000000000..6cf676b7a9
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1645781.html
@@ -0,0 +1,90 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Test for Bug 1590762</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ </head>
+ <body>
+ <form id="form" action="form_submit.sjs" method="POST" target="targetFrame">
+ <input id="input" type="text" name="name" value="">
+ <input id="button" type="submit">
+ </form>
+ <script>
+ "use strict";
+ const PATH = "/tests/docshell/test/mochitest/";
+ const SAME_ORIGIN = new URL(PATH, window.location.origin);;
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ const CROSS_ORIGIN_1 = new URL(PATH, "http://test1.example.com/");
+ const CROSS_ORIGIN_2 = new URL(PATH, "https://example.com/");
+ const TARGET = "ping.html";
+ const ACTION = "form_submit.sjs";
+
+ function generateBody(size) {
+ let data = new Uint8Array(size);
+ for (let i = 0; i < size; ++i) {
+ data[i] = 97 + Math.random() * (123 - 97);
+ }
+
+ return new TextDecoder().decode(data);
+ }
+
+ async function withFrame(url) {
+ info("Creating frame");
+ let frame = document.createElement('iframe');
+ frame.name = "targetFrame";
+
+ return new Promise(resolve => {
+ addEventListener('message', async function({source}) {
+ info("Frame loaded");
+ if (frame.contentWindow == source) {
+ resolve(frame);
+ }
+ }, { once: true });
+ frame.src = url;
+ document.body.appendChild(frame);
+ });
+ }
+
+ function click() {
+ synthesizeMouse(document.getElementById('button'), 5, 5, {});
+ }
+
+ function* spec() {
+ let urls = [SAME_ORIGIN, CROSS_ORIGIN_1, CROSS_ORIGIN_2];
+ for (let action of urls) {
+ for (let target of urls) {
+ yield { action: new URL(ACTION, action),
+ target: new URL(TARGET, target) };
+ }
+ }
+ }
+
+ info("Starting tests");
+ let form = document.getElementById('form');
+
+ // The body of the POST needs to be large to trigger this.
+ // 1024*1024 seems to be enough, but scaling to get a margin.
+ document.getElementById('input').value = generateBody(1024*1024);
+ for (let { target, action } of spec()) {
+ add_task(async function runTest() {
+ info(`Running test ${target} with ${action}`);
+ form.action = action;
+ let frame = await withFrame(target);
+ await new Promise(resolve => {
+ addEventListener('message', async function() {
+ info("Form loaded");
+ frame.remove();
+ resolve();
+ }, { once: true });
+
+ click();
+ });
+
+ ok(true, `Submitted to ${origin} with target ${action}`)
+ });
+ };
+ </script>
+ </body>
+</html>
diff --git a/docshell/test/mochitest/test_bug1729662.html b/docshell/test/mochitest/test_bug1729662.html
new file mode 100644
index 0000000000..ec43508494
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1729662.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test back/forward after pushState</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("Need to wait to make sure an event does not fire");
+
+ async function runTest() {
+ let win = window.open();
+ let goneBackAndForwardOnce = new Promise((resolve) => {
+ let timeoutID;
+
+ // We should only get one load event in win.
+ let bc = new BroadcastChannel("bug1729662");
+ bc.addEventListener("message", () => {
+ bc.addEventListener("message", () => {
+ clearTimeout(timeoutID);
+ resolve(false);
+ });
+ }, { once: true });
+
+ let goneBack = false, goneForward = false;
+ win.addEventListener("popstate", ({ state }) => {
+ // We should only go back and forward once, if we get another
+ // popstate after that then we should fall through to the
+ // failure case below.
+ if (!(goneBack && goneForward)) {
+ // Check if this is the popstate for the forward (the one for
+ // back will have state == undefined).
+ if (state == 1) {
+ ok(goneBack, "We should have gone back before going forward");
+
+ goneForward = true;
+
+ // Wait a bit to make sure there are no more popstate events.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ timeoutID = setTimeout(resolve, 1000, true);
+
+ return;
+ }
+
+ // Check if we've gone back once before, if we get another
+ // popstate after that then we should fall through to the
+ // failure case below.
+ if (!goneBack) {
+ goneBack = true;
+
+ return;
+ }
+ }
+
+ clearTimeout(timeoutID);
+ resolve(false);
+ });
+ });
+
+ win.location = "file_bug1729662.html";
+
+ ok(await goneBackAndForwardOnce, "Stopped navigating history");
+
+ win.close();
+
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body onload="runTest();">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug1740516.html b/docshell/test/mochitest/test_bug1740516.html
new file mode 100644
index 0000000000..b54932c736
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1740516.html
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test pageshow event order for iframe</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ function waitForPageShow(outer, inner) {
+ return new Promise((resolve) => {
+ let results = [];
+ outer.addEventListener("message", ({ data: persisted }) => {
+ results.push({ name: outer.name, persisted });
+ if (results.length == 2) {
+ resolve(results);
+ }
+ }, { once: true });
+ inner.addEventListener("message", ({ data: persisted }) => {
+ results.push({ name: inner.name, persisted });
+ if (results.length == 2) {
+ resolve(results);
+ }
+ }, { once: true });
+ });
+ }
+ async function runTest() {
+ let outerBC = new BroadcastChannel("bug1740516_1");
+ let innerBC = new BroadcastChannel("bug1740516_1_inner");
+
+ let check = waitForPageShow(outerBC, innerBC).then(([first, second]) => {
+ is(first.name, "bug1740516_1_inner", "Should get pageShow from inner iframe page first.");
+ ok(!first.persisted, "First navigation shouldn't come from BFCache.");
+ is(second.name, "bug1740516_1", "Should get pageShow from outer page second.");
+ ok(!second.persisted, "First navigation shouldn't come from BFCache.");
+ }, () => {
+ ok(false, "The promises should not be rejected.");
+ });
+ window.open("file_bug1740516_1.html", "", "noopener");
+ await check;
+
+ check = waitForPageShow(outerBC, innerBC).then(([first, second]) => {
+ is(first.name, "bug1740516_1_inner", "Should get pageShow from inner iframe page first.");
+ ok(first.persisted, "Second navigation should come from BFCache");
+ is(second.name, "bug1740516_1", "Should get pageShow from outer page second.");
+ ok(second.persisted, "Second navigation should come from BFCache");
+ }, () => {
+ ok(false, "The promises should not be rejected.");
+ });
+ outerBC.postMessage("navigate");
+ await check;
+
+ check = waitForPageShow(outerBC, innerBC).then(([first, second]) => {
+ is(first.name, "bug1740516_1_inner", "Should get pageShow from inner iframe page first.");
+ ok(!first.persisted, "Third navigation should not come from BFCache");
+ is(second.name, "bug1740516_1", "Should get pageShow from outer page second.");
+ ok(!second.persisted, "Third navigation should not come from BFCache");
+ }, () => {
+ ok(false, "The promises should not be rejected.");
+ });
+ outerBC.postMessage("block_bfcache_and_navigate");
+ await check;
+
+ outerBC.postMessage("close");
+
+ outerBC.close();
+ innerBC.close();
+
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body onload="runTest();">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug1741132.html b/docshell/test/mochitest/test_bug1741132.html
new file mode 100644
index 0000000000..1ae9727d9c
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1741132.html
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test form restoration for no-store pages</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ // The number of entries which we keep in the BFCache (see nsSHistory.h).
+ const VIEWER_WINDOW = 3;
+
+ SimpleTest.waitForExplicitFinish();
+
+ function runTest() {
+ let bc = new BroadcastChannel("bug1741132");
+
+ // Setting the pref to 0 should evict all content viewers.
+ let load = SpecialPowers.pushPrefEnv({
+ set: [["browser.sessionhistory.max_total_viewers", 0]],
+ }).then(() => {
+ // Set the pref to VIEWER_WINDOW + 2 now, to be sure we
+ // could fit all entries.
+ SpecialPowers.pushPrefEnv({
+ set: [["browser.sessionhistory.max_total_viewers", VIEWER_WINDOW + 2]],
+ });
+ }).then(() => {
+ return new Promise(resolve => {
+ bc.addEventListener("message", resolve, { once: true });
+ window.open("file_bug1741132.html", "", "noopener");
+ });
+ });
+ // We want to try to keep one entry too many in the BFCache,
+ // so we ensure that there's at least VIEWER_WINDOW + 2
+ // entries in session history (with one for the displayed
+ // page).
+ for (let i = 0; i < VIEWER_WINDOW + 2; ++i) {
+ load = load.then(() => {
+ return new Promise((resolve) => {
+ bc.addEventListener("message", resolve, { once: true });
+ bc.postMessage({ cmd: "load", arg: `file_bug1741132.html?${i}` });
+ });
+ });
+ }
+ load.then(() => {
+ return new Promise((resolve) => {
+ bc.addEventListener("message", ({ data: persisted }) => {
+ resolve(persisted);
+ }, { once: true });
+ // Go back past the first entry that should be in the BFCache.
+ bc.postMessage({ cmd: "go", arg: -(VIEWER_WINDOW + 1) });
+ });
+ }).then((persisted) => {
+ ok(!persisted, "Only 3 pages should be kept in the BFCache");
+ }).then(() => {
+ return new Promise((resolve) => {
+ bc.addEventListener("message", ({ data: persisted }) => {
+ resolve(persisted);
+ }, { once: true });
+ // Go forward to the first entry that should be in the BFCache.
+ bc.postMessage({ cmd: "go", arg: 1 });
+ });
+ }).then((persisted) => {
+ ok(persisted, "3 pages should be kept in the BFCache");
+
+ bc.postMessage("close");
+
+ bc.close();
+
+ SimpleTest.finish();
+ });
+ }
+ </script>
+</head>
+<body onload="runTest();">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug1742865.html b/docshell/test/mochitest/test_bug1742865.html
new file mode 100644
index 0000000000..c8f9a4eca3
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1742865.html
@@ -0,0 +1,137 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Auto refreshing pages shouldn't add an entry to session history</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ const REFRESH_REDIRECT_TIMER = 15;
+
+ // 2 tests (same and cross origin) consisting of 2 refreshes of maximum 1 seconds
+ // 2 tests (same and cross origin) consisting of 2 refreshes of REFRESH_REDIRECT_TIMER seconds
+ // => We need (2 * 1) + (2 * 15) seconds
+ SimpleTest.requestLongerTimeout(3);
+ SimpleTest.waitForExplicitFinish();
+
+ const SJS = new URL("file_bug1742865.sjs", location.href);
+ const SJS_OUTER = new URL("file_bug1742865_outer.sjs", location.href);
+ const SCROLL = 500;
+
+ let tolerance;
+ function setup() {
+ return SpecialPowers.spawn(window.top, [], () => {
+ return SpecialPowers.getDOMWindowUtils(content.window).getResolution();
+ }).then(resolution => {
+ // Allow a half pixel difference if the top document's resolution is lower
+ // than 1.0 because the scroll position is aligned with screen pixels
+ // instead of CSS pixels.
+ tolerance = resolution < 1.0 ? 0.5 : 0.0;
+ });
+ }
+
+ function checkScrollPosition(scrollPosition, shouldKeepScrollPosition) {
+ isfuzzy(scrollPosition, shouldKeepScrollPosition ? SCROLL : 0, tolerance,
+ `Scroll position ${shouldKeepScrollPosition ? "should" : "shouldn't"} be maintained for meta refresh`);
+ }
+
+ function openWindowAndCheckRefresh(url, params, shouldAddToHistory, shouldKeepScrollPosition) {
+ info(`Running test for ${JSON.stringify(params)}`);
+
+ url = new URL(url);
+ Object.entries(params).forEach(([k, v]) => { url.searchParams.append(k, v) });
+ url.searchParams.append("scrollTo", SCROLL);
+
+ let resetURL = new URL(SJS);
+ resetURL.search = "?reset";
+ return fetch(resetURL).then(() => {
+ return new Promise((resolve) => {
+ let count = 0;
+ window.addEventListener("message", function listener({ data: { commandType, commandData = {} } }) {
+ if (commandType == "onChangedInputValue") {
+ let { historyLength, inputValue } = commandData;
+
+ if (shouldAddToHistory) {
+ is(historyLength, count, "Auto-refresh should add entries to session history");
+ } else {
+ is(historyLength, 1, "Auto-refresh shouldn't add entries to session history");
+ }
+
+ is(inputValue, "1234", "Input's value should have been changed");
+
+ win.postMessage("loadNext", "*");
+ return;
+ }
+
+ is(commandType, "pageShow", "Unknown command type");
+
+ let { inputValue, scrollPosition } = commandData;
+
+ switch (++count) {
+ // file_bug1742865.sjs causes 3 loads:
+ // * first load, returns first meta refresh
+ // * second load, caused by first meta refresh, returns second meta refresh
+ // * third load, caused by second meta refresh, doesn't return a meta refresh
+ case 2:
+ checkScrollPosition(scrollPosition, shouldKeepScrollPosition);
+ break;
+ case 3:
+ checkScrollPosition(scrollPosition, shouldKeepScrollPosition);
+ win.postMessage("changeInputValue", "*");
+ break;
+ case 4:
+ win.postMessage("back", "*");
+ break;
+ case 5:
+ is(inputValue, "1234", "Entries for auto-refresh should be attached to session history");
+ checkScrollPosition(scrollPosition, shouldKeepScrollPosition);
+ removeEventListener("message", listener);
+ win.close();
+ resolve();
+ break;
+ }
+ });
+ let win = window.open(url);
+ });
+ });
+ }
+
+ function doTest(seconds, crossOrigin, shouldAddToHistory, shouldKeepScrollPosition) {
+ let params = {
+ seconds,
+ crossOrigin,
+ };
+
+ return openWindowAndCheckRefresh(SJS, params, shouldAddToHistory, shouldKeepScrollPosition).then(() =>
+ openWindowAndCheckRefresh(SJS_OUTER, params, shouldAddToHistory, shouldKeepScrollPosition)
+ );
+ }
+
+ async function runTest() {
+ const FAST = Math.min(1, REFRESH_REDIRECT_TIMER);
+ const SLOW = REFRESH_REDIRECT_TIMER + 1;
+ let tests = [
+ // [ time, crossOrigin, shouldAddToHistory, shouldKeepScrollPosition ]
+ [ FAST, false, false, true ],
+ [ FAST, true, false, false ],
+ [ SLOW, false, false, true ],
+ [ SLOW, true, true, false ],
+ ];
+
+ await setup();
+
+ for (let [ time, crossOrigin, shouldAddToHistory, shouldKeepScrollPosition ] of tests) {
+ await doTest(time, crossOrigin, shouldAddToHistory, shouldKeepScrollPosition);
+ }
+
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body onload="runTest();">
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug1743353.html b/docshell/test/mochitest/test_bug1743353.html
new file mode 100644
index 0000000000..a5d88df3f6
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1743353.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test back/forward after pushState</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ function runTest() {
+ let bc = new BroadcastChannel("bug1743353");
+ new Promise((resolve) => {
+ bc.addEventListener("message", () => {
+ resolve();
+ }, { once: true });
+
+ window.open("file_bug1743353.html", "", "noopener");
+ }).then(() => {
+ return new Promise(resolve => {
+ bc.addEventListener("message", () => {
+ resolve();
+ }, { once: true });
+
+ bc.postMessage("load");
+ })
+ }).then(() => {
+ return new Promise(resolve => {
+ let results = [];
+ bc.addEventListener("message", function listener({ data }) {
+ results.push(data);
+ if (results.length == 3) {
+ bc.removeEventListener("message", listener);
+ resolve(results);
+ }
+ });
+
+ bc.postMessage("back");
+ });
+ }).then((results) => {
+ is(results[0], "pagehide", "First event should be 'pagehide'.");
+ is(results[1], "unload", "Second event should be 'unload'.");
+ is(results[2], "pageshow", "Third event should be 'pageshow'.");
+
+ bc.postMessage("close");
+
+ SimpleTest.finish();
+ });
+ }
+ </script>
+</head>
+<body onload="runTest();">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug1747033.html b/docshell/test/mochitest/test_bug1747033.html
new file mode 100644
index 0000000000..539b78fec0
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1747033.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test history after loading multipart</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ function runTest() {
+ let bc = new BroadcastChannel("bug1747033");
+ new Promise(resolve => {
+ bc.addEventListener("message", ({ data: { historyLength } }) => {
+ is(historyLength, 1, "Correct length for first normal load.");
+
+ resolve();
+ }, { once: true });
+
+ window.open("file_bug1747033.sjs", "", "noopener");
+ }).then(() => {
+ return new Promise(resolve => {
+ let loaded = 0;
+ bc.addEventListener("message", function listener({ data: { historyLength } }) {
+ ++loaded;
+
+ is(historyLength, 2, `Correct length for multipart load ${loaded}.`);
+
+ // We want 3 parts in total.
+ if (loaded < 3) {
+ if (loaded == 2) {
+ // We've had 2 parts, make the server send the last part.
+ fetch("file_bug1747033.sjs?sendLastPart");
+ } else {
+ fetch("file_bug1747033.sjs?sendNextPart");
+ }
+ return;
+ }
+
+ bc.removeEventListener("message", listener);
+ resolve();
+ });
+
+ bc.postMessage({ cmd: "load", arg: "file_bug1747033.sjs?multipart" });
+ });
+ }).then(() => {
+ return new Promise(resolve => {
+ bc.addEventListener("message", ({ data: { historyLength } }) => {
+ is(historyLength, 2, "Correct length after calling replaceState in multipart.");
+
+ resolve();
+ }, { once: true });
+
+ bc.postMessage({ cmd: "replaceState", arg: "file_bug1747033.sjs?replaced" });
+ });
+ }).then(() => {
+ return new Promise(resolve => {
+ bc.addEventListener("message", ({ data: { historyLength } }) => {
+ is(historyLength, 3, "Correct length for first normal load after multipart.");
+
+ resolve();
+ }, { once: true });
+
+ bc.postMessage({ cmd: "load", arg: "file_bug1747033.sjs" });
+ });
+ }).then(() => {
+ return new Promise(resolve => {
+ let goneBack = 0;
+ bc.addEventListener("message", function listener({ data: { historyLength } }) {
+ ++goneBack;
+
+ is(historyLength, 3, "Correct length after going back.");
+
+ if (goneBack == 1) {
+ bc.postMessage({ cmd: "back" });
+ } else if (goneBack == 2) {
+ bc.removeEventListener("message", listener);
+ resolve();
+ }
+ });
+
+ bc.postMessage({ cmd: "back" });
+ });
+ }).then(() => {
+ bc.postMessage({ cmd: "close" });
+
+ SimpleTest.finish();
+ });
+ }
+ </script>
+</head>
+<body onload="runTest();">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug1773192.html b/docshell/test/mochitest/test_bug1773192.html
new file mode 100644
index 0000000000..d4c42dc1a7
--- /dev/null
+++ b/docshell/test/mochitest/test_bug1773192.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test referrer with going back</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ // file_bug1773192_1.html will send a message with some data on pageshow.
+ function waitForData(bc) {
+ return new Promise(resolve => {
+ bc.addEventListener(
+ "message",
+ ({ data }) => {
+ resolve(data);
+ },
+ { once: true }
+ );
+ });
+ }
+ async function runTest() {
+ let bc = new BroadcastChannel("bug1743353");
+
+ let getData = waitForData(bc);
+
+ window.open("file_bug1773192_1.html", "", "noreferrer");
+
+ await getData.then(({ referrer }) => {
+ is(referrer, "", "Referrer should be empty at first.");
+ });
+
+ getData = waitForData(bc);
+
+ // When file_bug1773192_1.html receives this message it will navigate to
+ // file_bug1773192_2.html. file_bug1773192_2.html removes itself from
+ // history with replaceState and submits a form with the POST method to
+ // file_bug1773192_3.sjs. file_bug1773192_3.sjs goes back in history.
+ // We should end up back at file_bug1773192_1.html, which will send a
+ // message with some data on pageshow.
+ bc.postMessage("next");
+
+ await getData.then(({ location, referrer }) => {
+ let firstURL = new URL("file_bug1773192_1.html", location).toString();
+ is(location, firstURL, "Location should be the first page again.");
+ is(referrer, firstURL, "Referrer should also be the first page.");
+ });
+
+ bc.postMessage("close");
+
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body onload="runTest();">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug385434.html b/docshell/test/mochitest/test_bug385434.html
new file mode 100644
index 0000000000..bc82de9daf
--- /dev/null
+++ b/docshell/test/mochitest/test_bug385434.html
@@ -0,0 +1,211 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=385434
+-->
+<head>
+ <title>Test for Bug 385434</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=385434">Mozilla Bug 385434</a>
+<p id="display"></p>
+<div id="content">
+ <iframe id="frame" style="height:100px; width:100px; border:0"></iframe>
+ <div id="status" style="display: none"></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 385434 */
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+SimpleTest.expectAssertions(0, 1); // bug 1333702
+
+var gNumHashchanges = 0;
+var gCallbackOnIframeLoad = false;
+var gSampleEvent;
+
+function statusMsg(msg) {
+ var msgElem = document.createElement("p");
+ msgElem.appendChild(document.createTextNode(msg));
+
+ document.getElementById("status").appendChild(msgElem);
+}
+
+function longWait() {
+ setTimeout(function() { gGen.next(); }, 1000);
+}
+
+// onIframeHashchange, onIframeLoad, and onIframeScroll are all called by the
+// content we load into our iframe in order to notify the parent frame of an
+// event which was fired.
+function onIframeHashchange() {
+ gNumHashchanges++;
+ gGen.next();
+}
+
+function onIframeLoad() {
+ if (gCallbackOnIframeLoad) {
+ gCallbackOnIframeLoad = false;
+ gGen.next();
+ }
+}
+
+function onIframeScroll() {
+ is(gNumHashchanges, 0, "onscroll should fire before onhashchange.");
+}
+
+function enableIframeLoadCallback() {
+ gCallbackOnIframeLoad = true;
+}
+
+function noEventExpected(msg) {
+ is(gNumHashchanges, 0, msg);
+
+ // Even if there's an error, set gNumHashchanges to 0 so other tests don't
+ // fail.
+ gNumHashchanges = 0;
+}
+
+function eventExpected(msg) {
+ is(gNumHashchanges, 1, msg);
+
+ // Eat up this event, whether the test above was true or not
+ gNumHashchanges = 0;
+}
+
+/*
+ * The hashchange event is dispatched asynchronously, so if we want to observe
+ * it, we have to yield within run_test(), transferring control back to the
+ * event loop.
+ *
+ * When we're expecting our iframe to observe a hashchange event after we poke
+ * it, we just yield and wait for onIframeHashchange() to call gGen.next() and
+ * wake us up.
+ *
+ * When we're testing to ensure that the iframe doesn't dispatch a hashchange
+ * event, we try to hook onto the iframe's load event. We call
+ * enableIframeLoadCallback(), which causes onIframeLoad() to call gGen.next()
+ * upon the next observed load. After we get our callback, we check that a
+ * hashchange didn't occur.
+ *
+ * We can't always just wait for page load in order to observe that a
+ * hashchange didn't happen. In these cases, we call longWait() and yield
+ * until either a hashchange occurs or longWait's callback is scheduled. This
+ * is something of a hack; it's entirely possible that longWait won't wait long
+ * enough, and we won't observe what should have been a failure of the test.
+ * But it shouldn't happen that good code will randomly *fail* this test.
+ */
+function* run_test() {
+ /*
+ * TEST 1 tests that:
+ * <body onhashchange = ... > works,
+ * the event is (not) fired at the correct times
+ */
+ var frame = document.getElementById("frame");
+ var frameCw = frame.contentWindow;
+
+ enableIframeLoadCallback();
+ frameCw.document.location = "file_bug385434_1.html";
+ // Wait for the iframe to load and for our callback to fire
+ yield undefined;
+
+ noEventExpected("No hashchange expected initially.");
+
+ sendMouseEvent({type: "click"}, "link1", frameCw);
+ yield undefined;
+ eventExpected("Clicking link1 should trigger a hashchange.");
+
+ sendMouseEvent({type: "click"}, "link1", frameCw);
+ longWait();
+ yield undefined;
+ // succeed if a hashchange event wasn't triggered while we were waiting
+ noEventExpected("Clicking link1 again should not trigger a hashchange.");
+
+ sendMouseEvent({type: "click"}, "link2", frameCw);
+ yield undefined;
+ eventExpected("Clicking link2 should trigger a hashchange.");
+
+ frameCw.history.go(-1);
+ yield undefined;
+ eventExpected("Going back should trigger a hashchange.");
+
+ frameCw.history.go(1);
+ yield undefined;
+ eventExpected("Going forward should trigger a hashchange.");
+
+ // window.location has a trailing '#' right now, so we append "link1", not
+ // "#link1".
+ frameCw.window.location = frameCw.window.location + "link1";
+ yield undefined;
+ eventExpected("Assigning to window.location should trigger a hashchange.");
+
+ // Set up history in the iframe which looks like:
+ // file_bug385434_1.html#link1
+ // file_bug385434_2.html
+ // file_bug385434_1.html#foo <-- current page
+ enableIframeLoadCallback();
+ frameCw.window.location = "file_bug385434_2.html";
+ yield undefined;
+
+ enableIframeLoadCallback();
+ frameCw.window.location = "file_bug385434_1.html#foo";
+ yield undefined;
+
+ // Now when we do history.go(-2) on the frame, it *shouldn't* fire a
+ // hashchange. Although the URIs differ only by their hashes, they belong to
+ // two different Documents.
+ frameCw.history.go(-2);
+ longWait();
+ yield undefined;
+ noEventExpected("Moving between different Documents shouldn't " +
+ "trigger a hashchange.");
+
+ /*
+ * TEST 2 tests that:
+ * <frameset onhashchange = ... > works,
+ * the event is targeted at the window object
+ * the event's cancelable, bubbles settings are correct
+ */
+
+ enableIframeLoadCallback();
+ frameCw.document.location = "file_bug385434_2.html";
+ yield undefined;
+
+ frameCw.document.location = "file_bug385434_2.html#foo";
+ yield undefined;
+
+ eventExpected("frame onhashchange should fire events.");
+ // iframe should set gSampleEvent
+ is(gSampleEvent.target, frameCw,
+ "The hashchange event should be targeted to the window.");
+ is(gSampleEvent.type, "hashchange",
+ "Event type should be 'hashchange'.");
+ is(gSampleEvent.cancelable, false,
+ "The hashchange event shouldn't be cancelable.");
+ is(gSampleEvent.bubbles, false,
+ "The hashchange event should not bubble.");
+
+ /*
+ * TEST 3 tests that:
+ * hashchange is dispatched if the current document readyState is
+ * not "complete" (bug 504837).
+ */
+ frameCw.document.location = "file_bug385434_3.html";
+ yield undefined;
+ eventExpected("Hashchange should fire even if the document " +
+ "hasn't finished loading.");
+
+ SimpleTest.finish();
+}
+
+var gGen = run_test();
+gGen.next();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug387979.html b/docshell/test/mochitest/test_bug387979.html
new file mode 100644
index 0000000000..0aca2ee89b
--- /dev/null
+++ b/docshell/test/mochitest/test_bug387979.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=387979
+-->
+<head>
+ <title>Test for Bug 387979</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=387979">Mozilla Bug 387979</a>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 387979 */
+function a(s) {
+ var r;
+ try { r = frames[0].document.body; } catch (e) { r = e; }
+ is(r instanceof frames[0].HTMLBodyElement, true, "Can't get body" + s);
+}
+var p = 0;
+function b() {
+ switch (++p) {
+ case 1:
+ frames[0].location = "about:blank";
+ break;
+ case 2:
+ a("before reload");
+ frames[0].location.reload();
+ break;
+ case 3:
+ a("after reload");
+ SimpleTest.finish();
+ break;
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+<p id="display">
+ <iframe onload="b()"></iframe>
+ <pre id="p">-</pre>
+</p>
+</body>
+</html>
+
diff --git a/docshell/test/mochitest/test_bug402210.html b/docshell/test/mochitest/test_bug402210.html
new file mode 100644
index 0000000000..326f98cf9f
--- /dev/null
+++ b/docshell/test/mochitest/test_bug402210.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+While working on bug 402210, it came up that the code was doing
+
+a.href = proto + host
+
+which technically produces "https:host" instead of "https://host" and
+that the code was relying on href's setting having fixup behaviour
+for this kind of thing.
+
+If we rely on it, we might as well test for it, even if it isn't the
+problem 402210 was meant to fix.
+
+https://bugzilla.mozilla.org/show_bug.cgi?id=402210
+-->
+<head>
+ <title>Test for Bug 402210</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=402210">Mozilla Bug 402210</a>
+<p id="display">
+ <a id="testlink">Test Link</a>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+function runTest() {
+ $("testlink").href = "https:example.com";
+ is($("testlink").href, "https://example.com/", "Setting href on an anchor tag should fixup missing slashes after https protocol");
+
+ $("testlink").href = "ftp:example.com";
+ is($("testlink").href, "ftp://example.com/", "Setting href on an anchor tag should fixup missing slashes after non-http protocol");
+
+ SimpleTest.finish();
+}
+
+addLoadEvent(runTest);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/docshell/test/mochitest/test_bug404548.html b/docshell/test/mochitest/test_bug404548.html
new file mode 100644
index 0000000000..a8a773dce5
--- /dev/null
+++ b/docshell/test/mochitest/test_bug404548.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=404548
+-->
+<head>
+ <title>Test for Bug 404548</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=404548">Mozilla Bug 404548</a>
+<p id="display">
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 404548 */
+var firstRemoved = false;
+var secondHidden = false;
+
+SimpleTest.waitForExplicitFinish();
+
+var w = window.open("bug404548-subframe.html", "", "width=10,height=10");
+
+function finishTest() {
+ is(firstRemoved, true, "Should have removed iframe from the DOM");
+ is(secondHidden, true, "Should have fired pagehide on second kid");
+ w.close();
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/docshell/test/mochitest/test_bug413310.html b/docshell/test/mochitest/test_bug413310.html
new file mode 100644
index 0000000000..4299605575
--- /dev/null
+++ b/docshell/test/mochitest/test_bug413310.html
@@ -0,0 +1,106 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=413310
+-->
+<head>
+ <title>Test for Bug 413310</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=413310">Mozilla Bug 413310</a>
+<p id="display">
+<script class="testbody" type="text/javascript">
+
+if (navigator.platform.startsWith("Mac")) {
+ SimpleTest.expectAssertions(0, 2);
+} else {
+ SimpleTest.expectAssertions(0, 1);
+}
+
+/** Test for Bug 413310 */
+
+// NOTE: If we ever make subframes do bfcache stuff, this test will need to be
+// modified accordingly! It assumes that subframes do NOT get bfcached.
+var onloadCount = 0;
+
+var step = -1; // One increment will come from the initial subframe onload.
+ // Note that this script should come before the subframe,
+ // so that doNextStep is defined when its onload handler fires.
+
+var textContent;
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(doNextStep);
+
+function doNextStep() {
+ ++step;
+ switch (step) {
+ case 1:
+ is(onloadCount, 1, "Loaded initial page");
+ is($("i").contentWindow.location.href,
+ location.href.replace(/test_bug413310.html/,
+ "bug413310-subframe.html"),
+ "Unexpected subframe location after initial load");
+ $("i").contentDocument.forms[0].submit();
+ break;
+ case 2:
+ is(onloadCount, 2, "Loaded POST result");
+
+ is($("i").contentWindow.location.href,
+ location.href.replace(/test_bug413310.html/,
+ "bug413310-post.sjs"),
+ "Unexpected subframe location after POST load");
+
+ textContent = $("i").contentDocument.body.textContent;
+ isDeeply(textContent.match(/^POST /), ["POST "], "Not a POST?");
+
+ $("i").contentWindow.location.hash = "foo";
+ setTimeout(doNextStep, 0);
+ break;
+ case 3:
+ is(onloadCount, 2, "Anchor scroll should not fire onload");
+ is($("i").contentWindow.location.href,
+ location.href.replace(/test_bug413310.html/,
+ "bug413310-post.sjs#foo"),
+ "Unexpected subframe location after anchor scroll");
+ is(textContent, $("i").contentDocument.body.textContent,
+ "Did a load when scrolling?");
+ $("i").contentWindow.location.href = "bug413310-subframe.html";
+ break;
+ case 4:
+ is(onloadCount, 3, "Done new load");
+ is($("i").contentWindow.location.href,
+ location.href.replace(/test_bug413310.html/,
+ "bug413310-subframe.html"),
+ "Unexpected subframe location after new load");
+ history.back();
+ break;
+ case 5:
+ is(onloadCount, 4,
+ "History traversal didn't fire onload: bfcache issues!");
+ is($("i").contentWindow.location.href,
+ location.href.replace(/test_bug413310.html/,
+ "bug413310-post.sjs#foo"),
+ "Unexpected subframe location");
+ is(textContent, $("i").contentDocument.body.textContent,
+ "Did a load when going back?");
+ SimpleTest.finish();
+ break;
+ }
+}
+</script>
+<!-- Use a timeout in onload so that we don't do a load immediately inside onload -->
+<iframe id="i" src="bug413310-subframe.html" onload="setTimeout(doNextStep, 20)">
+</iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
+
diff --git a/docshell/test/mochitest/test_bug475636.html b/docshell/test/mochitest/test_bug475636.html
new file mode 100644
index 0000000000..fb1827ad04
--- /dev/null
+++ b/docshell/test/mochitest/test_bug475636.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=475636
+Test that refresh to data: URIs don't inherit the principal
+-->
+<head>
+ <title>Test for Bug 475636</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="gen.next()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=475636">Mozilla Bug 475636</a>
+
+<div id="content" style="display: none">
+
+</div>
+<iframe id=loader></iframe>
+<pre id="test">
+<script class="testbody" type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var gen = runTests();
+
+window.private = 42;
+
+window.addEventListener("message", function(e) {
+ gen.next(e.data);
+});
+
+var url = "file_bug475636.sjs?";
+
+function* runTests() {
+ var loader = document.getElementById("loader");
+ for (var testNum = 1; ; ++testNum) {
+ loader.src = url + testNum;
+ let res = (yield);
+ if (res == "done") {
+ SimpleTest.finish();
+ return;
+ }
+ is(res, "pass");
+ }
+}
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug509055.html b/docshell/test/mochitest/test_bug509055.html
new file mode 100644
index 0000000000..57ede19b43
--- /dev/null
+++ b/docshell/test/mochitest/test_bug509055.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=509055
+-->
+<head>
+ <title>Test for Bug 509055</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=509055">Mozilla Bug 509055</a>
+<p id="display"></p>
+<div id="status"></div>
+<div id="content">
+</div>
+<pre id="test">
+ <script type="application/javascript">
+
+ /** Test for Bug 509055 */
+
+ SimpleTest.waitForExplicitFinish();
+
+ var gGen;
+
+ function shortWait() {
+ setTimeout(function() { gGen.next(); }, 0, false);
+ }
+
+ function onChildHashchange(e) {
+ // gGen might be undefined when we refresh the page, so we have to check here
+ dump("onChildHashchange() called.\n");
+ if (gGen)
+ gGen.next();
+ }
+
+ function onChildLoad(e) {
+ if (gGen)
+ gGen.next();
+ }
+
+ async function* runTest() {
+ var popup = window.open("file_bug509055.html", "popup 0",
+ "height=200,width=200,location=yes," +
+ "menubar=yes,status=yes,toolbar=yes,dependent=yes");
+ popup.hashchangeCallback = onChildHashchange;
+ popup.onload = onChildLoad;
+ dump("Waiting for initial load.\n");
+ yield undefined;
+
+ // Without this wait, the change to location.hash below doesn't create a
+ // SHEntry or enable the back button.
+ shortWait();
+ dump("Got initial load. Spinning event loop.\n");
+ yield undefined;
+
+ popup.location.hash = "#1";
+ dump("Waiting for hashchange.\n");
+ yield undefined;
+
+ popup.history.back();
+ dump("Waiting for second hashchange.\n");
+ yield undefined; // wait for hashchange
+
+ popup.document.title = "Changed";
+
+ // Wait for listeners to be notified of the title change.
+ shortWait();
+ dump("Got second hashchange. Spinning event loop.\n");
+ yield undefined;
+
+ let sheTitle = "";
+ if (!SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ var sh = SpecialPowers.wrap(popup)
+ .docShell
+ .QueryInterface(SpecialPowers.Ci.nsIWebNavigation)
+ .sessionHistory;
+
+ // Get the title of the inner popup's current SHEntry
+ sheTitle = sh.legacySHistory.getEntryAtIndex(sh.index).title;
+ } else {
+ let chromeScript = SpecialPowers.loadChromeScript(() => {
+ /* eslint-env mozilla/chrome-script */
+ addMessageListener("getTitle", browsingContext => {
+ // eslint-disable-next-line no-shadow
+ let sh = browsingContext.sessionHistory;
+ let title = sh.getEntryAtIndex(sh.index).title;
+ sendAsyncMessage("title", title);
+ });
+ });
+
+ let p = chromeScript.promiseOneMessage("title");
+ let browsingContext = SpecialPowers.wrap(popup)
+ .docShell.browsingContext;
+ chromeScript.sendAsyncMessage("getTitle", browsingContext);
+ sheTitle = await p;
+ chromeScript.destroy();
+ }
+ is(sheTitle, "Changed", "SHEntry's title should change when we change.");
+
+ popup.close();
+
+ SimpleTest.executeSoon(SimpleTest.finish);
+ }
+
+ window.addEventListener("load", function() {
+ gGen = runTest();
+ gGen.next();
+ });
+
+ </script>
+
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug511449.html b/docshell/test/mochitest/test_bug511449.html
new file mode 100644
index 0000000000..da95909d1c
--- /dev/null
+++ b/docshell/test/mochitest/test_bug511449.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=511449
+-->
+<head>
+ <title>Test for Bug 511449</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/NativeKeyCodes.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=511449">Mozilla Bug 511449</a>
+<p id="display"></p>
+<div id="status"></div>
+<div id="content">
+</div>
+<input type="text" id="input">
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 511449 */
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+window.addEventListener("load", runTest);
+
+var win = null;
+
+function runTest() {
+ document.getElementById("input").focus();
+ win = window.open("file_bug511449.html", "");
+ SimpleTest.waitForFocus(runNextTest, win);
+}
+
+function runNextTest() {
+ var didClose = false;
+ win.onunload = function() {
+ didClose = true;
+ };
+ synthesizeNativeKey(KEYBOARD_LAYOUT_EN_US, MAC_VK_ANSI_W, {metaKey: 1}, "w", "w");
+
+ setTimeout(function() {
+ ok(didClose, "Cmd+W should have closed the tab");
+ if (!didClose) {
+ win.close();
+ }
+ SimpleTest.finish();
+ }, 1000);
+}
+
+</script>
+
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug529119-1.html b/docshell/test/mochitest/test_bug529119-1.html
new file mode 100644
index 0000000000..1c89780fc7
--- /dev/null
+++ b/docshell/test/mochitest/test_bug529119-1.html
@@ -0,0 +1,110 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test bug 529119</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+var workingURL = "http://mochi.test:8888/tests/docshell/test/mochitest/bug529119-window.html";
+var faultyURL = "https://www.some-nonexistent-domain-27489274c892748217cn2384.test/";
+
+var w = null;
+var phase = 0;
+var gotWrongPageOnTryAgainClick = false;
+// Token that represents which page we currently have loaded.
+var token = 0;
+
+function delay(msec) {
+ return new Promise(resolve => setTimeout(resolve, msec));
+}
+
+async function assignToken(tokenToAssign) {
+ await SpecialPowers.spawn(w, [tokenToAssign],
+ newToken => { this.content.token = newToken });
+}
+
+async function pollForPage(win) {
+ while (true) {
+ try {
+ // When we do our navigation, there may be an interstitial about:blank
+ // page if the navigation involves a process switch. That about:blank
+ // will exist between the new process's docshell being created and the
+ // actual page that's being loaded loading (which can happen async from
+ // the docshell creation). We want to avoid treating the initial
+ // about:blank as a new page.
+ //
+ // We could conceivably expose Document::IsInitialDocument() as a
+ // ChromeOnly thing and use it here, but let's just filter out all
+ // about:blank, since we don't expect any in this test.
+ var haveNewPage = await SpecialPowers.spawn(w, [token],
+ currentToken => this.content.token != currentToken &&
+ this.content.location.href != "about:blank");
+
+ if (haveNewPage) {
+ ++token;
+ assignToken(token);
+ break;
+ }
+ } catch (e) {
+ // Something went wrong; just keep waiting.
+ }
+
+ await delay(100);
+ }
+}
+
+async function windowLoaded() {
+ switch (phase) {
+ case 0:
+ assignToken(token);
+
+ /* 2. We have succeededfully loaded a page, now go to a faulty URL */
+ window.setTimeout(function() {
+ w.location.href = faultyURL;
+ }, 0);
+
+ phase = 1;
+
+ await pollForPage(w);
+ is(await SpecialPowers.spawn(w, [], () => this.content.location.href),
+ faultyURL,
+ "Is on an error page initially");
+
+ /* 3. now, while we are on the error page, try to reload it, actually
+ click the "Try Again" button */
+ SpecialPowers.spawn(w, [], () => this.content.location.reload());
+
+ await pollForPage(w);
+
+ /* 4-finish, check we are still on the error page */
+ is(await SpecialPowers.spawn(w, [], () => this.content.location.href),
+ faultyURL,
+ "Is on an error page");
+ is(gotWrongPageOnTryAgainClick, false,
+ "Must not get www.example.com page on reload of an error page");
+ w.close();
+ SimpleTest.finish();
+ break;
+
+ case 1:
+ /* 4-check, we must not get here! */
+ gotWrongPageOnTryAgainClick = true;
+ break;
+ }
+}
+
+function startTest() {
+ /* 1. load a URL that leads to an error page */
+ w = window.open(workingURL);
+}
+
+</script>
+</head>
+<body onload="startTest();">
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug529119-2.html b/docshell/test/mochitest/test_bug529119-2.html
new file mode 100644
index 0000000000..a8bd57d4f7
--- /dev/null
+++ b/docshell/test/mochitest/test_bug529119-2.html
@@ -0,0 +1,116 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test bug 529119</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+var workingURL = "http://mochi.test:8888/tests/docshell/test/mochitest/bug529119-window.html";
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+var faultyURL = "http://some-nonexistent-domain-27489274c892748217cn2384.test/";
+
+var w = null;
+var phase = 0;
+var isWindowLoaded = false;
+// Token that represents which page we currently have loaded.
+var token = 0;
+
+function delay(msec) {
+ return new Promise(resolve => setTimeout(resolve, msec));
+}
+
+async function assignToken(tokenToAssign) {
+ await SpecialPowers.spawn(w, [tokenToAssign],
+ newToken => { this.content.token = newToken });
+}
+
+// Returns when a new page is loaded and returns whether that page is an
+// error page.
+async function pollForPage(win) {
+ while (true) {
+ try {
+ // When we do our navigation, there may be an interstitial about:blank
+ // page if the navigation involves a process switch. That about:blank
+ // will exist between the new process's docshell being created and the
+ // actual page that's being loaded loading (which can happen async from
+ // the docshell creation). We want to avoid treating the initial
+ // about:blank as a new page.
+ //
+ // We could conceivably expose Document::IsInitialDocument() as a
+ // ChromeOnly thing and use it here, but let's just filter out all
+ // about:blank, since we don't expect any in this test.
+ var haveNewPage = await SpecialPowers.spawn(w, [token],
+ currentToken => this.content.token != currentToken &&
+ this.content.location.href != "about:blank");
+
+ if (haveNewPage) {
+ ++token;
+ assignToken(token);
+
+ // In this test, error pages are non-same-origin with us, and non-error
+ // pages are same-origin.
+ let haveErrorPage = false;
+ try {
+ win.document.title;
+ } catch (ex) {
+ haveErrorPage = true;
+ }
+ return haveErrorPage;
+ }
+ } catch (e) {
+ // Something went wrong; just keep waiting.
+ }
+
+ await delay(100);
+ }
+}
+
+async function windowLoaded() {
+ // The code under here should only be run once
+ // The test popup window workingURL was already opened
+ if (isWindowLoaded)
+ return;
+ isWindowLoaded = true;
+
+ assignToken(token);
+
+ /* 2. We have successfully loaded a page, now go to a faulty URL */
+ // XXX The test fails when we change the location synchronously
+ window.setTimeout(function() {
+ w.location.href = faultyURL;
+ }, 0);
+
+ ok(await pollForPage(w), "Waiting for error page succeeded");
+ /* 3. now, while we are on the error page, navigate back */
+ try {
+ // We need the SpecialPowers bit, because this is a cross-origin window
+ // and we normally can't touch .history on those.
+ await SpecialPowers.spawn(w, [], () => this.content.history.back());
+ } catch (ex) {
+ ok(false, "w.history.back() threw " + ex);
+ }
+
+ ok(!await pollForPage(w), "Waiting for original page succeeded");
+ /* 4-finish, check we are back at the original page */
+ is(await SpecialPowers.spawn(w, [], () => this.content.location.href),
+ workingURL,
+ "Is on the previous page");
+ w.close();
+ SimpleTest.finish();
+}
+
+function startTest() {
+ /* 1. load a URL that leads to an error page */
+ w = window.open(workingURL);
+}
+
+</script>
+</head>
+<body onload="startTest();">
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug530396.html b/docshell/test/mochitest/test_bug530396.html
new file mode 100644
index 0000000000..fa3ddc6db6
--- /dev/null
+++ b/docshell/test/mochitest/test_bug530396.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=530396
+-->
+<head>
+ <title>Test for Bug 530396</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=530396">Mozilla Bug 530396</a>
+
+<p>
+
+<iframe id="testFrame" src="http://mochi.test:8888/tests/docshell/test/mochitest/bug530396-subframe.html"></iframe>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// NOTE: If we ever make subframes do bfcache stuff, this test will need to be
+// modified accordingly! It assumes that subframes do NOT get bfcached.
+var onloadCount = 0;
+
+var step = 0;
+
+var gTestFrame = document.getElementById("testFrame");
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+addLoadEvent(doNextStep);
+
+function doNextStep() {
+ ++step;
+ switch (step) {
+ case 1:
+ is(onloadCount, 1, "Loaded initial page");
+ sendMouseEvent({type: "click"}, "target2", gTestFrame.contentWindow);
+ window.setTimeout(doNextStep, 1000);
+ break;
+
+ case 2:
+ is(onloadCount, 1, "opener must be null");
+ sendMouseEvent({type: "click"}, "target1", gTestFrame.contentWindow);
+ break;
+
+ case 3:
+ is(onloadCount, 2, "don't send referrer with rel=referrer");
+ SimpleTest.finish();
+ break;
+ }
+}
+</script>
+</pre>
+</html>
diff --git a/docshell/test/mochitest/test_bug540462.html b/docshell/test/mochitest/test_bug540462.html
new file mode 100644
index 0000000000..e0a0861aaf
--- /dev/null
+++ b/docshell/test/mochitest/test_bug540462.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=540462
+-->
+<head>
+ <title>Test for Bug 540462</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=540462">Mozilla Bug 540462</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 540462 */
+
+var win;
+function runTest() {
+ win = window.open("file_bug540462.html", "", "width=100,height=100");
+}
+
+var dwlCount = 0;
+var originalURL;
+function documentWriteLoad() {
+ if (++dwlCount == 1) {
+ originalURL = win.document.body.firstChild.href;
+ } else if (dwlCount == 2) {
+ is(win.document.body.firstChild.href, originalURL, "Wrong href!");
+ win.close();
+ SimpleTest.finish();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug551225.html b/docshell/test/mochitest/test_bug551225.html
new file mode 100644
index 0000000000..b2862fcad8
--- /dev/null
+++ b/docshell/test/mochitest/test_bug551225.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=551225
+-->
+<head>
+ <title>Test for Bug 551225</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=551225">Mozilla Bug 551225</a>
+
+<script type="application/javascript">
+
+/** Test for Bug 551225 */
+
+var obj = {
+ a: new Date("1/1/2000"),
+ b: /^foo$/,
+ c: "bar",
+};
+
+history.replaceState(obj, "", "");
+is(history.state.a.toString(), new Date("1/1/2000").toString(), "Date object.");
+is(history.state.b.toString(), "/^foo$/", "Regex");
+is(history.state.c, "bar", "Other state");
+
+</script>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug570341.html b/docshell/test/mochitest/test_bug570341.html
new file mode 100644
index 0000000000..363f985407
--- /dev/null
+++ b/docshell/test/mochitest/test_bug570341.html
@@ -0,0 +1,142 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=570341
+-->
+<head>
+ <title>Test for Bug 570341</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<script>
+ var start = Date.now();
+ var moments = {};
+
+ var unload = 0;
+ var wasEnabled = true;
+
+ function collectMoments() {
+ var win = frames[0];
+ var timing = (win.performance && win.performance.timing) || {};
+ for (let p in timing) {
+ moments[p] = timing[p];
+ }
+ for (let p in win) {
+ if (p.substring(0, 9) == "_testing_") {
+ moments[p.substring(9)] = win[p];
+ }
+ }
+ moments.evt_unload = unload;
+ return moments;
+ }
+
+ function showSequence(node) {
+ while (node.firstChild) {
+ node.firstChild.remove();
+ }
+ var sequence = [];
+ for (var p in moments) {
+ sequence.push(p);
+ }
+ sequence.sort(function(a, b) {
+ return moments[a] - moments[b];
+ });
+ var table = document.createElement("table");
+ node.appendChild(table);
+ var row = document.createElement("tr");
+ table.appendChild(row);
+ var cell = document.createElement("td");
+ row.appendChild(cell);
+ cell.appendChild(document.createTextNode("start"));
+ cell = document.createElement("td");
+ row.appendChild(cell);
+ cell.appendChild(document.createTextNode(start));
+ for (var i = 0; i < sequence.length; ++i) {
+ var prop = sequence[i];
+ row = document.createElement("tr");
+ table.appendChild(row);
+ cell = document.createElement("td");
+ row.appendChild(cell);
+ cell.appendChild(document.createTextNode(prop));
+ cell = document.createElement("td");
+ row.appendChild(cell);
+ cell.appendChild(document.createTextNode(moments[prop]));
+ }
+ }
+
+ function checkValues() {
+ var win = frames[0];
+ ok(win.performance,
+ "window.performance is missing or not accessible for frame");
+ ok(!win.performance || win.performance.timing,
+ "window.performance.timing is missing or not accessible for frame");
+ collectMoments();
+
+ var sequences = [
+ ["navigationStart", "unloadEventStart", "unloadEventEnd"],
+ ["navigationStart", "fetchStart", "domainLookupStart", "domainLookupEnd",
+ "connectStart", "connectEnd", "requestStart", "responseStart", "responseEnd"],
+ ["responseStart", "domLoading", "domInteractive", "domComplete"],
+ ["domContentLoadedEventStart", "domContentLoadedEventEnd",
+ "loadEventStart", "loadEventEnd"],
+ ];
+
+ for (var i = 0; i < sequences.length; ++i) {
+ var seq = sequences[i];
+ for (var j = 0; j < seq.length; ++j) {
+ var prop = seq[j];
+ if (j > 0) {
+ var prevProp = seq[j - 1];
+ ok(moments[prevProp] <= moments[prop],
+ ["Expected ", prevProp, " to happen before ", prop,
+ ", got ", prevProp, " = ", moments[prevProp],
+ ", ", prop, " = ", moments[prop]].join(""));
+ }
+ }
+ }
+
+ SimpleTest.finish();
+ }
+
+window.onload = function() {
+ var win = frames[0];
+ win.addEventListener("unload", function() {
+ unload = Date.now();
+ }, true);
+ var seenLoad = 0;
+ win.addEventListener("load", function() {
+ seenLoad = Date.now();
+ }, true);
+ frames[0].location = "bug570341_recordevents.html";
+ var interval = setInterval(function() {
+ // time constants here are arbitrary, chosen to allow the test to pass
+ var stopPolling = (win.performance && win.performance.loadEventEnd) ||
+ (seenLoad && Date.now() >= seenLoad + 3000) ||
+ Date.now() >= start + 30000;
+ if (stopPolling) {
+ clearInterval(interval);
+ checkValues();
+ } else if (win._testing_evt_load) {
+ seenLoad = Date.now();
+ }
+ }, 100);
+};
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=570341">Mozilla Bug 570341</a>
+<div id="frames">
+<iframe name="child0" src="navigation/blank.html"></iframe>
+</div>
+<button type="button" onclick="showSequence(document.getElementById('display'))">
+ Show Events</button>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug580069.html b/docshell/test/mochitest/test_bug580069.html
new file mode 100644
index 0000000000..bb0a3bc823
--- /dev/null
+++ b/docshell/test/mochitest/test_bug580069.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=580069
+-->
+<head>
+ <title>Test for Bug 580069</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=580069">Mozilla Bug 580069</a>
+
+<script type="application/javascript">
+
+add_task(async function() {
+ let iframe = document.createElement("iframe");
+ iframe.setAttribute("src", "file_bug580069_1.html");
+
+ // Insert the initial <iframe> document, and wait for page1Load to be called
+ // after it loads.
+ document.body.appendChild(iframe);
+ await new Promise(resolve => {
+ window.page1Load = resolve;
+ });
+ let iframeCw = iframe.contentWindow;
+
+ info("iframe's location is: " + iframeCw.location + "\n");
+
+ // Submit the forum and wait for the initial page load using a POST load.
+ iframeCw.document.getElementById("form").submit();
+ let method1 = await new Promise(resolve => {
+ window.page2Load = resolve;
+ });
+ info("iframe's location is: " + iframeCw.location + ", method is " + method1 + "\n");
+ is(method1, "POST", "Method for first load should be POST.");
+
+ // Push a new state, and refresh the page. This refresh shouldn't pop up the
+ // "are you sure you want to refresh a page with POST data?" dialog. If it
+ // does, this test will hang and fail, and we'll see 'Refreshing iframe...' at
+ // the end of the test log.
+ iframeCw.history.replaceState("", "", "?replaced");
+
+ info("Refreshing iframe...\n");
+ iframeCw.location.reload();
+ let method2 = await new Promise(resolve => {
+ window.page2Load = resolve;
+ });
+
+ info("iframe's location is: " + iframeCw.location + ", method is " + method2 + "\n");
+ is(method2, "GET", "Method for second load should be GET.");
+ is(iframeCw.location.search, "?replaced", "Wrong search on iframe after refresh.");
+});
+</script>
+
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug590573.html b/docshell/test/mochitest/test_bug590573.html
new file mode 100644
index 0000000000..83554a7a66
--- /dev/null
+++ b/docshell/test/mochitest/test_bug590573.html
@@ -0,0 +1,198 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=590573
+-->
+<head>
+ <title>Test for Bug 590573</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=590573">Mozilla Bug 590573</a>
+
+<script type='application/javascript'>
+SimpleTest.waitForExplicitFinish();
+
+// Listen to the first callback, since this indicates that the page loaded.
+var page1LoadCallbackEnabled = true;
+function page1Load() {
+ if (page1LoadCallbackEnabled) {
+ page1LoadCallbackEnabled = false;
+ dump("Got page1 load.\n");
+ pageLoad();
+ } else {
+ dump("Ignoring page1 load.\n");
+ }
+}
+
+var page1PageShowCallbackEnabled = false;
+function page1PageShow() {
+ if (page1PageShowCallbackEnabled) {
+ page1PageShowCallbackEnabled = false;
+ dump("Got page1 pageshow.\n");
+ pageLoad();
+ } else {
+ dump("Ignoring page1 pageshow.\n");
+ }
+}
+
+var page2LoadCallbackEnabled = false;
+function page2Load() {
+ if (page2LoadCallbackEnabled) {
+ page2LoadCallbackEnabled = false;
+ dump("Got page2 popstate.\n");
+ pageLoad();
+ } else {
+ dump("Ignoring page2 popstate.\n");
+ }
+}
+
+var page2PopstateCallbackEnabled = false;
+function page2Popstate() {
+ if (page2PopstateCallbackEnabled) {
+ page2PopstateCallbackEnabled = false;
+ dump("Got page2 popstate.\n");
+ pageLoad();
+ } else {
+ dump("Ignoring page2 popstate.\n");
+ }
+}
+
+var page2PageShowCallbackEnabled = false;
+function page2PageShow() {
+ if (page2PageShowCallbackEnabled) {
+ page2PageShowCallbackEnabled = false;
+ dump("Got page2 pageshow.\n");
+ pageLoad();
+ } else {
+ dump("Ignoring page2 pageshow.\n");
+ }
+}
+
+var popup = window.open("file_bug590573_1.html");
+
+var gTestContinuation = null;
+var loads = 0;
+function pageLoad() {
+ loads++;
+ dump("pageLoad(loads=" + loads + ", page location=" + popup.location + ")\n");
+
+ if (!gTestContinuation) {
+ gTestContinuation = testBody();
+ }
+ var ret = gTestContinuation.next();
+ if (ret.done) {
+ SimpleTest.finish();
+ }
+}
+
+function continueAsync() {
+ popup.addEventListener("popstate", function() {
+ popup.requestAnimationFrame(function() { gTestContinuation.next(); });
+ },
+ {once: true});
+}
+
+function* testBody() {
+ is(popup.scrollY, 0, "test 1");
+ popup.scroll(0, 100);
+
+ popup.history.pushState("", "", "?pushed");
+ is(Math.round(popup.scrollY), 100, "test 2");
+ popup.scroll(0, 200); // set state-2's position to 200
+
+ popup.history.back();
+ continueAsync();
+ yield;
+ is(Math.round(popup.scrollY), 100, "test 3");
+ popup.scroll(0, 150); // set original page's position to 150
+
+ popup.history.forward();
+ continueAsync();
+ yield;
+ is(Math.round(popup.scrollY), 200, "test 4");
+
+ popup.history.back();
+ continueAsync();
+ yield;
+ is(Math.round(popup.scrollY), 150, "test 5");
+
+ popup.history.forward();
+ continueAsync();
+ yield;
+ is(Math.round(popup.scrollY), 200, "test 6");
+
+ // At this point, the history looks like:
+ // PATH POSITION
+ // file_bug590573_1.html 150 <-- oldest
+ // file_bug590573_1.html?pushed 200 <-- newest, current
+
+ // Now test that the scroll position is persisted when we have real
+ // navigations involved. First, we need to spin the event loop so that the
+ // navigation doesn't replace our current history entry.
+
+ setTimeout(pageLoad, 0);
+ yield;
+
+ page2LoadCallbackEnabled = true;
+ popup.location = "file_bug590573_2.html";
+ yield;
+
+ ok(popup.location.href.match("file_bug590573_2.html$"),
+ "Location was " + popup.location +
+ " but should end with file_bug590573_2.html");
+
+ is(popup.scrollY, 0, "test 7");
+ popup.scroll(0, 300);
+
+ // We need to spin the event loop again before we go back, otherwise the
+ // scroll positions don't get updated properly.
+ setTimeout(pageLoad, 0);
+ yield;
+
+ page1PageShowCallbackEnabled = true;
+ popup.history.back();
+ yield;
+
+ // Spin the event loop again so that we get the right scroll positions.
+ setTimeout(pageLoad, 0);
+ yield;
+
+ is(popup.location.search, "?pushed");
+ ok(popup.document.getElementById("div1"), "page should have div1.");
+
+ is(Math.round(popup.scrollY), 200, "test 8");
+
+ popup.history.back();
+ continueAsync();
+ yield;
+ is(Math.round(popup.scrollY), 150, "test 9");
+ popup.history.forward();
+ continueAsync();
+ yield;
+
+ is(Math.round(popup.scrollY), 200, "test 10");
+
+ // Spin one last time...
+ setTimeout(pageLoad, 0);
+ yield;
+
+ page2PageShowCallbackEnabled = true;
+ popup.history.forward();
+ yield;
+
+ // Bug 821821, on Android tegras we get 299 instead of 300 sometimes
+ const scrollY = Math.floor(popup.scrollY);
+ if (scrollY >= 299 && scrollY <= 300) {
+ is(1, 1, "test 11");
+ } else {
+ is(1, 0, "test 11, got " + popup.scrollY + " for popup.scrollY instead of 299|300");
+ }
+ popup.close();
+}
+</script>
+
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug598895.html b/docshell/test/mochitest/test_bug598895.html
new file mode 100644
index 0000000000..e0b17e2663
--- /dev/null
+++ b/docshell/test/mochitest/test_bug598895.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=598895
+-->
+<head>
+ <title>Test for Bug 598895</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=598895">Mozilla Bug 598895</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 598895 */
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+var win1 = window.open();
+win1.document.body.textContent = "Should show";
+
+var windowsLoaded = 0;
+
+window.onmessage = async function(ev) {
+ is(ev.data, "loaded", "Message should be 'loaded'");
+ if (++windowsLoaded == 2) {
+ var one = await snapshotWindow(win1);
+ var two = await snapshotWindow(win2);
+ var three = await snapshotWindow(win3);
+ win1.close();
+ win2.close();
+ win3.close();
+ ok(compareSnapshots(one, two, true)[0], "Popups should look identical");
+ ok(compareSnapshots(one, three, false)[0], "Popups should not look identical");
+
+ SimpleTest.finish();
+ }
+};
+
+var win2 = window.open("file_bug598895_1.html");
+var win3 = window.open("file_bug598895_2.html");
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug634834.html b/docshell/test/mochitest/test_bug634834.html
new file mode 100644
index 0000000000..e1f87de000
--- /dev/null
+++ b/docshell/test/mochitest/test_bug634834.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=634834
+-->
+<head>
+ <title>Test for Bug 634834</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=634834">Mozilla Bug 634834</a>
+
+<script type='application/javascript'>
+SimpleTest.waitForExplicitFinish();
+
+function iframe_loaded() {
+ var loadedAfterPushstate = false;
+ $("iframe").onload = function() {
+ loadedAfterPushstate = true;
+ };
+
+ var obj = { name: "name" };
+ obj.__defineGetter__("a", function() {
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ $("iframe").contentWindow.location = "http://example.com";
+
+ // Wait until we've loaded example.com.
+ do {
+ var r = new XMLHttpRequest();
+ r.open("GET", location.href, false);
+ r.overrideMimeType("text/plain");
+ try { r.send(null); } catch (e) {}
+ } while (!loadedAfterPushstate);
+ });
+
+ try {
+ $("iframe").contentWindow.history.pushState(obj, "");
+ ok(false, "pushState should throw exception.");
+ } catch (e) {
+ ok(true, "pushState threw an exception.");
+ }
+ SimpleTest.finish();
+}
+
+</script>
+
+<iframe id='iframe' src='file_bug634834.html' onload='iframe_loaded()'></iframe>
+
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug637644.html b/docshell/test/mochitest/test_bug637644.html
new file mode 100644
index 0000000000..1e5f4380b4
--- /dev/null
+++ b/docshell/test/mochitest/test_bug637644.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=637644
+-->
+<head>
+ <title>Test for Bug 637644</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=637644">Mozilla Bug 637644</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 637644 */
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+var win1 = window.open("", "", "height=500,width=500");
+win1.document.body.textContent = "Should show";
+
+var windowsLoaded = 0;
+
+window.onmessage = async function(ev) {
+ is(ev.data, "loaded", "Message should be 'loaded'");
+ if (++windowsLoaded == 2) {
+ var one = await snapshotWindow(win1);
+ var two = await snapshotWindow(win2);
+ var three = await snapshotWindow(win3);
+ win1.close();
+ win2.close();
+ win3.close();
+ ok(compareSnapshots(one, two, true)[0], "Popups should look identical");
+ ok(compareSnapshots(one, three, false)[0], "Popups should not look identical");
+
+ SimpleTest.finish();
+ }
+};
+
+var win2 = window.open("file_bug637644_1.html", "", "height=500,width=500");
+var win3 = window.open("file_bug637644_2.html", "", "height=500,width=500");
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug640387_1.html b/docshell/test/mochitest/test_bug640387_1.html
new file mode 100644
index 0000000000..b8aab054a1
--- /dev/null
+++ b/docshell/test/mochitest/test_bug640387_1.html
@@ -0,0 +1,107 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=640387
+-->
+<head>
+ <title>Test for Bug 640387</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=640387">Mozilla Bug 640387</a>
+
+<script type='application/javascript'>
+SimpleTest.waitForExplicitFinish();
+
+function* test() {
+ /* Spin the event loop so we get out of the onload handler. */
+ SimpleTest.executeSoon(function() { gGen.next(); });
+ yield undefined;
+
+ popup.history.pushState("", "", "#hash1");
+ popup.history.pushState("", "", "#hash2");
+
+ // Now the history looks like:
+ // file_bug640387.html
+ // file_bug640387.html#hash1
+ // file_bug640387.html#hash2 <-- current
+
+ // Going back should trigger a hashchange, which will wake us up from the
+ // yield.
+ popup.history.back();
+ yield undefined;
+ ok(true, "Got first hashchange.");
+
+ // Going back should wake us up again.
+ popup.history.back();
+ yield undefined;
+ ok(true, "Got second hashchange.");
+
+ // Now the history looks like:
+ // file_bug640387.html <-- current
+ // file_bug640387.html#hash1
+ // file_bug640387.html#hash2
+
+ // Going forward should trigger a hashchange.
+ popup.history.forward();
+ yield undefined;
+ ok(true, "Got third hashchange.");
+
+ // Now modify the history so it looks like:
+ // file_bug640387.html
+ // file_bug640387.html#hash1
+ // file_bug640387.html#hash1 <-- current
+ popup.history.pushState("", "", "#hash1");
+
+ // Now when we go back, we should not get a hashchange. Instead, wait for a
+ // popstate. We need to asynchronously go back because popstate is fired
+ // sync.
+ gHashchangeExpected = false;
+ gCallbackOnPopstate = true;
+ SimpleTest.executeSoon(function() { popup.history.back(); });
+ yield undefined;
+ ok(true, "Got popstate.");
+ gCallbackOnPopstate = false;
+
+ // Spin the event loop so hashchange has a chance to fire, if it's going to.
+ SimpleTest.executeSoon(function() { gGen.next(); });
+ yield undefined;
+
+ popup.close();
+ SimpleTest.finish();
+}
+
+var gGen = null;
+function childLoad() {
+ gGen = test();
+ gGen.next();
+}
+
+var gHashchangeExpected = true;
+function childHashchange() {
+ if (gHashchangeExpected) {
+ gGen.next();
+ } else {
+ ok(false, "Got hashchange when we weren't expecting one.");
+ }
+}
+
+var gCallbackOnPopstate = false;
+function childPopstate() {
+ if (gCallbackOnPopstate) {
+ gGen.next();
+ }
+}
+
+/* We need to run this test in a popup, because navigating an iframe
+ * back/forwards tends to cause intermittent orange. */
+var popup = window.open("file_bug640387.html");
+
+/* Control now flows up to childLoad(), called once the popup loads. */
+
+</script>
+
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug640387_2.html b/docshell/test/mochitest/test_bug640387_2.html
new file mode 100644
index 0000000000..c248a64836
--- /dev/null
+++ b/docshell/test/mochitest/test_bug640387_2.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=640387
+-->
+<head>
+ <title>Test for Bug 640387</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=640387">Mozilla Bug 640387</a>
+
+<!-- Test that, when going from
+
+ http://example.com/#foo
+
+to
+
+ http://example.com/
+
+via a non-history load, we do a true load, rather than a scroll. -->
+
+<script type='application/javascript'>
+SimpleTest.waitForExplicitFinish();
+
+var callbackOnLoad = false;
+function childLoad() {
+ if (callbackOnLoad) {
+ callbackOnLoad = false;
+ gGen.next();
+ }
+}
+
+var errorOnHashchange = false;
+var callbackOnHashchange = false;
+function childHashchange() {
+ if (errorOnHashchange) {
+ ok(false, "Got unexpected hashchange.");
+ }
+ if (callbackOnHashchange) {
+ callbackOnHashchange = false;
+ gGen.next();
+ }
+}
+
+function* run_test() {
+ var iframe = $("iframe").contentWindow;
+
+ ok(true, "Got first load");
+
+ // Spin the event loop so we exit the onload handler.
+ SimpleTest.executeSoon(function() { gGen.next(); });
+ yield undefined;
+
+ let origLocation = iframe.location + "";
+ callbackOnHashchange = true;
+ iframe.location.hash = "#1";
+ // Wait for a hashchange event.
+ yield undefined;
+
+ ok(true, "Got hashchange.");
+
+ iframe.location = origLocation;
+ // This should produce a load event and *not* a hashchange, because the
+ // result of the load is a different document than we had previously.
+ callbackOnLoad = true;
+ errorOnHashchange = true;
+ yield undefined;
+
+ ok(true, "Got final load.");
+
+ // Spin the event loop to give hashchange a chance to fire, if it's going to.
+ SimpleTest.executeSoon(function() { gGen.next(); });
+ yield undefined;
+
+ SimpleTest.finish();
+}
+
+callbackOnLoad = true;
+var gGen = run_test();
+
+</script>
+
+<iframe id='iframe' src='file_bug640387.html'></iframe>
+
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug653741.html b/docshell/test/mochitest/test_bug653741.html
new file mode 100644
index 0000000000..33ba7077e4
--- /dev/null
+++ b/docshell/test/mochitest/test_bug653741.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=653741
+-->
+<head>
+ <title>Test for Bug 653741</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=653741">Mozilla Bug 653741</a>
+
+<script type="application/javascript">
+
+/** Test for Bug 653741 */
+SimpleTest.waitForExplicitFinish();
+
+function childLoad() {
+ // Spin the event loop so we leave the onload handler.
+ SimpleTest.executeSoon(childLoad2);
+}
+
+function childLoad2() {
+ let cw = $("iframe").contentWindow;
+
+ // Save the Y offset. For sanity's sake, make sure it's not 0, because we
+ // should be at the bottom of the page!
+ let origYOffset = Math.round(cw.pageYOffset);
+ ok(origYOffset != 0, "Original Y offset is not 0.");
+
+ // Scroll the iframe to the top, then navigate to #bottom again.
+ cw.scrollTo(0, 0);
+
+ // Our current location is #bottom, so this should scroll us down to the
+ // bottom again.
+ cw.location = cw.location + "";
+
+ is(Math.round(cw.pageYOffset), origYOffset, "Correct offset after reloading page.");
+ SimpleTest.finish();
+}
+
+</script>
+
+<iframe height='100px' id='iframe' src='file_bug653741.html#bottom'></iframe>
+
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug660404.html b/docshell/test/mochitest/test_bug660404.html
new file mode 100644
index 0000000000..94e3f67aa1
--- /dev/null
+++ b/docshell/test/mochitest/test_bug660404.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=660404
+-->
+<head>
+ <title>Test for Bug 660404</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=660404">Mozilla Bug 660404</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 660404 */
+SimpleTest.waitForExplicitFinish();
+
+var textContent =
+`
+ var bc = new BroadcastChannel("bug660404_multipart");
+ bc.postMessage({command: "finishTest",
+ textContent: window.document.documentElement.textContent,
+ innerHTML: window.document.documentElement.innerHTML
+ });
+ bc.close();
+ window.close();
+`;
+var innerHTML =
+`<head><script>
+ var bc = new BroadcastChannel("bug660404_multipart");
+ bc.postMessage({command: "finishTest",
+ textContent: window.document.documentElement.textContent,
+ innerHTML: window.document.documentElement.innerHTML
+ });
+ bc.close();
+ window.close();
+</`
+// eslint-disable-next-line no-useless-concat
++ `script></head>`
+;
+var bc_multipart = new BroadcastChannel("bug660404_multipart");
+bc_multipart.onmessage = (msgEvent) => {
+ var msg = msgEvent.data;
+ var command = msg.command;
+ if (command == "finishTest") {
+ is(msg.textContent, textContent);
+ is(msg.innerHTML, innerHTML);
+ bc_multipart.close();
+ SimpleTest.finish();
+ }
+}
+var bc = new BroadcastChannel("bug660404");
+bc.onmessage = (msgEvent) => {
+ var msg = msgEvent.data;
+ var command = msg.command;
+ if (command == "pagehide") {
+ is(msg.persisted, true, "Should be bfcached when navigating to multipart");
+ bc.close();
+ }
+}
+
+// If Fission is disabled, the pref is no-op.
+SpecialPowers.pushPrefEnv({set: [["fission.bfcacheInParent", true]]}, () => {
+ // Have to open a new window, since there's no bfcache in subframes
+ window.open("file_bug660404-1.html", "", "noopener");
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug662170.html b/docshell/test/mochitest/test_bug662170.html
new file mode 100644
index 0000000000..d25ee3bd0f
--- /dev/null
+++ b/docshell/test/mochitest/test_bug662170.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=662170
+-->
+<head>
+ <title>Test for Bug 662170</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=662170">Mozilla Bug 662170</a>
+
+<script type="application/javascript">
+
+/** Test for Bug 662170 */
+SimpleTest.waitForExplicitFinish();
+
+function childLoad() {
+ // Spin the event loop so we leave the onload handler.
+ SimpleTest.executeSoon(childLoad2);
+}
+
+function childLoad2() {
+ let cw = $("iframe").contentWindow;
+
+ // When we initially load the page, we should be at the top.
+ is(cw.pageYOffset, 0, "Initial Y offset should be 0.");
+
+ // Scroll the iframe to the bottom.
+ cw.scrollTo(0, 300);
+
+ // Did we actually scroll somewhere?
+ isnot(Math.round(cw.pageYOffset), 0, "Y offset should be non-zero after scrolling.");
+
+ // Now load file_bug662170.html#, which should take us to the top of the
+ // page.
+ cw.location = cw.location + "#";
+
+ is(cw.pageYOffset, 0, "Correct Y offset after loading #.");
+ SimpleTest.finish();
+}
+
+</script>
+
+<!-- When the iframe loads, it calls childLoad(). -->
+<iframe height='100px' id='iframe' src='file_bug662170.html'></iframe>
+
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug668513.html b/docshell/test/mochitest/test_bug668513.html
new file mode 100644
index 0000000000..09c848b6c1
--- /dev/null
+++ b/docshell/test/mochitest/test_bug668513.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=668513
+-->
+<head>
+ <title>Test for Bug 668513</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=668513">Mozilla Bug 668513</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+if (navigator.platform.startsWith("Linux")) {
+ SimpleTest.expectAssertions(0, 1);
+}
+
+SimpleTest.waitForExplicitFinish();
+window.open("file_bug668513.html");
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug669671.html b/docshell/test/mochitest/test_bug669671.html
new file mode 100644
index 0000000000..4470cd6682
--- /dev/null
+++ b/docshell/test/mochitest/test_bug669671.html
@@ -0,0 +1,145 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=669671
+-->
+<head>
+ <title>Test for Bug 669671</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=669671">Mozilla Bug 669671</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/**
+ * Test for Bug 669671.
+ *
+ * This is a bit complicated. We have a script, file_bug669671.sjs, which counts
+ * how many times it's loaded and returns that count in the body of an HTML
+ * document. For brevity, call this page X.
+ *
+ * X is sent with Cache-Control: max-age=0 and can't be bfcached (it has an
+ * onunload handler). Our test does the following in a popup:
+ *
+ * 1) Load X?pushed, to prime the cache.
+ * 2) Navigate to X.
+ * 3) Call pushState and navigate from X to X?pushed.
+ * 4) Navigate to X?navigated.
+ * 5) Go back (to X?pushed).
+ *
+ * We do all this work so we can check that in step 5, we fetch X?pushed from
+ * the network -- we shouldn't use our cached copy, because of the
+ * cache-control header X sends.
+ *
+ * Then we go back and repeat the whole process but call history.replaceState
+ * instead of pushState. And for good measure, we test once more, this time
+ * modifying only the hash of the URI using replaceState. In this case, we
+ * should* load from the cache.
+ *
+ */
+SimpleTest.requestLongerTimeout(2);
+SimpleTest.waitForExplicitFinish();
+
+function onChildLoad() {
+ SimpleTest.executeSoon(function() { gGen.next(); });
+}
+
+var _loadCount = 0;
+function checkPopupLoadCount() {
+ is(popup.document.body.innerHTML, _loadCount + "", "Load count");
+
+ // We normally want to increment _loadCount here. But if the test fails
+ // because we didn't do a load we should have, let's not cause a cascade of
+ // failures by incrementing _loadCount.
+ var origCount = _loadCount;
+ if (popup.document.body.innerHTML >= _loadCount + "")
+ _loadCount++;
+ return origCount;
+}
+
+function* test() {
+ // Step 0 - Make sure the count is reset to 0 in case of reload
+ popup.location = "file_bug669671.sjs?countreset";
+ yield;
+ is(popup.document.body.innerHTML, "0",
+ "Load count should be reset to 0");
+
+ // Step 1 - The popup's body counts how many times we've requested the
+ // resource. This is the first time we've requested it, so it should be '0'.
+ checkPopupLoadCount();
+
+ // Step 2 - We'll get another onChildLoad when this finishes.
+ popup.location = "file_bug669671.sjs";
+ yield undefined;
+
+ // Step 3 - Call pushState and change the URI back to ?pushed.
+ checkPopupLoadCount();
+ popup.history.pushState("", "", "?pushed");
+
+ // Step 4 - Navigate away. This should trigger another onChildLoad.
+ popup.location = "file_bug669671.sjs?navigated-1";
+ yield undefined;
+
+ // Step 5 - Go back. This should result in another onload (because the file is
+ // not in bfcache) and should be the fourth time we've requested the sjs file.
+ checkPopupLoadCount();
+ popup.history.back();
+ yield undefined;
+
+ // This is the check which was failing before we fixed the bug.
+ checkPopupLoadCount();
+
+ popup.close();
+
+ // Do the whole thing again, but with replaceState.
+ popup = window.open("file_bug669671.sjs?replaced");
+ yield undefined;
+ checkPopupLoadCount();
+ popup.location = "file_bug669671.sjs";
+ yield undefined;
+ checkPopupLoadCount();
+ popup.history.replaceState("", "", "?replaced");
+ popup.location = "file_bug669671.sjs?navigated-2";
+ yield undefined;
+ checkPopupLoadCount();
+ popup.history.back();
+ yield undefined;
+ checkPopupLoadCount();
+ popup.close();
+
+ // Once more, with feeling. Notice that we don't have to prime the cache
+ // with an extra load here, because X and X#hash share the same cache entry.
+ popup = window.open("file_bug669671.sjs?hash-test");
+ yield undefined;
+ var initialCount = checkPopupLoadCount();
+ popup.history.replaceState("", "", "#hash");
+ popup.location = "file_bug669671.sjs?navigated-3";
+ yield undefined;
+ checkPopupLoadCount();
+ popup.history.back();
+ yield undefined;
+ is(popup.document.body.innerHTML, initialCount + "",
+ "Load count (should be cached)");
+ popup.close();
+
+ SimpleTest.finish();
+}
+
+var gGen = test();
+var popup;
+
+// Disable RCWN to make cache behavior deterministic.
+SpecialPowers.pushPrefEnv({set: [["network.http.rcwn.enabled", false]]}, () => {
+ // This will call into onChildLoad once it loads.
+ popup = window.open("file_bug669671.sjs?pushed");
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug675587.html b/docshell/test/mochitest/test_bug675587.html
new file mode 100644
index 0000000000..452bbc8058
--- /dev/null
+++ b/docshell/test/mochitest/test_bug675587.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=675587
+-->
+<head>
+ <title>Test for Bug 675587</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=675587">Mozilla Bug 675587</a>
+<p id="display">
+ <iframe src="file_bug675587.html#hash"></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 675587 */
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ ok(window.frames[0].location.href.endsWith("file_bug675587.html#"),
+ "Should have the right href");
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug680257.html b/docshell/test/mochitest/test_bug680257.html
new file mode 100644
index 0000000000..4d5736ac0a
--- /dev/null
+++ b/docshell/test/mochitest/test_bug680257.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=680257
+-->
+<head>
+ <title>Test for Bug 680257</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=680257">Mozilla Bug 680257</a>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var popup = window.open("file_bug680257.html");
+
+var gTestContinuation = null;
+function continueAsync() {
+ popup.addEventListener("hashchange",
+ function(e) { gTestContinuation.next(); }, { once: true });
+}
+
+// The popup will call into popupLoaded() once it loads.
+function popupLoaded() {
+ // runTests() needs to be called from outside popupLoaded's onload handler.
+ // Otherwise, the navigations we do in runTests won't create new SHEntries.
+ SimpleTest.executeSoon(function() {
+ if (!gTestContinuation) {
+ gTestContinuation = runTests();
+ }
+ gTestContinuation.next();
+ });
+}
+
+function* runTests() {
+ checkPopupLinkStyle(false, "Initial");
+
+ popup.location.hash = "a";
+ continueAsync();
+ yield;
+ checkPopupLinkStyle(true, "After setting hash");
+
+ popup.history.back();
+ continueAsync();
+ yield;
+
+ checkPopupLinkStyle(false, "After going back");
+
+ popup.history.forward();
+ continueAsync();
+ yield;
+ checkPopupLinkStyle(true, "After going forward");
+
+ popup.close();
+ SimpleTest.finish();
+}
+
+function checkPopupLinkStyle(isTarget, desc) {
+ var link = popup.document.getElementById("a");
+ var style = popup.getComputedStyle(link);
+ var color = style.getPropertyValue("color");
+
+ // Color is red if isTarget, black otherwise.
+ if (isTarget) {
+ is(color, "rgb(255, 0, 0)", desc);
+ } else {
+ is(color, "rgb(0, 0, 0)", desc);
+ }
+}
+
+</script>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug691547.html b/docshell/test/mochitest/test_bug691547.html
new file mode 100644
index 0000000000..706cd5013b
--- /dev/null
+++ b/docshell/test/mochitest/test_bug691547.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=691547
+-->
+<head>
+ <title>Test for Bug 691547</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+ var navStart = 0;
+ var beforeReload = 0;
+ function onContentLoad() {
+ var frame = frames[0];
+ if (!navStart) {
+ // First time we perform navigation in subframe. The bug is that
+ // load in subframe causes timing.navigationStart to be recorded
+ // as if it was a start of the next navigation.
+ var innerFrame = frame.frames[0];
+ navStart = frame.performance.timing.navigationStart;
+ innerFrame.location = "bug570341_recordevents.html";
+ // Let's wait a bit so the difference is clear anough.
+ setTimeout(reload, 3000);
+ } else {
+ // Content reloaded, time to check. We are allowing a huge time slack,
+ // in case clock is imprecise. If we have a bug, the difference is
+ // expected to be about the timeout value set above.
+ var diff = frame.performance.timing.navigationStart - beforeReload;
+ ok(diff >= -200,
+ "navigationStart should be set after reload request. " +
+ "Measured difference: " + diff + " (should be positive)");
+ SimpleTest.finish();
+ }
+ }
+ function reload() {
+ var frame = frames[0];
+ ok(navStart == frame.performance.timing.navigationStart,
+ "navigationStart should not change when frame loads.");
+ beforeReload = Date.now();
+ frame.location.reload();
+ }
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=570341">Mozilla Bug 570341</a>
+<div id="frames">
+<iframe name="frame0" id="frame0" src="bug691547_frame.html" onload="onContentLoad()"></iframe>
+</div>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug694612.html b/docshell/test/mochitest/test_bug694612.html
new file mode 100644
index 0000000000..8ea4331c43
--- /dev/null
+++ b/docshell/test/mochitest/test_bug694612.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=694612
+-->
+<head>
+ <title>Test for Bug 694612</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=694612">Mozilla Bug 694612</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 694612 */
+SimpleTest.waitForExplicitFinish();
+
+window.addEventListener("message", receiveMessage);
+function receiveMessage(event) {
+ ok(event.data.result, "should have performance API in an <object>");
+ window.removeEventListener("message", receiveMessage);
+ SimpleTest.finish();
+}
+</script>
+<object type="text/html"
+ data="data:text/html,<script>parent.postMessage({result:performance!=null},'*');</script>">
+</object>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug703855.html b/docshell/test/mochitest/test_bug703855.html
new file mode 100644
index 0000000000..4aa7b08800
--- /dev/null
+++ b/docshell/test/mochitest/test_bug703855.html
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=703855
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 703855</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=703855">Mozilla Bug 703855</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <iframe id="f" src="file_bug703855.html"></iframe>
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 703855 */
+
+SimpleTest.waitForExplicitFinish();
+
+var timingAttributes = [
+ "connectEnd",
+ "connectStart",
+ "domComplete",
+ "domContentLoadedEventEnd",
+ "domContentLoadedEventStart",
+ "domInteractive",
+ "domLoading",
+ "domainLookupEnd",
+ "domainLookupStart",
+ "fetchStart",
+ "loadEventEnd",
+ "loadEventStart",
+ "navigationStart",
+ "redirectEnd",
+ "redirectStart",
+ "requestStart",
+ "responseEnd",
+ "responseStart",
+ "unloadEventEnd",
+ "unloadEventStart",
+];
+var originalTiming = {};
+
+function runTest() {
+ var timing = $("f").contentWindow.performance.timing;
+ for (let i in timingAttributes) {
+ originalTiming[timingAttributes[i]] = timing[timingAttributes[i]];
+ }
+
+ var doc = $("f").contentDocument;
+ doc.open();
+ doc.write("<!DOCTYPE html>");
+ doc.close();
+
+ SimpleTest.executeSoon(function() {
+ var newTiming = $("f").contentWindow.performance.timing;
+ for (let i in timingAttributes) {
+ is(newTiming[timingAttributes[i]], originalTiming[timingAttributes[i]],
+ "document.open should not affect value of " + timingAttributes[i]);
+ }
+ SimpleTest.finish();
+ });
+}
+
+addLoadEvent(function() {
+ SimpleTest.executeSoon(runTest);
+});
+
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug728939.html b/docshell/test/mochitest/test_bug728939.html
new file mode 100644
index 0000000000..168184099a
--- /dev/null
+++ b/docshell/test/mochitest/test_bug728939.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=728939
+-->
+<head>
+ <title>Test for Bug 728939</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=728939">Mozilla Bug 728939</a>
+
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+// Called when the popup finishes loading.
+function popupLoaded() {
+ popup.location.hash = "#foo";
+ is(popup.document.URL, popup.location.href, "After hashchange.");
+
+ popup.history.pushState("", "", "bar");
+ is(popup.document.URL, popup.location.href, "After pushState.");
+
+ popup.history.replaceState("", "", "baz");
+ is(popup.document.URL, popup.location.href, "After replaceState.");
+
+ popup.close();
+ SimpleTest.finish();
+}
+
+var popup = window.open("file_bug728939.html");
+
+</script>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_bug797909.html b/docshell/test/mochitest/test_bug797909.html
new file mode 100644
index 0000000000..15b7c27c0f
--- /dev/null
+++ b/docshell/test/mochitest/test_bug797909.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=797909
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 797909</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=797909">Mozilla Bug 797909</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+ /** Test for Bug 797909 */
+
+ SimpleTest.waitForExplicitFinish();
+
+ function runTest() {
+ var iframe = document.getElementById("ifr");
+ try {
+ iframe.contentWindow.document;
+ ok(false, "Should have thrown an exception");
+ } catch (ex) {
+ ok(true, "Got an exception");
+ }
+
+ iframe = document.createElement("iframe");
+ // set sandbox attribute
+ iframe.sandbox = "allow-scripts";
+ // and then insert into the doc
+ document.body.appendChild(iframe);
+
+ try {
+ iframe.contentWindow.document;
+ ok(false, "Should have thrown an exception");
+ } catch (ex) {
+ ok(true, "Got an exception");
+ }
+
+ iframe = document.createElement("iframe");
+ // set sandbox attribute
+ iframe.sandbox = "allow-same-origin";
+ // and then insert into the doc
+ document.body.appendChild(iframe);
+
+ try {
+ iframe.contentWindow.document;
+ ok(true, "Shouldn't have thrown an exception");
+ } catch (ex) {
+ ok(false, "Got an unexpected exception");
+ }
+
+ SimpleTest.finish();
+ }
+
+</script>
+</pre>
+<iframe id="ifr" sandbox = "allow-scripts"></iframe>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_close_onpagehide_by_history_back.html b/docshell/test/mochitest/test_close_onpagehide_by_history_back.html
new file mode 100644
index 0000000000..33140502f7
--- /dev/null
+++ b/docshell/test/mochitest/test_close_onpagehide_by_history_back.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<title>Test for closing window in pagehide event callback caused by history.back()</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1432396">Mozilla Bug 1432396</a>
+<p id="display"></p>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+const w = window.open("file_close_onpagehide1.html");
+window.addEventListener("message", e => {
+ is(e.data, "initial", "The initial page loaded");
+ window.addEventListener("message", evt => {
+ is(evt.data, "second", "The second page loaded");
+ w.onpagehide = () => {
+ w.close();
+ info("try to close the popped up window in onpagehide");
+ SimpleTest.finish();
+ };
+ w.history.back();
+ }, { once: true });
+ w.location = "file_close_onpagehide2.html";
+}, { once: true });
+</script>
diff --git a/docshell/test/mochitest/test_close_onpagehide_by_window_close.html b/docshell/test/mochitest/test_close_onpagehide_by_window_close.html
new file mode 100644
index 0000000000..8b094cdaa4
--- /dev/null
+++ b/docshell/test/mochitest/test_close_onpagehide_by_window_close.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<title>Test for closing window in pagehide event callback caused by window.close()</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1432396">Mozilla Bug 1432396</a>
+<p id="display"></p>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+const w = window.open("file_close_onpagehide1.html");
+window.addEventListener("message", e => {
+ is(e.data, "initial", "The initial page loaded");
+ w.onpagehide = () => {
+ w.close();
+ info("try to close the popped up window in onpagehide");
+ SimpleTest.finish();
+ };
+ w.close();
+}, { once: true });
+</script>
diff --git a/docshell/test/mochitest/test_compressed_multipart.html b/docshell/test/mochitest/test_compressed_multipart.html
new file mode 100644
index 0000000000..438819b643
--- /dev/null
+++ b/docshell/test/mochitest/test_compressed_multipart.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1600211
+
+Loads a document that is served as multipart/x-mixed-replace as well as gzip compressed.
+Checks that we correctly decompress and display it (via running JS within the document to notify us).
+-->
+<head>
+ <title>Test for Bug 1600211</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1600211">Mozilla Bug 1600211</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1600211 */
+SimpleTest.waitForExplicitFinish();
+
+var w;
+
+function finishTest() {
+ is(w.document.documentElement.textContent, "opener.finishTest();");
+ is(w.document.documentElement.innerHTML, "<head><script>opener.finishTest();</" +
+ "script></head>");
+ w.close();
+ SimpleTest.finish();
+}
+
+w = window.open("file_compressed_multipart");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_content_javascript_loads.html b/docshell/test/mochitest/test_content_javascript_loads.html
new file mode 100644
index 0000000000..eabc1d314e
--- /dev/null
+++ b/docshell/test/mochitest/test_content_javascript_loads.html
@@ -0,0 +1,163 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Test for Bug 1647519</title>
+ <meta charset="utf-8">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1647519">Mozilla Bug 1647519</a>
+
+<script type="application/javascript">
+"use strict";
+
+function promiseMessage(source, filter = event => true) {
+ return new Promise(resolve => {
+ function listener(event) {
+ if (event.source == source && filter(event)) {
+ window.removeEventListener("message", listener);
+ resolve(event);
+ }
+ }
+ window.addEventListener("message", listener);
+ });
+}
+
+async function runTests(resourcePath) {
+ /* globals Assert, content */
+ let doc = content.document;
+
+ // Sends a message to the given target window and waits for a response a few
+ // times to (more or less) ensure that a `javascript:` load request has had
+ // time to succeed, if it were going to.
+ async function doSomeRoundTrips(target) {
+ for (let i = 0; i < 3; i++) {
+ // Note: The ping message needs to be sent from a script running in the
+ // content scope or there will be no source window for the reply to be
+ // sent to.
+ await content.wrappedJSObject.ping(target);
+ }
+ }
+
+ function promiseEvent(target, name) {
+ return new Promise(resolve => {
+ target.addEventListener(name, resolve, { once: true });
+ });
+ }
+
+ function createIframe(host, id) {
+ let iframe = doc.createElement("iframe");
+ iframe.id = id;
+ iframe.name = id;
+ iframe.src = `https://${host}${resourcePath}file_content_javascript_loads_frame.html`;
+ doc.body.appendChild(iframe);
+ return promiseEvent(iframe, "load");
+ }
+
+ const ID_SAME_ORIGIN = "frame-same-origin";
+ const ID_SAME_BASE_DOMAIN = "frame-same-base-domain";
+ const ID_CROSS_BASE_DOMAIN = "frame-cross-base-domain";
+
+ await Promise.all([
+ createIframe("example.com", ID_SAME_ORIGIN),
+ createIframe("test1.example.com", ID_SAME_BASE_DOMAIN),
+ createIframe("example.org", ID_CROSS_BASE_DOMAIN),
+ ]);
+
+ let gotJSLoadFrom = null;
+ let pendingJSLoadID = null;
+ content.addEventListener("message", event => {
+ if ("javascriptLoadID" in event.data) {
+ Assert.equal(
+ event.data.javascriptLoadID,
+ pendingJSLoadID,
+ "Message from javascript: load should have the expected ID"
+ );
+ Assert.equal(
+ gotJSLoadFrom,
+ null,
+ "Should not have seen a previous load message this cycle"
+ );
+ gotJSLoadFrom = event.source.name;
+ }
+ });
+
+ async function watchForJSLoads(frameName, expected, task) {
+ let loadId = Math.random();
+
+ let jsURI =
+ "javascript:" +
+ encodeURI(`parent.postMessage({ javascriptLoadID: ${loadId} }, "*")`);
+
+ pendingJSLoadID = loadId;
+ gotJSLoadFrom = null;
+
+ await task(jsURI);
+
+ await doSomeRoundTrips(content.wrappedJSObject[frameName]);
+
+ if (expected) {
+ Assert.equal(
+ gotJSLoadFrom,
+ frameName,
+ `Should have seen javascript: URI loaded into ${frameName}`
+ );
+ } else {
+ Assert.equal(
+ gotJSLoadFrom,
+ null,
+ "Should not have seen javascript: URI loaded"
+ );
+ }
+ }
+
+ let frames = [
+ { name: ID_SAME_ORIGIN, expectLoad: true },
+ { name: ID_SAME_BASE_DOMAIN, expectLoad: false },
+ { name: ID_CROSS_BASE_DOMAIN, expectLoad: false },
+ ];
+ for (let { name, expectLoad } of frames) {
+ info(`Checking loads for frame "${name}". Expecting loads: ${expectLoad}`);
+
+ info("Checking location setter");
+ await watchForJSLoads(name, expectLoad, jsURI => {
+ // Note: We need to do this from the content scope since security checks
+ // depend on the JS caller scope.
+ content.wrappedJSObject.setFrameLocation(name, jsURI);
+ });
+
+ info("Checking targeted <a> load");
+ await watchForJSLoads(name, expectLoad, jsURI => {
+ let a = doc.createElement("a");
+ a.target = name;
+ a.href = jsURI;
+ doc.body.appendChild(a);
+ a.click();
+ a.remove();
+ });
+
+ info("Checking targeted window.open load");
+ await watchForJSLoads(name, expectLoad, jsURI => {
+ content.wrappedJSObject.open(jsURI, name);
+ });
+ }
+}
+
+add_task(async function() {
+ const resourcePath = location.pathname.replace(/[^\/]+$/, "");
+
+ let win = window.open(
+ `https://example.com${resourcePath}file_content_javascript_loads_root.html`
+ );
+ await promiseMessage(win, event => event.data == "ready");
+
+ await SpecialPowers.spawn(win, [resourcePath], runTests);
+
+ win.close();
+});
+</script>
+
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_double_submit.html b/docshell/test/mochitest/test_double_submit.html
new file mode 100644
index 0000000000..640930718d
--- /dev/null
+++ b/docshell/test/mochitest/test_double_submit.html
@@ -0,0 +1,98 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Test for Bug 1590762</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ </head>
+ <body>
+ <iframe name="targetFrame" id="targetFrame"></iframe>
+ <form id="form" action="double_submit.sjs?delay=1000" method="POST" target="targetFrame">
+ <input id="token" type="text" name="token" value="">
+ <input id="button" type="submit">
+ </form>
+ <script>
+ "use strict";
+
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ const CROSS_ORIGIN_URI = "http://test1.example.com/tests/docshell/test/mochitest/ping.html";
+
+ function asyncClick(counts) {
+ let frame = document.createElement('iframe');
+ frame.addEventListener(
+ 'load', () => frame.contentWindow.postMessage({command: "start"}, "*"),
+ { once:true });
+ frame.src = "clicker.html";
+
+ addEventListener('message', ({source}) => {
+ if (source === frame.contentWindow) {
+ counts.click++;
+ synthesizeMouse(document.getElementById('button'), 5, 5, {});
+ }
+ }, { once: true });
+
+ document.body.appendChild(frame);
+ return stop;
+ }
+
+ function click(button) {
+ synthesizeMouse(button, 5, 5, {});
+ }
+
+ add_task(async function runTest() {
+ let frame = document.getElementById('targetFrame');
+ await new Promise(resolve => {
+ addEventListener('message', resolve, {once: true});
+ frame.src = CROSS_ORIGIN_URI;
+ });
+
+ let form = document.getElementById('form');
+ let button = document.getElementById('button');
+
+ let token = document.getElementById('token');
+ token.value = "first";
+
+ await new Promise((resolve, reject) => {
+ let counts = { click: 0, submit: 0 };
+ form.addEventListener('submit', () => counts.submit++);
+ asyncClick(counts);
+ form.requestSubmit(button);
+ token.value = "bad";
+ let steps = {
+ good: {
+ entered: false,
+ next: () => { steps.good.entered = true; resolve(); },
+ assertion: () => {
+ ok(steps.first.entered && !steps.bad.entered, "good comes after first, but not bad")
+ }
+ },
+ first: {
+ entered: false,
+ next: () => { steps.first.entered = true; token.value = "good"; click(button); },
+ assertion: () => {
+ ok(!steps.good.entered && !steps.bad.entered, "first message is first")
+ is(counts.click, 1, "clicked");
+ is(counts.submit, 2, "did submit");
+ }
+ },
+ bad: {
+ entered: false,
+ next: () => { reject(); },
+ assertion: () => ok(false, "we got a bad message")
+ }
+ };
+ addEventListener('message', ({source, data}) => {
+ if (source !== frame.contentWindow) {
+ return;
+ }
+
+ let step = steps[data] || reject;
+ step.assertion();
+ step.next();
+ })
+ });
+ });
+ </script>
+ </body>
+</html>
diff --git a/docshell/test/mochitest/test_forceinheritprincipal_overrule_owner.html b/docshell/test/mochitest/test_forceinheritprincipal_overrule_owner.html
new file mode 100644
index 0000000000..70d610a677
--- /dev/null
+++ b/docshell/test/mochitest/test_forceinheritprincipal_overrule_owner.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<script type="text/javascript">
+
+var channel = SpecialPowers.wrap(window).docShell.currentDocumentChannel;
+var loadInfo = channel.loadInfo;
+
+// 1) perform some sanity checks
+var triggeringPrincipal = channel.loadInfo.triggeringPrincipal.asciiSpec;
+var loadingPrincipal = channel.loadInfo.loadingPrincipal.asciiSpec;
+var principalToInherit = channel.loadInfo.principalToInherit.asciiSpec;
+
+ok(triggeringPrincipal.startsWith("http://mochi.test:8888/")
+ || triggeringPrincipal.startsWith("http://mochi.xorigin-test:8888/"),
+ "initial triggeringPrincipal correct");
+ok(loadingPrincipal.startsWith("http://mochi.test:8888/")
+ || loadingPrincipal.startsWith("http://mochi.xorigin-test:8888/"),
+ "initial loadingPrincipal correct");
+ok(principalToInherit.startsWith("http://mochi.test:8888/")
+ || principalToInherit.startsWith("http://mochi.xorigin-test:8888/"),
+ "initial principalToInherit correct");
+
+// reset principals on the loadinfo
+loadInfo.resetPrincipalToInheritToNullPrincipal();
+
+// 2) verify loadInfo contains the correct principals
+triggeringPrincipal = channel.loadInfo.triggeringPrincipal.asciiSpec;
+loadingPrincipal = channel.loadInfo.loadingPrincipal.asciiSpec;
+principalToInherit = channel.loadInfo.principalToInherit;
+
+ok(triggeringPrincipal.startsWith("http://mochi.test:8888/")
+ || triggeringPrincipal.startsWith("http://mochi.xorigin-test:8888/"),
+ "triggeringPrincipal after resetting correct");
+ok(loadingPrincipal.startsWith("http://mochi.test:8888/")
+ || loadingPrincipal.startsWith("http://mochi.xorigin-test:8888/"),
+ "loadingPrincipal after resetting correct");
+ok(principalToInherit.isNullPrincipal
+ || principalToInherit.startsWith("http://mochi.xorigin-test:8888/"),
+ "principalToInherit after resetting correct");
+
+// 3) verify that getChannelResultPrincipal returns right principal
+var resultPrincipal = SpecialPowers.Services.scriptSecurityManager
+ .getChannelResultPrincipal(channel);
+
+ok(resultPrincipal.isNullPrincipal,
+ "resultPrincipal after resetting correct");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_form_restoration.html b/docshell/test/mochitest/test_form_restoration.html
new file mode 100644
index 0000000000..b929236770
--- /dev/null
+++ b/docshell/test/mochitest/test_form_restoration.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test form restoration for no-store pages</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ function waitForMessage(aBroadcastChannel) {
+ return new Promise(resolve => {
+ aBroadcastChannel.addEventListener("message", ({ data }) => {
+ resolve(data);
+ }, { once: true });
+ });
+ }
+
+ function postMessageAndWait(aBroadcastChannel, aMsg) {
+ let promise = waitForMessage(aBroadcastChannel);
+ aBroadcastChannel.postMessage(aMsg);
+ return promise;
+ }
+
+ async function startTest(aTestFun) {
+ let bc = new BroadcastChannel("form_restoration");
+
+ let promise = waitForMessage(bc);
+ window.open("file_form_restoration_no_store.html", "", "noopener");
+ await promise;
+
+ // test steps
+ await aTestFun(bc);
+
+ // close broadcast channel and window
+ bc.postMessage("close");
+ bc.close();
+ }
+
+ /* Test for bug1740517 */
+ add_task(async function history_back() {
+ await startTest(async (aBroadcastChannel) => {
+ // update form data
+ aBroadcastChannel.postMessage("enter_data");
+
+ // navigate
+ await postMessageAndWait(aBroadcastChannel, "navigate");
+
+ // history back
+ let { persisted, formData } = await postMessageAndWait(aBroadcastChannel, "back");
+
+ // check form data
+ ok(!persisted, "Page with a no-store header shouldn't be bfcached.");
+ is(formData, "initial", "We shouldn't restore form data when going back to a page with a no-store header.");
+ });
+ });
+
+ /* Test for bug1752250 */
+ add_task(async function location_reload() {
+ await startTest(async (aBroadcastChannel) => {
+ // update form data
+ aBroadcastChannel.postMessage("enter_data");
+
+ // reload
+ let { persisted, formData } = await postMessageAndWait(aBroadcastChannel, "reload");
+
+ // check form data
+ ok(!persisted, "Page with a no-store header shouldn't be bfcached.");
+ is(formData, "initial", "We shouldn't restore form data when reload a page with a no-store header.");
+ });
+ });
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_framedhistoryframes.html b/docshell/test/mochitest/test_framedhistoryframes.html
new file mode 100644
index 0000000000..f90028af0a
--- /dev/null
+++ b/docshell/test/mochitest/test_framedhistoryframes.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=602256
+-->
+<head>
+ <title>Test for Bug 602256</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=602256">Mozilla Bug 602256</a>
+<p id="display"></p>
+<div id="content">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 602256 */
+
+SimpleTest.waitForExplicitFinish();
+var win = window.open("file_framedhistoryframes.html");
+
+function done() {
+ win.close();
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_iframe_srcdoc_to_remote.html b/docshell/test/mochitest/test_iframe_srcdoc_to_remote.html
new file mode 100644
index 0000000000..05e0934d50
--- /dev/null
+++ b/docshell/test/mochitest/test_iframe_srcdoc_to_remote.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<head>
+ <meta charset="utf-8">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body onload="test()">
+ <script>
+ /*
+ Test to verify that when we change an OOP iframe to one that has a
+ srcdoc it loads in the correct process, which in this case is this
+ test document.
+ */
+ SimpleTest.waitForExplicitFinish();
+ async function test() {
+ // Create an OOP iframe
+ let frame = document.createElement("iframe");
+ await new Promise(r => {
+ frame.onload = r;
+ document.body.appendChild(frame);
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ frame.contentWindow.location = "http://example.net/tests/docshell/test/dummy_page.html";
+ });
+ if (SpecialPowers.useRemoteSubframes) {
+ ok(SpecialPowers.Cu.isRemoteProxy(frame.contentWindow), "should be a remote frame");
+ }
+
+ // Remove the attribute so we can set a srcdoc attribute on it
+ frame.removeAttribute("src");
+
+ // Set a srcdoc attribute on this iframe and wait for the load
+ await new Promise(r => {
+ frame.onload = r;
+ frame.setAttribute("srcdoc", '<html><body>body of the srcdoc frame</body></html>');
+ });
+
+ // We should be in the same process as this test document
+ ok(!SpecialPowers.Cu.isRemoteProxy(frame.contentWindow), "should NOT be a remote frame");
+ SimpleTest.finish();
+ }
+ </script>
+</body>
+
diff --git a/docshell/test/mochitest/test_javascript_sandboxed_popup.html b/docshell/test/mochitest/test_javascript_sandboxed_popup.html
new file mode 100644
index 0000000000..edce93c26f
--- /dev/null
+++ b/docshell/test/mochitest/test_javascript_sandboxed_popup.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<head>
+<meta charset="utf-8">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+<iframe srcdoc="<a href='javascript:opener.parent.ok(false, `The JS ran!`)' target=_blank rel=opener>click</a>"
+ sandbox="allow-popups allow-same-origin"></iframe>
+
+<script>
+add_task(async function() {
+ let promise = new Promise(resolve =>{
+ SpecialPowers.addObserver(function obs(subject) {
+ is(subject.opener, window[0],
+ "blocked javascript URI should have been targeting the pop-up document");
+ subject.close();
+ SpecialPowers.removeObserver(obs, "javascript-uri-blocked-by-sandbox");
+ resolve();
+ }, "javascript-uri-blocked-by-sandbox");
+ });
+ document.querySelector("iframe").contentDocument.querySelector("a").click();
+ await promise;
+});
+</script>
+</body>
diff --git a/docshell/test/mochitest/test_load_during_reload.html b/docshell/test/mochitest/test_load_during_reload.html
new file mode 100644
index 0000000000..88856b0100
--- /dev/null
+++ b/docshell/test/mochitest/test_load_during_reload.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test loading a new page after calling reload()</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+
+ function promiseForLoad() {
+ return new Promise(resolve => {
+ addEventListener("message", resolve, { once: true });
+ });
+ }
+
+ add_task(async function runTest() {
+ let win = window.open("file_load_during_reload.html");
+ await promiseForLoad();
+
+ win.location.reload();
+ win.location.href = "file_load_during_reload.html?nextpage";
+ await promiseForLoad();
+
+ ok(win.location.href.includes("nextpage"), "Should have loaded the next page.");
+ win.close();
+ });
+
+ add_task(async function runTest2() {
+ let win = window.open("file_load_during_reload.html");
+ await promiseForLoad();
+
+ win.history.replaceState("", "", "?1");
+ win.location.reload();
+ win.history.pushState("", "", "?2");
+ win.location.reload();
+ await promiseForLoad();
+
+ ok(win.location.href.includes("2"), "Should have loaded the second page.");
+ win.close();
+ });
+
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_navigate_after_pagehide.html b/docshell/test/mochitest/test_navigate_after_pagehide.html
new file mode 100644
index 0000000000..17d58d6e62
--- /dev/null
+++ b/docshell/test/mochitest/test_navigate_after_pagehide.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Test for navigation attempts by scripts in inactive inner window</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ </head>
+<body>
+<iframe src="dummy_page.html" id="iframe"></iframe>
+
+<script>
+"use strict";
+
+add_task(async function() {
+ let iframe = document.getElementById("iframe");
+
+ let navigate = iframe.contentWindow.eval(`(function() {
+ location.href = "/";
+ })`);
+
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ iframe.src = "http://example.com/";
+ await new Promise(resolve =>
+ iframe.addEventListener("load", resolve, { once: true })
+ );
+
+ // This should do nothing. But, importantly, it should especially not crash.
+ navigate();
+
+ ok(true, "We didn't crash");
+});
+</script>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_pushState_after_document_open.html b/docshell/test/mochitest/test_pushState_after_document_open.html
new file mode 100644
index 0000000000..b81951b7ae
--- /dev/null
+++ b/docshell/test/mochitest/test_pushState_after_document_open.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=957479
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 957479</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 957479 */
+ SimpleTest.waitForExplicitFinish();
+ // Child needs to invoke us, otherwise our onload will fire before the child
+ // has done the write/close bit.
+ onmessage = function doTest() {
+ is(frames[0].location.pathname, "/tests/docshell/test/mochitest/file_pushState_after_document_open.html",
+ "Should have the right path here");
+ is(frames[0].location.hash, "", "Should have the right hash here");
+ frames[0].history.pushState({}, "", frames[0].document.URL + "#foopy");
+ is(frames[0].location.pathname, "/tests/docshell/test/mochitest/file_pushState_after_document_open.html",
+ "Pathname should not have changed");
+ is(frames[0].location.hash, "#foopy", "Hash should have changed");
+ SimpleTest.finish();
+ };
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=957479">Mozilla Bug 957479</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe src="file_pushState_after_document_open.html"></iframe>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_redirect_history.html b/docshell/test/mochitest/test_redirect_history.html
new file mode 100644
index 0000000000..a67c808405
--- /dev/null
+++ b/docshell/test/mochitest/test_redirect_history.html
@@ -0,0 +1,58 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Test for redirect from POST</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ </head>
+ <body>
+ <script>
+ "use strict";
+
+ info("Starting tests");
+
+ let tests = new Map([
+ ["sameorigin", window.location.origin],
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ ["crossorigin", "http://test1.example.com"],
+ ]);
+ for (let [kind, origin] of tests) {
+ add_task(async function runTest() {
+ info(`Submitting to ${origin}`);
+
+ let win;
+ await new Promise(resolve => {
+ addEventListener("message", resolve, { once: true });
+ info("Loading file_redirect_history.html");
+ win = window.open("file_redirect_history.html");
+ });
+ info("Done loading file_redirect_history.html");
+
+ let length = win.history.length;
+ let loc = win.location.toString();
+
+ await new Promise(resolve => {
+ addEventListener("message", resolve, { once: true });
+ info("Posting");
+ win.postMessage(`${origin}/tests/docshell/test/mochitest/form_submit_redirect.sjs?redirectTo=${loc}`, "*")
+ });
+ info("Done posting\n");
+ is(win.history.length, length, `Test ${kind}: history length should not change.`);
+ info(`Length=${win.history.length}`);
+ is(win.location.toString(), loc, `Test ${kind}: location should not change.`);
+
+ await new Promise(resolve => {
+ addEventListener("message", resolve, { once: true });
+ info("Reloading");
+ win.location.reload();
+ });
+ info("Done reloading\n");
+ is(win.location.toString(), loc, `Test ${kind}: location should not change after reload.`);
+
+ win.close();
+ });
+ }
+ </script>
+ </body>
+</html>
diff --git a/docshell/test/mochitest/test_triggeringprincipal_location_seturi.html b/docshell/test/mochitest/test_triggeringprincipal_location_seturi.html
new file mode 100644
index 0000000000..92e20b03ea
--- /dev/null
+++ b/docshell/test/mochitest/test_triggeringprincipal_location_seturi.html
@@ -0,0 +1,105 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<script type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+const SAME_ORIGIN_URI = "http://mochi.test:8888/tests/docshell/test/dummy_page.html";
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+const CROSS_ORIGIN_URI = "http://example.com/tests/docshell/test/dummy_page.html";
+const NUMBER_OF_TESTS = 3;
+let testCounter = 0;
+
+function checkFinish() {
+ testCounter++;
+ if (testCounter < NUMBER_OF_TESTS) {
+ return;
+ }
+ SimpleTest.finish();
+}
+
+// ---- test 1 ----
+
+let myFrame1 = document.createElement("iframe");
+myFrame1.src = SAME_ORIGIN_URI;
+myFrame1.addEventListener("load", checkLoadFrame1);
+document.documentElement.appendChild(myFrame1);
+
+function checkLoadFrame1() {
+ myFrame1.removeEventListener("load", checkLoadFrame1);
+ // window.location.href is no longer cross-origin accessible in gecko.
+ is(SpecialPowers.wrap(myFrame1.contentWindow).location.href, SAME_ORIGIN_URI,
+ "initial same origin dummy loaded into frame1");
+
+ SpecialPowers.wrap(myFrame1.contentWindow).location.hash = "#bar";
+ is(SpecialPowers.wrap(myFrame1.contentWindow).location.href, SAME_ORIGIN_URI + "#bar",
+ "initial same origin dummy#bar loaded into iframe1");
+
+ myFrame1.addEventListener("load", checkNavFrame1);
+ myFrame1.src = CROSS_ORIGIN_URI;
+}
+
+async function checkNavFrame1() {
+ myFrame1.removeEventListener("load", checkNavFrame1);
+ is(await SpecialPowers.spawn(myFrame1, [], () => this.content.location.href),
+ CROSS_ORIGIN_URI,
+ "cross origin dummy loaded into frame1");
+
+ myFrame1.addEventListener("load", checkBackNavFrame1);
+ myFrame1.src = SAME_ORIGIN_URI + "#bar";
+}
+
+async function checkBackNavFrame1() {
+ myFrame1.removeEventListener("load", checkBackNavFrame1);
+ is(await SpecialPowers.spawn(myFrame1, [], () => this.content.location.href),
+ SAME_ORIGIN_URI + "#bar",
+ "navagiating back to same origin dummy for frame1");
+ checkFinish();
+}
+
+// ---- test 2 ----
+
+let myFrame2 = document.createElement("iframe");
+myFrame2.src = "about:blank";
+myFrame2.addEventListener("load", checkLoadFrame2);
+document.documentElement.appendChild(myFrame2);
+
+function checkLoadFrame2() {
+ myFrame2.removeEventListener("load", checkLoadFrame2);
+ is(SpecialPowers.wrap(myFrame2.contentWindow).location.href, "about:blank",
+ "initial about:blank frame loaded");
+
+ myFrame2.contentWindow.location.hash = "#foo";
+ is(SpecialPowers.wrap(myFrame2.contentWindow).location.href, "about:blank#foo",
+ "about:blank#foo frame loaded");
+
+ myFrame2.addEventListener("load", checkHistoryFrame2);
+ myFrame2.src = "about:blank";
+}
+
+function checkHistoryFrame2() {
+ myFrame2.removeEventListener("load", checkHistoryFrame2);
+ is(SpecialPowers.wrap(myFrame2.contentWindow).location.href, "about:blank",
+ "about:blank frame loaded again");
+ checkFinish();
+}
+
+// ---- test 3 ----
+
+let myFrame3 = document.createElement("frame");
+document.documentElement.appendChild(myFrame3);
+myFrame3.contentWindow.location.hash = "#foo";
+
+is(myFrame3.contentWindow.location.href, "about:blank#foo",
+ "created history entry with about:blank#foo");
+checkFinish();
+
+</script>
+</body>
+</html>
diff --git a/docshell/test/mochitest/test_windowedhistoryframes.html b/docshell/test/mochitest/test_windowedhistoryframes.html
new file mode 100644
index 0000000000..a874da098c
--- /dev/null
+++ b/docshell/test/mochitest/test_windowedhistoryframes.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=602256
+-->
+<head>
+ <title>Test for Bug 602256</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=602256">Mozilla Bug 602256</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 602256 */
+
+SimpleTest.waitForExplicitFinish();
+
+function done() {
+ subWin.close();
+ SimpleTest.finish();
+}
+
+var subWin = window.open("historyframes.html", "_blank");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/mochitest/url1_historyframe.html b/docshell/test/mochitest/url1_historyframe.html
new file mode 100644
index 0000000000..b86af4b3fa
--- /dev/null
+++ b/docshell/test/mochitest/url1_historyframe.html
@@ -0,0 +1 @@
+<p id='text'>Test1</p>
diff --git a/docshell/test/mochitest/url2_historyframe.html b/docshell/test/mochitest/url2_historyframe.html
new file mode 100644
index 0000000000..24374d1a5b
--- /dev/null
+++ b/docshell/test/mochitest/url2_historyframe.html
@@ -0,0 +1 @@
+<p id='text'>Test2</p>
diff --git a/docshell/test/moz.build b/docshell/test/moz.build
new file mode 100644
index 0000000000..7cebe0339f
--- /dev/null
+++ b/docshell/test/moz.build
@@ -0,0 +1,137 @@
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DOM: Navigation")
+
+with Files("browser/*_bug234628*"):
+ BUG_COMPONENT = ("Core", "Internationalization")
+
+with Files("browser/*_bug349769*"):
+ BUG_COMPONENT = ("Core", "DOM: Core & HTML")
+
+with Files("browser/*_bug388121*"):
+ BUG_COMPONENT = ("Core", "DOM: Core & HTML")
+
+with Files("browser/*_bug655270*"):
+ BUG_COMPONENT = ("Toolkit", "Places")
+
+with Files("browser/*_bug655273*"):
+ BUG_COMPONENT = ("Firefox", "Menus")
+
+with Files("browser/*_bug852909*"):
+ BUG_COMPONENT = ("Firefox", "Menus")
+
+with Files("browser/*bug92473*"):
+ BUG_COMPONENT = ("Core", "Internationalization")
+
+with Files("browser/*loadDisallowInherit*"):
+ BUG_COMPONENT = ("Firefox", "Address Bar")
+
+with Files("browser/*tab_touch_events*"):
+ BUG_COMPONENT = ("Core", "DOM: Events")
+
+with Files("browser/*timelineMarkers*"):
+ BUG_COMPONENT = ("DevTools", "Performance Tools (Profiler/Timeline)")
+
+with Files("browser/*ua_emulation*"):
+ BUG_COMPONENT = ("DevTools", "General")
+
+with Files("chrome/*112564*"):
+ BUG_COMPONENT = ("Core", "Networking: HTTP")
+
+with Files("chrome/*303267*"):
+ BUG_COMPONENT = ("Core", "DOM: Core & HTML")
+
+with Files("chrome/*453650*"):
+ BUG_COMPONENT = ("Core", "Layout")
+
+with Files("chrome/*565388*"):
+ BUG_COMPONENT = ("Core", "Widget")
+
+with Files("chrome/*582176*"):
+ BUG_COMPONENT = ("Core", "DOM: Core & HTML")
+
+with Files("chrome/*608669*"):
+ BUG_COMPONENT = ("Core", "DOM: Core & HTML")
+
+with Files("chrome/*690056*"):
+ BUG_COMPONENT = ("Core", "DOM: Core & HTML")
+
+with Files("chrome/*92598*"):
+ BUG_COMPONENT = ("Core", "Networking: HTTP")
+
+with Files("iframesandbox/**"):
+ BUG_COMPONENT = ("Core", "Security")
+
+with Files("iframesandbox/*marquee_event_handlers*"):
+ BUG_COMPONENT = ("Core", "DOM: Security")
+
+
+with Files("mochitest/*1045096*"):
+ BUG_COMPONENT = ("Core", "DOM: Core & HTML")
+
+with Files("mochitest/*1151421*"):
+ BUG_COMPONENT = ("Core", "DOM: Core & HTML")
+
+with Files("mochitest/*402210*"):
+ BUG_COMPONENT = ("Core", "DOM: Security")
+
+with Files("mochitest/*509055*"):
+ BUG_COMPONENT = ("Firefox", "Bookmarks & History")
+
+with Files("mochitest/*511449*"):
+ BUG_COMPONENT = ("Core", "Widget: Cocoa")
+
+with Files("mochitest/*551225*"):
+ BUG_COMPONENT = ("Core", "DOM: Core & HTML")
+
+with Files("mochitest/*570341*"):
+ BUG_COMPONENT = ("Core", "DOM: Core & HTML")
+
+with Files("mochitest/*580069*"):
+ BUG_COMPONENT = ("Core", "DOM: Core & HTML")
+
+with Files("mochitest/*637644*"):
+ BUG_COMPONENT = ("Core", "DOM: Core & HTML")
+
+with Files("mochitest/*640387*"):
+ BUG_COMPONENT = ("Core", "DOM: Events")
+
+with Files("mochitest/*668513*"):
+ BUG_COMPONENT = ("Core", "DOM: Core & HTML")
+
+with Files("mochitest/*797909*"):
+ BUG_COMPONENT = ("Core", "DOM: Core & HTML")
+
+with Files("mochitest/*forceinheritprincipal*"):
+ BUG_COMPONENT = ("Core", "DOM: Security")
+
+
+with Files("navigation/*13871.html"):
+ BUG_COMPONENT = ("Core", "Security")
+
+with Files("navigation/*386782*"):
+ BUG_COMPONENT = ("Core", "DOM: Editor")
+
+with Files("navigation/*430624*"):
+ BUG_COMPONENT = ("Core", "DOM: Editor")
+
+with Files("navigation/*430723*"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+with Files("navigation/*child*"):
+ BUG_COMPONENT = ("Core", "Security")
+
+with Files("navigation/*opener*"):
+ BUG_COMPONENT = ("Core", "Security")
+
+with Files("navigation/*reserved*"):
+ BUG_COMPONENT = ("Core", "Security")
+
+with Files("navigation/*triggering*"):
+ BUG_COMPONENT = ("Core", "DOM: Security")
+
+
+with Files("unit/*442584*"):
+ BUG_COMPONENT = ("Core", "Networking: Cache")
+
+with Files("unit/*setUsePrivateBrowsing*"):
+ BUG_COMPONENT = ("Firefox", "Extension Compatibility")
diff --git a/docshell/test/navigation/NavigationUtils.js b/docshell/test/navigation/NavigationUtils.js
new file mode 100644
index 0000000000..c4b52dc62f
--- /dev/null
+++ b/docshell/test/navigation/NavigationUtils.js
@@ -0,0 +1,203 @@
+/* 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 navigation tests
+//
+// /////////////////////////////////////////////////////////////////////////
+
+var body = "This frame was navigated.";
+var target_url = "navigation_target_url.html";
+
+var popup_body = "This is a popup";
+var target_popup_url = "navigation_target_popup_url.html";
+
+// /////////////////////////////////////////////////////////////////////////
+// Functions that navigate frames
+// /////////////////////////////////////////////////////////////////////////
+
+function navigateByLocation(wnd) {
+ try {
+ wnd.location = target_url;
+ } catch (ex) {
+ // We need to keep our finished frames count consistent.
+ // Oddly, this ends up simulating the behavior of IE7.
+ window.open(target_url, "_blank", "width=10,height=10");
+ }
+}
+
+function navigateByOpen(name) {
+ window.open(target_url, name, "width=10,height=10");
+}
+
+function navigateByForm(name) {
+ var form = document.createElement("form");
+ form.action = target_url;
+ form.method = "POST";
+ form.target = name;
+ document.body.appendChild(form);
+ form.submit();
+}
+
+var hyperlink_count = 0;
+
+function navigateByHyperlink(name) {
+ var link = document.createElement("a");
+ link.href = target_url;
+ link.target = name;
+ link.id = "navigation_hyperlink_" + hyperlink_count++;
+ document.body.appendChild(link);
+ sendMouseEvent({ type: "click" }, link.id);
+}
+
+// /////////////////////////////////////////////////////////////////////////
+// Functions that call into Mochitest framework
+// /////////////////////////////////////////////////////////////////////////
+
+async function isNavigated(wnd, message) {
+ var result = null;
+ try {
+ result = await SpecialPowers.spawn(wnd, [], () =>
+ this.content.document.body.innerHTML.trim()
+ );
+ } catch (ex) {
+ result = ex;
+ }
+ is(result, body, message);
+}
+
+function isBlank(wnd, message) {
+ var result = null;
+ try {
+ result = wnd.document.body.innerHTML.trim();
+ } catch (ex) {
+ result = ex;
+ }
+ is(result, "This is a blank document.", message);
+}
+
+function isAccessible(wnd, message) {
+ try {
+ wnd.document.body.innerHTML;
+ ok(true, message);
+ } catch (ex) {
+ ok(false, message);
+ }
+}
+
+function isInaccessible(wnd, message) {
+ try {
+ wnd.document.body.innerHTML;
+ ok(false, message);
+ } catch (ex) {
+ ok(true, message);
+ }
+}
+
+function delay(msec) {
+ return new Promise(resolve => setTimeout(resolve, msec));
+}
+
+// /////////////////////////////////////////////////////////////////////////
+// Functions that uses SpecialPowers.spawn
+// /////////////////////////////////////////////////////////////////////////
+
+async function waitForFinishedFrames(numFrames) {
+ SimpleTest.requestFlakyTimeout("Polling");
+
+ var finishedWindows = new Set();
+
+ async function searchForFinishedFrames(win) {
+ try {
+ let { href, bodyText, readyState } = await SpecialPowers.spawn(
+ win,
+ [],
+ () => {
+ return {
+ href: this.content.location.href,
+ bodyText:
+ this.content.document.body &&
+ this.content.document.body.textContent.trim(),
+ readyState: this.content.document.readyState,
+ };
+ }
+ );
+
+ if (
+ (href.endsWith(target_url) || href.endsWith(target_popup_url)) &&
+ (bodyText == body || bodyText == popup_body) &&
+ readyState == "complete"
+ ) {
+ finishedWindows.add(SpecialPowers.getBrowsingContextID(win));
+ }
+ } catch (e) {
+ // This may throw if a frame is not fully initialized, in which
+ // case we'll handle it in a later iteration.
+ }
+
+ for (let i = 0; i < win.frames.length; i++) {
+ await searchForFinishedFrames(win.frames[i]);
+ }
+ }
+
+ while (finishedWindows.size < numFrames) {
+ await delay(500);
+
+ for (let win of SpecialPowers.getGroupTopLevelWindows(window)) {
+ win = SpecialPowers.unwrap(win);
+ await searchForFinishedFrames(win);
+ }
+ }
+
+ if (finishedWindows.size > numFrames) {
+ throw new Error("Too many frames loaded.");
+ }
+}
+
+async function getFramesByName(name) {
+ let results = [];
+ for (let win of SpecialPowers.getGroupTopLevelWindows(window)) {
+ win = SpecialPowers.unwrap(win);
+ if (
+ (await SpecialPowers.spawn(win, [], () => this.content.name)) === name
+ ) {
+ results.push(win);
+ }
+ }
+
+ return results;
+}
+
+async function cleanupWindows() {
+ for (let win of SpecialPowers.getGroupTopLevelWindows(window)) {
+ win = SpecialPowers.unwrap(win);
+ if (win.closed) {
+ continue;
+ }
+
+ let href = "";
+ try {
+ href = await SpecialPowers.spawn(
+ win,
+ [],
+ () =>
+ this.content && this.content.location && this.content.location.href
+ );
+ } catch (error) {
+ // SpecialPowers.spawn(win, ...) throws if win is closed. We did
+ // our best to not call it on a closed window, but races happen.
+ if (!win.closed) {
+ throw error;
+ }
+ }
+
+ if (
+ href &&
+ (href.endsWith(target_url) || href.endsWith(target_popup_url))
+ ) {
+ win.close();
+ }
+ }
+}
diff --git a/docshell/test/navigation/blank.html b/docshell/test/navigation/blank.html
new file mode 100644
index 0000000000..5360333f1d
--- /dev/null
+++ b/docshell/test/navigation/blank.html
@@ -0,0 +1 @@
+<html><body>This is a blank document.</body></html> \ No newline at end of file
diff --git a/docshell/test/navigation/bluebox_bug430723.html b/docshell/test/navigation/bluebox_bug430723.html
new file mode 100644
index 0000000000..5dcc533562
--- /dev/null
+++ b/docshell/test/navigation/bluebox_bug430723.html
@@ -0,0 +1,6 @@
+<html><head>
+<script> window.addEventListener("pageshow", function() { opener.nextTest(); }); </script>
+</head><body>
+<div style="position:absolute; left:0px; top:0px; width:50%; height:150%; background-color:blue">
+<p>This is a very tall blue box.</p>
+</div></body></html>
diff --git a/docshell/test/navigation/browser.ini b/docshell/test/navigation/browser.ini
new file mode 100644
index 0000000000..baca6b401c
--- /dev/null
+++ b/docshell/test/navigation/browser.ini
@@ -0,0 +1,23 @@
+[DEFAULT]
+support-files =
+ bug343515_pg1.html
+ bug343515_pg2.html
+ bug343515_pg3.html
+ bug343515_pg3_1.html
+ bug343515_pg3_1_1.html
+ bug343515_pg3_2.html
+ blank.html
+ redirect_to_blank.sjs
+
+[browser_bug1757458.js]
+[browser_test_bfcache_eviction.js]
+[browser_bug343515.js]
+[browser_test-content-chromeflags.js]
+tags = openwindow
+[browser_ghistorymaxsize_is_0.js]
+[browser_test_shentry_wireframe.js]
+skip-if =
+ !sessionHistoryInParent
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+[browser_test_simultaneous_normal_and_history_loads.js]
+skip-if = !sessionHistoryInParent # The test is for the new session history
diff --git a/docshell/test/navigation/browser_bug1757458.js b/docshell/test/navigation/browser_bug1757458.js
new file mode 100644
index 0000000000..cc9504ac1c
--- /dev/null
+++ b/docshell/test/navigation/browser_bug1757458.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function () {
+ let testPage =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "view-source:http://example.com"
+ ) + "redirect_to_blank.sjs";
+
+ let testPage2 = "data:text/html,<div>Second page</div>";
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: testPage },
+ async function (browser) {
+ await ContentTask.spawn(browser, [], async () => {
+ Assert.ok(
+ content.document.getElementById("viewsource").localName == "body",
+ "view-source document's body should have id='viewsource'."
+ );
+ content.document
+ .getElementById("viewsource")
+ .setAttribute("onunload", "/* disable bfcache*/");
+ });
+
+ BrowserTestUtils.loadURIString(browser, testPage2);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ let pageShownPromise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "pageshow",
+ true
+ );
+ browser.browsingContext.goBack();
+ await pageShownPromise;
+
+ await ContentTask.spawn(browser, [], async () => {
+ Assert.ok(
+ content.document.getElementById("viewsource").localName == "body",
+ "view-source document's body should have id='viewsource'."
+ );
+ });
+ }
+ );
+});
diff --git a/docshell/test/navigation/browser_bug343515.js b/docshell/test/navigation/browser_bug343515.js
new file mode 100644
index 0000000000..722b1bc018
--- /dev/null
+++ b/docshell/test/navigation/browser_bug343515.js
@@ -0,0 +1,276 @@
+// Test for bug 343515 - Need API for tabbrowsers to tell docshells they're visible/hidden
+
+// Globals
+var testPath = "http://mochi.test:8888/browser/docshell/test/navigation/";
+var ctx = {};
+
+add_task(async function () {
+ // Step 1.
+
+ // Get a handle on the initial tab
+ ctx.tab0 = gBrowser.selectedTab;
+ ctx.tab0Browser = gBrowser.getBrowserForTab(ctx.tab0);
+
+ await BrowserTestUtils.waitForCondition(
+ () => ctx.tab0Browser.docShellIsActive,
+ "Timed out waiting for initial tab to be active."
+ );
+
+ // Open a New Tab
+ ctx.tab1 = BrowserTestUtils.addTab(gBrowser, testPath + "bug343515_pg1.html");
+ ctx.tab1Browser = gBrowser.getBrowserForTab(ctx.tab1);
+ await BrowserTestUtils.browserLoaded(ctx.tab1Browser);
+
+ // Step 2.
+ is(
+ testPath + "bug343515_pg1.html",
+ ctx.tab1Browser.currentURI.spec,
+ "Got expected tab 1 url in step 2"
+ );
+
+ // Our current tab should still be active
+ ok(ctx.tab0Browser.docShellIsActive, "Tab 0 should still be active");
+ ok(!ctx.tab1Browser.docShellIsActive, "Tab 1 should not be active");
+
+ // Switch to tab 1
+ await BrowserTestUtils.switchTab(gBrowser, ctx.tab1);
+
+ // Tab 1 should now be active
+ ok(!ctx.tab0Browser.docShellIsActive, "Tab 0 should be inactive");
+ ok(ctx.tab1Browser.docShellIsActive, "Tab 1 should be active");
+
+ // Open another tab
+ ctx.tab2 = BrowserTestUtils.addTab(gBrowser, testPath + "bug343515_pg2.html");
+ ctx.tab2Browser = gBrowser.getBrowserForTab(ctx.tab2);
+
+ await BrowserTestUtils.browserLoaded(ctx.tab2Browser);
+
+ // Step 3.
+ is(
+ testPath + "bug343515_pg2.html",
+ ctx.tab2Browser.currentURI.spec,
+ "Got expected tab 2 url in step 3"
+ );
+
+ // Tab 0 should be inactive, Tab 1 should be active
+ ok(!ctx.tab0Browser.docShellIsActive, "Tab 0 should be inactive");
+ ok(ctx.tab1Browser.docShellIsActive, "Tab 1 should be active");
+
+ // Tab 2's window _and_ its iframes should be inactive
+ ok(!ctx.tab2Browser.docShellIsActive, "Tab 2 should be inactive");
+
+ await SpecialPowers.spawn(ctx.tab2Browser, [], async function () {
+ Assert.equal(content.frames.length, 2, "Tab 2 should have 2 iframes");
+ for (var i = 0; i < content.frames.length; i++) {
+ info("step 3, frame " + i + " info: " + content.frames[i].location);
+ let bc = content.frames[i].browsingContext;
+ Assert.ok(!bc.isActive, `Tab2 iframe ${i} should be inactive`);
+ }
+ });
+
+ // Navigate tab 2 to a different page
+ BrowserTestUtils.loadURIString(
+ ctx.tab2Browser,
+ testPath + "bug343515_pg3.html"
+ );
+
+ await BrowserTestUtils.browserLoaded(ctx.tab2Browser);
+
+ // Step 4.
+
+ async function checkTab2Active(outerExpected) {
+ await SpecialPowers.spawn(
+ ctx.tab2Browser,
+ [outerExpected],
+ async function (expected) {
+ function isActive(aWindow) {
+ var docshell = aWindow.docShell;
+ info(`checking ${docshell.browsingContext.id}`);
+ return docshell.browsingContext.isActive;
+ }
+
+ let active = expected ? "active" : "inactive";
+ Assert.equal(content.frames.length, 2, "Tab 2 should have 2 iframes");
+ for (var i = 0; i < content.frames.length; i++) {
+ info("step 4, frame " + i + " info: " + content.frames[i].location);
+ }
+ Assert.equal(
+ content.frames[0].frames.length,
+ 1,
+ "Tab 2 iframe 0 should have 1 iframes"
+ );
+ Assert.equal(
+ isActive(content.frames[0]),
+ expected,
+ `Tab2 iframe 0 should be ${active}`
+ );
+ Assert.equal(
+ isActive(content.frames[0].frames[0]),
+ expected,
+ `Tab2 iframe 0 subiframe 0 should be ${active}`
+ );
+ Assert.equal(
+ isActive(content.frames[1]),
+ expected,
+ `Tab2 iframe 1 should be ${active}`
+ );
+ }
+ );
+ }
+
+ is(
+ testPath + "bug343515_pg3.html",
+ ctx.tab2Browser.currentURI.spec,
+ "Got expected tab 2 url in step 4"
+ );
+
+ // Tab 0 should be inactive, Tab 1 should be active
+ ok(!ctx.tab0Browser.docShellIsActive, "Tab 0 should be inactive");
+ ok(ctx.tab1Browser.docShellIsActive, "Tab 1 should be active");
+
+ // Tab2 and all descendants should be inactive
+ await checkTab2Active(false);
+
+ // Switch to Tab 2
+ await BrowserTestUtils.switchTab(gBrowser, ctx.tab2);
+
+ // Check everything
+ ok(!ctx.tab0Browser.docShellIsActive, "Tab 0 should be inactive");
+ ok(!ctx.tab1Browser.docShellIsActive, "Tab 1 should be inactive");
+ ok(ctx.tab2Browser.docShellIsActive, "Tab 2 should be active");
+
+ await checkTab2Active(true);
+
+ // Go back
+ let backDone = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ testPath + "bug343515_pg2.html"
+ );
+ ctx.tab2Browser.goBack();
+ await backDone;
+
+ // Step 5.
+
+ // Check everything
+ ok(!ctx.tab0Browser.docShellIsActive, "Tab 0 should be inactive");
+ ok(!ctx.tab1Browser.docShellIsActive, "Tab 1 should be inactive");
+ ok(ctx.tab2Browser.docShellIsActive, "Tab 2 should be active");
+ is(
+ testPath + "bug343515_pg2.html",
+ ctx.tab2Browser.currentURI.spec,
+ "Got expected tab 2 url in step 5"
+ );
+
+ await SpecialPowers.spawn(ctx.tab2Browser, [], async function () {
+ for (var i = 0; i < content.frames.length; i++) {
+ let bc = content.frames[i].browsingContext;
+ Assert.ok(bc.isActive, `Tab2 iframe ${i} should be active`);
+ }
+ });
+
+ // Switch to tab 1
+ await BrowserTestUtils.switchTab(gBrowser, ctx.tab1);
+
+ // Navigate to page 3
+ BrowserTestUtils.loadURIString(
+ ctx.tab1Browser,
+ testPath + "bug343515_pg3.html"
+ );
+
+ await BrowserTestUtils.browserLoaded(ctx.tab1Browser);
+
+ // Step 6.
+
+ // Check everything
+ ok(!ctx.tab0Browser.docShellIsActive, "Tab 0 should be inactive");
+ ok(ctx.tab1Browser.docShellIsActive, "Tab 1 should be active");
+ is(
+ testPath + "bug343515_pg3.html",
+ ctx.tab1Browser.currentURI.spec,
+ "Got expected tab 1 url in step 6"
+ );
+
+ await SpecialPowers.spawn(ctx.tab1Browser, [], async function () {
+ function isActive(aWindow) {
+ var docshell = aWindow.docShell;
+ info(`checking ${docshell.browsingContext.id}`);
+ return docshell.browsingContext.isActive;
+ }
+
+ Assert.ok(isActive(content.frames[0]), "Tab1 iframe 0 should be active");
+ Assert.ok(
+ isActive(content.frames[0].frames[0]),
+ "Tab1 iframe 0 subiframe 0 should be active"
+ );
+ Assert.ok(isActive(content.frames[1]), "Tab1 iframe 1 should be active");
+ });
+
+ ok(!ctx.tab2Browser.docShellIsActive, "Tab 2 should be inactive");
+
+ await SpecialPowers.spawn(ctx.tab2Browser, [], async function () {
+ for (var i = 0; i < content.frames.length; i++) {
+ let bc = content.frames[i].browsingContext;
+ Assert.ok(!bc.isActive, `Tab2 iframe ${i} should be inactive`);
+ }
+ });
+
+ // Go forward on tab 2
+ let forwardDone = BrowserTestUtils.waitForLocationChange(
+ gBrowser,
+ testPath + "bug343515_pg3.html"
+ );
+ ctx.tab2Browser.goForward();
+ await forwardDone;
+
+ // Step 7.
+
+ async function checkBrowser(browser, outerTabNum, outerActive) {
+ let data = { tabNum: outerTabNum, active: outerActive };
+ await SpecialPowers.spawn(
+ browser,
+ [data],
+ async function ({ tabNum, active }) {
+ function isActive(aWindow) {
+ var docshell = aWindow.docShell;
+ info(`checking ${docshell.browsingContext.id}`);
+ return docshell.browsingContext.isActive;
+ }
+
+ let activestr = active ? "active" : "inactive";
+ Assert.equal(
+ isActive(content.frames[0]),
+ active,
+ `Tab${tabNum} iframe 0 should be ${activestr}`
+ );
+ Assert.equal(
+ isActive(content.frames[0].frames[0]),
+ active,
+ `Tab${tabNum} iframe 0 subiframe 0 should be ${activestr}`
+ );
+ Assert.equal(
+ isActive(content.frames[1]),
+ active,
+ `Tab${tabNum} iframe 1 should be ${activestr}`
+ );
+ }
+ );
+ }
+
+ // Check everything
+ ok(!ctx.tab0Browser.docShellIsActive, "Tab 0 should be inactive");
+ ok(ctx.tab1Browser.docShellIsActive, "Tab 1 should be active");
+ is(
+ testPath + "bug343515_pg3.html",
+ ctx.tab2Browser.currentURI.spec,
+ "Got expected tab 2 url in step 7"
+ );
+
+ await checkBrowser(ctx.tab1Browser, 1, true);
+
+ ok(!ctx.tab2Browser.docShellIsActive, "Tab 2 should be inactive");
+ await checkBrowser(ctx.tab2Browser, 2, false);
+
+ // Close the tabs we made
+ BrowserTestUtils.removeTab(ctx.tab1);
+ BrowserTestUtils.removeTab(ctx.tab2);
+});
diff --git a/docshell/test/navigation/browser_ghistorymaxsize_is_0.js b/docshell/test/navigation/browser_ghistorymaxsize_is_0.js
new file mode 100644
index 0000000000..43c36bbaf2
--- /dev/null
+++ b/docshell/test/navigation/browser_ghistorymaxsize_is_0.js
@@ -0,0 +1,82 @@
+add_task(async function () {
+ // The urls don't really matter as long as they are of the same origin
+ var URL =
+ "http://mochi.test:8888/browser/docshell/test/navigation/bug343515_pg1.html";
+ var URL2 =
+ "http://mochi.test:8888/browser/docshell/test/navigation/bug343515_pg3_1.html";
+
+ // We want to test a specific code path that leads to this call
+ // https://searchfox.org/mozilla-central/rev/e7c61f4a68b974d5fecd216dc7407b631a24eb8f/docshell/base/nsDocShell.cpp#10795
+ // when gHistoryMaxSize is 0 and mIndex and mRequestedIndex are -1
+
+ // 1. Navigate to URL
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: URL },
+ async function (browser) {
+ // At this point, we haven't set gHistoryMaxSize to 0, and it is still 50 (default value).
+ if (SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ let sh = browser.browsingContext.sessionHistory;
+ is(
+ sh.count,
+ 1,
+ "We should have entry in session history because we haven't changed gHistoryMaxSize to be 0 yet"
+ );
+ is(
+ sh.index,
+ 0,
+ "Shistory's current index should be 0 because we haven't purged history yet"
+ );
+ } else {
+ await ContentTask.spawn(browser, null, () => {
+ var sh = content.window.docShell.QueryInterface(Ci.nsIWebNavigation)
+ .sessionHistory.legacySHistory;
+ is(
+ sh.count,
+ 1,
+ "We should have entry in session history because we haven't changed gHistoryMaxSize to be 0 yet"
+ );
+ is(
+ sh.index,
+ 0,
+ "Shistory's current index should be 0 because we haven't purged history yet"
+ );
+ });
+ }
+
+ var loadPromise = BrowserTestUtils.browserLoaded(browser, false, URL2);
+ // If we set the pref at the beginning of this page, then when we launch a child process
+ // to navigate to URL in Step 1, because of
+ // https://searchfox.org/mozilla-central/rev/e7c61f4a68b974d5fecd216dc7407b631a24eb8f/docshell/shistory/nsSHistory.cpp#308-312
+ // this pref will be set to the default value (currently 50). Setting this pref after the child process launches
+ // is a robust way to make sure it stays 0
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.sessionhistory.max_entries", 0]],
+ });
+ // 2. Navigate to URL2
+ // We are navigating to a page with the same origin so that we will stay in the same process
+ BrowserTestUtils.loadURIString(browser, URL2);
+ await loadPromise;
+
+ // 3. Reload the browser with specific flags so that we end up here
+ // https://searchfox.org/mozilla-central/rev/e7c61f4a68b974d5fecd216dc7407b631a24eb8f/docshell/base/nsDocShell.cpp#10795
+ var promise = BrowserTestUtils.browserLoaded(browser);
+ browser.reloadWithFlags(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE);
+ await promise;
+
+ if (SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ let sh = browser.browsingContext.sessionHistory;
+ is(sh.count, 0, "We should not save any entries in session history");
+ is(sh.index, -1);
+ is(sh.requestedIndex, -1);
+ } else {
+ await ContentTask.spawn(browser, null, () => {
+ var sh = content.window.docShell.QueryInterface(Ci.nsIWebNavigation)
+ .sessionHistory.legacySHistory;
+ is(sh.count, 0, "We should not save any entries in session history");
+ is(sh.index, -1);
+ is(sh.requestedIndex, -1);
+ });
+ }
+ }
+ );
+});
diff --git a/docshell/test/navigation/browser_test-content-chromeflags.js b/docshell/test/navigation/browser_test-content-chromeflags.js
new file mode 100644
index 0000000000..ab1ae41c0c
--- /dev/null
+++ b/docshell/test/navigation/browser_test-content-chromeflags.js
@@ -0,0 +1,54 @@
+const TEST_PAGE = `data:text/html,<html><body><a href="about:blank" target="_blank">Test</a></body></html>`;
+const { CHROME_ALL, CHROME_REMOTE_WINDOW, CHROME_FISSION_WINDOW } =
+ Ci.nsIWebBrowserChrome;
+
+/**
+ * Tests that when we open new browser windows from content they
+ * get the full browser chrome.
+ */
+add_task(async function () {
+ // Make sure that the window.open call will open a new
+ // window instead of a new tab.
+ await new Promise(resolve => {
+ SpecialPowers.pushPrefEnv(
+ {
+ set: [["browser.link.open_newwindow", 2]],
+ },
+ resolve
+ );
+ });
+
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: TEST_PAGE,
+ },
+ async function (browser) {
+ let openedPromise = BrowserTestUtils.waitForNewWindow();
+ BrowserTestUtils.synthesizeMouse("a", 0, 0, {}, browser);
+ let win = await openedPromise;
+
+ let chromeFlags = win.docShell.treeOwner
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIAppWindow).chromeFlags;
+
+ let expected = CHROME_ALL;
+
+ // In the multi-process tab case, the new window will have the
+ // CHROME_REMOTE_WINDOW flag set.
+ if (gMultiProcessBrowser) {
+ expected |= CHROME_REMOTE_WINDOW;
+ }
+
+ // In the multi-process subframe case, the new window will have the
+ // CHROME_FISSION_WINDOW flag set.
+ if (gFissionBrowser) {
+ expected |= CHROME_FISSION_WINDOW;
+ }
+
+ is(chromeFlags, expected, "Window should have opened with all chrome");
+
+ await BrowserTestUtils.closeWindow(win);
+ }
+ );
+});
diff --git a/docshell/test/navigation/browser_test_bfcache_eviction.js b/docshell/test/navigation/browser_test_bfcache_eviction.js
new file mode 100644
index 0000000000..32772fe42c
--- /dev/null
+++ b/docshell/test/navigation/browser_test_bfcache_eviction.js
@@ -0,0 +1,102 @@
+add_task(async function () {
+ // We don't want the number of total viewers to be calculated by the available size
+ // for this test case. Instead, fix the number of viewers.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.sessionhistory.max_total_viewers", 3],
+ ["docshell.shistory.testing.bfevict", true],
+ // If Fission is disabled, the pref is no-op.
+ ["fission.bfcacheInParent", true],
+ ],
+ });
+
+ // 1. Open a tab
+ var testPage =
+ "data:text/html,<html id='html1'><body id='body1'>First tab ever opened</body></html>";
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: testPage },
+ async function (browser) {
+ let testDone = {};
+ if (!SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ // 2. Add a promise that will be resolved when the 'content viewer evicted' event goes off
+ testDone.promise = SpecialPowers.spawn(browser, [], async function () {
+ return new Promise(resolve => {
+ let webNavigation = content.docShell.QueryInterface(
+ Ci.nsIWebNavigation
+ );
+ let { legacySHistory } = webNavigation.sessionHistory;
+ // 3. Register a session history listener to listen for a 'content viewer evicted' event.
+ let historyListener = {
+ OnContentViewerEvicted() {
+ ok(
+ true,
+ "History listener got called after a content viewer was evicted"
+ );
+ legacySHistory.removeSHistoryListener(historyListener);
+ // 6. Resolve the promise when we got our 'content viewer evicted' event
+ resolve();
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ "nsISHistoryListener",
+ "nsISupportsWeakReference",
+ ]),
+ };
+ legacySHistory.addSHistoryListener(historyListener);
+ // Keep the weak shistory listener alive
+ content._testListener = historyListener;
+ });
+ });
+ } else {
+ // 2. Add a promise that will be resolved when the 'content viewer evicted' event goes off
+ testDone.promise = new Promise(resolve => {
+ testDone.resolve = resolve;
+ });
+ let shistory = browser.browsingContext.sessionHistory;
+ // 3. Register a session history listener to listen for a 'content viewer evicted' event.
+ let historyListener = {
+ OnContentViewerEvicted() {
+ ok(
+ true,
+ "History listener got called after a content viewer was evicted"
+ );
+ shistory.removeSHistoryListener(historyListener);
+ delete window._testListener;
+ // 6. Resolve the promise when we got our 'content viewer evicted' event
+ testDone.resolve();
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ "nsISHistoryListener",
+ "nsISupportsWeakReference",
+ ]),
+ };
+ shistory.addSHistoryListener(historyListener);
+ // Keep the weak shistory listener alive
+ window._testListener = historyListener;
+ }
+
+ // 4. Open a second tab
+ testPage = `data:text/html,<html id='html1'><body id='body1'>I am a second tab!</body></html>`;
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ testPage
+ );
+
+ // 5. Navigate the first tab to 4 different pages.
+ // We should get 1 content viewer evicted because it will be outside of the range.
+ // If we have the following pages in our session history: P1 P2 P3 P4 P5
+ // and we are currently at P5, then P1 is outside of the range
+ // (it is more than 3 entries away from current entry) and thus will be evicted.
+ for (var i = 0; i < 4; i++) {
+ testPage = `data:text/html,<html id='html1'><body id='body1'>${i}</body></html>`;
+ let pagePromise = BrowserTestUtils.browserLoaded(browser);
+ BrowserTestUtils.loadURIString(browser, testPage);
+ await pagePromise;
+ }
+ // 7. Wait for 'content viewer evicted' event to go off
+ await testDone.promise;
+
+ // 8. Close the second tab
+ BrowserTestUtils.removeTab(tab2);
+ }
+ );
+});
diff --git a/docshell/test/navigation/browser_test_shentry_wireframe.js b/docshell/test/navigation/browser_test_shentry_wireframe.js
new file mode 100644
index 0000000000..953a2eb6e2
--- /dev/null
+++ b/docshell/test/navigation/browser_test_shentry_wireframe.js
@@ -0,0 +1,128 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const BUILDER = "http://mochi.test:8888/document-builder.sjs?html=";
+const PAGE_1 = BUILDER + encodeURIComponent(`<html><body>Page 1</body></html>`);
+const PAGE_2 = BUILDER + encodeURIComponent(`<html><body>Page 2</body></html>`);
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.history.collectWireframes", true]],
+ });
+});
+
+/**
+ * Test that capturing wireframes on nsISHEntriy's in the parent process
+ * happens at the right times.
+ */
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(PAGE_1, async browser => {
+ let sh = browser.browsingContext.sessionHistory;
+ Assert.equal(
+ sh.count,
+ 1,
+ "Got the right SessionHistory entry count after initial tab load."
+ );
+ Assert.ok(
+ !sh.getEntryAtIndex(0).wireframe,
+ "No wireframe for the loaded entry after initial tab load."
+ );
+
+ let loaded = BrowserTestUtils.browserLoaded(browser, false, PAGE_2);
+ BrowserTestUtils.loadURIString(browser, PAGE_2);
+ await loaded;
+
+ Assert.equal(
+ sh.count,
+ 2,
+ "Got the right SessionHistory entry count after loading page 2."
+ );
+ Assert.ok(
+ sh.getEntryAtIndex(0).wireframe,
+ "A wireframe was captured for the first entry after loading page 2."
+ );
+ Assert.ok(
+ !sh.getEntryAtIndex(1).wireframe,
+ "No wireframe for the loaded entry after loading page 2."
+ );
+
+ // Now go back
+ loaded = BrowserTestUtils.waitForContentEvent(browser, "pageshow");
+ browser.goBack();
+ await loaded;
+ Assert.ok(
+ sh.getEntryAtIndex(1).wireframe,
+ "A wireframe was captured for the second entry after going back."
+ );
+ Assert.ok(
+ !sh.getEntryAtIndex(0).wireframe,
+ "No wireframe for the loaded entry after going back."
+ );
+
+ // Now forward again
+ loaded = BrowserTestUtils.waitForContentEvent(browser, "pageshow");
+ browser.goForward();
+ await loaded;
+
+ Assert.equal(
+ sh.count,
+ 2,
+ "Got the right SessionHistory entry count after going forward again."
+ );
+ Assert.ok(
+ sh.getEntryAtIndex(0).wireframe,
+ "A wireframe was captured for the first entry after going forward again."
+ );
+ Assert.ok(
+ !sh.getEntryAtIndex(1).wireframe,
+ "No wireframe for the loaded entry after going forward again."
+ );
+
+ // And using pushState
+ await SpecialPowers.spawn(browser, [], async () => {
+ content.history.pushState({}, "", "nothing-1.html");
+ content.history.pushState({}, "", "nothing-2.html");
+ });
+
+ Assert.equal(
+ sh.count,
+ 4,
+ "Got the right SessionHistory entry count after using pushState."
+ );
+ Assert.ok(
+ sh.getEntryAtIndex(0).wireframe,
+ "A wireframe was captured for the first entry after using pushState."
+ );
+ Assert.ok(
+ sh.getEntryAtIndex(1).wireframe,
+ "A wireframe was captured for the second entry after using pushState."
+ );
+ Assert.ok(
+ sh.getEntryAtIndex(2).wireframe,
+ "A wireframe was captured for the third entry after using pushState."
+ );
+ Assert.ok(
+ !sh.getEntryAtIndex(3).wireframe,
+ "No wireframe for the loaded entry after using pushState."
+ );
+
+ // Now check that wireframes can be written to in case we're restoring
+ // an nsISHEntry from serialization.
+ let wireframe = sh.getEntryAtIndex(2).wireframe;
+ sh.getEntryAtIndex(2).wireframe = null;
+ Assert.equal(
+ sh.getEntryAtIndex(2).wireframe,
+ null,
+ "Successfully cleared wireframe."
+ );
+
+ sh.getEntryAtIndex(3).wireframe = wireframe;
+ Assert.deepEqual(
+ sh.getEntryAtIndex(3).wireframe,
+ wireframe,
+ "Successfully wrote a wireframe to an nsISHEntry."
+ );
+ });
+});
diff --git a/docshell/test/navigation/browser_test_simultaneous_normal_and_history_loads.js b/docshell/test/navigation/browser_test_simultaneous_normal_and_history_loads.js
new file mode 100644
index 0000000000..a400d15466
--- /dev/null
+++ b/docshell/test/navigation/browser_test_simultaneous_normal_and_history_loads.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_normal_and_history_loads() {
+ // This test is for the case when session history lives in the parent process.
+ // BFCache is disabled to get more asynchronousness.
+ await SpecialPowers.pushPrefEnv({
+ set: [["fission.bfcacheInParent", false]],
+ });
+
+ let testPage =
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com"
+ ) + "blank.html";
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: testPage },
+ async function (browser) {
+ for (let i = 0; i < 2; ++i) {
+ BrowserTestUtils.loadURIString(browser, testPage + "?" + i);
+ await BrowserTestUtils.browserLoaded(browser);
+ }
+
+ let sh = browser.browsingContext.sessionHistory;
+ is(sh.count, 3, "Should have 3 entries in the session history.");
+ is(sh.index, 2, "index should be 2");
+ is(sh.requestedIndex, -1, "requestedIndex should be -1");
+
+ // The following part is racy by definition. It is testing that
+ // eventually requestedIndex should become -1 again.
+ browser.browsingContext.goToIndex(1);
+ let historyLoad = BrowserTestUtils.browserLoaded(browser);
+ /* eslint-disable mozilla/no-arbitrary-setTimeout */
+ await new Promise(r => {
+ setTimeout(r, 10);
+ });
+ browser.browsingContext.loadURI(
+ Services.io.newURI(testPage + "?newload"),
+ {
+ triggeringPrincipal: browser.nodePrincipal,
+ }
+ );
+ let newLoad = BrowserTestUtils.browserLoaded(browser);
+ // Note, the loads are racy.
+ await historyLoad;
+ await newLoad;
+ is(sh.requestedIndex, -1, "requestedIndex should be -1");
+
+ browser.browsingContext.goBack();
+ await BrowserTestUtils.browserLoaded(browser);
+ is(sh.requestedIndex, -1, "requestedIndex should be -1");
+ }
+ );
+});
diff --git a/docshell/test/navigation/bug343515_pg1.html b/docshell/test/navigation/bug343515_pg1.html
new file mode 100644
index 0000000000..a8337c7f70
--- /dev/null
+++ b/docshell/test/navigation/bug343515_pg1.html
@@ -0,0 +1,5 @@
+<html>
+ <head><meta charset="UTF-8"/></head>
+ <body>Page 1
+ </body>
+</html>
diff --git a/docshell/test/navigation/bug343515_pg2.html b/docshell/test/navigation/bug343515_pg2.html
new file mode 100644
index 0000000000..c5f5665de5
--- /dev/null
+++ b/docshell/test/navigation/bug343515_pg2.html
@@ -0,0 +1,7 @@
+<html>
+ <head><meta charset="UTF-8"/></head>
+ <body>Page 2
+ <iframe src="data:text/html;charset=UTF8,<html><head></head><body>pg2 iframe 0</body></html>"></iframe>
+ <iframe src="data:text/html;charset=UTF8,<html><head></head><body>pg2 iframe 1</body></html>"></iframe>
+ </body>
+</html>
diff --git a/docshell/test/navigation/bug343515_pg3.html b/docshell/test/navigation/bug343515_pg3.html
new file mode 100644
index 0000000000..fdc79fbf7a
--- /dev/null
+++ b/docshell/test/navigation/bug343515_pg3.html
@@ -0,0 +1,7 @@
+<html>
+ <head><meta charset="UTF-8"/></head>
+ <body>Page 3
+ <iframe src="bug343515_pg3_1.html"></iframe>
+ <iframe src="bug343515_pg3_2.html"></iframe>
+ </body>
+</html>
diff --git a/docshell/test/navigation/bug343515_pg3_1.html b/docshell/test/navigation/bug343515_pg3_1.html
new file mode 100644
index 0000000000..254164c9f0
--- /dev/null
+++ b/docshell/test/navigation/bug343515_pg3_1.html
@@ -0,0 +1,6 @@
+<html>
+ <head><meta charset="UTF-8"/></head>
+ <body>pg3 - iframe 0
+ <iframe src="bug343515_pg3_1_1.html"></iframe>
+ </body>
+</html>
diff --git a/docshell/test/navigation/bug343515_pg3_1_1.html b/docshell/test/navigation/bug343515_pg3_1_1.html
new file mode 100644
index 0000000000..be05b74888
--- /dev/null
+++ b/docshell/test/navigation/bug343515_pg3_1_1.html
@@ -0,0 +1 @@
+<html><head><meta charset="UTF-8"/></head><body>How far does the rabbit hole go?</body></html>
diff --git a/docshell/test/navigation/bug343515_pg3_2.html b/docshell/test/navigation/bug343515_pg3_2.html
new file mode 100644
index 0000000000..7655eb526d
--- /dev/null
+++ b/docshell/test/navigation/bug343515_pg3_2.html
@@ -0,0 +1 @@
+<html><head><meta charset="UTF-8"/></head><body>pg3 iframe 1</body></html>
diff --git a/docshell/test/navigation/cache_control_max_age_3600.sjs b/docshell/test/navigation/cache_control_max_age_3600.sjs
new file mode 100644
index 0000000000..49b6439c9f
--- /dev/null
+++ b/docshell/test/navigation/cache_control_max_age_3600.sjs
@@ -0,0 +1,20 @@
+function handleRequest(request, response) {
+ let query = request.queryString;
+ let action =
+ query == "initial"
+ ? "cache_control_max_age_3600.sjs?second"
+ : "goback.html";
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Cache-Control", "max-age=3600");
+ response.write(
+ "<html><head><script>window.blockBFCache = new RTCPeerConnection();</script></head>" +
+ '<body onload=\'opener.postMessage("loaded", "*")\'>' +
+ "<div id='content'>" +
+ new Date().getTime() +
+ "</div>" +
+ "<form action='" +
+ action +
+ "' method='POST'></form>" +
+ "</body></html>"
+ );
+}
diff --git a/docshell/test/navigation/file_beforeunload_and_bfcache.html b/docshell/test/navigation/file_beforeunload_and_bfcache.html
new file mode 100644
index 0000000000..5c5aea2918
--- /dev/null
+++ b/docshell/test/navigation/file_beforeunload_and_bfcache.html
@@ -0,0 +1,31 @@
+<html>
+ <head>
+ <script>
+ onpageshow = function(pageShowEvent) {
+ var bc = new BroadcastChannel("beforeunload_and_bfcache");
+ bc.onmessage = function(event) {
+ if (event.data == "nextpage") {
+ bc.close();
+ location.href += "?nextpage";
+ } else if (event.data == "back") {
+ bc.close();
+ history.back();
+ } else if (event.data == "forward") {
+ onbeforeunload = function() {
+ bc.postMessage("beforeunload");
+ bc.close();
+ }
+ history.forward();
+ } else if (event.data == "close") {
+ bc.postMessage("closed");
+ bc.close();
+ window.close();
+ }
+ };
+ bc.postMessage({type: pageShowEvent.type, persisted: pageShowEvent.persisted});
+ };
+ </script>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_blockBFCache.html b/docshell/test/navigation/file_blockBFCache.html
new file mode 100644
index 0000000000..dc743cdc67
--- /dev/null
+++ b/docshell/test/navigation/file_blockBFCache.html
@@ -0,0 +1,33 @@
+<script>
+let keepAlive;
+window.onpageshow = (pageShow) => {
+ let bc = new BroadcastChannel("bfcache_blocking");
+
+ bc.onmessage = async function(m) {
+ switch(m.data.message) {
+ case "load":
+ bc.close();
+ location.href = m.data.url;
+ break;
+ case "runScript":
+ let test = new Function(`return ${m.data.fun};`)();
+ keepAlive = await test.call(window);
+ bc.postMessage({ type: "runScriptDone" });
+ break;
+ case "back":
+ bc.close();
+ history.back();
+ break;
+ case "forward":
+ bc.close();
+ history.forward();
+ break;
+ case "close":
+ window.close();
+ break;
+ }
+ };
+
+ bc.postMessage({ type: pageShow.type, persisted: pageShow.persisted })
+};
+</script>
diff --git a/docshell/test/navigation/file_bug1300461.html b/docshell/test/navigation/file_bug1300461.html
new file mode 100644
index 0000000000..d7abe8be90
--- /dev/null
+++ b/docshell/test/navigation/file_bug1300461.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>Bug 1300461</title>
+ </head>
+ <body onload="test();">
+ <script>
+ /**
+ * Bug 1300461 identifies that if a history entry was not bfcached, and
+ * a http redirection happens when navigating to that entry, the history
+ * index would mess up.
+ *
+ * The test case emulates the circumstance by the following steps
+ * 1) Navigate to file_bug1300461_back.html which is not bf-cachable.
+ * 2) In file_bug1300461_back.html, replace its own history state to
+ * file_bug1300461_redirect.html.
+ * 3) Back, and then forward. Since the document is not in bfcache, it
+ * tries to load file_bug1300461_redirect.html directly.
+ * 4) file_bug1300461_redirect.html redirects UA to
+ * file_bug1300461_back.html through HTTP 301 header.
+ *
+ * We verify the history index, canGoBack, canGoForward, etc. keep correct
+ * in this process.
+ */
+ let Ci = SpecialPowers.Ci;
+ let webNav = SpecialPowers.wrap(window)
+ .docShell
+ .QueryInterface(Ci.nsIWebNavigation);
+ let shistory = webNav.sessionHistory;
+ let testSteps = [
+ function() {
+ opener.is(shistory.count, 1, "check history length");
+ opener.is(shistory.index, 0, "check history index");
+ opener.ok(!webNav.canGoForward, "check canGoForward");
+ setTimeout(() => window.location = "file_bug1300461_back.html", 0);
+ },
+ function() {
+ opener.is(shistory.count, 2, "check history length");
+ opener.is(shistory.index, 0, "check history index");
+ opener.ok(webNav.canGoForward, "check canGoForward");
+ window.history.forward();
+ },
+ function() {
+ opener.is(shistory.count, 2, "check history length");
+ opener.is(shistory.index, 0, "check history index");
+ opener.ok(webNav.canGoForward, "check canGoForward");
+ opener.info("file_bug1300461.html tests finished");
+ opener.finishTest();
+ },
+ ];
+
+ function test() {
+ if (opener) {
+ opener.info("file_bug1300461.html test " + opener.testCount);
+ testSteps[opener.testCount++]();
+ }
+ }
+ </script>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_bug1300461_back.html b/docshell/test/navigation/file_bug1300461_back.html
new file mode 100644
index 0000000000..3ec431c7c1
--- /dev/null
+++ b/docshell/test/navigation/file_bug1300461_back.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>Bug 1300461</title>
+ </head>
+ <!-- The empty unload handler is to prevent bfcache. -->
+ <body onload="test();" onunload="">
+ <script>
+ let Ci = SpecialPowers.Ci;
+ let webNav = SpecialPowers.wrap(window)
+ .docShell
+ .QueryInterface(Ci.nsIWebNavigation);
+ let shistory = webNav.sessionHistory;
+ async function test() {
+ if (opener) {
+ opener.info("file_bug1300461_back.html");
+ opener.is(shistory.count, 2, "check history length");
+ opener.is(shistory.index, 1, "check history index");
+ if (!SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ opener.is(shistory.legacySHistory.requestedIndex, -1, "check requestedIndex");
+ } else {
+ let index = await opener.getSHRequestedIndex(SpecialPowers.wrap(window).browsingContext.id);
+ opener.is(index, -1, "check requestedIndex");
+ }
+
+ opener.ok(webNav.canGoBack, "check canGoBack");
+ if (opener.testCount == 1) {
+ opener.info("replaceState to redirect.html");
+ window.history.replaceState({}, "", "file_bug1300461_redirect.html");
+ }
+ window.history.back();
+ }
+ }
+ </script>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_bug1300461_redirect.html b/docshell/test/navigation/file_bug1300461_redirect.html
new file mode 100644
index 0000000000..979530c5cf
--- /dev/null
+++ b/docshell/test/navigation/file_bug1300461_redirect.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <title>Bug 1300461</title>
+ </head>
+ <body>
+ Redirect to file_bug1300461_back.html.
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_bug1300461_redirect.html^headers^ b/docshell/test/navigation/file_bug1300461_redirect.html^headers^
new file mode 100644
index 0000000000..241b891826
--- /dev/null
+++ b/docshell/test/navigation/file_bug1300461_redirect.html^headers^
@@ -0,0 +1,2 @@
+HTTP 301 Moved Permanently
+Location: file_bug1300461_back.html
diff --git a/docshell/test/navigation/file_bug1326251.html b/docshell/test/navigation/file_bug1326251.html
new file mode 100644
index 0000000000..57a81f46f1
--- /dev/null
+++ b/docshell/test/navigation/file_bug1326251.html
@@ -0,0 +1,212 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Bug 1326251</title>
+ <script>
+ var bc = new BroadcastChannel("file_bug1326251");
+ bc.onmessage = function(event) {
+ if ("nextTest" in event.data) {
+ testSteps[event.data.nextTest]();
+ }
+ }
+
+ function is(val1, val2, msg) {
+ bc.postMessage({type: "is", value1: val1, value2: val2, message: msg});
+ }
+
+ function ok(val, msg) {
+ bc.postMessage({type: "ok", value: val, message: msg});
+ }
+
+ const BASE_URL = "http://mochi.test:8888/tests/docshell/test/navigation/";
+ let testSteps = [
+ async function() {
+ // Test 1: Create dynamic iframe with bfcache enabled.
+ // Navigate static / dynamic iframes, then navigate top level window
+ // and navigate back. Both iframes should still exist with history
+ // entries preserved.
+ window.onunload = null; // enable bfcache
+ await createDynamicFrame(document);
+ await loadUriInFrame(document.getElementById("staticFrame"), "frame1.html");
+ await loadUriInFrame(document.getElementById("dynamicFrame"), "frame1.html");
+ await loadUriInFrame(document.getElementById("staticFrame"), "frame2.html");
+ await loadUriInFrame(document.getElementById("dynamicFrame"), "frame2.html");
+ is(history.length, 5, "history.length");
+ window.location = "goback.html";
+ },
+ async function() {
+ let webNav = SpecialPowers.wrap(window)
+ .docShell
+ .QueryInterface(SpecialPowers.Ci.nsIWebNavigation);
+ let shistory = webNav.sessionHistory;
+ is(webNav.canGoForward, true, "canGoForward");
+ is(shistory.index, 4, "shistory.index");
+ is(history.length, 6, "history.length");
+ is(document.getElementById("staticFrame").contentWindow.location.href, BASE_URL + "frame2.html", "staticFrame location");
+ is(document.getElementById("dynamicFrame").contentWindow.location.href, BASE_URL + "frame2.html", "dynamicFrame location");
+
+ // Test 2: Load another page in dynamic iframe, canGoForward should be
+ // false.
+ await loadUriInFrame(document.getElementById("dynamicFrame"), "frame3.html");
+ is(webNav.canGoForward, false, "canGoForward");
+ is(shistory.index, 5, "shistory.index");
+ is(history.length, 6, "history.length");
+
+ // Test 3: Navigate to antoher page with bfcache disabled, all dynamic
+ // iframe entries should be removed.
+ window.onunload = function() {}; // disable bfcache
+ window.location = "goback.html";
+ },
+ async function() {
+ let windowWrap = SpecialPowers.wrap(window);
+ let docShell = windowWrap.docShell;
+ let shistory = docShell.QueryInterface(SpecialPowers.Ci.nsIWebNavigation)
+ .sessionHistory;
+ // Now staticFrame has frame0 -> frame1 -> frame2.
+ if (!SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ // *EntryIndex attributes aren't meaningful when the session history
+ // lives in the parent process.
+ is(docShell.previousEntryIndex, 3, "docShell.previousEntryIndex");
+ is(docShell.loadedEntryIndex, 2, "docShell.loadedEntryIndex");
+ }
+ is(shistory.index, 2, "shistory.index");
+ is(history.length, 4, "history.length");
+ is(document.getElementById("staticFrame").contentWindow.location.href, BASE_URL + "frame2.html", "staticFrame location");
+ ok(!document.getElementById("dynamicFrame"), "dynamicFrame should not exist");
+
+ // Test 4: Load a nested frame in the static frame, navigate the inner
+ // static frame, add a inner dynamic frame and navigate the dynamic
+ // frame. Then navigate the outer static frame and go back. The inner
+ // iframe should show the last entry of inner static frame.
+ let staticFrame = document.getElementById("staticFrame");
+ staticFrame.width = "320px";
+ staticFrame.height = "360px";
+ await loadUriInFrame(staticFrame, "iframe_static.html");
+ let innerStaticFrame = staticFrame.contentDocument.getElementById("staticFrame");
+ await loadUriInFrame(innerStaticFrame, "frame1.html");
+ let innerDynamicFrame = await createDynamicFrame(staticFrame.contentDocument, "frame2.html");
+ await loadUriInFrame(innerDynamicFrame, "frame3.html");
+ // staticFrame: frame0 -> frame1 -> frame2 -> iframe_static
+ // innerStaticFrame: frame0 -> frame1
+ // innerDynamicFrame: frame2 -> frame3
+ is(shistory.index, 5, "shistory.index");
+ is(history.length, 6, "history.length");
+
+ // Wait for 2 load events - navigation and goback.
+ let onloadPromise = awaitOnload(staticFrame, 2);
+ await loadUriInFrame(staticFrame, "goback.html");
+ await onloadPromise;
+ // staticFrame: frame0 -> frame1 -> frame2 -> iframe_static -> goback
+ // innerStaticFrame: frame0 -> frame1
+ is(shistory.index, 4, "shistory.index");
+ is(history.length, 6, "history.length");
+ innerStaticFrame = staticFrame.contentDocument.getElementById("staticFrame");
+ is(innerStaticFrame.contentDocument.location.href, BASE_URL + "frame1.html", "innerStaticFrame location");
+ ok(!staticFrame.contentDocument.getElementById("dynamicFrame"), "innerDynamicFrame should not exist");
+
+ // Test 5: Insert and navigate inner dynamic frame again with bfcache
+ // enabled, and navigate top level window to a special page which will
+ // evict bfcache then goback. Verify that dynamic entries are correctly
+ // removed in this case.
+ window.onunload = null; // enable bfcache
+ staticFrame.width = "320px";
+ staticFrame.height = "360px";
+ innerDynamicFrame = await createDynamicFrame(staticFrame.contentDocument, "frame2.html");
+ await loadUriInFrame(innerDynamicFrame, "frame3.html");
+ // staticFrame: frame0 -> frame1 -> frame2 -> iframe_static
+ // innerStaticFrame: frame0 -> frame1
+ // innerDynamicFrame: frame2 -> frame3
+ is(shistory.index, 5, "shistory.index");
+ is(history.length, 6, "history.length");
+ window.location = "file_bug1326251_evict_cache.html";
+ },
+ async function() {
+ let windowWrap = SpecialPowers.wrap(window);
+ let docShell = windowWrap.docShell;
+ let shistory = docShell.QueryInterface(SpecialPowers.Ci.nsIWebNavigation)
+ .sessionHistory;
+ // staticFrame: frame0 -> frame1 -> frame2 -> iframe_static
+ // innerStaticFrame: frame0 -> frame1
+ if (!SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ // *EntryIndex attributes aren't meaningful when the session history
+ // lives in the parent process.
+ is(docShell.previousEntryIndex, 5, "docShell.previousEntryIndex");
+ is(docShell.loadedEntryIndex, 4, "docShell.loadedEntryIndex");
+ }
+ is(shistory.index, 4, "shistory.index");
+ is(history.length, 6, "history.length");
+ let staticFrame = document.getElementById("staticFrame");
+ let innerStaticFrame = staticFrame.contentDocument.getElementById("staticFrame");
+ is(innerStaticFrame.contentDocument.location.href, BASE_URL + "frame1.html", "innerStaticFrame location");
+ ok(!staticFrame.contentDocument.getElementById("dynamicFrame"), "innerDynamicFrame should not exist");
+
+ // Test 6: Insert and navigate inner dynamic frame and then reload outer
+ // frame. Verify that inner dynamic frame entries are all removed.
+ staticFrame.width = "320px";
+ staticFrame.height = "360px";
+ let innerDynamicFrame = await createDynamicFrame(staticFrame.contentDocument, "frame2.html");
+ await loadUriInFrame(innerDynamicFrame, "frame3.html");
+ // staticFrame: frame0 -> frame1 -> frame2 -> iframe_static
+ // innerStaticFrame: frame0 -> frame1
+ // innerDynamicFrame: frame2 -> frame3
+ is(shistory.index, 5, "shistory.index");
+ is(history.length, 6, "history.length");
+ let staticFrameLoadPromise = new Promise(resolve => {
+ staticFrame.onload = resolve;
+ });
+ staticFrame.contentWindow.location.reload();
+ await staticFrameLoadPromise;
+ // staticFrame: frame0 -> frame1 -> frame2 -> iframe_static
+ // innerStaticFrame: frame0 -> frame1
+ is(shistory.index, 4, "shistory.index");
+ is(history.length, 5, "history.length");
+ innerStaticFrame = staticFrame.contentDocument.getElementById("staticFrame");
+ is(innerStaticFrame.contentDocument.location.href, BASE_URL + "frame1.html", "innerStaticFrame location");
+ ok(!staticFrame.contentDocument.getElementById("dynamicFrame"), "innerDynamicFrame should not exist");
+ bc.postMessage("finishTest");
+ bc.close();
+ window.close();
+ },
+ ];
+
+ function awaitOnload(frame, occurances = 1) {
+ return new Promise(function(resolve, reject) {
+ let count = 0;
+ frame.addEventListener("load", function listener(e) {
+ if (++count == occurances) {
+ frame.removeEventListener("load", listener);
+ setTimeout(resolve, 0);
+ }
+ });
+ });
+ }
+
+ async function createDynamicFrame(targetDocument, frameSrc = "frame0.html") {
+ let dynamicFrame = targetDocument.createElement("iframe");
+ let onloadPromise = awaitOnload(dynamicFrame);
+ dynamicFrame.id = "dynamicFrame";
+ dynamicFrame.src = frameSrc;
+ let container = targetDocument.getElementById("frameContainer");
+ container.appendChild(dynamicFrame);
+ await onloadPromise;
+ return dynamicFrame;
+ }
+
+ async function loadUriInFrame(frame, uri) {
+ let onloadPromise = awaitOnload(frame);
+ frame.src = uri;
+ return onloadPromise;
+ }
+
+ function test() {
+ bc.postMessage("requestNextTest");
+ }
+ </script>
+ </head>
+ <body onpageshow="test();">
+ <div id="frameContainer">
+ <iframe id="staticFrame" src="frame0.html"></iframe>
+ </div>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_bug1326251_evict_cache.html b/docshell/test/navigation/file_bug1326251_evict_cache.html
new file mode 100644
index 0000000000..b9873947f4
--- /dev/null
+++ b/docshell/test/navigation/file_bug1326251_evict_cache.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Bug 1326251</title>
+ <script>
+ // Evict bfcache and then go back.
+ async function evictCache() {
+ await SpecialPowers.evictAllContentViewers();
+
+ history.back();
+ }
+ </script>
+ </head>
+ <body onload="setTimeout(evictCache, 0);">
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_bug1364364-1.html b/docshell/test/navigation/file_bug1364364-1.html
new file mode 100644
index 0000000000..d4ecc42ad4
--- /dev/null
+++ b/docshell/test/navigation/file_bug1364364-1.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>title</title>
+ </head>
+ <body onload="loadFramesAndNavigate();">
+ <p id="content"></p>
+ <div id="frameContainer">
+ </div>
+ <script type="application/javascript">
+ function waitForLoad(frame) {
+ return new Promise(r => frame.onload = () => setTimeout(r, 0));
+ }
+
+ async function loadFramesAndNavigate() {
+ let dynamicFrame = document.createElement("iframe");
+ dynamicFrame.src = "data:text/html,iframe1";
+ document.querySelector("#frameContainer").appendChild(dynamicFrame);
+ await waitForLoad(dynamicFrame);
+ dynamicFrame.src = "data:text/html,iframe2";
+ await waitForLoad(dynamicFrame);
+ dynamicFrame.src = "data:text/html,iframe3";
+ await waitForLoad(dynamicFrame);
+ dynamicFrame.src = "data:text/html,iframe4";
+ await waitForLoad(dynamicFrame);
+ dynamicFrame.src = "data:text/html,iframe5";
+ await waitForLoad(dynamicFrame);
+ location.href = "file_bug1364364-2.html";
+ }
+ </script>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_bug1364364-2.html b/docshell/test/navigation/file_bug1364364-2.html
new file mode 100644
index 0000000000..6e52ecaaa9
--- /dev/null
+++ b/docshell/test/navigation/file_bug1364364-2.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>title</title>
+ </head>
+ <body onload="notifyOpener();">
+ <script type="application/javascript">
+ function notifyOpener() {
+ opener.postMessage("navigation-done", "*");
+ }
+ </script>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_bug1375833-frame1.html b/docshell/test/navigation/file_bug1375833-frame1.html
new file mode 100644
index 0000000000..ea38326479
--- /dev/null
+++ b/docshell/test/navigation/file_bug1375833-frame1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>iframe test page 1</title>
+ </head>
+ <body>iframe test page 1</body>
+</html>
diff --git a/docshell/test/navigation/file_bug1375833-frame2.html b/docshell/test/navigation/file_bug1375833-frame2.html
new file mode 100644
index 0000000000..6e76ab7e47
--- /dev/null
+++ b/docshell/test/navigation/file_bug1375833-frame2.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>iframe test page 2</title>
+ </head>
+ <body>iframe test page 2</body>
+</html>
diff --git a/docshell/test/navigation/file_bug1375833.html b/docshell/test/navigation/file_bug1375833.html
new file mode 100644
index 0000000000..373a7fe08e
--- /dev/null
+++ b/docshell/test/navigation/file_bug1375833.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Test for bug 1375833</title>
+ </head>
+ <body onload="test();">
+ <iframe id="testFrame" src="file_bug1375833-frame1.html"></iframe>
+ <script type="application/javascript">
+ function test() {
+ let iframe = document.querySelector("#testFrame");
+ setTimeout(function() { iframe.src = "file_bug1375833-frame1.html"; }, 0);
+ iframe.onload = function(e) {
+ setTimeout(function() { iframe.src = "file_bug1375833-frame2.html"; }, 0);
+ iframe.onload = function() {
+ opener.postMessage(iframe.contentWindow.location.href, "*");
+ };
+ };
+ }
+ </script>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_bug1379762-1.html b/docshell/test/navigation/file_bug1379762-1.html
new file mode 100644
index 0000000000..c8cd666667
--- /dev/null
+++ b/docshell/test/navigation/file_bug1379762-1.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Bug 1379762</title>
+ </head>
+ <img srcset> <!-- This tries to add load blockers during bfcache restoration -->
+ <script>
+ onunload = null; // enable bfcache
+ var bc = new BroadcastChannel('bug1379762');
+ bc.postMessage("init");
+ onpageshow = function() {
+ bc.onmessage = (messageEvent) => {
+ let message = messageEvent.data;
+ if (message == "forward_back") {
+ // Navigate forward and then back.
+ // eslint-disable-next-line no-global-assign
+ setTimeout(function() { location = "goback.html"; }, 0);
+ } else if (message == "finish_test") {
+ // Do this async so our load event gets a chance to fire if it plans to
+ // do it.
+ setTimeout(function() {
+ bc.postMessage("finished");
+ bc.close();
+ window.close();
+ });
+ }
+ }
+ bc.postMessage("increment_testCount");
+ };
+ onload = function() {
+ bc.postMessage("increment_loadCount");
+ };
+ </script>
+</html>
diff --git a/docshell/test/navigation/file_bug1536471.html b/docshell/test/navigation/file_bug1536471.html
new file mode 100644
index 0000000000..53012257ee
--- /dev/null
+++ b/docshell/test/navigation/file_bug1536471.html
@@ -0,0 +1,8 @@
+<html>
+ <body onload="opener.bodyOnLoad()">
+ Nested Frame
+ <div id="frameContainer">
+ <iframe id="staticFrame" src="frame0.html"></iframe>
+ </div>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_bug1583110.html b/docshell/test/navigation/file_bug1583110.html
new file mode 100644
index 0000000000..5b08f54d21
--- /dev/null
+++ b/docshell/test/navigation/file_bug1583110.html
@@ -0,0 +1,26 @@
+<html>
+ <head>
+ <script>
+ var bc;
+ window.onpageshow = function(pageshow) {
+ bc = new BroadcastChannel("bug1583110");
+ bc.onmessage = function(event) {
+ bc.close();
+ if (event.data == "loadnext") {
+ location.search = "?nextpage";
+ } else if (event.data == "back") {
+ history.back();
+ }
+ }
+ bc.postMessage({type: "pageshow", persisted: pageshow.persisted });
+ if (pageshow.persisted) {
+ document.body.appendChild(document.createElement("iframe"));
+ bc.close();
+ window.close();
+ }
+ }
+ </script>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_bug1609475.html b/docshell/test/navigation/file_bug1609475.html
new file mode 100644
index 0000000000..7699d46b08
--- /dev/null
+++ b/docshell/test/navigation/file_bug1609475.html
@@ -0,0 +1,51 @@
+<html>
+ <head>
+ <script>
+
+ var loadCount = 0;
+ function loadListener(event) {
+ ++loadCount;
+ if (loadCount == 2) {
+ // Use a timer to ensure we don't get extra load events.
+ setTimeout(function() {
+ var doc1URI = document.getElementById("i1").contentDocument.documentURI;
+ opener.ok(doc1URI.includes("frame1.html"),
+ "Should have loaded the initial page to the first iframe. Got " + doc1URI);
+ var doc2URI = document.getElementById("i2").contentDocument.documentURI;
+ opener.ok(doc2URI.includes("frame1.html"),
+ "Should have loaded the initial page to the second iframe. Got " + doc2URI);
+ opener.finishTest();
+ }, 1000);
+ } else if (loadCount > 2) {
+ opener.ok(false, "Too many load events");
+ }
+ // if we don't get enough load events, the test will time out.
+ }
+
+ function setupIframe(id) {
+ var ifr = document.getElementById(id);
+ return new Promise(function(resolve) {
+ ifr.onload = function() {
+ // Replace load listener to catch page loads from the session history.
+ ifr.onload = loadListener;
+ // Need to use setTimeout, because triggering loads inside
+ // load event listener has special behavior since at the moment
+ // the docshell keeps track of whether it is executing a load handler or not.
+ setTimeout(resolve);
+ }
+ ifr.contentWindow.location.href = "frame2.html";
+ });
+ }
+
+ async function test() {
+ await setupIframe("i1");
+ await setupIframe("i2");
+ history.go(-2);
+ }
+ </script>
+ </head>
+ <body onload="setTimeout(test)">
+ <iframe id="i1" src="frame1.html"></iframe>
+ <iframe id="i2" src="frame1.html"></iframe>
+ </body>
+</html> \ No newline at end of file
diff --git a/docshell/test/navigation/file_bug1706090.html b/docshell/test/navigation/file_bug1706090.html
new file mode 100644
index 0000000000..9c5bc025d3
--- /dev/null
+++ b/docshell/test/navigation/file_bug1706090.html
@@ -0,0 +1,40 @@
+<html>
+ <head>
+ <script>
+ onpageshow = function(pageShowEvent) {
+ if (location.hostname == "example.com" ||
+ location.hostname == "test1.mochi.test") {
+ // BroadcastChannel doesn't work across domains, so just go to the
+ // previous page explicitly.
+ history.back();
+ return;
+ }
+
+ var bc = new BroadcastChannel("bug1706090");
+ bc.onmessage = function(event) {
+ if (event.data == "close") {
+ bc.postMessage("closed");
+ bc.close();
+ window.close();
+ } else if (event.data == "sameOrigin") {
+ bc.close();
+ location.href = location.href + "?foo"
+ } else if (event.data == "back") {
+ history.back();
+ } else if (event.data == "crossOrigin") {
+ bc.close();
+ location.href = "https://example.com:443" + location.pathname;
+ } else if (event.data == "sameSite") {
+ bc.close();
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ location.href = "http://test1.mochi.test:8888" + location.pathname;
+ }
+ }
+
+ bc.postMessage({type: pageShowEvent.type, persisted: pageShowEvent.persisted});
+ }
+ </script>
+ </head>
+ <body onunload="/* dummy listener*/">
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_bug1745638.html b/docshell/test/navigation/file_bug1745638.html
new file mode 100644
index 0000000000..b98c8de91f
--- /dev/null
+++ b/docshell/test/navigation/file_bug1745638.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<script>
+ function go() {
+ var doc = document.getElementById("testFrame").contentWindow.document;
+ doc.open();
+ doc.close();
+ doc.body.innerText = "passed";
+ }
+</script>
+<body onload="setTimeout(opener.pageLoaded);">
+The iframe below should not be blank after a reload.<br>
+<iframe src="about:blank" id="testFrame"></iframe>
+<script>
+ go();
+</script>
diff --git a/docshell/test/navigation/file_bug1750973.html b/docshell/test/navigation/file_bug1750973.html
new file mode 100644
index 0000000000..28b2f995ae
--- /dev/null
+++ b/docshell/test/navigation/file_bug1750973.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script>
+ function test() {
+ history.scrollRestoration = "manual";
+ document.getElementById("initialfocus").focus();
+ history.pushState('data', '', '');
+ history.back();
+ }
+
+ window.onpopstate = function() {
+ window.onscroll = function() {
+ window.onscroll = null;
+ document.documentElement.style.display = "none";
+ // focus() triggers recreation of the nsIFrames without a reflow.
+ document.getElementById("focustarget").focus();
+ document.documentElement.style.display = "";
+ // Flush the layout.
+ document.documentElement.getBoundingClientRect();
+ opener.isnot(window.scrollY, 0, "The page should have been scrolled down(1)");
+ requestAnimationFrame(
+ function() {
+ // Extra timeout to ensure we're called after rAF has triggered a
+ // reflow.
+ setTimeout(function() {
+ opener.isnot(window.scrollY, 0, "The page should have been scrolled down(2)");
+ window.close();
+ opener.SimpleTest.finish();
+ });
+ });
+ }
+ window.scrollTo(0, 1000);
+ }
+ </script>
+</head>
+<body onload="setTimeout(test)">
+<div style="position: fixed;">
+ <input type="button" value="" id="initialfocus">
+ <input type="button" value="" id="focustarget">
+</div>
+<div style="border: 1px solid black; width: 100px; height: 9000px;"></div>
+</body>
+</html>
diff --git a/docshell/test/navigation/file_bug1758664.html b/docshell/test/navigation/file_bug1758664.html
new file mode 100644
index 0000000000..07798dfddd
--- /dev/null
+++ b/docshell/test/navigation/file_bug1758664.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+var onIframeOnload = function() {
+ var iframe = window.document.getElementById('applicationIframe');
+ opener.is(iframe.contentWindow.location.search, "?iframe", "Should have loaded the iframe");
+ window.close();
+ opener.SimpleTest.finish();
+}
+
+var onPageOnload = function() {
+ if (location.search == "?iframe") {
+ return;
+ }
+ if(!window.name) {
+ window.name = 'file_bug1758664.html';
+ window.location.reload();
+ return;
+ }
+ var iframe = window.document.getElementById('applicationIframe');
+ iframe.addEventListener('load', onIframeOnload);
+ iframe.src = "file_bug1758664.html?iframe";
+}
+window.document.addEventListener("DOMContentLoaded", onPageOnload);
+
+</script>
+</head>
+<body>
+ <iframe id="applicationIframe"></iframe>
+</body>
+</html>
diff --git a/docshell/test/navigation/file_bug386782_contenteditable.html b/docshell/test/navigation/file_bug386782_contenteditable.html
new file mode 100644
index 0000000000..4515d015d9
--- /dev/null
+++ b/docshell/test/navigation/file_bug386782_contenteditable.html
@@ -0,0 +1 @@
+<html><head><meta charset="utf-8"><script>window.addEventListener("pageshow", function(event) { window.opener.postMessage({persisted: event.persisted}, "*"); });</script></head><body contentEditable="true"><p>contentEditable</p></body></html> \ No newline at end of file
diff --git a/docshell/test/navigation/file_bug386782_designmode.html b/docshell/test/navigation/file_bug386782_designmode.html
new file mode 100644
index 0000000000..faa063cbae
--- /dev/null
+++ b/docshell/test/navigation/file_bug386782_designmode.html
@@ -0,0 +1 @@
+<html><head><meta charset="utf-8"><script>window.addEventListener("pageshow", function(event) { window.opener.postMessage({persisted: event.persisted}, "*"); });</script></head><body><p>designModeDocument</p></body></html> \ No newline at end of file
diff --git a/docshell/test/navigation/file_bug462076_1.html b/docshell/test/navigation/file_bug462076_1.html
new file mode 100644
index 0000000000..5050e79fdc
--- /dev/null
+++ b/docshell/test/navigation/file_bug462076_1.html
@@ -0,0 +1,55 @@
+<html>
+ <head>
+ <title>Bug 462076</title>
+ <script>
+ var srcs = [ "frame0.html",
+ "frame1.html",
+ "frame2.html",
+ "frame3.html" ];
+
+ var checkCount = 0;
+
+ function makeFrame(index) {
+ var ifr = document.createElement("iframe");
+ ifr.src = srcs[index];
+ ifr.onload = checkFrame;
+ document.getElementById("container" + index).appendChild(ifr);
+ }
+
+ function runTest() {
+ var randomNumber = Math.floor(Math.random() * 4);
+ for (var i = randomNumber; i < 4; ++i) {
+ makeFrame(i);
+ }
+ for (var k = 0; k < randomNumber; ++k) {
+ makeFrame(k);
+ }
+ }
+
+ function checkFrame(evt) {
+ var ifr = evt.target;
+ opener.ok(String(ifr.contentWindow.location).includes(ifr.src),
+ "Wrong document loaded (" + ifr.src + ", " +
+ ifr.contentWindow.location + ")!");
+
+ if (++checkCount == 4) {
+ if (++opener.testCount == 10) {
+ opener.nextTest();
+ window.close();
+ } else {
+ window.location.reload();
+ }
+ }
+ }
+ </script>
+ </head>
+ <body>
+ <div id="container0"></div>
+ <div id="container1"></div>
+ <div id="container2"></div>
+ <div id="container3"></div>
+ <script>
+ runTest();
+ </script>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_bug462076_2.html b/docshell/test/navigation/file_bug462076_2.html
new file mode 100644
index 0000000000..63cf3de3f9
--- /dev/null
+++ b/docshell/test/navigation/file_bug462076_2.html
@@ -0,0 +1,52 @@
+<html>
+ <head>
+ <title>Bug 462076</title>
+ <script>
+ var srcs = [ "frame0.html",
+ "frame1.html",
+ "frame2.html",
+ "frame3.html" ];
+
+ var checkCount = 0;
+
+ function makeFrame(index) {
+ var ifr = document.createElement("iframe");
+ ifr.src = srcs[index];
+ ifr.onload = checkFrame;
+ document.getElementById("container" + index).appendChild(ifr);
+ }
+
+ function runTest() {
+ var randomNumber = Math.floor(Math.random() * 4);
+ for (var i = randomNumber; i < 4; ++i) {
+ makeFrame(i);
+ }
+ for (var k = 0; k < randomNumber; ++k) {
+ makeFrame(k);
+ }
+ }
+
+ function checkFrame(evt) {
+ var ifr = evt.target;
+ opener.ok(String(ifr.contentWindow.location).includes(ifr.src),
+ "Wrong document loaded (" + ifr.src + ", " +
+ ifr.contentWindow.location + ")!");
+
+ if (++checkCount == 4) {
+ if (++opener.testCount == 10) {
+ opener.nextTest();
+ window.close();
+ } else {
+ window.location.reload();
+ }
+ }
+ }
+ </script>
+ </head>
+ <body onload="runTest();">
+ <div id="container0"></div>
+ <div id="container1"></div>
+ <div id="container2"></div>
+ <div id="container3"></div>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_bug462076_3.html b/docshell/test/navigation/file_bug462076_3.html
new file mode 100644
index 0000000000..5c779d2f49
--- /dev/null
+++ b/docshell/test/navigation/file_bug462076_3.html
@@ -0,0 +1,52 @@
+<html>
+ <head>
+ <title>Bug 462076</title>
+ <script>
+ var srcs = [ "frame0.html",
+ "frame1.html",
+ "frame2.html",
+ "frame3.html" ];
+
+ var checkCount = 0;
+
+ function makeFrame(index) {
+ var ifr = document.createElement("iframe");
+ ifr.src = srcs[index];
+ ifr.onload = checkFrame;
+ document.getElementById("container" + index).appendChild(ifr);
+ }
+
+ function runTest() {
+ var randomNumber = Math.floor(Math.random() * 4);
+ for (var i = randomNumber; i < 4; ++i) {
+ makeFrame(i);
+ }
+ for (var k = 0; k < randomNumber; ++k) {
+ makeFrame(k);
+ }
+ }
+
+ function checkFrame(evt) {
+ var ifr = evt.target;
+ opener.ok(String(ifr.contentWindow.location).includes(ifr.src),
+ "Wrong document loaded (" + ifr.src + ", " +
+ ifr.contentWindow.location + ")!");
+
+ if (++checkCount == 4) {
+ if (++opener.testCount == 10) {
+ opener.nextTest();
+ window.close();
+ } else {
+ window.location.reload();
+ }
+ }
+ }
+ </script>
+ </head>
+ <body onload="setTimeout(runTest, 0);">
+ <div id="container0"></div>
+ <div id="container1"></div>
+ <div id="container2"></div>
+ <div id="container3"></div>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_bug508537_1.html b/docshell/test/navigation/file_bug508537_1.html
new file mode 100644
index 0000000000..182c085670
--- /dev/null
+++ b/docshell/test/navigation/file_bug508537_1.html
@@ -0,0 +1,33 @@
+<html>
+ <head>
+ <script>
+ function dynFrameLoad() {
+ var ifrs = document.getElementsByTagName("iframe");
+ opener.ok(String(ifrs[0].contentWindow.location).includes(ifrs[0].src),
+ "Wrong document loaded (1)\n");
+ opener.ok(String(ifrs[1].contentWindow.location).includes(ifrs[1].src),
+ "Wrong document loaded (2)\n");
+ if (opener && ++opener.testCount == 1) {
+ window.location = "goback.html";
+ } else {
+ opener.finishTest();
+ }
+ }
+
+ window.addEventListener("load",
+ function() {
+ var container = document.getElementById("t1");
+ container.addEventListener("load", dynFrameLoad, true);
+ container.appendChild(container.appendChild(document.getElementById("i1")));
+ });
+ </script>
+ </head>
+ <body>
+ <h5>Container:</h5>
+ <div id="t1"></div>
+ <h5>Original frames:</h5>
+ <iframe id="i1" src="frame0.html"></iframe>
+ <iframe src="frame1.html"></iframe>
+ </body>
+</html>
+
diff --git a/docshell/test/navigation/file_bug534178.html b/docshell/test/navigation/file_bug534178.html
new file mode 100644
index 0000000000..4d77dd824b
--- /dev/null
+++ b/docshell/test/navigation/file_bug534178.html
@@ -0,0 +1,30 @@
+<html>
+ <head>
+ <script>
+
+ function testDone() {
+ document.body.firstChild.remove();
+ var isOK = false;
+ try {
+ isOK = history.previous != location;
+ } catch (ex) {
+ // history.previous should throw if this is the first page in shistory.
+ isOK = true;
+ }
+ document.body.textContent = isOK ? "PASSED" : "FAILED";
+ opener.ok(isOK, "Duplicate session history entries should have been removed!");
+ opener.finishTest();
+ }
+ function ifrload() {
+ setTimeout(testDone, 0);
+ }
+ function test() {
+ var ifr = document.getElementsByTagName("iframe")[0];
+ ifr.onload = ifrload;
+ ifr.src = "data:text/html,doc2";
+ }
+ </script>
+ </head>
+ <body onload="setTimeout(test, 0)"><iframe src="data:text/html,doc1"></iframe>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_contentpolicy_block_window.html b/docshell/test/navigation/file_contentpolicy_block_window.html
new file mode 100644
index 0000000000..c51e574e5e
--- /dev/null
+++ b/docshell/test/navigation/file_contentpolicy_block_window.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+This window should never be openend!
+</body>
+</html>
diff --git a/docshell/test/navigation/file_docshell_gotoindex.html b/docshell/test/navigation/file_docshell_gotoindex.html
new file mode 100644
index 0000000000..f3e8919822
--- /dev/null
+++ b/docshell/test/navigation/file_docshell_gotoindex.html
@@ -0,0 +1,42 @@
+<html>
+ <head>
+ <script>
+ function loaded() {
+ if (location.search == "") {
+ if (opener.loadedInitialPage) {
+ opener.ok(true, "got back to the initial page.");
+ opener.setTimeout("SimpleTest.finish();");
+ window.close();
+ return;
+ }
+ opener.loadedInitialPage = true;
+ opener.info("Loaded initial page.");
+ // Load another page (which is this same document, but different URL.)
+ location.href = location.href + "?anotherPage";
+ } else {
+ opener.info("Loaded the second page.");
+ location.hash = "1";
+ window.onhashchange = function() {
+ opener.info("hash: " + location.hash);
+ location.hash = "2";
+ window.onhashchange = function() {
+ opener.info("hash: " + location.hash);
+ var docShell = SpecialPowers.wrap(window).docShell;
+ var webNavigation =
+ SpecialPowers.do_QueryInterface(docShell, "nsIWebNavigation");
+ webNavigation.gotoIndex(history.length - 2);
+ window.onhashchange = function() {
+ opener.info("hash: " + location.hash);
+ webNavigation.gotoIndex(history.length - 4);
+ }
+ }
+ }
+ }
+ }
+ </script>
+ </head>
+ <body onpageshow="setTimeout(loaded)">
+ <a href="#1" name="1">1</a>
+ <a href="#2" name="2">2</a>
+ </body>
+</html> \ No newline at end of file
diff --git a/docshell/test/navigation/file_document_write_1.html b/docshell/test/navigation/file_document_write_1.html
new file mode 100644
index 0000000000..be52b60231
--- /dev/null
+++ b/docshell/test/navigation/file_document_write_1.html
@@ -0,0 +1,18 @@
+<html>
+ <head>
+ <script>
+ function start() {
+ var length = history.length;
+ document.open();
+ document.write("<h5 id='dynamic'>document.written content</h5>");
+ document.close();
+ opener.is(history.length, length,
+ "document.open/close should not change history");
+ opener.finishTest();
+ }
+ </script>
+ </head>
+ <body onload="start();">
+ <h5>static content</h5>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_evict_from_bfcache.html b/docshell/test/navigation/file_evict_from_bfcache.html
new file mode 100644
index 0000000000..9f50533543
--- /dev/null
+++ b/docshell/test/navigation/file_evict_from_bfcache.html
@@ -0,0 +1,29 @@
+<html>
+ <head>
+ <script>
+ onpageshow = function(pageShowEvent) {
+ var bc = new BroadcastChannel("evict_from_bfcache");
+ bc.onmessage = function(event) {
+ if (event.data == "nextpage") {
+ bc.close();
+ location.href += "?nextpage";
+ } else if (event.data == "back") {
+ bc.close();
+ history.back();
+ } else if (event.data == "forward") {
+ // Note, we don't close BroadcastChannel
+ history.forward();
+ } else if (event.data == "close") {
+ bc.postMessage("closed");
+ bc.close();
+ window.close();
+ }
+ }
+
+ bc.postMessage({ type: "pageshow", persisted: pageShowEvent.persisted});
+ };
+ </script>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_fragment_handling_during_load.html b/docshell/test/navigation/file_fragment_handling_during_load.html
new file mode 100644
index 0000000000..a7f468c32d
--- /dev/null
+++ b/docshell/test/navigation/file_fragment_handling_during_load.html
@@ -0,0 +1,27 @@
+<html>
+ <head>
+ <script>
+ function checkHaveLoadedNewDoc() {
+ let l = document.body.firstChild.contentWindow.location.href;
+ if (!l.endsWith("file_fragment_handling_during_load_frame2.sjs")) {
+ opener.ok(true, "Fine. We will check later.");
+ setTimeout(checkHaveLoadedNewDoc, 500);
+ return;
+ }
+ opener.ok(true, "Have loaded a new document.");
+ opener.finishTest();
+ }
+ function test() {
+ // Test that executing back() before load has started doesn't interrupt
+ // the load.
+ var ifr = document.getElementsByTagName("iframe")[0];
+ ifr.onload = checkHaveLoadedNewDoc;
+ ifr.contentWindow.location.hash = "b";
+ ifr.contentWindow.location.href = "file_fragment_handling_during_load_frame2.sjs";
+ history.back();
+ }
+ </script>
+ </head>
+ <body onload="setTimeout(test, 0)"><iframe src="file_fragment_handling_during_load_frame1.html#a"></iframe>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_fragment_handling_during_load_frame1.html b/docshell/test/navigation/file_fragment_handling_during_load_frame1.html
new file mode 100644
index 0000000000..c03ba2bda6
--- /dev/null
+++ b/docshell/test/navigation/file_fragment_handling_during_load_frame1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+foo
+</body>
+</html>
diff --git a/docshell/test/navigation/file_fragment_handling_during_load_frame2.sjs b/docshell/test/navigation/file_fragment_handling_during_load_frame2.sjs
new file mode 100644
index 0000000000..77abe5949e
--- /dev/null
+++ b/docshell/test/navigation/file_fragment_handling_during_load_frame2.sjs
@@ -0,0 +1,20 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80 ft=javascript: */
+"use strict";
+
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ // Wait a bit.
+ var s = Date.now();
+ // eslint-disable-next-line no-empty
+ while (Date.now() - s < 1000) {}
+
+ response.write(`<!DOCTYPE HTML>
+ <html>
+ <body>
+ bar
+ </body>
+ </html>
+ `);
+}
diff --git a/docshell/test/navigation/file_load_history_entry_page_with_one_link.html b/docshell/test/navigation/file_load_history_entry_page_with_one_link.html
new file mode 100644
index 0000000000..a4d1b62176
--- /dev/null
+++ b/docshell/test/navigation/file_load_history_entry_page_with_one_link.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+<body onpageshow="opener.bodyOnLoad()">
+<a id="link1" href="#1">Link 1</a>
+<a name="1">link 1</a>
+</body>
+</html>
diff --git a/docshell/test/navigation/file_load_history_entry_page_with_two_links.html b/docshell/test/navigation/file_load_history_entry_page_with_two_links.html
new file mode 100644
index 0000000000..4be2ea6f4e
--- /dev/null
+++ b/docshell/test/navigation/file_load_history_entry_page_with_two_links.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<body onpageshow="opener.bodyOnLoad()">
+<a id="link1" href="#1">Link 1</a>
+<a id="link2" href="#2">Link 2</a>
+<a name="1">link 1</a>
+<a name="2">link 2</a>
+</body>
+</html>
diff --git a/docshell/test/navigation/file_meta_refresh.html b/docshell/test/navigation/file_meta_refresh.html
new file mode 100644
index 0000000000..2a06cc5acf
--- /dev/null
+++ b/docshell/test/navigation/file_meta_refresh.html
@@ -0,0 +1,40 @@
+<html>
+ <head>
+ </head>
+ <body>
+ <script>
+ function addMetaRefresh() {
+ // eslint-disable-next-line no-unsanitized/property
+ document.head.innerHTML = "<meta http-equiv='refresh' content='5; url=" +
+ location.href.replace("?initial", "?refresh") + "'>";
+ }
+
+ onpageshow = function() {
+ let bc = new BroadcastChannel("test_meta_refresh");
+ bc.onmessage = function(event) {
+ if (event.data == "loadnext") {
+ bc.close();
+ addMetaRefresh();
+ location.href = location.href.replace("?initial", "?nextpage");
+ } else if (event.data == "back") {
+ bc.close();
+ history.back();
+ } else if (event.data == "ensuremetarefresh") {
+ bc.close();
+ // This test is designed to work with and without bfcache, but
+ // if bfcache is enabled, meta refresh should be suspended/resumed.
+ if (document.head.firstChild.localName != "meta") {
+ addMetaRefresh();
+ }
+ } else if (event.data == "close") {
+ bc.postMessage("closed");
+ bc.close();
+ window.close();
+ }
+ };
+
+ bc.postMessage({ load: location.search.substr(1) });
+ }
+ </script>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_navigation_type.html b/docshell/test/navigation/file_navigation_type.html
new file mode 100644
index 0000000000..bb538eefec
--- /dev/null
+++ b/docshell/test/navigation/file_navigation_type.html
@@ -0,0 +1,25 @@
+<html>
+ <head>
+ <script>
+ onpageshow = function() {
+ let bc = new BroadcastChannel("navigation_type");
+ bc.onmessage = function(event) {
+ if (event.data == "loadNewPage") {
+ bc.close();
+ location.href = location.href + "?next";
+ } else if (event.data == "back") {
+ bc.close();
+ history.back();
+ } else if (event.data == "close") {
+ window.close();
+ bc.postMessage("closed");
+ bc.close();
+ }
+ }
+ bc.postMessage({ navigationType: performance.navigation.type });
+ }
+ </script>
+ </head>
+ <body>
+ </body>
+</html> \ No newline at end of file
diff --git a/docshell/test/navigation/file_nested_frames.html b/docshell/test/navigation/file_nested_frames.html
new file mode 100644
index 0000000000..6ec286aa3e
--- /dev/null
+++ b/docshell/test/navigation/file_nested_frames.html
@@ -0,0 +1,27 @@
+<html>
+ <head>
+ <script>
+ function nestedIframeLoaded() {
+ var tf = document.getElementById("testframe");
+ var innerf = tf.contentDocument.getElementsByTagName("iframe")[0];
+ if (!innerf.contentDocument.documentURI.includes("frame0")) {
+ innerf.contentWindow.location.href = "http://mochi.test:8888/tests/docshell/test/navigation/frame0.html";
+ return;
+ }
+ innerf.onload = null;
+ innerf.src = "about:blank";
+ var d = innerf.contentDocument;
+ d.open();
+ d.write("test");
+ d.close();
+ opener.is(window.history.length, 1, "Unexpected history length");
+ opener.finishTest();
+ }
+ </script>
+ </head>
+ <body>
+ <iframe id="testframe" src="file_nested_frames_innerframe.html" onload="frameLoaded()"></iframe>
+ <script>
+ </script>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_nested_frames_innerframe.html b/docshell/test/navigation/file_nested_frames_innerframe.html
new file mode 100644
index 0000000000..e25b6a4f6a
--- /dev/null
+++ b/docshell/test/navigation/file_nested_frames_innerframe.html
@@ -0,0 +1 @@
+<iframe onload='parent.nestedIframeLoaded();'></iframe>
diff --git a/docshell/test/navigation/file_nested_srcdoc.html b/docshell/test/navigation/file_nested_srcdoc.html
new file mode 100644
index 0000000000..ae6d656f27
--- /dev/null
+++ b/docshell/test/navigation/file_nested_srcdoc.html
@@ -0,0 +1,3 @@
+
+iframe loaded inside of a srcdoc
+<iframe id="static" srcdoc="Second nested srcdoc<iframe id='static' srcdoc='Third nested srcdoc'&gt;</iframe&gt;"></iframe>
diff --git a/docshell/test/navigation/file_new_shentry_during_history_navigation_1.html b/docshell/test/navigation/file_new_shentry_during_history_navigation_1.html
new file mode 100644
index 0000000000..2f9a41e1d1
--- /dev/null
+++ b/docshell/test/navigation/file_new_shentry_during_history_navigation_1.html
@@ -0,0 +1,5 @@
+<script>
+ onload = function() {
+ opener.postMessage("load");
+ }
+</script>
diff --git a/docshell/test/navigation/file_new_shentry_during_history_navigation_1.html^headers^ b/docshell/test/navigation/file_new_shentry_during_history_navigation_1.html^headers^
new file mode 100644
index 0000000000..59ba296103
--- /dev/null
+++ b/docshell/test/navigation/file_new_shentry_during_history_navigation_1.html^headers^
@@ -0,0 +1 @@
+Cache-control: no-store
diff --git a/docshell/test/navigation/file_new_shentry_during_history_navigation_2.html b/docshell/test/navigation/file_new_shentry_during_history_navigation_2.html
new file mode 100644
index 0000000000..de456f8f1c
--- /dev/null
+++ b/docshell/test/navigation/file_new_shentry_during_history_navigation_2.html
@@ -0,0 +1,10 @@
+<script>
+ onload = function() {
+ opener.postMessage("load");
+ }
+
+ onbeforeunload = function() {
+ opener.postMessage("beforeunload");
+ history.pushState("data1", "", "?push1");
+ }
+</script>
diff --git a/docshell/test/navigation/file_new_shentry_during_history_navigation_2.html^headers^ b/docshell/test/navigation/file_new_shentry_during_history_navigation_2.html^headers^
new file mode 100644
index 0000000000..59ba296103
--- /dev/null
+++ b/docshell/test/navigation/file_new_shentry_during_history_navigation_2.html^headers^
@@ -0,0 +1 @@
+Cache-control: no-store
diff --git a/docshell/test/navigation/file_new_shentry_during_history_navigation_3.html b/docshell/test/navigation/file_new_shentry_during_history_navigation_3.html
new file mode 100644
index 0000000000..2237e3e367
--- /dev/null
+++ b/docshell/test/navigation/file_new_shentry_during_history_navigation_3.html
@@ -0,0 +1,22 @@
+<script>
+ window.onpageshow = function(e) {
+ if (location.search == "?v2") {
+ onbeforeunload = function() {
+ history.pushState("data1", "", "?push1");
+ }
+ }
+
+ let bc = new BroadcastChannel("new_shentry_during_history_navigation");
+ bc.onmessage = function(event) {
+ bc.close();
+ if (event.data == "loadnext") {
+ location.href = "file_new_shentry_during_history_navigation_4.html";
+ } else if (event.data == "back") {
+ history.back();
+ } else if (event.data == "close") {
+ window.close();
+ }
+ }
+ bc.postMessage({page: location.href, type: e.type, persisted: e.persisted});
+ }
+</script>
diff --git a/docshell/test/navigation/file_new_shentry_during_history_navigation_3.html^headers^ b/docshell/test/navigation/file_new_shentry_during_history_navigation_3.html^headers^
new file mode 100644
index 0000000000..59ba296103
--- /dev/null
+++ b/docshell/test/navigation/file_new_shentry_during_history_navigation_3.html^headers^
@@ -0,0 +1 @@
+Cache-control: no-store
diff --git a/docshell/test/navigation/file_new_shentry_during_history_navigation_4.html b/docshell/test/navigation/file_new_shentry_during_history_navigation_4.html
new file mode 100644
index 0000000000..d5c3f61794
--- /dev/null
+++ b/docshell/test/navigation/file_new_shentry_during_history_navigation_4.html
@@ -0,0 +1,16 @@
+<script>
+ window.onpageshow = function(e) {
+ let bc = new BroadcastChannel("new_shentry_during_history_navigation");
+ bc.onmessage = function(event) {
+ bc.close();
+ if (event.data == "loadnext") {
+ location.href = "file_new_shentry_during_history_navigation_3.html?v2";
+ } else if (event.data == "forward") {
+ history.forward();
+ } else if (event.data == "close") {
+ window.close();
+ }
+ }
+ bc.postMessage({page: location.href, type: e.type, persisted: e.persisted});
+ }
+</script>
diff --git a/docshell/test/navigation/file_online_offline_bfcache.html b/docshell/test/navigation/file_online_offline_bfcache.html
new file mode 100644
index 0000000000..7f8138e758
--- /dev/null
+++ b/docshell/test/navigation/file_online_offline_bfcache.html
@@ -0,0 +1,41 @@
+<html>
+ <head>
+ <script>
+ onpageshow = function(pageshowEvent) {
+ let bc = new BroadcastChannel("online_offline_bfcache");
+ bc.onmessage = function(event) {
+ if (event.data == "nextpage") {
+ bc.close();
+ location.href = location.href + "?nextpage";
+ } else if (event.data == "back") {
+ bc.close();
+ history.back();
+ } else if (event.data == "forward") {
+ bc.close();
+ history.forward();
+ } else if (event.data == "close") {
+ bc.postMessage("closed");
+ bc.close();
+ window.close();
+ }
+ };
+ bc.postMessage({ event: pageshowEvent.type, persisted: pageshowEvent.persisted });
+ }
+
+ onoffline = function(event) {
+ let bc = new BroadcastChannel("online_offline_bfcache");
+ bc.postMessage(event.type);
+ bc.close();
+ }
+
+ ononline = function(event) {
+ let bc = new BroadcastChannel("online_offline_bfcache");
+ bc.postMessage(event.type);
+ bc.close();
+ }
+
+ </script>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_reload.html b/docshell/test/navigation/file_reload.html
new file mode 100644
index 0000000000..f0cb1c2d52
--- /dev/null
+++ b/docshell/test/navigation/file_reload.html
@@ -0,0 +1,23 @@
+<html>
+ <head>
+ <script>
+ window.onpageshow = function() {
+ let bc = new BroadcastChannel("test_reload");
+ bc.onmessage = function(event) {
+ if (event.data == "reload") {
+ bc.close();
+ location.reload(true);
+ } else if (event.data == "close") {
+ bc.postMessage("closed");
+ bc.close();
+ window.close();
+ }
+ }
+
+ bc.postMessage("pageshow");
+ }
+ </script>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_reload_large_postdata.sjs b/docshell/test/navigation/file_reload_large_postdata.sjs
new file mode 100644
index 0000000000..385d43d99f
--- /dev/null
+++ b/docshell/test/navigation/file_reload_large_postdata.sjs
@@ -0,0 +1,46 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80 ft=javascript: */
+"use strict";
+
+const BinaryInputStream = Components.Constructor(
+ "@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream"
+);
+
+Cu.importGlobalProperties(["URLSearchParams"]);
+
+function readStream(inputStream) {
+ let available = 0;
+ let result = [];
+ while ((available = inputStream.available()) > 0) {
+ result.push(inputStream.readBytes(available));
+ }
+ return result.join("");
+}
+
+function handleRequest(request, response) {
+ let rv = (() => {
+ try {
+ if (request.method != "POST") {
+ return "ERROR: not a POST request";
+ }
+
+ let body = new URLSearchParams(
+ readStream(new BinaryInputStream(request.bodyInputStream))
+ );
+ return body.get("payload").length;
+ } catch (e) {
+ return "ERROR: Exception: " + e;
+ }
+ })();
+
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.write(`<!DOCTYPE HTML>
+<script>
+let rv = (${JSON.stringify(rv)});
+opener.postMessage(rv, "*");
+</script>
+ `);
+}
diff --git a/docshell/test/navigation/file_reload_nonbfcached_srcdoc.sjs b/docshell/test/navigation/file_reload_nonbfcached_srcdoc.sjs
new file mode 100644
index 0000000000..e1ef75efa0
--- /dev/null
+++ b/docshell/test/navigation/file_reload_nonbfcached_srcdoc.sjs
@@ -0,0 +1,27 @@
+const createPage = function (msg) {
+ return `
+<html>
+<script>
+ onpageshow = function() {
+ opener.postMessage(document.body.firstChild.contentDocument.body.textContent);
+ }
+</script>
+<body><iframe srcdoc="${msg}"></iframe><body>
+</html>
+`;
+};
+
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-store");
+ response.setHeader("Content-Type", "text/html");
+
+ let currentState = getState("reload_nonbfcached_srcdoc");
+ let srcdoc = "pageload:" + currentState;
+ if (currentState != "second") {
+ setState("reload_nonbfcached_srcdoc", "second");
+ } else {
+ setState("reload_nonbfcached_srcdoc", "");
+ }
+
+ response.write(createPage(srcdoc));
+}
diff --git a/docshell/test/navigation/file_same_url.html b/docshell/test/navigation/file_same_url.html
new file mode 100644
index 0000000000..72a1dd2564
--- /dev/null
+++ b/docshell/test/navigation/file_same_url.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+let bc = new BroadcastChannel("test_same_url");
+let listener = e => {
+ switch (e.data) {
+ case "linkClick":
+ var link = document.getElementById("link1");
+ link.click();
+ break;
+ case "closeWin":
+ self.close();
+ break;
+ }
+};
+bc.addEventListener("message", listener);
+</script>
+</head>
+<body onpageshow="bc.postMessage({bodyOnLoad: history.length})">
+<a id="link1" href="file_same_url.html">Link 1</a>
+<a name="1">link 1</a>
+</body>
+</html>
diff --git a/docshell/test/navigation/file_scrollRestoration_bfcache_and_nobfcache.html b/docshell/test/navigation/file_scrollRestoration_bfcache_and_nobfcache.html
new file mode 100644
index 0000000000..fec51f821d
--- /dev/null
+++ b/docshell/test/navigation/file_scrollRestoration_bfcache_and_nobfcache.html
@@ -0,0 +1,30 @@
+<html>
+ <head>
+ <script>
+ // Ensure layout is flushed before doing anything with scrolling.
+ function flushAndExecute(callback) {
+ window.requestAnimationFrame(function() {
+ setTimeout(callback);
+ });
+ }
+
+ var bc = new BroadcastChannel("bfcached");
+ bc.onmessage = (msgEvent) => {
+ if (msgEvent.data == "loadNext") {
+ flushAndExecute(() => {
+ location.href = "file_scrollRestoration_bfcache_and_nobfcache_part2.html";
+ });
+ } else if (msgEvent.data == "forward") {
+ flushAndExecute(() => {
+ history.forward();
+ });
+ }
+ };
+ window.onpageshow = (event) => {
+ bc.postMessage({command: "pageshow", persisted: event.persisted});
+ };
+ </script>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_scrollRestoration_bfcache_and_nobfcache_part2.html b/docshell/test/navigation/file_scrollRestoration_bfcache_and_nobfcache_part2.html
new file mode 100644
index 0000000000..40e0578515
--- /dev/null
+++ b/docshell/test/navigation/file_scrollRestoration_bfcache_and_nobfcache_part2.html
@@ -0,0 +1,35 @@
+<html>
+ <head>
+ <script>
+ // Note, this page does not enter bfcache because of an HTTP header.
+
+ // Ensure layout is flushed before doing anything with scrolling.
+ function flushAndExecute(callback) {
+ window.requestAnimationFrame(function() {
+ setTimeout(callback);
+ });
+ }
+
+ var bc = new BroadcastChannel("notbfcached");
+ bc.onmessage = (msgEvent) => {
+ if (msgEvent.data == "scroll") {
+ flushAndExecute(() => { window.scrollTo(0, 4000); });
+ } else if (msgEvent.data == "getScrollY") {
+ flushAndExecute(() => { bc.postMessage({ scrollY: window.scrollY}); });
+ } else if (msgEvent.data == "back") {
+ flushAndExecute(() => { bc.close(); history.back(); });
+ } else if (msgEvent.data == "close") {
+ bc.postMessage("closed");
+ bc.close();
+ window.close();
+ }
+ };
+ window.onpageshow = (event) => {
+ bc.postMessage({command: "pageshow", persisted: event.persisted});
+ };
+ </script>
+ </head>
+ <body>
+ <div style="height: 5000px; border: 1px solid black;">content</div>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_scrollRestoration_bfcache_and_nobfcache_part2.html^headers^ b/docshell/test/navigation/file_scrollRestoration_bfcache_and_nobfcache_part2.html^headers^
new file mode 100644
index 0000000000..59ba296103
--- /dev/null
+++ b/docshell/test/navigation/file_scrollRestoration_bfcache_and_nobfcache_part2.html^headers^
@@ -0,0 +1 @@
+Cache-control: no-store
diff --git a/docshell/test/navigation/file_scrollRestoration_navigate.html b/docshell/test/navigation/file_scrollRestoration_navigate.html
new file mode 100644
index 0000000000..ac78f0abbe
--- /dev/null
+++ b/docshell/test/navigation/file_scrollRestoration_navigate.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<script>
+ var bc = new BroadcastChannel("navigate");
+ window.onload = () => {
+ bc.onmessage = (event) => {
+ if (event.data.command == "navigate") {
+ window.location = event.data.location;
+ bc.close();
+ }
+ if (event.data.command == "back") {
+ history.back();
+ bc.close();
+ }
+ }
+ bc.postMessage({command: "loaded", scrollRestoration: history.scrollRestoration});
+ }
+</script> \ No newline at end of file
diff --git a/docshell/test/navigation/file_scrollRestoration_part1_nobfcache.html b/docshell/test/navigation/file_scrollRestoration_part1_nobfcache.html
new file mode 100644
index 0000000000..1c94899ac2
--- /dev/null
+++ b/docshell/test/navigation/file_scrollRestoration_part1_nobfcache.html
@@ -0,0 +1,63 @@
+<html>
+ <head>
+ <script>
+ var oldHistoryObject = null;
+ var bc = new BroadcastChannel("bug1155730_part1");
+ bc.onmessage = (msgEvent) => {
+ var msg = msgEvent.data;
+ var command = msg.command;
+ if (command == "test") {
+ var currentCase = msg.currentCase;
+ test(currentCase);
+ }
+ }
+
+ function test(currentCase) {
+ var assertIs = [];
+ var assertOk = [];
+ var assertIsNot = [];
+ switch (currentCase) {
+ case 1: {
+ assertOk.push([history.scrollRestoration, "History object has scrollRestoration property."]);
+ assertIs.push([history.scrollRestoration, "auto", "history.scrollRestoration's default value should be 'auto'."]);
+ history.scrollRestoration = "foobar";
+ assertIs.push([history.scrollRestoration, "auto", "Invalid enum value should not change the value of an attribute."]);
+ history.scrollRestoration = "manual";
+ assertIs.push([history.scrollRestoration, "manual", "Valid enum value should change the value of an attribute."]);
+ history.scrollRestoration = "auto";
+ assertIs.push([history.scrollRestoration, "auto", "Valid enum value should change the value of an attribute."]);
+ bc.postMessage({command: "asserts", currentCase, assertIs, assertOk});
+ document.getElementById("bottom").scrollIntoView();
+ window.location.reload(false);
+ break;
+ }
+ case 2: {
+ assertIsNot.push([Math.round(window.scrollY), 0, "Should have restored scrolling."]);
+ assertIs.push([history.scrollRestoration, "auto", "Should have the same scrollRestoration as before reload."]);
+ history.scrollRestoration = "manual";
+ bc.postMessage({command: "asserts", currentCase, assertIs, assertIsNot});
+ window.location.reload(false);
+ break;
+ }
+ case 3: {
+ assertIs.push([window.scrollY, 0, "Should not have restored scrolling."]);
+ assertIs.push([history.scrollRestoration, "manual", "Should have the same scrollRestoration as before reload."]);
+ bc.postMessage({command: "asserts", currentCase, assertIs});
+ bc.close();
+ window.close();
+ break;
+ }
+ }
+ }
+ window.onpageshow = (event) => {
+ bc.postMessage({command: "pageshow", persisted: event.persisted});
+ }
+ </script>
+ </head>
+ <body>
+ <div style="border: 1px solid black; height: 5000px;">
+ &nbsp;</div>
+ <div id="bottom">Hello world</div>
+ <a href="#hash" name="hash">hash</a>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_scrollRestoration_part1_nobfcache.html^headers^ b/docshell/test/navigation/file_scrollRestoration_part1_nobfcache.html^headers^
new file mode 100644
index 0000000000..f944e3806d
--- /dev/null
+++ b/docshell/test/navigation/file_scrollRestoration_part1_nobfcache.html^headers^
@@ -0,0 +1 @@
+Cache-control: no-store \ No newline at end of file
diff --git a/docshell/test/navigation/file_scrollRestoration_part2_bfcache.html b/docshell/test/navigation/file_scrollRestoration_part2_bfcache.html
new file mode 100644
index 0000000000..2776e42a6e
--- /dev/null
+++ b/docshell/test/navigation/file_scrollRestoration_part2_bfcache.html
@@ -0,0 +1,57 @@
+<html>
+ <head>
+ <script>
+ var bc = new BroadcastChannel("bug1155730_part2");
+ bc.onmessage = (msgEvent) => {
+ var msg = msgEvent.data;
+ var command = msg.command;
+ if (command == "test") {
+ var currentCase = msg.currentCase;
+ test(currentCase);
+ }
+ }
+
+ function test(currentCase) {
+ var assertIs = [];
+ var assertIsNot = [];
+ switch (currentCase) {
+ case 1: {
+ history.scrollRestoration = "manual";
+ document.getElementById("bottom").scrollIntoView();
+ window.location.href = "file_scrollRestoration_navigate.html";
+ break;
+ }
+ case 2: {
+ assertIsNot.push([Math.round(window.scrollY), 0, "Should have kept the old scroll position."]);
+ assertIs.push([history.scrollRestoration, "manual", "Should have the same scrollRestoration as before reload."]);
+ bc.postMessage({command: "asserts", currentCase, assertIs, assertIsNot, assert2: "assert2"});
+ window.scrollTo(0, 0);
+ window.location.hash = "hash";
+ bc.postMessage({command: "nextCase"});
+ requestAnimationFrame(() => {
+ test(currentCase + 1);
+ });
+ break;
+ }
+ case 3: {
+ assertIsNot.push([Math.round(window.scrollY), 0, "Should have scrolled to #hash."]);
+ assertIs.push([history.scrollRestoration, "manual", "Should have the same scrollRestoration mode as before fragment navigation."]);
+ bc.postMessage({command: "asserts", currentCase, assertIs, assertIsNot});
+ bc.close();
+ window.close();
+ break;
+ }
+ }
+ }
+ window.onpageshow = (event) => {
+ bc.postMessage({command: "pageshow", persisted: event.persisted});
+ }
+ </script>
+ </head>
+ <body>
+ <div style="border: 1px solid black; height: 5000px;">
+ &nbsp;</div>
+ <div id="bottom">Hello world</div>
+ <a href="#hash" name="hash">hash</a>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_scrollRestoration_part3_nobfcache.html b/docshell/test/navigation/file_scrollRestoration_part3_nobfcache.html
new file mode 100644
index 0000000000..ffc68d6ccc
--- /dev/null
+++ b/docshell/test/navigation/file_scrollRestoration_part3_nobfcache.html
@@ -0,0 +1,157 @@
+<html>
+ <head>
+ <script>
+ var oldHistoryObject = null;
+ var currCaseForIframe = 0;
+ var bc = new BroadcastChannel("bug1155730_part3");
+ bc.onmessage = (msgEvent) => {
+ var msg = msgEvent.data;
+ var command = msg.command;
+ if (command == "test") {
+ var currentCase = msg.currentCase;
+ test(currentCase);
+ }
+ }
+
+ // If onpopstate event takes place, check if we need to call 'test()'
+ var callTest = false;
+ var nextCase = 0;
+ window.onpopstate = (e) => {
+ if (callTest) {
+ callTest = false;
+ setTimeout(() => {
+ test(nextCase);
+ });
+ }
+ }
+
+ function test(currentCase) {
+ var assertIs = [];
+ var assertOk = [];
+ var assertIsNot = [];
+ switch (currentCase) {
+ case 1: {
+ history.scrollRestoration = "manual";
+ window.location.hash = "hash";
+ bc.postMessage({command: "nextCase"});
+ requestAnimationFrame(() => {
+ test(currentCase + 1);
+ });
+ break;
+ }
+ case 2: {
+ assertIsNot.push([Math.round(window.scrollY), 0, "Should have scrolled to #hash."]);
+ assertIs.push([history.scrollRestoration, "manual", "Should have the same scrollRestoration mode as before fragment navigation."]);
+ bc.postMessage({command: "asserts", currentCase, assertIs, assertIsNot});
+ window.location.href = "file_scrollRestoration_navigate.html";
+ break;
+ }
+ case 3: {
+ assertIs.push([window.scrollY, 0, "Shouldn't have kept the old scroll position."]);
+ assertIs.push([history.scrollRestoration, "manual", "Should have the same scrollRestoration mode as before fragment navigation."]);
+ bc.postMessage({command: "asserts", currentCase, assertIs});
+ history.scrollRestoration = "auto";
+ document.getElementById("bottom").scrollIntoView();
+ history.pushState({ state: "state1" }, "state1");
+ history.pushState({ state: "state2" }, "state2");
+ window.scrollTo(0, 0);
+ bc.postMessage({command: "nextCase"});
+ callTest = true;
+ nextCase = currentCase + 1;
+ history.back(); // go back to state 1
+ break;
+ }
+ case 4: {
+ assertIsNot.push([Math.round(window.scrollY), 0, "Should have scrolled back to the state1's position"]);
+ assertIs.push([history.state.state, "state1", "Unexpected state."]);
+ bc.postMessage({command: "asserts", currentCase, assertIs, assertIsNot});
+
+ history.scrollRestoration = "manual";
+ document.getElementById("bottom").scrollIntoView();
+ history.pushState({ state: "state3" }, "state3");
+ history.pushState({ state: "state4" }, "state4");
+ window.scrollTo(0, 0);
+ bc.postMessage({command: "nextCase"});
+ callTest = true;
+ nextCase = currentCase + 1;
+ history.back(); // go back to state 3
+ break;
+ }
+ case 5: {
+ assertIs.push([Math.round(window.scrollY), 0, "Shouldn't have scrolled back to the state3's position"]);
+ assertIs.push([history.state.state, "state3", "Unexpected state."]);
+
+ history.pushState({ state: "state5" }, "state5");
+ history.scrollRestoration = "auto";
+ document.getElementById("bottom").scrollIntoView();
+ assertIsNot.push([Math.round(window.scrollY), 0, "Should have scrolled to 'bottom'."]);
+ bc.postMessage({command: "asserts", currentCase, assertIs, assertIsNot});
+ bc.postMessage({command: "nextCase"});
+ callTest = true;
+ nextCase = currentCase + 1;
+ // go back to state 3 (state 4 was removed when state 5 was pushed)
+ history.back();
+ break;
+ }
+ case 6: {
+ window.scrollTo(0, 0);
+ bc.postMessage({command: "nextCase"});
+ callTest = true;
+ nextCase = currentCase + 1;
+ history.forward();
+ break;
+ }
+ case 7: {
+ assertIsNot.push([Math.round(window.scrollY), 0, "Should have scrolled back to the state5's position"]);
+ bc.postMessage({command: "asserts", currentCase, assertIsNot});
+
+ var ifr = document.createElement("iframe");
+ ifr.src = "data:text/html,";
+ document.body.appendChild(ifr);
+ bc.postMessage({command: "nextCase"});
+ currCaseForIframe = currentCase + 1;
+ ifr.onload = () => {
+ test(currCaseForIframe);
+ };
+ break;
+ }
+ case 8: {
+ oldHistoryObject = SpecialPowers.wrap(document.getElementsByTagName("iframe")[0]).contentWindow.history;
+ bc.postMessage({command: "nextCase"});
+ currCaseForIframe++;
+ document.getElementsByTagName("iframe")[0].src = "about:blank";
+ break;
+ }
+ case 9: {
+ try {
+ oldHistoryObject.scrollRestoration;
+ assertOk.push([false, "Should have thrown an exception."]);
+ } catch (ex) {
+ assertOk.push([ex != null, "Did get an exception"]);
+ }
+ try {
+ oldHistoryObject.scrollRestoration = "auto";
+ assertOk.push([false, "Should have thrown an exception."]);
+ } catch (ex) {
+ assertOk.push([ex != null, "Did get an exception"]);
+ }
+ bc.postMessage({command: "asserts", currentCase, assertOk});
+ bc.postMessage({command: "finishing"});
+ bc.close();
+ window.close();
+ break;
+ }
+ }
+ }
+ window.onpageshow = (event) => {
+ bc.postMessage({command: "pageshow", persisted: event.persisted});
+ }
+ </script>
+ </head>
+ <body>
+ <div style="border: 1px solid black; height: 5000px;">
+ &nbsp;</div>
+ <div id="bottom">Hello world</div>
+ <a href="#hash" name="hash">hash</a>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_scrollRestoration_part3_nobfcache.html^headers^ b/docshell/test/navigation/file_scrollRestoration_part3_nobfcache.html^headers^
new file mode 100644
index 0000000000..f944e3806d
--- /dev/null
+++ b/docshell/test/navigation/file_scrollRestoration_part3_nobfcache.html^headers^
@@ -0,0 +1 @@
+Cache-control: no-store \ No newline at end of file
diff --git a/docshell/test/navigation/file_session_history_on_redirect.html b/docshell/test/navigation/file_session_history_on_redirect.html
new file mode 100644
index 0000000000..df7e99a1dd
--- /dev/null
+++ b/docshell/test/navigation/file_session_history_on_redirect.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script>
+ window.onpagehide = function(event) {
+ opener.is(event.persisted, false, "Should have disabled bfcache for this test.");
+ }
+
+ window.onpageshow = function(event) {
+ opener.pageshow();
+ }
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/docshell/test/navigation/file_session_history_on_redirect.html^headers^ b/docshell/test/navigation/file_session_history_on_redirect.html^headers^
new file mode 100644
index 0000000000..59ba296103
--- /dev/null
+++ b/docshell/test/navigation/file_session_history_on_redirect.html^headers^
@@ -0,0 +1 @@
+Cache-control: no-store
diff --git a/docshell/test/navigation/file_session_history_on_redirect_2.html b/docshell/test/navigation/file_session_history_on_redirect_2.html
new file mode 100644
index 0000000000..df7e99a1dd
--- /dev/null
+++ b/docshell/test/navigation/file_session_history_on_redirect_2.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script>
+ window.onpagehide = function(event) {
+ opener.is(event.persisted, false, "Should have disabled bfcache for this test.");
+ }
+
+ window.onpageshow = function(event) {
+ opener.pageshow();
+ }
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/docshell/test/navigation/file_session_history_on_redirect_2.html^headers^ b/docshell/test/navigation/file_session_history_on_redirect_2.html^headers^
new file mode 100644
index 0000000000..59ba296103
--- /dev/null
+++ b/docshell/test/navigation/file_session_history_on_redirect_2.html^headers^
@@ -0,0 +1 @@
+Cache-control: no-store
diff --git a/docshell/test/navigation/file_sessionhistory_iframe_removal.html b/docshell/test/navigation/file_sessionhistory_iframe_removal.html
new file mode 100644
index 0000000000..f18e849942
--- /dev/null
+++ b/docshell/test/navigation/file_sessionhistory_iframe_removal.html
@@ -0,0 +1,37 @@
+<html>
+ <head>
+ <script>
+ async function wait() {
+ return opener.SpecialPowers.spawnChrome([], function() { /*dummy */ });
+ }
+ async function run() {
+ var counter = 0;
+ while(true) {
+ // Push new history entries until we hit the limit and start removing
+ // entries from the front.
+ var len = history.length;
+ document.getElementById("ifr1").contentWindow.history.pushState(++counter, null, null);
+ await wait();
+ if (len == history.length) {
+ opener.ok(true, "Found max length " + len);
+ document.getElementById("ifr2").contentWindow.history.pushState(++counter, null, null);
+ await wait();
+ document.getElementById("ifr1").contentWindow.history.pushState(++counter, null, null);
+ await wait();
+ opener.is(len, history.length, "Adding session history entries in different iframe should behave the same way");
+ // This should remove all the history entries for ifr1, leaving just
+ // the ones for ifr2.
+ document.getElementById("ifr1").remove();
+ opener.is(history.length, 2, "Should have entries for the initial load and the pushState for ifr2");
+ opener.finishTest();
+ break;
+ }
+ }
+ }
+ </script>
+ </head>
+ <body onload="setTimeout(run)">
+ <iframe src="blank.html" id="ifr1"></iframe>
+ <iframe src="blank.html" id="ifr2"></iframe>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_shiftReload_and_pushState.html b/docshell/test/navigation/file_shiftReload_and_pushState.html
new file mode 100644
index 0000000000..7882143c83
--- /dev/null
+++ b/docshell/test/navigation/file_shiftReload_and_pushState.html
@@ -0,0 +1,28 @@
+<html>
+ <head>
+ <script>
+ function test() {
+ try {
+ frames[0].history.pushState({}, "state", "?pushed");
+ } catch (ex) {
+ opener.ok(false, "history.pushState shouldn't throw");
+ }
+
+ if (!opener.shiftReloadPushStateFirstRound) {
+ opener.shiftReloadPushStateFirstRound = true;
+ window.location.reload(true);
+ } else {
+ opener.ok(true, "Did run history.push");
+ opener.finishTest();
+ }
+ }
+
+ window.addEventListener("load", function() { setTimeout(test, 0); });
+ </script>
+ </head>
+ <body>
+ <iframe src="frame0.html"></iframe>
+ <script>
+ </script>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_ship_beforeunload_fired.html b/docshell/test/navigation/file_ship_beforeunload_fired.html
new file mode 100644
index 0000000000..a1f416f959
--- /dev/null
+++ b/docshell/test/navigation/file_ship_beforeunload_fired.html
@@ -0,0 +1,37 @@
+<html>
+ <script>
+ onpageshow = function(pageShowEvent) {
+ var bc = new BroadcastChannel("ship_beforeunload");
+ bc.onmessage = function(event) {
+ if (event.data.action == "register_beforeunload") {
+ onbeforeunload = function() {
+ bc.postMessage("beforeunload_fired");
+ bc.close();
+ }
+ if (event.data.loadNextPageFromSessionHistory) {
+ history.back();
+ } else {
+ location.href += "?differentpage";
+ }
+ } else if (event.data.action == "navigate_to_page_b") {
+ bc.close();
+ if (event.data.blockBFCache) {
+ window.blockBFCache = new RTCPeerConnection();
+ }
+ location.href += "?pageb";
+ } else if (event.data.action == "back_to_page_b") {
+ if (event.data.forwardNavigateToPageB) {
+ history.forward();
+ } else {
+ history.back();
+ }
+ bc.close();
+ } else if (event.data.action == "close") {
+ bc.close();
+ window.close();
+ }
+ }
+ bc.postMessage({type: pageShowEvent.type, persisted: pageShowEvent.persisted});
+ }
+ </script>
+</html>
diff --git a/docshell/test/navigation/file_static_and_dynamic_1.html b/docshell/test/navigation/file_static_and_dynamic_1.html
new file mode 100644
index 0000000000..e66216c41e
--- /dev/null
+++ b/docshell/test/navigation/file_static_and_dynamic_1.html
@@ -0,0 +1,31 @@
+<html>
+ <head>
+ <script>
+ function test() {
+ var ifr = document.createElement("iframe");
+ ifr.src = "frame0.html";
+ document.getElementById("dynamic").appendChild(ifr);
+ var staticFrame = document.getElementById("staticframe");
+ staticFrame.onload = window.location = "goback.html";
+ staticFrame.contentWindow.location = "frame1.html";
+ }
+
+ function start() {
+ if (++opener.testCount == 1) {
+ test();
+ } else {
+ var staticFrame = document.getElementById("staticframe");
+ opener.ok(String(staticFrame.contentWindow.location).includes(staticFrame.src),
+ "Wrong document loaded!");
+ opener.finishTest();
+ }
+ }
+ </script>
+ </head>
+ <body onload="setTimeout('start()', 0)">
+ <h5>Dynamic</h5>
+ <div id="dynamic"></div>
+ <h5>Static</h5>
+ <div id="static"><iframe id="staticframe" src="frame0.html"></iframe></div>
+ </body>
+</html>
diff --git a/docshell/test/navigation/file_tell_opener.html b/docshell/test/navigation/file_tell_opener.html
new file mode 100644
index 0000000000..bd45c275e6
--- /dev/null
+++ b/docshell/test/navigation/file_tell_opener.html
@@ -0,0 +1,8 @@
+<html>
+ <body onload="bodyLoaded()">Frame 1</body>
+ <script>
+ function bodyLoaded() {
+ opener.postMessage("body-loaded", "*");
+ }
+ </script>
+</html>
diff --git a/docshell/test/navigation/file_triggeringprincipal_frame_1.html b/docshell/test/navigation/file_triggeringprincipal_frame_1.html
new file mode 100644
index 0000000000..528437f892
--- /dev/null
+++ b/docshell/test/navigation/file_triggeringprincipal_frame_1.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head><meta charset="utf-8"></head>
+<body>
+<b>Frame 1</b><br/>
+<a href="#"" id="testlink" onclick="parent.frames[1].frames[0].location='http://test2.mochi.test:8888/tests/docshell/test/navigation/file_triggeringprincipal_subframe_nav.html'">click me</a>
+
+<script type="application/javascript">
+ // make sure to set document.domain to the same domain as the subframe
+ window.onload = function() {
+ document.domain = "mochi.test";
+ };
+ window.addEventListener("message", receiveMessage);
+ function receiveMessage(event) {
+ // make sure to get the right start command, otherwise
+ // let the parent know and fail the test
+ if (event.data.start !== "startTest") {
+ window.removeEventListener("message", receiveMessage);
+ window.parent.postMessage({triggeringPrincipalURI: "false"}, "*");
+ }
+ // click the link to navigate the subframe
+ document.getElementById("testlink").click();
+ }
+</script>
+
+</body>
+</html>
diff --git a/docshell/test/navigation/file_triggeringprincipal_frame_2.html b/docshell/test/navigation/file_triggeringprincipal_frame_2.html
new file mode 100644
index 0000000000..ef7cdfc178
--- /dev/null
+++ b/docshell/test/navigation/file_triggeringprincipal_frame_2.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<html>
+<head><meta charset="utf-8"></head>
+<body>
+<b>Frame 2</b><br/>
+<iframe src="http://test2.mochi.test:8888/tests/docshell/test/navigation/file_triggeringprincipal_subframe.html"></iframe>
+</body>
+</html>
diff --git a/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_a.html b/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_a.html
new file mode 100644
index 0000000000..75b2933c1b
--- /dev/null
+++ b/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_a.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+Frame A
+</body>
+</html>
diff --git a/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_a_nav.html b/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_a_nav.html
new file mode 100644
index 0000000000..0479f5e1e5
--- /dev/null
+++ b/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_a_nav.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+Frame A navigated by Frame B
+</body>
+</html>
diff --git a/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_b.html b/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_b.html
new file mode 100644
index 0000000000..e5d40b267a
--- /dev/null
+++ b/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_b.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<body>
+Frame B navigating Frame A
+
+<script type="text/javascript">
+
+window.open("file_triggeringprincipal_iframe_iframe_window_open_frame_a_nav.html", "framea");
+
+</script>
+
+</body>
+</html>
+
+
diff --git a/docshell/test/navigation/file_triggeringprincipal_parent_iframe_window_open_base.html b/docshell/test/navigation/file_triggeringprincipal_parent_iframe_window_open_base.html
new file mode 100644
index 0000000000..caa6b275b9
--- /dev/null
+++ b/docshell/test/navigation/file_triggeringprincipal_parent_iframe_window_open_base.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+base test frame
+</body>
+</html>
diff --git a/docshell/test/navigation/file_triggeringprincipal_parent_iframe_window_open_nav.html b/docshell/test/navigation/file_triggeringprincipal_parent_iframe_window_open_nav.html
new file mode 100644
index 0000000000..f4a4d0e631
--- /dev/null
+++ b/docshell/test/navigation/file_triggeringprincipal_parent_iframe_window_open_nav.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+navigated by window.open()
+</body>
+</html>
diff --git a/docshell/test/navigation/file_triggeringprincipal_subframe.html b/docshell/test/navigation/file_triggeringprincipal_subframe.html
new file mode 100644
index 0000000000..ba6b6dc09a
--- /dev/null
+++ b/docshell/test/navigation/file_triggeringprincipal_subframe.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head><meta charset='utf-8'></head>
+<body>
+<b>Sub Frame 2</b><br/>
+<script type='application/javascript'>
+ // make sure to set document.domain to same domain as frame 1
+ window.onload = function() {
+ document.domain = "mochi.test";
+ // let Frame 1 know that we are ready to run the test
+ window.parent.parent.frames[0].postMessage({start: "startTest"}, "*");
+ };
+</script>
+</body>
+</html>
diff --git a/docshell/test/navigation/file_triggeringprincipal_subframe_nav.html b/docshell/test/navigation/file_triggeringprincipal_subframe_nav.html
new file mode 100644
index 0000000000..582181c00d
--- /dev/null
+++ b/docshell/test/navigation/file_triggeringprincipal_subframe_nav.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<head><meta charset="utf-8"></head>
+<body onload="checkResults()">
+<b>Sub Frame 2 Navigated</b><br/>
+
+<script type='application/javascript'>
+ function checkResults() {
+ // query the uri of the loadingPrincipal and the TriggeringPrincipal and pass
+ // that information on to the parent for verification.
+ var channel = SpecialPowers.wrap(window).docShell.currentDocumentChannel;
+ var triggeringPrincipalURI = channel.loadInfo.triggeringPrincipal.asciiSpec;
+ var loadingPrincipalURI = channel.loadInfo.loadingPrincipal.asciiSpec;
+ var referrerURI = document.referrer;
+ window.parent.parent.postMessage({triggeringPrincipalURI,
+ loadingPrincipalURI,
+ referrerURI}, "*");
+ }
+</script>
+</body>
+</html>
diff --git a/docshell/test/navigation/file_triggeringprincipal_subframe_same_origin_nav.html b/docshell/test/navigation/file_triggeringprincipal_subframe_same_origin_nav.html
new file mode 100644
index 0000000000..c84e216ae8
--- /dev/null
+++ b/docshell/test/navigation/file_triggeringprincipal_subframe_same_origin_nav.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head><meta charset="utf-8"></head>
+<body onload="checkResults()">
+<b>SubFrame Same-Origin Navigated</b><br/>
+
+<script type='application/javascript'>
+ function checkResults() {
+ // query the uri of the loadingPrincipal and the TriggeringPrincipal and pass
+ // that information on to the parent for verification.
+ var channel = SpecialPowers.wrap(window).docShell.currentDocumentChannel;
+ var triggeringPrincipalURI = channel.loadInfo.triggeringPrincipal.asciiSpec;
+ var loadingPrincipalURI = channel.loadInfo.loadingPrincipal.asciiSpec;
+
+ window.parent.postMessage({triggeringPrincipalURI,
+ loadingPrincipalURI}, "*");
+ }
+</script>
+</body>
+</html>
diff --git a/docshell/test/navigation/file_triggeringprincipal_window_open.html b/docshell/test/navigation/file_triggeringprincipal_window_open.html
new file mode 100644
index 0000000000..d0644a4d5c
--- /dev/null
+++ b/docshell/test/navigation/file_triggeringprincipal_window_open.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+http
+</body>
+</html>
diff --git a/docshell/test/navigation/frame0.html b/docshell/test/navigation/frame0.html
new file mode 100644
index 0000000000..93d1c9c822
--- /dev/null
+++ b/docshell/test/navigation/frame0.html
@@ -0,0 +1,3 @@
+<html>
+ <body>Frame 0</body>
+</html>
diff --git a/docshell/test/navigation/frame1.html b/docshell/test/navigation/frame1.html
new file mode 100644
index 0000000000..4d06c09d1c
--- /dev/null
+++ b/docshell/test/navigation/frame1.html
@@ -0,0 +1,3 @@
+<html>
+ <body>Frame 1</body>
+</html>
diff --git a/docshell/test/navigation/frame2.html b/docshell/test/navigation/frame2.html
new file mode 100644
index 0000000000..7a3b5e0b9b
--- /dev/null
+++ b/docshell/test/navigation/frame2.html
@@ -0,0 +1,3 @@
+<html>
+ <body>Frame 2</body>
+</html>
diff --git a/docshell/test/navigation/frame3.html b/docshell/test/navigation/frame3.html
new file mode 100644
index 0000000000..fd24293873
--- /dev/null
+++ b/docshell/test/navigation/frame3.html
@@ -0,0 +1,3 @@
+<html>
+ <body>Frame 3</body>
+</html>
diff --git a/docshell/test/navigation/frame_1_out_of_6.html b/docshell/test/navigation/frame_1_out_of_6.html
new file mode 100644
index 0000000000..93547cd1c4
--- /dev/null
+++ b/docshell/test/navigation/frame_1_out_of_6.html
@@ -0,0 +1,6 @@
+<html>
+ <body>
+ Page 1
+ <iframe style="height: 100vh; width: 100%;" id="static" src="http://test1.mochi.test:8888/tests/docshell/test/navigation/frame_2_out_of_6.html"></iframe>
+ </body>
+</html> \ No newline at end of file
diff --git a/docshell/test/navigation/frame_2_out_of_6.html b/docshell/test/navigation/frame_2_out_of_6.html
new file mode 100644
index 0000000000..02056acce8
--- /dev/null
+++ b/docshell/test/navigation/frame_2_out_of_6.html
@@ -0,0 +1,6 @@
+<html>
+ <body>
+ Page 2
+ <iframe style="height: 100vh; width: 100%;" id="static" src="http://sub1.test1.mochi.test:8888/tests/docshell/test/navigation/frame_3_out_of_6.html"></iframe>
+ </body>
+</html> \ No newline at end of file
diff --git a/docshell/test/navigation/frame_3_out_of_6.html b/docshell/test/navigation/frame_3_out_of_6.html
new file mode 100644
index 0000000000..e9dc308f6e
--- /dev/null
+++ b/docshell/test/navigation/frame_3_out_of_6.html
@@ -0,0 +1,6 @@
+<html>
+ <body>
+ Page 3
+ <iframe style="height: 100vh; width: 100%;" id="static" src="http://sub2.xn--lt-uia.mochi.test:8888/tests/docshell/test/navigation/frame_4_out_of_6.html"></iframe>
+ </body>
+</html> \ No newline at end of file
diff --git a/docshell/test/navigation/frame_4_out_of_6.html b/docshell/test/navigation/frame_4_out_of_6.html
new file mode 100644
index 0000000000..66a5083e4f
--- /dev/null
+++ b/docshell/test/navigation/frame_4_out_of_6.html
@@ -0,0 +1,6 @@
+<html>
+ <body>
+ Page 4
+ <iframe style="height: 100vh; width: 100%;" id="static" src="http://test2.mochi.test:8888/tests/docshell/test/navigation/frame_5_out_of_6.html"></iframe>
+ </body>
+</html> \ No newline at end of file
diff --git a/docshell/test/navigation/frame_5_out_of_6.html b/docshell/test/navigation/frame_5_out_of_6.html
new file mode 100644
index 0000000000..0121f0f749
--- /dev/null
+++ b/docshell/test/navigation/frame_5_out_of_6.html
@@ -0,0 +1,6 @@
+<html>
+ <body>
+ Page 5
+ <iframe style="height: 100vh; width: 100%;" id="static" src="http://example.org:80/tests/docshell/test/navigation/frame_6_out_of_6.html"></iframe>
+ </body>
+</html> \ No newline at end of file
diff --git a/docshell/test/navigation/frame_6_out_of_6.html b/docshell/test/navigation/frame_6_out_of_6.html
new file mode 100644
index 0000000000..c9827ccaae
--- /dev/null
+++ b/docshell/test/navigation/frame_6_out_of_6.html
@@ -0,0 +1,6 @@
+<html>
+ <body>
+ Page 6
+ <iframe style="height: 100vh; width: 100%;" id="static" src="http://example.com/tests/docshell/test/navigation/frame_1_out_of_6.html"></iframe>
+ </body>
+</html> \ No newline at end of file
diff --git a/docshell/test/navigation/frame_load_as_example_com.html b/docshell/test/navigation/frame_load_as_example_com.html
new file mode 100644
index 0000000000..a1a4e7110a
--- /dev/null
+++ b/docshell/test/navigation/frame_load_as_example_com.html
@@ -0,0 +1,6 @@
+<html>
+ <body>
+ example.com
+ <iframe style="height: 100vh; width:100%;" id="static" src="http://example.org/tests/docshell/test/navigation/frame_load_as_example_org.html"></iframe>
+ </body>
+</html>
diff --git a/docshell/test/navigation/frame_load_as_example_org.html b/docshell/test/navigation/frame_load_as_example_org.html
new file mode 100644
index 0000000000..2fbb8038c9
--- /dev/null
+++ b/docshell/test/navigation/frame_load_as_example_org.html
@@ -0,0 +1,6 @@
+<html>
+ <body>
+ example.org
+ <iframe style="height: 100vh; width:100%;" id="static" src="http://example.com/tests/docshell/test/navigation/frame_load_as_example_com.html"></iframe>
+ </body>
+</html>
diff --git a/docshell/test/navigation/frame_load_as_host1.html b/docshell/test/navigation/frame_load_as_host1.html
new file mode 100644
index 0000000000..eb006b21a1
--- /dev/null
+++ b/docshell/test/navigation/frame_load_as_host1.html
@@ -0,0 +1,6 @@
+<html>
+ <body>
+ Page 1
+ <iframe style="height: 100vh; width: 100%;" id="static" src="http://example.com/tests/docshell/test/navigation/frame_load_as_host2.html"></iframe>
+ </body>
+</html>
diff --git a/docshell/test/navigation/frame_load_as_host2.html b/docshell/test/navigation/frame_load_as_host2.html
new file mode 100644
index 0000000000..5457c17e9b
--- /dev/null
+++ b/docshell/test/navigation/frame_load_as_host2.html
@@ -0,0 +1,6 @@
+<html>
+ <body>
+ Page 2
+ <iframe style="height: 100vh; width: 100%;" id="static" src="http://test1.mochi.test:8888/tests/docshell/test/navigation/frame_load_as_host3.html"></iframe>
+ </body>
+</html>
diff --git a/docshell/test/navigation/frame_load_as_host3.html b/docshell/test/navigation/frame_load_as_host3.html
new file mode 100644
index 0000000000..a9064ec867
--- /dev/null
+++ b/docshell/test/navigation/frame_load_as_host3.html
@@ -0,0 +1,6 @@
+<html>
+ <body>
+ Page 3
+ <iframe style="height: 100vh; width: 100%;" id="static" src="http://sub1.test1.mochi.test:8888/tests/docshell/test/navigation/frame_load_as_host1.html"></iframe>
+ </body>
+</html>
diff --git a/docshell/test/navigation/frame_recursive.html b/docshell/test/navigation/frame_recursive.html
new file mode 100644
index 0000000000..835d9d63a2
--- /dev/null
+++ b/docshell/test/navigation/frame_recursive.html
@@ -0,0 +1,6 @@
+<html>
+ <body>
+ example.com
+ <iframe style="height: 100vh; width:100%;" id="static" src="http://example.com/tests/docshell/test/navigation/frame_recursive.html"></iframe>
+ </body>
+</html>
diff --git a/docshell/test/navigation/goback.html b/docshell/test/navigation/goback.html
new file mode 100644
index 0000000000..ce2968374e
--- /dev/null
+++ b/docshell/test/navigation/goback.html
@@ -0,0 +1,5 @@
+<html>
+ <body onload="setTimeout('window.history.go(-1)', 1000);">
+ window.history.go(-1);
+ </body>
+</html>
diff --git a/docshell/test/navigation/iframe.html b/docshell/test/navigation/iframe.html
new file mode 100644
index 0000000000..f8fce53c55
--- /dev/null
+++ b/docshell/test/navigation/iframe.html
@@ -0,0 +1,9 @@
+<html>
+<body>
+<script>
+var src = window.location.hash.substring(1);
+// eslint-disable-next-line no-unsanitized/method
+document.write('<iframe src="' + src + '"></iframe>');
+</script>
+</body>
+</html>
diff --git a/docshell/test/navigation/iframe_slow_onload.html b/docshell/test/navigation/iframe_slow_onload.html
new file mode 100644
index 0000000000..e8555699bb
--- /dev/null
+++ b/docshell/test/navigation/iframe_slow_onload.html
@@ -0,0 +1,5 @@
+<html>
+ <body>
+ <iframe id="inner" src="iframe_slow_onload_inner.html">
+ </body>
+</html>
diff --git a/docshell/test/navigation/iframe_slow_onload_inner.html b/docshell/test/navigation/iframe_slow_onload_inner.html
new file mode 100644
index 0000000000..ad39eba795
--- /dev/null
+++ b/docshell/test/navigation/iframe_slow_onload_inner.html
@@ -0,0 +1,19 @@
+<html>
+ <head>
+ <script>
+ function load() {
+ // Let the test page know that it can try to navigate.
+ top.postMessage("onload", "*");
+ // We're starting an infinite loop, but we need to time out after a
+ // while, or the loop will keep running until shutdown.
+ let t0 = performance.now();
+ while (performance.now() - t0 < 5000) {
+ document.getElementById("output").innerText = Math.random();
+ }
+ }
+ </script>
+ </head>
+ <body onload="load()">
+ <p id="output"></p>
+ </body>
+</html>
diff --git a/docshell/test/navigation/iframe_static.html b/docshell/test/navigation/iframe_static.html
new file mode 100644
index 0000000000..1bdd1437c1
--- /dev/null
+++ b/docshell/test/navigation/iframe_static.html
@@ -0,0 +1,8 @@
+<html>
+ <body>
+ Nested Frame
+ <div id="frameContainer">
+ <iframe id="staticFrame" src="frame0.html"></iframe>
+ </div>
+ </body>
+</html>
diff --git a/docshell/test/navigation/mochitest.ini b/docshell/test/navigation/mochitest.ini
new file mode 100644
index 0000000000..1c4fe28309
--- /dev/null
+++ b/docshell/test/navigation/mochitest.ini
@@ -0,0 +1,255 @@
+[DEFAULT]
+support-files =
+ NavigationUtils.js
+ navigation_target_url.html
+ navigation_target_popup_url.html
+ blank.html
+ file_bug386782_contenteditable.html
+ file_bug386782_designmode.html
+ redbox_bug430723.html
+ bluebox_bug430723.html
+ file_bug462076_1.html
+ file_bug462076_2.html
+ file_bug462076_3.html
+ file_bug508537_1.html
+ file_bug534178.html
+ file_document_write_1.html
+ file_fragment_handling_during_load.html
+ file_fragment_handling_during_load_frame1.html
+ file_fragment_handling_during_load_frame2.sjs
+ file_nested_frames.html
+ file_nested_frames_innerframe.html
+ file_shiftReload_and_pushState.html
+ file_static_and_dynamic_1.html
+ frame0.html
+ frame1.html
+ frame2.html
+ frame3.html
+ goback.html
+ iframe.html
+ iframe_static.html
+ navigate.html
+ open.html
+ parent.html
+ file_tell_opener.html
+ file_triggeringprincipal_frame_1.html
+ file_triggeringprincipal_frame_2.html
+ file_triggeringprincipal_subframe.html
+ file_triggeringprincipal_subframe_nav.html
+ file_triggeringprincipal_subframe_same_origin_nav.html
+ file_triggeringprincipal_window_open.html
+ file_triggeringprincipal_parent_iframe_window_open_base.html
+ file_triggeringprincipal_parent_iframe_window_open_nav.html
+ file_triggeringprincipal_iframe_iframe_window_open_frame_a.html
+ file_triggeringprincipal_iframe_iframe_window_open_frame_b.html
+ file_triggeringprincipal_iframe_iframe_window_open_frame_a_nav.html
+ file_load_history_entry_page_with_one_link.html
+ file_load_history_entry_page_with_two_links.html
+ file_bug1300461.html
+ file_bug1300461_redirect.html
+ file_bug1300461_redirect.html^headers^
+ file_bug1300461_back.html
+ file_contentpolicy_block_window.html
+ file_bug1326251.html
+ file_bug1326251_evict_cache.html
+ file_bug1364364-1.html
+ file_bug1364364-2.html
+ file_bug1375833.html
+ file_bug1375833-frame1.html
+ file_bug1375833-frame2.html
+ test_bug145971.html
+ file_bug1609475.html
+ file_bug1379762-1.html
+ file_scrollRestoration_bfcache_and_nobfcache.html
+ file_scrollRestoration_bfcache_and_nobfcache_part2.html
+ file_scrollRestoration_bfcache_and_nobfcache_part2.html^headers^
+ file_scrollRestoration_navigate.html
+ file_scrollRestoration_part1_nobfcache.html
+ file_scrollRestoration_part1_nobfcache.html^headers^
+ file_scrollRestoration_part2_bfcache.html
+ file_scrollRestoration_part3_nobfcache.html
+ file_scrollRestoration_part3_nobfcache.html^headers^
+ file_session_history_on_redirect.html
+ file_session_history_on_redirect.html^headers^
+ file_session_history_on_redirect_2.html
+ file_session_history_on_redirect_2.html^headers^
+ redirect_handlers.sjs
+ frame_load_as_example_com.html
+ frame_load_as_example_org.html
+ frame_load_as_host1.html
+ frame_load_as_host2.html
+ frame_load_as_host3.html
+ frame_1_out_of_6.html
+ frame_2_out_of_6.html
+ frame_3_out_of_6.html
+ frame_4_out_of_6.html
+ frame_5_out_of_6.html
+ frame_6_out_of_6.html
+ frame_recursive.html
+ object_recursive_load.html
+ file_nested_srcdoc.html
+
+[test_aboutblank_change_process.html]
+[test_beforeunload_and_bfcache.html]
+support-files = file_beforeunload_and_bfcache.html
+[test_bug13871.html]
+skip-if =
+ http3
+[test_bug1583110.html]
+support-files = file_bug1583110.html
+[test_bug1706090.html]
+support-files = file_bug1706090.html
+skip-if = sessionHistoryInParent # The test is currently for the old bfcache implementation
+[test_bug1745638.html]
+support-files = file_bug1745638.html
+[test_bug1747019.html]
+support-files =
+ goback.html
+ cache_control_max_age_3600.sjs
+[test_bug1750973.html]
+support-files = file_bug1750973.html
+[test_bug1758664.html]
+support-files = file_bug1758664.html
+skip-if = !sessionHistoryInParent # the old implementation behaves inconsistently
+[test_bug270414.html]
+skip-if =
+ http3
+[test_bug278916.html]
+[test_bug279495.html]
+[test_bug344861.html]
+skip-if = toolkit == "android"
+[test_bug386782.html]
+[test_bug430624.html]
+[test_bug430723.html]
+skip-if =
+ (!debug && (os == 'mac' || os == 'win')) # Bug 874423
+ http3
+[test_bug1364364.html]
+skip-if = (os == "android") # Bug 1560378
+[test_bug1375833.html]
+[test_bug1536471.html]
+support-files = file_bug1536471.html
+[test_blockBFCache.html]
+support-files =
+ file_blockBFCache.html
+ slow.sjs
+ iframe_slow_onload.html
+ iframe_slow_onload_inner.html
+skip-if =
+ http3
+[test_child.html]
+[test_docshell_gotoindex.html]
+support-files = file_docshell_gotoindex.html
+[test_evict_from_bfcache.html]
+support-files = file_evict_from_bfcache.html
+[test_grandchild.html]
+skip-if =
+ http3
+[test_load_history_entry.html]
+[test_meta_refresh.html]
+support-files = file_meta_refresh.html
+[test_navigation_type.html]
+support-files = file_navigation_type.html
+[test_new_shentry_during_history_navigation.html]
+support-files =
+ file_new_shentry_during_history_navigation_1.html
+ file_new_shentry_during_history_navigation_1.html^headers^
+ file_new_shentry_during_history_navigation_2.html
+ file_new_shentry_during_history_navigation_2.html^headers^
+ file_new_shentry_during_history_navigation_3.html
+ file_new_shentry_during_history_navigation_3.html^headers^
+ file_new_shentry_during_history_navigation_4.html
+[test_not-opener.html]
+skip-if =
+ http3
+[test_online_offline_bfcache.html]
+support-files = file_online_offline_bfcache.html
+[test_opener.html]
+skip-if =
+ fission && xorigin # Bug 1716402 - New fission platform triage
+ os == "linux" && bits == 64 # Bug 1572299
+ win10_2004 # Bug 1572299
+ win11_2009 # Bug 1797751
+ fission && os == "android" # Bug 1827323
+[test_popup-navigates-children.html]
+skip-if =
+ http3
+[test_reload.html]
+support-files = file_reload.html
+[test_reload_nonbfcached_srcdoc.html]
+support-files = file_reload_nonbfcached_srcdoc.sjs
+skip-if =
+ http3
+[test_reserved.html]
+skip-if =
+ debug # bug 1263213
+[test_performance_navigation.html]
+[test_same_url.html]
+support-files = file_same_url.html
+[test_session_history_on_redirect.html]
+[test_sessionhistory.html]
+skip-if = verify && (os == 'mac') && debug # Hit MOZ_CRASH(Shutdown too long, probably frozen, causing a crash.) bug 1677545
+[test_dynamic_frame_forward_back.html]
+[test_sessionhistory_document_write.html]
+[test_sessionhistory_iframe_removal.html]
+support-files = file_sessionhistory_iframe_removal.html
+[test_session_history_entry_cleanup.html]
+[test_fragment_handling_during_load.html]
+[test_nested_frames.html]
+skip-if =
+ http3
+[test_shiftReload_and_pushState.html]
+[test_scrollRestoration.html]
+[test_bug1609475.html]
+[test_bug1300461.html]
+[test_bug1326251.html]
+skip-if = toolkit == 'android' || sessionHistoryInParent # It relies on the bfcache
+[test_bug1379762.html]
+[test_state_size.html]
+[test_static_and_dynamic.html]
+skip-if = true # This was disabled for a few years now anyway, bug 1677544
+[test_sibling-matching-parent.html]
+[test_sibling-off-domain.html]
+skip-if =
+ http3
+[test_triggeringprincipal_frame_nav.html]
+skip-if =
+ http3
+[test_triggeringprincipal_frame_same_origin_nav.html]
+skip-if =
+ http3
+[test_triggeringprincipal_window_open.html]
+skip-if =
+ http3
+[test_triggeringprincipal_parent_iframe_window_open.html]
+skip-if =
+ http3
+[test_triggeringprincipal_iframe_iframe_window_open.html]
+skip-if =
+ http3
+[test_contentpolicy_block_window.html]
+[test_rate_limit_location_change.html]
+[test_reload_large_postdata.html]
+support-files =
+ file_reload_large_postdata.sjs
+[test_recursive_frames.html]
+skip-if =
+ http3
+ fission && os == "android" # Bug 1714698
+[test_bug1699721.html]
+skip-if = !fission # tests fission-only process switching behaviour
+[test_ship_beforeunload_fired.html]
+support-files =
+ file_ship_beforeunload_fired.html
+skip-if =
+ !sessionHistoryInParent
+ http3
+[test_ship_beforeunload_fired_2.html]
+support-files =
+ file_ship_beforeunload_fired.html
+skip-if = !sessionHistoryInParent
+[test_ship_beforeunload_fired_3.html]
+support-files =
+ file_ship_beforeunload_fired.html
+skip-if = !sessionHistoryInParent
+[test_open_javascript_noopener.html]
diff --git a/docshell/test/navigation/navigate.html b/docshell/test/navigation/navigate.html
new file mode 100644
index 0000000000..f68123188e
--- /dev/null
+++ b/docshell/test/navigation/navigate.html
@@ -0,0 +1,38 @@
+<html>
+<head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="NavigationUtils.js"></script>
+ <script>
+ function navigate() {
+ var args = window.location.hash.substring(1).split(",");
+ var target = args[0];
+ var mechanism = args[1];
+
+ switch (mechanism) {
+ case "location":
+ // eslint-disable-next-line no-eval
+ navigateByLocation(eval(target));
+ break;
+ case "open":
+ navigateByOpen(target);
+ break;
+ case "form":
+ navigateByForm(target);
+ break;
+ case "hyperlink":
+ navigateByHyperlink(target);
+ break;
+ }
+ }
+ </script>
+</head>
+<body onload="navigate();">
+<script>
+var args = window.location.hash.substring(1).split(",");
+var target = args[0];
+var mechanism = args[1];
+// eslint-disable-next-line no-unsanitized/method
+document.write("target=" + target + " mechanism=" + mechanism);
+</script>
+</body>
+</html>
diff --git a/docshell/test/navigation/navigation_target_popup_url.html b/docshell/test/navigation/navigation_target_popup_url.html
new file mode 100644
index 0000000000..cfe6de009d
--- /dev/null
+++ b/docshell/test/navigation/navigation_target_popup_url.html
@@ -0,0 +1 @@
+<html><body>This is a popup</body></html>
diff --git a/docshell/test/navigation/navigation_target_url.html b/docshell/test/navigation/navigation_target_url.html
new file mode 100644
index 0000000000..a485e8133f
--- /dev/null
+++ b/docshell/test/navigation/navigation_target_url.html
@@ -0,0 +1 @@
+<html><body>This frame was navigated.</body></html>
diff --git a/docshell/test/navigation/object_recursive_load.html b/docshell/test/navigation/object_recursive_load.html
new file mode 100644
index 0000000000..3ae9521e63
--- /dev/null
+++ b/docshell/test/navigation/object_recursive_load.html
@@ -0,0 +1,6 @@
+<html>
+ <body width="400" height="300">
+ Frame 0
+ <object id="static" width="400" height="300" data="http://sub2.xn--lt-uia.mochi.test:8888/tests/docshell/test/navigation/object_recursive_load.html"></object>
+ </body>
+</html>
diff --git a/docshell/test/navigation/open.html b/docshell/test/navigation/open.html
new file mode 100644
index 0000000000..9a96a8dda7
--- /dev/null
+++ b/docshell/test/navigation/open.html
@@ -0,0 +1,10 @@
+<html>
+<body>
+<script>
+var target = window.location.hash.substring(1);
+// eslint-disable-next-line no-unsanitized/method
+document.write("target=" + target);
+window.open("navigation_target_popup_url.html", target, "width=10,height=10");
+</script>
+</body>
+</html>
diff --git a/docshell/test/navigation/parent.html b/docshell/test/navigation/parent.html
new file mode 100644
index 0000000000..74722b8bdf
--- /dev/null
+++ b/docshell/test/navigation/parent.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<body>
+This document contains a frame.
+<div><iframe src="blank.html"></iframe></div>
+<script>
+frames[0].name = window.name + "_child0";
+window.onload = function() {
+ opener.postMessage("ready", "*");
+};
+</script>
+</body>
+</html>
+
diff --git a/docshell/test/navigation/redbox_bug430723.html b/docshell/test/navigation/redbox_bug430723.html
new file mode 100644
index 0000000000..c2d1f98092
--- /dev/null
+++ b/docshell/test/navigation/redbox_bug430723.html
@@ -0,0 +1,6 @@
+<html><head>
+<script> window.addEventListener("pageshow", function() { opener.nextTest(); }); </script>
+</head><body>
+<div style="position:absolute; left:0px; top:0px; width:50%; height:150%; background-color:red">
+<p>This is a very tall red box.</p>
+</div></body></html>
diff --git a/docshell/test/navigation/redirect_handlers.sjs b/docshell/test/navigation/redirect_handlers.sjs
new file mode 100644
index 0000000000..c2b39e61c9
--- /dev/null
+++ b/docshell/test/navigation/redirect_handlers.sjs
@@ -0,0 +1,29 @@
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Cache-Control", "no-store", false);
+
+ let state = getState("sessionhistory_do_redirect");
+ if (state != "doredirect") {
+ response.setHeader("Content-Type", "text/html");
+ const contents = `
+ <script>
+ window.onpageshow = function(event) {
+ opener.pageshow();
+ }
+ </script>
+ `;
+ response.write(contents);
+
+ // The next load should do a redirect.
+ setState("sessionhistory_do_redirect", "doredirect");
+ } else {
+ setState("sessionhistory_do_redirect", "");
+
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader(
+ "Location",
+ "file_session_history_on_redirect_2.html",
+ false
+ );
+ }
+}
diff --git a/docshell/test/navigation/redirect_to_blank.sjs b/docshell/test/navigation/redirect_to_blank.sjs
new file mode 100644
index 0000000000..b1668401ea
--- /dev/null
+++ b/docshell/test/navigation/redirect_to_blank.sjs
@@ -0,0 +1,6 @@
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache", false);
+ response.setHeader("Cache-Control", "no-store", false);
+ response.setStatusLine("1.1", 302, "Found");
+ response.setHeader("Location", "blank.html", false);
+}
diff --git a/docshell/test/navigation/slow.sjs b/docshell/test/navigation/slow.sjs
new file mode 100644
index 0000000000..9720f807f6
--- /dev/null
+++ b/docshell/test/navigation/slow.sjs
@@ -0,0 +1,16 @@
+function handleRequest(request, response) {
+ response.processAsync();
+
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.init(
+ function () {
+ response.finish();
+ },
+ 5000,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+
+ response.setStatusLine(null, 200, "OK");
+ response.setHeader("Content-Type", "text/plain", false);
+ response.write("Start of the content.");
+}
diff --git a/docshell/test/navigation/test_aboutblank_change_process.html b/docshell/test/navigation/test_aboutblank_change_process.html
new file mode 100644
index 0000000000..61325570f3
--- /dev/null
+++ b/docshell/test/navigation/test_aboutblank_change_process.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<head>
+ <meta charset="utf-8">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css" />
+</head>
+<script>
+// Open a window and navigate it from https://example.com to about:blank
+// With fission, we should switch processes and about:blank should load in
+// the same process as this test page.
+// This is a crash test.
+add_task(async function test_aboutblank_change_process() {
+ let exampleLoaded = new Promise(resolve => {
+ function onMessage(event) {
+ if (event.data == "body-loaded") {
+ window.removeEventListener("message", onMessage);
+ resolve();
+ }
+ }
+ window.addEventListener("message", onMessage);
+ });
+ let win = window.open();
+ win.location = "https://example.com/tests/docshell/test/navigation/file_tell_opener.html";
+ await exampleLoaded;
+
+ win.location = "about:blank";
+
+ // A crash happens somewhere here when about:blank does not go via
+ // DocumentChannel with fission enabled
+
+ // Wait for about:blank to load in this process
+ await SimpleTest.promiseWaitForCondition(() => {
+ try {
+ return win.location.href == "about:blank";
+ } catch (e) {
+ // While the `win` still has example.com page loaded, `win.location` will
+ // be a cross origin object and querying win.location.href will throw a
+ // SecurityError. Return false as long as this is the case.
+ return false;
+ }
+ })
+
+ ok(true, "We did not crash");
+ win.close();
+});
+</script>
diff --git a/docshell/test/navigation/test_beforeunload_and_bfcache.html b/docshell/test/navigation/test_beforeunload_and_bfcache.html
new file mode 100644
index 0000000000..6bb958c6c6
--- /dev/null
+++ b/docshell/test/navigation/test_beforeunload_and_bfcache.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Loading a page from BFCache and firing beforeunload on the current page</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ /*
+ * This is a simple test to ensure beforeunload is fired on the current page
+ * when restoring a page from bfcache.
+ * (1) The controller page opens a new window. Another page is loaded there
+ * and session history is navigated back to check whether bfcache is
+ * enabled. If not, close message is sent and the opened window closes
+ * and the test ends.
+ * (2) beforeunload event listener is added to the page and history.forward()
+ * is called. The event listener should send a message to the controller
+ * page.
+ * (3) Close message is sent to close the opened window and the test finishes.
+ */
+
+ var pageshowCount = 0;
+ var gotBeforeUnload = false;
+ var bfcacheDisabled = false;
+
+
+ function executeTest() {
+ var bc = new BroadcastChannel("beforeunload_and_bfcache");
+ bc.onmessage = function(event) {
+ if (event.data.type == "pageshow") {
+ ++pageshowCount;
+ if (pageshowCount == 1) {
+ bc.postMessage("nextpage");
+ } else if (pageshowCount == 2) {
+ bc.postMessage("back");
+ } else if (pageshowCount == 3) {
+ if (!event.data.persisted) {
+ ok(true, "BFCache not enabled");
+ bfcacheDisabled = true;
+ bc.postMessage("close");
+ return;
+ }
+ bc.postMessage("forward");
+ } else if (pageshowCount == 4) {
+ ok(event.data.persisted, "Should have loaded a page from bfcache.");
+ bc.postMessage("close");
+ }
+ } else if (event.data == "beforeunload") {
+ gotBeforeUnload = true;
+ } else if (event.data == "closed") {
+ isnot(bfcacheDisabled, gotBeforeUnload,
+ "Either BFCache shouldn't be enabled or a beforeunload event should have been fired.");
+ bc.close();
+ SimpleTest.finish();
+ }
+ };
+
+ function runTest() {
+ SpecialPowers.pushPrefEnv({"set": [["docshell.shistory.bfcache.allow_unload_listeners", false]]},
+ function() {
+ window.open("file_beforeunload_and_bfcache.html", "", "noopener");
+ }
+ );
+ }
+ runTest();
+ }
+
+ if (isXOrigin) {
+ // Bug 1746646: Make mochitests work with TCP enabled (cookieBehavior = 5)
+ // Acquire storage access permission here so that the BroadcastChannel used to
+ // communicate with the opened windows works in xorigin tests. Otherwise,
+ // the iframe containing this page is isolated from first-party storage access,
+ // which isolates BroadcastChannel communication.
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ SpecialPowers.addPermission("storageAccessAPI", true, window.location.href).then(() => {
+ SpecialPowers.wrap(document).requestStorageAccess().then(() => {
+ SpecialPowers.pushPrefEnv({
+ set: [["privacy.partition.always_partition_third_party_non_cookie_storage", false]]
+ }).then(() => {
+ executeTest();
+ });
+ });
+ });
+ } else {
+ executeTest();
+ }
+
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_blockBFCache.html b/docshell/test/navigation/test_blockBFCache.html
new file mode 100644
index 0000000000..3d4a418369
--- /dev/null
+++ b/docshell/test/navigation/test_blockBFCache.html
@@ -0,0 +1,294 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Blocking pages from entering BFCache</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="">
+<script>
+
+const getUserMediaPrefs = {
+ set: [
+ ["media.devices.insecure.enabled", true],
+ ["media.getusermedia.insecure.enabled", true],
+ ["media.navigator.permission.disabled", true],
+ ],
+};
+const msePrefs = {
+ set: [
+ ["media.mediasource.enabled", true],
+ ["media.audio-max-decode-error", 0],
+ ["media.video-max-decode-error", 0],
+ ]
+};
+
+const blockBFCacheTests = [
+ {
+ name: "Request",
+ test: () => {
+ return new Promise((resolve) => {
+ const xhr = new XMLHttpRequest();
+ xhr.open("GET", "slow.sjs");
+ xhr.addEventListener("progress", () => { resolve(xhr); }, { once: true });
+ xhr.send();
+ });
+ },
+ },
+ {
+ name: "Background request",
+ test: () => {
+ return new Promise((resolve) => {
+ const xhr = new XMLHttpRequest();
+ xhr.open("GET", "slow.sjs");
+ xhr.addEventListener("readystatechange", () => { if (xhr.readyState == xhr.HEADERS_RECEIVED) resolve(xhr); });
+ xhr.send();
+ });
+ },
+ },
+ {
+ name: "getUserMedia",
+ prefs: getUserMediaPrefs,
+ test: () => {
+ return navigator.mediaDevices.getUserMedia({ audio: true, fake: true });
+ },
+ },
+ {
+ name: "RTCPeerConnection",
+ test: () => {
+ let pc = new RTCPeerConnection();
+ return pc.createOffer();
+ },
+ },
+ {
+ name: "MSE",
+ prefs: msePrefs,
+ test: () => {
+ const ms = new MediaSource();
+ const el = document.createElement("video");
+ el.src = URL.createObjectURL(ms);
+ el.preload = "auto";
+ return el;
+ },
+ },
+ {
+ name: "WebSpeech",
+ test: () => {
+ return new Promise((resolve) => {
+ const utterance = new SpeechSynthesisUtterance('bfcache');
+ utterance.lang = 'it-IT-noend';
+ utterance.addEventListener('start', () => { resolve(utterance); })
+ speechSynthesis.speak(utterance);
+ });
+ },
+ },
+ {
+ name: "WebVR",
+ prefs: {
+ set: [
+ ["dom.vr.test.enabled", true],
+ ["dom.vr.puppet.enabled", true],
+ ["dom.vr.require-gesture", false],
+ ],
+ },
+ test: () => {
+ return navigator.requestVRServiceTest();
+ }
+ },
+];
+
+if (SpecialPowers.Services.appinfo.fissionAutostart) {
+ blockBFCacheTests.push({
+ name: "Loading OOP iframe",
+ test: () => {
+ return new Promise((resolve) => {
+ const el = document.body.appendChild(document.createElement("iframe"));
+ el.id = "frame";
+ addEventListener("message", ({ data }) => {
+ if (data == "onload") {
+ resolve();
+ }
+ });
+ el.src = "https://example.com/tests/docshell/test/navigation/iframe_slow_onload.html";
+ });
+ },
+ waitForDone: () => {
+ SimpleTest.requestFlakyTimeout("Test has a loop in an onload handler that runs for 5000ms, we need to make sure the loop is done before moving to the next test.");
+ return new Promise(resolve => {
+ setTimeout(resolve, 5000);
+ });
+ },
+ });
+}
+
+const dontBlockBFCacheTests = [
+ {
+ name: "getUserMedia",
+ prefs: getUserMediaPrefs,
+ test: () => {
+ return navigator.mediaDevices.getUserMedia({ video: true, fake: true }).then(stream => {
+ stream.getTracks().forEach(track => track.stop());
+ return stream;
+ });
+ },
+ },
+/*
+ Disabled because MediaKeys rely on being destroyed by the CC before they
+ notify their window, so the test would intermittently fail depending on
+ when the CC runs.
+
+ {
+ name: "MSE",
+ prefs: msePrefs,
+ test: () => {
+ return new Promise((resolve) => {
+ const ms = new MediaSource();
+ const el = document.createElement("video");
+ ms.addEventListener("sourceopen", () => { resolve(el) }, { once: true });
+ el.src = URL.createObjectURL(ms);
+ el.preload = "auto";
+ }).then(el => {
+ el.src = "";
+ return el;
+ });
+ },
+ },
+*/
+];
+
+
+
+function executeTest() {
+
+ let bc = new BroadcastChannel("bfcache_blocking");
+
+ function promiseMessage(type) {
+ return new Promise((resolve, reject) => {
+ bc.addEventListener("message", (e) => {
+ if (e.data.type == type) {
+ resolve(e.data);
+ }
+ }, { once: true });
+ });
+ }
+
+ function promisePageShow(shouldBePersisted) {
+ return promiseMessage("pageshow").then(data => data.persisted == shouldBePersisted);
+ }
+
+ function promisePageShowFromBFCache(e) {
+ return promisePageShow(true);
+ }
+
+ function promisePageShowNotFromBFCache(e) {
+ return promisePageShow(false);
+ }
+
+ function runTests(testArray, shouldBlockBFCache) {
+ for (const { name, prefs = {}, test, waitForDone } of testArray) {
+ add_task(async function() {
+ await SpecialPowers.pushPrefEnv(prefs, async function() {
+ // Load a mostly blank page that we can communicate with over
+ // BroadcastChannel (though it will close the BroadcastChannel after
+ // receiving the next "load" message, to avoid blocking BFCache).
+ let loaded = promisePageShowNotFromBFCache();
+ window.open("file_blockBFCache.html", "", "noopener");
+ await loaded;
+
+ // Load the same page with a different URL.
+ loaded = promisePageShowNotFromBFCache();
+ bc.postMessage({ message: "load", url: `file_blockBFCache.html?${name}_${shouldBlockBFCache}` });
+ await loaded;
+
+ // Run test script in the second page.
+ bc.postMessage({ message: "runScript", fun: test.toString() });
+ await promiseMessage("runScriptDone");
+
+ // Go back to the first page (this should just come from the BFCache).
+ let goneBack = promisePageShowFromBFCache();
+ bc.postMessage({ message: "back" });
+ await goneBack;
+
+ // Go forward again to the second page and check that it does/doesn't come
+ // from the BFCache.
+ let goneForward = promisePageShow(!shouldBlockBFCache);
+ bc.postMessage({ message: "forward" });
+ let result = await goneForward;
+ ok(result, `Page ${shouldBlockBFCache ? "should" : "should not"} have been blocked from going into the BFCache (${name})`);
+
+ // If the test will keep running after navigation, then we need to make
+ // sure it's completely done before moving to the next test, to avoid
+ // interfering with any following tests. If waitForDone is defined then
+ // it'll return a Promise that we can use to wait for the end of the
+ // test.
+ if (waitForDone) {
+ await waitForDone();
+ }
+
+ // Do a similar test, but replace the bfcache test page with a new page,
+ // not a page coming from the session history.
+
+ // Load the same page with a different URL.
+ loaded = promisePageShowNotFromBFCache();
+ bc.postMessage({ message: "load", url: `file_blockBFCache.html?p2_${name}_${shouldBlockBFCache}` });
+ await loaded;
+
+ // Run the test script.
+ bc.postMessage({ message: "runScript", fun: test.toString() });
+ await promiseMessage("runScriptDone");
+
+ // Load a new page.
+ loaded = promisePageShowNotFromBFCache();
+ bc.postMessage({ message: "load", url: "file_blockBFCache.html" });
+ await loaded;
+
+ // Go back to the previous page and check that it does/doesn't come
+ // from the BFCache.
+ goneBack = promisePageShow(!shouldBlockBFCache);
+ bc.postMessage({ message: "back" });
+ result = await goneBack;
+ ok(result, `Page ${shouldBlockBFCache ? "should" : "should not"} have been blocked from going into the BFCache (${name})`);
+
+ if (waitForDone) {
+ await waitForDone();
+ }
+
+ bc.postMessage({ message: "close" });
+
+ SpecialPowers.popPrefEnv();
+ });
+ });
+ }
+ }
+
+ // If Fission is disabled, the pref is no-op.
+ SpecialPowers.pushPrefEnv({set: [["fission.bfcacheInParent", true]]}, () => {
+ runTests(blockBFCacheTests, true);
+ runTests(dontBlockBFCacheTests, false);
+ });
+}
+
+if (isXOrigin) {
+ // Bug 1746646: Make mochitests work with TCP enabled (cookieBehavior = 5)
+ // Acquire storage access permission here so that the BroadcastChannel used to
+ // communicate with the opened windows works in xorigin tests. Otherwise,
+ // the iframe containing this page is isolated from first-party storage access,
+ // which isolates BroadcastChannel communication.
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ SpecialPowers.addPermission("storageAccessAPI", true, window.location.href).then(() => {
+ SpecialPowers.wrap(document).requestStorageAccess().then(() => {
+ SpecialPowers.pushPrefEnv({
+ set: [["privacy.partition.always_partition_third_party_non_cookie_storage", false]]
+ }).then(() => {
+ executeTest();
+ });
+ });
+ });
+} else {
+ executeTest();
+}
+
+</script>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug1300461.html b/docshell/test/navigation/test_bug1300461.html
new file mode 100644
index 0000000000..22783c07c2
--- /dev/null
+++ b/docshell/test/navigation/test_bug1300461.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Bug 1300461</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1300461">Mozilla Bug 1300461</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+
+ let chromeScript = null;
+ if (SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ chromeScript = SpecialPowers.loadChromeScript(() => {
+ /* eslint-env mozilla/chrome-script */
+ function doSend(message, fn) {
+ try {
+ sendAsyncMessage(message, {success: true, value: fn()});
+ } catch(_) {
+ sendAsyncMessage(message, {success: false});
+ }
+ }
+
+ addMessageListener("requestedIndex", (id) => {
+ doSend("requestedIndex", () => {
+ let shistory = BrowsingContext.get(id).top.sessionHistory;
+ return shistory.requestedIndex;
+ })
+ });
+ });
+ }
+
+ async function getSHRequestedIndex(browsingContextId) {
+ let p = chromeScript.promiseOneMessage("requestedIndex");
+ chromeScript.sendAsyncMessage("requestedIndex", browsingContextId);
+ let result = await p;
+ ok(result.success, "Got requested index from parent");
+ return result.value;
+ }
+
+ var testCount = 0;
+
+ SimpleTest.waitForExplicitFinish();
+
+ var testWindow;
+ function runTest() {
+ testWindow = window.open("file_bug1300461.html", "", "width=360,height=480");
+ testWindow.onunload = function() { }; // to prevent bfcache
+ }
+
+ function finishTest() {
+ if (chromeScript) {
+ chromeScript.destroy();
+ }
+ testWindow.close();
+ SimpleTest.finish();
+ }
+
+ </script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug1326251.html b/docshell/test/navigation/test_bug1326251.html
new file mode 100644
index 0000000000..3c951729e6
--- /dev/null
+++ b/docshell/test/navigation/test_bug1326251.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Bug 1326251</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1326251">Mozilla Bug 1326251</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+
+ var testCount = 0;
+
+ var bc = new BroadcastChannel("file_bug1326251");
+ bc.onmessage = function(event) {
+ if (event.data == "requestNextTest") {
+ bc.postMessage({ nextTest: testCount++ });
+ } else if (event.data.type == "is") {
+ is(event.data.value1, event.data.value2, event.data.message);
+ } else if (event.data.type == "ok") {
+ ok(event.data.value, event.data.message);
+ } else if (event.data == "finishTest") {
+ SimpleTest.finish();
+ }
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ function runTest() {
+ // If Fission is disabled, the pref is no-op.
+ SpecialPowers.pushPrefEnv({set: [["fission.bfcacheInParent", true]]}, () => {
+ window.open("file_bug1326251.html", "", "width=360,height=480,noopener");
+ });
+ }
+
+ </script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug1364364.html b/docshell/test/navigation/test_bug1364364.html
new file mode 100644
index 0000000000..90d2009df4
--- /dev/null
+++ b/docshell/test/navigation/test_bug1364364.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1364364
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1364364</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1364364 */
+ let testWin, testDoc;
+ async function test() {
+ SimpleTest.waitForExplicitFinish();
+ if (SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ // This test relies on the possibility to modify the bfcached document.
+ // The new implementation is more restricted and thus works only
+ // when the bfcached browsing context is the only top level one
+ // in the browsing context group.
+ ok(true, "This test is for the old bfcache implementation only.");
+ SimpleTest.finish();
+ return;
+ }
+ testWin = window.open("file_bug1364364-1.html");
+ await waitForLoad(testWin);
+ testDoc = testWin.document;
+
+ // file_bug1364364-1.html will load a few dynamic iframes and then navigate
+ // top browsing context to file_bug1364364-2.html, which will postMessage
+ // back.
+ }
+
+ function waitForLoad(win) {
+ return new Promise(r => win.addEventListener("load", r, { once: true}));
+ }
+
+ window.addEventListener("message", async function(msg) {
+ if (msg.data == "navigation-done") {
+ is(testWin.history.length, 6, "check history.length");
+
+ // Modify a document in bfcache should cause the cache being dropped tho
+ // RemoveFromBFCacheAsync.
+ testDoc.querySelector("#content").textContent = "modified";
+ await new Promise(r => setTimeout(r, 0));
+
+ is(testWin.history.length, 2, "check history.length after bfcache dropped");
+ testWin.close();
+ SimpleTest.finish();
+ }
+ });
+
+ </script>
+</head>
+<body onload="test();">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1364364">Mozilla Bug 1364364</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug1375833.html b/docshell/test/navigation/test_bug1375833.html
new file mode 100644
index 0000000000..c2a7750a4e
--- /dev/null
+++ b/docshell/test/navigation/test_bug1375833.html
@@ -0,0 +1,131 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1375833
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1375833</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ SimpleTest.waitForExplicitFinish();
+
+ /**
+ * Test for Bug 1375833. It tests for 2 things in a normal reload -
+ * 1. Static frame history should not be dropped.
+ * 2. In a reload, docshell would parse the reloaded root document and
+ * genearate new child docshells, and then use the child offset
+ */
+
+ let testWin = window.open("file_bug1375833.html");
+ let count = 0;
+ let webNav, shistory;
+ let frameDocShellId;
+ let chromeScript = null;
+ if (SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ chromeScript = SpecialPowers.loadChromeScript(() => {
+ /* eslint-env mozilla/chrome-script */
+ function doSend(message, fn) {
+ try {
+ sendAsyncMessage(message, {success: true, value: fn()});
+ } catch(_) {
+ sendAsyncMessage(message, {success: false});
+ }
+ }
+
+ addMessageListener("test1", id => {
+ doSend("test1", () => {
+ let sessionHistory = BrowsingContext.get(id).top.sessionHistory;
+ let entry = sessionHistory.getEntryAtIndex(sessionHistory.index);
+ let frameEntry = entry.GetChildAt(0);
+ return String(frameEntry.docshellID);
+ })
+ });
+ });
+ }
+
+ window.addEventListener("message", async e => {
+ switch (count++) {
+ case 0:
+ ok(e.data.endsWith("file_bug1375833-frame2.html"), "check location");
+
+ webNav = SpecialPowers.wrap(testWin)
+ .docShell
+ .QueryInterface(SpecialPowers.Ci.nsIWebNavigation);
+ shistory = webNav.sessionHistory;
+ is(shistory.count, 2, "check history length");
+ is(shistory.index, 1, "check history index");
+
+ frameDocShellId = String(getFrameDocShell().historyID);
+ ok(frameDocShellId, "sanity check for docshell ID");
+
+ testWin.location.reload();
+ break;
+ case 1:
+ ok(e.data.endsWith("file_bug1375833-frame2.html"), "check location");
+ is(shistory.count, 4, "check history length");
+ is(shistory.index, 3, "check history index");
+
+ let newFrameDocShellId = String(getFrameDocShell().historyID);
+ ok(newFrameDocShellId, "sanity check for docshell ID");
+ is(newFrameDocShellId, frameDocShellId, "check docshell ID remains after reload");
+
+ if (!SpecialPowers.Services.appinfo.sessionHistoryInParent) {
+ let entry = shistory.legacySHistory.getEntryAtIndex(shistory.index);
+ let frameEntry = entry.GetChildAt(0);
+ is(String(frameEntry.docshellID), frameDocShellId, "check newly added shentry uses the same docshell ID");
+ } else {
+ let p = chromeScript.promiseOneMessage("test1");
+ chromeScript.sendAsyncMessage("test1", SpecialPowers.wrap(testWin).browsingContext.id);
+ let result = await p;
+ ok(result.success, "legacySHistory worked around ok");
+ is(result.value, frameDocShellId, "check newly added shentry uses the same docshell ID");
+ }
+
+ webNav.goBack();
+ break;
+ case 2:
+ ok(e.data.endsWith("file_bug1375833-frame1.html"), "check location");
+ is(shistory.count, 4, "check history length");
+ is(shistory.index, 2, "check history index");
+
+ webNav.goBack();
+ break;
+ case 3:
+ ok(e.data.endsWith("file_bug1375833-frame2.html"), "check location");
+ is(shistory.count, 4, "check history length");
+ is(shistory.index, 1, "check history index");
+
+ webNav.goBack();
+ break;
+ case 4:
+ ok(e.data.endsWith("file_bug1375833-frame1.html"), "check location");
+ is(shistory.count, 4, "check history length");
+ is(shistory.index, 0, "check history index");
+
+ if (chromeScript) {
+ chromeScript.destroy();
+ }
+ testWin.close();
+ SimpleTest.finish();
+ }
+ });
+
+ function getFrameDocShell() {
+ return SpecialPowers.wrap(testWin.window[0]).docShell;
+ }
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1375833">Mozilla Bug 1375833</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug1379762.html b/docshell/test/navigation/test_bug1379762.html
new file mode 100644
index 0000000000..eda3b539a5
--- /dev/null
+++ b/docshell/test/navigation/test_bug1379762.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Bug 1379762</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1379762">Mozilla Bug 1379762</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ /**
+ * - This page opens new window
+ * - new window sends 'init' msg
+ * - onload() in new window sends 'increment_loadCount' msg
+ * - onpageshow() in new window sends 'increment_testCount' msg
+ * - This page sends 'forward_back' msg
+ * - onpageshow() in new window 'increment_testCount'
+ * - This page sends 'finish_test' msg
+ * - onpageshow() in new window sends 'finished' msg
+ */
+ var testCount = 0; // Used by the test files.
+ var loadCount = 0;
+ var goneBack = false;
+ var bc = new BroadcastChannel("bug1379762");
+ bc.onmessage = (messageEvent) => {
+ let message = messageEvent.data;
+ if (message == "init") {
+ is(testCount, 0, "new window should only be loaded once; otherwise the loadCount variable makes no sense");
+ } else if (message == "increment_loadCount") {
+ loadCount++;
+ is(loadCount, 1, "Should only get one load")
+ } else if (message == 'increment_testCount') {
+ testCount++;
+ if (testCount == 1) {
+ bc.postMessage("forward_back");
+ goneBack = true;
+ } else if (testCount == 2) {
+ ok(goneBack, "We had a chance to navigate backwards and forwards in the new window to test BFCache");
+ bc.postMessage("finish_test");
+ }
+ } else if (message == "finished") {
+ bc.close();
+ SimpleTest.finish();
+ }
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ function runTest() {
+ // If Fission is disabled, the pref is no-op.
+ SpecialPowers.pushPrefEnv({set: [["fission.bfcacheInParent", true]]}, () => {
+ window.open("file_bug1379762-1.html", "", "width=360,height=480,noopener");
+ });
+ }
+
+ </script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug13871.html b/docshell/test/navigation/test_bug13871.html
new file mode 100644
index 0000000000..0532bc7b56
--- /dev/null
+++ b/docshell/test/navigation/test_bug13871.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+ <style type="text/css">
+ iframe { width: 90%; height: 50px; }
+ </style>
+<script>
+async function runTest() {
+ navigateByLocation(window0.frames[0]);
+ navigateByOpen("window1_child0");
+ navigateByForm("window2_child0");
+ navigateByHyperlink("window3_child0");
+
+ await waitForFinishedFrames(4);
+
+ isInaccessible(window0.frames[0], "Should not be able to navigate off-domain frame by setting location.");
+ isInaccessible(window1.frames[0], "Should not be able to navigate off-domain frame by calling window.open.");
+ isInaccessible(window2.frames[0], "Should not be able to navigate off-domain frame by submitting form.");
+ isInaccessible(window3.frames[0], "Should not be able to navigate off-domain frame by targeted hyperlink.");
+
+ window0.close();
+ window1.close();
+ window2.close();
+ window3.close();
+
+ await cleanupWindows();
+ SimpleTest.finish();
+}
+
+// Because our open()'d windows are cross-origin, we can't wait for onload.
+// We instead wait for a postMessage from parent.html.
+var windows = new Map();
+addEventListener("message", function windowLoaded(evt) {
+ // Because window.open spins the event loop in order to open new windows,
+ // we might receive the "ready" message before we call waitForLoad.
+ // In that case, windows won't contain evt.source and we just note that the
+ // window is ready. Otherwise, windows contains the "resolve" function for
+ // that window's promise and we just have to call it.
+ if (windows.has(evt.source)) {
+ windows.get(evt.source)();
+ } else {
+ windows.set(evt.source, true);
+ }
+});
+
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+var window0 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/parent.html", "window0", "width=10,height=10");
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+var window1 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/parent.html", "window1", "width=10,height=10");
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+var window2 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/parent.html", "window2", "width=10,height=10");
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+var window3 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/parent.html", "window3", "width=10,height=10");
+
+function waitForLoad(w) {
+ return new Promise(function(resolve, reject) {
+ // If we already got the "ready" message, resolve immediately.
+ if (windows.has(w)) {
+ resolve();
+ } else {
+ windows.set(w, resolve);
+ }
+ });
+}
+
+Promise.all([ waitForLoad(window0),
+ waitForLoad(window1),
+ waitForLoad(window2),
+ waitForLoad(window3) ])
+ .then(runTest);
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=13871">Mozilla Bug 13871</a>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug145971.html b/docshell/test/navigation/test_bug145971.html
new file mode 100644
index 0000000000..ffad27a9c3
--- /dev/null
+++ b/docshell/test/navigation/test_bug145971.html
@@ -0,0 +1,29 @@
+<html>
+ <head>
+ <script>
+ let pass = false;
+ let initialLoad = false;
+ var bc = new BroadcastChannel("bug145971");
+ function checkNavigationTypeEquals2() {
+ if (performance.navigation.type == 2) {
+ pass = true;
+ }
+ testDone();
+ }
+
+ function testDone() {
+ bc.postMessage({result: pass});
+ bc.close();
+ window.close();
+ }
+
+ function test() {
+ window.onpageshow = checkNavigationTypeEquals2;
+ window.location.href = 'goback.html';
+ }
+ </script>
+ </head>
+ <body onload="setTimeout(test, 0);">
+ Testing bug 145971.
+ </body>
+</html>
diff --git a/docshell/test/navigation/test_bug1536471.html b/docshell/test/navigation/test_bug1536471.html
new file mode 100644
index 0000000000..f37aedba21
--- /dev/null
+++ b/docshell/test/navigation/test_bug1536471.html
@@ -0,0 +1,75 @@
+
+<!DOCTYPE HTML>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1536471
+ -->
+<head>
+ <title>Test for Bug 1536471</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="application/javascript">
+
+ let testWin;
+ async function test() {
+ // Open a new tab and load a document with an iframe inside
+ testWin = window.open("file_bug1536471.html");
+ await waitForLoad();
+ var iframe = testWin.document.getElementById("staticFrame");
+ is(testWin.history.length, 1, "Checking the number of session history entries when there is only one iframe");
+
+ // Navigate the iframe to different pages
+ await loadUriInFrame(iframe, "frame1.html");
+ is(testWin.history.length, 2, "Checking the number of session history entries after having navigated a single iframe 1 time");
+ await loadUriInFrame(iframe, "frame2.html");
+ is(testWin.history.length, 3, "Checking the number of session history entries after having navigated a single iframe 2 times");
+ await loadUriInFrame(iframe, "frame3.html");
+ is(testWin.history.length, 4, "Checking the number of session history entries after having navigated a single iframe 3 times");
+
+ // Reload the top document
+ testWin.location.reload(true);
+ await waitForLoad();
+ is(testWin.history.length, 1, "Checking the number of session history entries after reloading the top document");
+
+ testWin.close();
+ SimpleTest.finish();
+ }
+
+ async function waitForLoad() {
+ await new Promise(resolve => {
+ window.bodyOnLoad = function() {
+ setTimeout(resolve, 0);
+ window.bodyOnLoad = undefined;
+ };
+ });
+ }
+
+ async function iframeOnload(frame) {
+ return new Promise(resolve => {
+ frame.addEventListener("load", () => {
+ setTimeout(resolve, 0);
+ }, {once: true});
+ });
+ }
+
+ async function loadUriInFrame(frame, uri) {
+ let onloadPromise = iframeOnload(frame);
+ frame.src = uri;
+ await onloadPromise;
+ }
+ </script>
+</head>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1536471">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+<body onload="test()">
+</body>
+</html>
+
diff --git a/docshell/test/navigation/test_bug1583110.html b/docshell/test/navigation/test_bug1583110.html
new file mode 100644
index 0000000000..f1c1b65e4d
--- /dev/null
+++ b/docshell/test/navigation/test_bug1583110.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>test bug 1583110</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ var bc = new BroadcastChannel("bug1583110");
+ var pageshowCount = 0;
+ bc.onmessage = function(event) {
+ ok(event.data.type == "pageshow");
+ ++pageshowCount;
+ if (pageshowCount == 1) {
+ bc.postMessage("loadnext");
+ } else if (pageshowCount == 2) {
+ bc.postMessage("back");
+ } else {
+ ok(event.data.persisted, "Should have persisted the first page");
+ bc.close();
+ SimpleTest.finish();
+ }
+ }
+
+ function test() {
+ window.open("file_bug1583110.html", "", "noopener");
+ }
+ </script>
+</head>
+<body onload="test()">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug1609475.html b/docshell/test/navigation/test_bug1609475.html
new file mode 100644
index 0000000000..4dbe7d17d6
--- /dev/null
+++ b/docshell/test/navigation/test_bug1609475.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Bug 1609475</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1609475">Mozilla Bug 1609475</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ var testWindow;
+ function runTest() {
+ testWindow = window.open("file_bug1609475.html", "", "width=360,height=480");
+ testWindow.onunload = function() { }; // to prevent bfcache
+ }
+
+ function finishTest() {
+ testWindow.close();
+ SimpleTest.finish();
+ }
+
+ </script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug1699721.html b/docshell/test/navigation/test_bug1699721.html
new file mode 100644
index 0000000000..687c5306cf
--- /dev/null
+++ b/docshell/test/navigation/test_bug1699721.html
@@ -0,0 +1,110 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script type="text/javascript">
+ add_task(async function() {
+ let popup = window.open("blank.html");
+
+ info("opened popup");
+ await new Promise(resolve => {
+ popup.addEventListener("load", resolve, { once: true });
+ });
+
+ info("popup blank.html loaded");
+ let tell_opener = new URL("file_tell_opener.html", location.href);
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ let xorigin_url = new URL(tell_opener.pathname, "http://example.com");
+
+ let resolveStartedUnload;
+ let startedUnload = new Promise(resolve => {
+ resolveStartedUnload = resolve;
+ });
+ let didFinishUnload = false;
+
+ let finishUnload = false;
+ popup.addEventListener("unload", function() {
+ resolveStartedUnload();
+ try {
+ // Spin a nested event loop in unload until we set `finishUnload`.
+ SpecialPowers.Services.tm.spinEventLoopUntil(
+ "Test(test_switch_back_nested.html)", () => finishUnload);
+ } finally {
+ info("exiting from unload nested event loop...");
+ didFinishUnload = true;
+ }
+ });
+
+ info("wait for message from popup");
+ let messagePromise = new Promise(resolve => {
+ addEventListener("message", evt => {
+ resolve();
+ }, { once: true });
+ });
+ popup.location = xorigin_url.href;
+ await messagePromise;
+
+ info("popup loaded, ensuring we're in unload");
+ await startedUnload;
+ is(didFinishUnload, false, "unload shouldn't have finished");
+
+ let switchStarted = SpecialPowers.spawnChrome([], async () => {
+ await new Promise(resolve => {
+ async function observer(subject, topic) {
+ is(topic, "http-on-examine-response");
+
+ let uri = subject.QueryInterface(Ci.nsIChannel).URI;
+ if (!uri.filePath.endsWith("file_tell_opener.html")) {
+ return;
+ }
+
+ Services.obs.removeObserver(observer, "http-on-examine-response");
+
+ // spin the event loop a few times to ensure we resolve after the process switch
+ for (let i = 0; i < 10; ++i) {
+ await new Promise(res => Services.tm.dispatchToMainThread(res));
+ }
+
+ info("resolving!");
+ resolve();
+ }
+ Services.obs.addObserver(observer, "http-on-examine-response");
+ });
+ });
+
+ info("Navigating back to the current process");
+ await SpecialPowers.spawn(popup, [tell_opener.href], (href) => {
+ content.location.href = href;
+ });
+
+ let messagePromise2 = new Promise(resolve => {
+ addEventListener("message", evt => {
+ resolve();
+ }, { once: true });
+ });
+
+ info("Waiting for the process switch to start");
+ await switchStarted;
+
+ // Finish unloading, and wait for the unload to complete
+ is(didFinishUnload, false, "unload shouldn't be finished");
+ finishUnload = true;
+ await new Promise(resolve => setTimeout(resolve, 0));
+ is(didFinishUnload, true, "unload should be finished");
+
+ info("waiting for navigation to complete");
+ await messagePromise2;
+
+ info("closing popup");
+ popup.close();
+
+ ok(true, "Didn't crash");
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug1706090.html b/docshell/test/navigation/test_bug1706090.html
new file mode 100644
index 0000000000..293148b9c6
--- /dev/null
+++ b/docshell/test/navigation/test_bug1706090.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1706090</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ var bc = new BroadcastChannel("bug1706090");
+ var pageshowCount = 0;
+ bc.onmessage = function(event) {
+ if (event.data.type == "pageshow") {
+ ++pageshowCount;
+ if (pageshowCount == 1) {
+ is(event.data.persisted, false, "Shouldn't have persisted the initial load.");
+ bc.postMessage("sameOrigin");
+ } else if (pageshowCount == 2) {
+ bc.postMessage("back");
+ } else if (pageshowCount == 3) {
+ is(event.data.persisted, false, "Shouldn't have persisted same origin load.");
+ bc.postMessage("crossOrigin");
+ } else if (pageshowCount == 4) {
+ is(event.data.persisted, true, "Should have persisted cross origin load.");
+ bc.postMessage("sameSite");
+ } else if (pageshowCount == 5) {
+ is(event.data.persisted, false, "Shouldn't have persisted same site load.");
+ bc.postMessage("close");
+ }
+ } else if (event.data == "closed") {
+ bc.close();
+ SimpleTest.finish();
+ }
+ };
+
+ function runTest() {
+ SpecialPowers.pushPrefEnv({set: [["docshell.shistory.bfcache.allow_unload_listeners", true]]}, () => {
+ window.open("file_bug1706090.html", "", "noopener");
+ });
+ }
+ </script>
+</head>
+<body onload="runTest()">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug1745638.html b/docshell/test/navigation/test_bug1745638.html
new file mode 100644
index 0000000000..594c464da3
--- /dev/null
+++ b/docshell/test/navigation/test_bug1745638.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>bug 1745638</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ // This test triggers an assertion in the old session history
+ // implementation.
+ SimpleTest.expectAssertions(0, 1);
+
+ SimpleTest.waitForExplicitFinish();
+ var testWindow;
+ var loadCount = 0;
+ function test() {
+ testWindow = window.open("file_bug1745638.html");
+ }
+
+ function pageLoaded() {
+ ++loadCount;
+ is(testWindow.document.getElementById('testFrame').contentDocument.body.innerHTML,
+ "passed",
+ "Iframe's textual content should be 'passed'.");
+ if (loadCount == 1) {
+ testWindow.history.go(0);
+ } else {
+ testWindow.close();
+ SimpleTest.finish();
+ }
+ }
+
+ </script>
+</head>
+<body onload="test()">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug1747019.html b/docshell/test/navigation/test_bug1747019.html
new file mode 100644
index 0000000000..c7995737df
--- /dev/null
+++ b/docshell/test/navigation/test_bug1747019.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test session history and caching</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ var win;
+ var loadCount = 0;
+ var initialContent;
+ // The test loads first a page in a new window, then using
+ // form submission loads another page and then using form submission
+ // again loads third page. That page triggers history.go(-1).
+ // The second page is loaded now again and should have the same content
+ // as it had before.
+ function test() {
+ win = window.open("cache_control_max_age_3600.sjs?initial");
+ window.onmessage = (e) => {
+ is(e.data, "loaded", "Should get load message 'loaded'");
+ ++loadCount;
+ if (loadCount == 1) {
+ win.document.forms[0].submit();
+ } else if (loadCount == 2) {
+ initialContent = win.document.body.textContent;
+ info("The initial content is [" + initialContent + "].");
+ win.document.forms[0].submit();
+ } else if (loadCount == 3) {
+ let newContent = win.document.body.textContent;
+ info("The new content is [" + newContent + "].");
+ win.close();
+ is(initialContent, newContent, "Should have loaded the page from cache.");
+ SimpleTest.finish();
+ } else {
+ ok(false, "Unexpected load count.");
+ }
+ }
+ }
+ </script>
+</head>
+<body onload="test()">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug1750973.html b/docshell/test/navigation/test_bug1750973.html
new file mode 100644
index 0000000000..9f87075b90
--- /dev/null
+++ b/docshell/test/navigation/test_bug1750973.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>The layout state restoration when reframing the root element</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ function test() {
+ window.open("file_bug1750973.html");
+ }
+ </script>
+</head>
+<body onload="setTimeout(test)">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug1758664.html b/docshell/test/navigation/test_bug1758664.html
new file mode 100644
index 0000000000..662242e44a
--- /dev/null
+++ b/docshell/test/navigation/test_bug1758664.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1758664</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ function test() {
+ window.open("file_bug1758664.html");
+ }
+ </script>
+</head>
+<body onload="test()">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug270414.html b/docshell/test/navigation/test_bug270414.html
new file mode 100644
index 0000000000..0635e32888
--- /dev/null
+++ b/docshell/test/navigation/test_bug270414.html
@@ -0,0 +1,103 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+ <style type="text/css">
+ iframe { width: 90%; height: 50px; }
+ </style>
+<script>
+/* eslint-disable no-useless-concat */
+/* global window0:true, window1:true, window2:true, window3:true */
+var headerHTML = "<html><head>" +
+ "<script src='/tests/SimpleTest/EventUtils.js'><\/script>" +
+ "<script src='NavigationUtils.js'><\/script>" +
+ "</head><body>";
+var footerHTML = "</body></html>";
+
+function testChild0() {
+ if (!window.window0) {
+ window0 = window.open("", "window0", "width=10,height=10");
+ window0.document.open();
+ // eslint-disable-next-line no-unsanitized/method
+ window0.document.write(headerHTML);
+ window0.document.write("<script>navigateByLocation(opener.frames[0])<\/script>");
+ // eslint-disable-next-line no-unsanitized/method
+ window0.document.write(footerHTML);
+ window0.document.close();
+ }
+}
+
+function testChild1() {
+ if (!window.window1) {
+ window1 = window.open("", "window1", "width=10,height=10");
+ window1.document.open();
+ // eslint-disable-next-line no-unsanitized/method
+ window1.document.write(headerHTML);
+ window1.document.write("<script>navigateByOpen('child1');<\/script>");
+ // eslint-disable-next-line no-unsanitized/method
+ window1.document.write(footerHTML);
+ window1.document.close();
+ }
+}
+
+function testChild2() {
+ if (!window.window2) {
+ window2 = window.open("", "window2", "width=10,height=10");
+ window2.document.open();
+ // eslint-disable-next-line no-unsanitized/method
+ window2.document.write(headerHTML);
+ window2.document.write("<script>navigateByForm('child2');<\/script>");
+ // eslint-disable-next-line no-unsanitized/method
+ window2.document.write(footerHTML);
+ window2.document.close();
+ }
+}
+
+function testChild3() {
+ if (!window.window3) {
+ window3 = window.open("", "window3", "width=10,height=10");
+ window3.document.open();
+ // eslint-disable-next-line no-unsanitized/method
+ window3.document.write(headerHTML);
+ window3.document.write("<script>navigateByHyperlink('child3');<\/script>");
+ // eslint-disable-next-line no-unsanitized/method
+ window3.document.write(footerHTML);
+ window3.document.close();
+ }
+}
+
+add_task(async function() {
+ await waitForFinishedFrames(4);
+
+ await isNavigated(frames[0], "Should be able to navigate on-domain opener's children by setting location.");
+ await isNavigated(frames[1], "Should be able to navigate on-domain opener's children by calling window.open.");
+ await isNavigated(frames[2], "Should be able to navigate on-domain opener's children by submitting form.");
+ await isNavigated(frames[3], "Should be able to navigate on-domain opener's children by targeted hyperlink.");
+
+ window0.close();
+ window1.close();
+ window2.close();
+ window3.close();
+
+ await cleanupWindows();
+});
+
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=270414">Mozilla Bug 270414</a>
+<div id="frames">
+<iframe onload="testChild0();" name="child0" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+<iframe onload="testChild1();" name="child1" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+<iframe onload="testChild2();" name="child2" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+<iframe onload="testChild3();" name="child3" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+</div>
+<pre id="test">
+<script type="text/javascript">
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug278916.html b/docshell/test/navigation/test_bug278916.html
new file mode 100644
index 0000000000..9e2335721e
--- /dev/null
+++ b/docshell/test/navigation/test_bug278916.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+<script>
+window.onload = async function() {
+ document.getElementById("link0").href = target_url;
+ sendMouseEvent({type: "click"}, "link0");
+
+ await waitForFinishedFrames(1);
+
+ var array_of_frames = await getFramesByName("window0");
+ is(array_of_frames.length, 1, "Should only open one window using a fancy hyperlink.");
+
+ for (var i = 0; i < array_of_frames.length; ++i)
+ array_of_frames[i].close();
+
+ await cleanupWindows();
+ SimpleTest.finish();
+};
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=278916">Mozilla Bug 278916</a>
+<div id="links">
+<a id="link0" target="window0" onclick="window.open('', 'window0', 'width=10,height=10');">This is a fancy hyperlink</a>
+</div>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug279495.html b/docshell/test/navigation/test_bug279495.html
new file mode 100644
index 0000000000..245ed14ed4
--- /dev/null
+++ b/docshell/test/navigation/test_bug279495.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+<script>
+window.onload = async function() {
+ document.getElementById("link0").href = target_url;
+ document.getElementById("link1").href = target_url;
+
+ sendMouseEvent({type: "click"}, "link0");
+ sendMouseEvent({type: "click"}, "link1");
+
+ await waitForFinishedFrames(2);
+ await countAndClose("window0", 1);
+ await countAndClose("window1", 1);
+
+ await cleanupWindows();
+ SimpleTest.finish();
+};
+
+async function countAndClose(name, expected_count) {
+ var array_of_frames = await getFramesByName(name);
+ is(array_of_frames.length, expected_count,
+ "Should only open " + expected_count +
+ " window(s) with name " + name + " using a fancy hyperlink.");
+}
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=279495">Mozilla Bug 279495</a>
+<div id="links">
+<a id="link0" target="window0" onclick="window.open('blank.html', 'window0', 'width=10,height=10');">This is a fancy hyperlink</a>
+<a id="link1" target="window1" onclick="window.open('https://test1.example.org/tests/docshell/test/navigation/blank.html', 'window1', 'width=10,height=10');">This is a fancy hyperlink</a>
+</div>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug344861.html b/docshell/test/navigation/test_bug344861.html
new file mode 100644
index 0000000000..5ab8809d27
--- /dev/null
+++ b/docshell/test/navigation/test_bug344861.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=344861
+-->
+<head>
+ <title>Test for Bug 344861</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=344861">Mozilla Bug 344861</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 344861 */
+SimpleTest.waitForExplicitFinish();
+
+var newwindow = window.open("/", "testwindow", "width=200,height=200");
+newwindow.onload = function() {
+ is(newwindow.innerHeight, 200, "window.open has correct height dimensions");
+ is(newwindow.innerWidth, 200, "window.open has correct width dimensions");
+ SimpleTest.finish();
+ newwindow.close();
+};
+</script>
+</pre>
+</body>
+</html>
+
+
diff --git a/docshell/test/navigation/test_bug386782.html b/docshell/test/navigation/test_bug386782.html
new file mode 100644
index 0000000000..f6ad85f5a6
--- /dev/null
+++ b/docshell/test/navigation/test_bug386782.html
@@ -0,0 +1,122 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=386782
+-->
+<head>
+ <title>Test for Bug 386782</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+ <script>
+
+ // This tests if we can load a document whose root is in designMode,
+ // edit it, navigate to a new page, navigate back, still edit, and still
+ // undo/redo. Note that this is different from the case where the
+ // designMode document is in a frame inside the window, as this means
+ // the editable region is not in the root docshell (a less complicated case).
+
+ var gTests = [
+ {
+ // <html><body><p>designModeDocument</p></body></html>
+ url: "file_bug386782_designmode.html",
+ name: "designModeNavigate",
+ onload(doc) { doc.designMode = "on"; },
+ expectedBodyBeforeEdit: "<p>designModeDocument</p>",
+ expectedBodyAfterEdit: "<p>EDITED designModeDocument</p>",
+ expectedBodyAfterSecondEdit: "<p>EDITED TWICE designModeDocument</p>",
+ },
+ {
+ // <html><body contentEditable="true"><p>contentEditable</p></body></html>
+ url: "file_bug386782_contenteditable.html",
+ name: "contentEditableNavigate",
+ expectedBodyBeforeEdit: "<p>contentEditable</p>",
+ expectedBodyAfterEdit: "EDITED <br><p>contentEditable</p>",
+ expectedBodyAfterSecondEdit: "EDITED TWICE <br><p>contentEditable</p>",
+ },
+ ];
+
+ var gTest = null;
+
+ add_task(async () => {
+ while (gTests.length) {
+ gTest = gTests.shift();
+ await runTest();
+ }
+ });
+
+ async function runTest() {
+ gTest.window = window.open(gTest.url, gTest.name, "width=500,height=500");
+ let e = await new Promise(r => window.onmessage = r);
+ is(e.data.persisted, false, "Initial load cannot be persisted");
+ if ("onload" in gTest) {
+ gTest.onload(gTest.window.document);
+ }
+ await SimpleTest.promiseFocus(gTest.window);
+
+ gTest.window.document.body.focus();
+
+ // WARNING: If the following test fails, give the setTimeout() in the onload()
+ // a bit longer; the doc hasn't had enough time to setup its editor.
+ is(gTest.window.document.body.innerHTML, gTest.expectedBodyBeforeEdit, "Is doc setup yet");
+ sendString("EDITED ", gTest.window);
+ is(gTest.window.document.body.innerHTML, gTest.expectedBodyAfterEdit, "Editing failed.");
+
+ gTest.window.location = "about:blank";
+ await new Promise(r => gTest.window.onpagehide = r);
+ // The active document is updated synchronously after "pagehide" (and
+ // its associated microtasks), so, after waiting for the next global
+ // task, gTest.window will be proxying the realm associated with the
+ // "about:blank" document.
+ // https://html.spec.whatwg.org/multipage/browsing-the-web.html#update-the-session-history-with-the-new-page
+ await new Promise(r => setTimeout(r));
+ is(gTest.window.location.href, "about:blank", "location.href");
+ await SimpleTest.promiseFocus(gTest.window, true);
+
+ gTest.window.history.back();
+ e = await new Promise(r => window.onmessage = r);
+ // Skip the test if the page is not loaded from the bf-cache when going back.
+ if (e.data.persisted) {
+ checkStillEditable();
+ }
+ gTest.window.close();
+ }
+
+ function checkStillEditable() {
+ // Check that the contents are correct.
+ is(gTest.window.document.body.innerHTML, gTest.expectedBodyAfterEdit, "Edited contents still correct?");
+
+ // Check that we can undo/redo and the contents are correct.
+ gTest.window.document.execCommand("undo", false, null);
+ is(gTest.window.document.body.innerHTML, gTest.expectedBodyBeforeEdit, "Can we undo?");
+
+ gTest.window.document.execCommand("redo", false, null);
+ is(gTest.window.document.body.innerHTML, gTest.expectedBodyAfterEdit, "Can we redo?");
+
+ // Check that we can still edit the page.
+ gTest.window.document.body.focus();
+ sendString("TWICE ", gTest.window);
+ is(gTest.window.document.body.innerHTML, gTest.expectedBodyAfterSecondEdit, "Can we still edit?");
+ }
+
+ </script>
+
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=386782">Mozilla Bug 386782</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 386782 */
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_bug430624.html b/docshell/test/navigation/test_bug430624.html
new file mode 100644
index 0000000000..49afb023b9
--- /dev/null
+++ b/docshell/test/navigation/test_bug430624.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=430624
+-->
+<head>
+ <title>Test for Bug 430624</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=430624">Mozilla Bug 430624</a>
+<p id="display"></p>
+
+
+
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 430624 */
+
+function onLoad() {
+ window.frames[0].frameElement.onload = onReload;
+ // eslint-disable-next-line no-self-assign
+ window.frames[0].frameElement.srcdoc = window.frames[0].frameElement.srcdoc;
+}
+
+function onReload() {
+ var iframe = window.frames[0].frameElement;
+ SimpleTest.waitForFocus(doTest, iframe.contentWindow);
+ iframe.contentDocument.body.focus();
+}
+
+function doTest() {
+ var bodyElement = window.frames[0].frameElement.contentDocument.body;
+ bodyElement.focus();
+ sendString("Still ", window.frames[0].frameElement.contentWindow);
+
+ is(bodyElement.innerHTML, "Still contentEditable", "Check we're contentEditable after reload");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+
+<iframe onload="onLoad()" srcdoc="<body contenteditable>contentEditable</body>"></iframe>
+
+</body>
+</html>
+
diff --git a/docshell/test/navigation/test_bug430723.html b/docshell/test/navigation/test_bug430723.html
new file mode 100644
index 0000000000..dd85c7fb08
--- /dev/null
+++ b/docshell/test/navigation/test_bug430723.html
@@ -0,0 +1,124 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=430723
+-->
+<head>
+ <title>Test for Bug 430723</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=430723">Mozilla Bug 430723</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+// <![CDATA[
+
+/** Test for Bug 430723 */
+
+var BASE_URI = "http://mochi.test:8888/tests/docshell/test/navigation/";
+var gTallRedBoxURI = BASE_URI + "redbox_bug430723.html";
+var gTallBlueBoxURI = BASE_URI + "bluebox_bug430723.html";
+
+window.onload = runTest;
+
+var testWindow;
+var testNum = 0;
+
+var smoothScrollPref = "general.smoothScroll";
+function runTest() {
+ SpecialPowers.pushPrefEnv({"set": [[smoothScrollPref, false]]}, function() {
+ testWindow = window.open(gTallRedBoxURI, "testWindow", "width=300,height=300,location=yes,scrollbars=yes");
+ });
+}
+
+var nextTest = function() {
+ testNum++;
+ switch (testNum) {
+ case 1: setTimeout(step1, 0); break;
+ case 2: setTimeout(step2, 0); break;
+ case 3: setTimeout(step3, 0); break;
+ }
+};
+
+var step1 = function() {
+ window.is(String(testWindow.location), gTallRedBoxURI, "Ensure red page loaded.");
+
+ // Navigate down and up.
+ is(testWindow.document.body.scrollTop, 0,
+ "Page1: Ensure the scrollpane is at the top before we start scrolling.");
+ testWindow.addEventListener("scroll", function() {
+ isnot(testWindow.document.body.scrollTop, 0,
+ "Page1: Ensure we can scroll down.");
+ SimpleTest.executeSoon(step1_2);
+ }, {capture: true, once: true});
+ sendKey("DOWN", testWindow);
+
+ function step1_2() {
+ testWindow.addEventListener("scroll", function() {
+ is(testWindow.document.body.scrollTop, 0,
+ "Page1: Ensure we can scroll up, back to the top.");
+
+ // Nav to blue box page. This should fire step2.
+ testWindow.location = gTallBlueBoxURI;
+ }, {capture: true, once: true});
+ sendKey("UP", testWindow);
+ }
+};
+
+
+var step2 = function() {
+ window.is(String(testWindow.location), gTallBlueBoxURI, "Ensure blue page loaded.");
+
+ // Scroll around a bit.
+ is(testWindow.document.body.scrollTop, 0,
+ "Page2: Ensure the scrollpane is at the top before we start scrolling.");
+
+ var scrollTest = function() {
+ if (++count < 2) {
+ SimpleTest.executeSoon(function() { sendKey("DOWN", testWindow); });
+ } else {
+ testWindow.removeEventListener("scroll", scrollTest, true);
+
+ isnot(testWindow.document.body.scrollTop, 0,
+ "Page2: Ensure we could scroll.");
+
+ // Navigate backwards. This should fire step3.
+ testWindow.history.back();
+ }
+ };
+
+ var count = 0;
+ testWindow.addEventListener("scroll", scrollTest, true);
+ sendKey("DOWN", testWindow);
+};
+
+var step3 = function() {
+ window.is(String(testWindow.location), gTallRedBoxURI,
+ "Ensure red page restored from history.");
+
+ // Check we can still scroll with the keys.
+ is(testWindow.document.body.scrollTop, 0,
+ "Page1Again: Ensure scroll pane at top before we scroll.");
+ testWindow.addEventListener("scroll", function() {
+ isnot(testWindow.document.body.scrollTop, 0,
+ "Page2Again: Ensure we can still scroll.");
+
+ testWindow.close();
+ window.SimpleTest.finish();
+ }, {capture: true, once: true});
+ sendKey("DOWN", testWindow);
+};
+
+SimpleTest.waitForExplicitFinish();
+
+// ]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_child.html b/docshell/test/navigation/test_child.html
new file mode 100644
index 0000000000..87237471cd
--- /dev/null
+++ b/docshell/test/navigation/test_child.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+ <style type="text/css">
+ iframe { width: 90%; height: 50px; }
+ </style>
+<script>
+if (!navigator.platform.startsWith("Win")) {
+ SimpleTest.expectAssertions(0, 1);
+}
+
+window.onload = async function() {
+ navigateByLocation(frames[0]);
+ navigateByOpen("child1");
+ navigateByForm("child2");
+ navigateByHyperlink("child3");
+
+ await waitForFinishedFrames(4);
+ await isNavigated(frames[0], "Should be able to navigate off-domain child by setting location.");
+ await isNavigated(frames[1], "Should be able to navigate off-domain child by calling window.open.");
+ await isNavigated(frames[2], "Should be able to navigate off-domain child by submitting form.");
+ await isNavigated(frames[3], "Should be able to navigate off-domain child by targeted hyperlink.");
+
+ await cleanupWindows();
+ SimpleTest.finish();
+};
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=408052">Mozilla Bug 408052</a>
+<div id="frames">
+<iframe name="child0" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+<iframe name="child1" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+<iframe name="child2" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+<iframe name="child3" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+</div>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_contentpolicy_block_window.html b/docshell/test/navigation/test_contentpolicy_block_window.html
new file mode 100644
index 0000000000..7ce337c131
--- /dev/null
+++ b/docshell/test/navigation/test_contentpolicy_block_window.html
@@ -0,0 +1,98 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1329288
+-->
+<head>
+ <title>Test for Bug 1329288</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1329288">Mozilla Bug 1329288</a>
+
+
+<!-- have a testlink which we can use for the test to open a new window -->
+<a href="http://test1.example.org/tests/docshell/test/navigation/file_contentpolicy_block_window.html"
+ target="_blank"
+ id="testlink">This is a link</a>
+
+<script class="testbody" type="text/javascript">
+/*
+ * Description of the test:
+ * The test tries to open a new window and makes sure that a registered contentPolicy
+ * gets called with the right (a non null) 'context' for the TYPE_DOCUMENT load.
+ */
+
+const Ci = SpecialPowers.Ci;
+
+var categoryManager = SpecialPowers.Services.catMan;
+var componentManager = SpecialPowers.Components.manager
+ .QueryInterface(Ci.nsIComponentRegistrar);
+
+// Content policy / factory implementation for the test
+var policyID = SpecialPowers.wrap(SpecialPowers.Components).ID("{b80e19d0-878f-d41b-2654-194714a4115c}");
+var policyName = "@mozilla.org/testpolicy;1";
+var policy = {
+ // nsISupports implementation
+ // eslint-disable-next-line mozilla/use-chromeutils-generateqi
+ QueryInterface(iid) {
+ iid = SpecialPowers.wrap(iid);
+ if (iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIFactory) ||
+ iid.equals(Ci.nsIContentPolicy))
+ return this;
+ throw SpecialPowers.Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ // nsIFactory implementation
+ createInstance(iid) {
+ return this.QueryInterface(iid);
+ },
+
+ // nsIContentPolicy implementation
+ shouldLoad(contentLocation, loadInfo, mimeTypeGuess) {
+ let contentType = loadInfo.externalContentPolicyType;
+ let context = loadInfo.loadingContext;
+
+ if (SpecialPowers.wrap(contentLocation).spec !== document.getElementById("testlink").href) {
+ // not the URI we are looking for, allow the load
+ return Ci.nsIContentPolicy.ACCEPT;
+ }
+
+ is(contentType, Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ "needs to be type document load");
+ ok(context, "context is not allowed to be null");
+ ok(context.name.endsWith("test_contentpolicy_block_window.html"),
+ "context should be the current window");
+
+ // remove the policy and finish test.
+ categoryManager.deleteCategoryEntry("content-policy", policyName, false);
+
+ setTimeout(function() {
+ // Component must be unregistered delayed, otherwise other content
+ // policy will not be removed from the category correctly
+ componentManager.unregisterFactory(policyID, policy);
+ }, 0);
+
+ SimpleTest.finish();
+ return Ci.nsIContentPolicy.REJECT_REQUEST;
+ },
+
+ shouldProcess(contentLocation, loadInfo, mimeTypeGuess) {
+ return Ci.nsIContentPolicy.ACCEPT;
+ },
+};
+
+policy = SpecialPowers.wrapCallbackObject(policy);
+componentManager.registerFactory(policyID, "Test content policy", policyName, policy);
+categoryManager.addCategoryEntry("content-policy", policyName, policyName, false, true);
+
+SimpleTest.waitForExplicitFinish();
+
+// now everything is set up, let's start the test
+document.getElementById("testlink").click();
+
+</script>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_docshell_gotoindex.html b/docshell/test/navigation/test_docshell_gotoindex.html
new file mode 100644
index 0000000000..992c9c9dbe
--- /dev/null
+++ b/docshell/test/navigation/test_docshell_gotoindex.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1684310</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ function test() {
+ /*
+ * This test is for nsIWebNavigation.gotoIndex.
+ *
+ * The test
+ * - opens a new window
+ * - loads a page there
+ * - loads another page
+ * - navigates to some fragments in the page
+ * - goes back to one of the fragments
+ * - tries to go back to the initial page.
+ */
+ window.open("file_docshell_gotoindex.html");
+ }
+ </script>
+</head>
+<body onload="test()">
+<p id="display"></p>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_dynamic_frame_forward_back.html b/docshell/test/navigation/test_dynamic_frame_forward_back.html
new file mode 100644
index 0000000000..f3a349e09a
--- /dev/null
+++ b/docshell/test/navigation/test_dynamic_frame_forward_back.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Bug 508537</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=508537">Mozilla Bug 508537</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ var testWindow;
+ function runTest() {
+ testWindow = window.open("file_bug508537_1.html", "", "width=360,height=480");
+ testWindow.onunload = function() { }; // to prevent bfcache
+ }
+
+ function finishTest() {
+ testWindow.close();
+ SimpleTest.finish();
+ }
+
+ </script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_evict_from_bfcache.html b/docshell/test/navigation/test_evict_from_bfcache.html
new file mode 100644
index 0000000000..0b1eb2fca4
--- /dev/null
+++ b/docshell/test/navigation/test_evict_from_bfcache.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Evict a page from bfcache</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ /*
+ * This test checks that a page can be evicted from bfcache. Sending a
+ * message to an open BroadcastChannel is used for this.
+ *
+ * First the test opens a window and loads a page there. Another page is then
+ * loaded and then session history is navigated back to check if bfcache is
+ * enabled. If not, close message is sent to close the opened window and this
+ * controller page will finish the test.
+ * If bfcache is enabled, session history goes forward, but the
+ * BroadcastChannel in the page isn't closed. Then sending the message to go
+ * back again should evict the bfcached page.
+ * Close message is sent and window closed and test finishes.
+ */
+
+ SimpleTest.waitForExplicitFinish();
+ var bc = new BroadcastChannel("evict_from_bfcache");
+ var pageshowCount = 0;
+ bc.onmessage = function(event) {
+ if (event.data.type == "pageshow") {
+ ++pageshowCount;
+ info("pageshow " + pageshowCount);
+ if (pageshowCount == 1) {
+ bc.postMessage("nextpage");
+ } else if (pageshowCount == 2) {
+ bc.postMessage("back");
+ } else if (pageshowCount == 3) {
+ if (!event.data.persisted) {
+ ok(true, "BFCache isn't enabled.");
+ bc.postMessage("close");
+ } else {
+ bc.postMessage("forward");
+ }
+ } else if (pageshowCount == 4) {
+ bc.postMessage("back");
+ } else if (pageshowCount == 5) {
+ ok(!event.data.persisted,
+ "The page should have been evicted from BFCache");
+ bc.postMessage("close");
+ }
+ } else if (event.data == "closed") {
+ SimpleTest.finish();
+ }
+ }
+
+ function runTest() {
+ window.open("file_evict_from_bfcache.html", "", "noopener");
+ }
+ </script>
+</head>
+<body onload="runTest()">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_fragment_handling_during_load.html b/docshell/test/navigation/test_fragment_handling_during_load.html
new file mode 100644
index 0000000000..9c082c2ecf
--- /dev/null
+++ b/docshell/test/navigation/test_fragment_handling_during_load.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for fragment navigation during load</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=978408">Mozilla Bug 978408</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ var testWindow;
+ function runTest() {
+ testWindow = window.open("file_fragment_handling_during_load.html", "", "width=360,height=480");
+ testWindow.onunload = function() { }; // to prevent bfcache
+ }
+
+ function finishTest() {
+ testWindow.close();
+ SimpleTest.finish();
+ }
+
+ </script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_grandchild.html b/docshell/test/navigation/test_grandchild.html
new file mode 100644
index 0000000000..10cf610664
--- /dev/null
+++ b/docshell/test/navigation/test_grandchild.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+ <style type="text/css">
+ iframe { width: 90%; height: 200px; }
+ </style>
+<script>
+if (!navigator.platform.startsWith("Win")) {
+ SimpleTest.expectAssertions(0, 1);
+}
+
+window.onload = async function() {
+ navigateByLocation(frames[0].frames[0]);
+ navigateByOpen("child1_child0");
+ navigateByForm("child2_child0");
+ navigateByHyperlink("child3_child0");
+
+ await waitForFinishedFrames(4);
+ await isNavigated(frames[0].frames[0], "Should be able to navigate off-domain grandchild by setting location.");
+ await isNavigated(frames[1].frames[0], "Should be able to navigate off-domain grandchild by calling window.open.");
+ await isNavigated(frames[2].frames[0], "Should be able to navigate off-domain grandchild by submitting form.");
+ await isNavigated(frames[3].frames[0], "Should be able to navigate off-domain grandchild by targeted hyperlink.");
+
+ await cleanupWindows();
+ SimpleTest.finish();
+};
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=408052">Mozilla Bug 408052</a>
+<div id="frames">
+<iframe name="child0" src="http://test1.example.org:80/tests/docshell/test/navigation/parent.html"></iframe>
+<iframe name="child1" src="http://test1.example.org:80/tests/docshell/test/navigation/parent.html"></iframe>
+<iframe name="child2" src="http://test1.example.org:80/tests/docshell/test/navigation/parent.html"></iframe>
+<iframe name="child3" src="http://test1.example.org:80/tests/docshell/test/navigation/parent.html"></iframe>
+</div>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_load_history_entry.html b/docshell/test/navigation/test_load_history_entry.html
new file mode 100644
index 0000000000..8ca3fcb913
--- /dev/null
+++ b/docshell/test/navigation/test_load_history_entry.html
@@ -0,0 +1,196 @@
+
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="application/javascript" src="/tests/SimpleTest/SpecialPowers.js"></script>
+ <script type="application/javascript">
+ /*
+ * Perform the following steps.
+ * 1) Go to file_load_history_entry_page_with_two_links.html, which contains two links, 'link1' and 'link2'
+ * 2) Click on 'link1' to be taken to file_load_history_entry_page_with_two_links.html#1
+ * 3) Click on 'link2' to be taken to file_load_history_entry_page_with_two_links.html#2
+ * 4) Go to file_load_history_entry_page_with_one_link.html
+ * 5) Push state to go to file_load_history_entry_page_with_one_link.html#1
+ *
+ * After each step
+ * - Check the number of session history entries
+ * - Reload the document and do the above again
+ * - Navigate back and check the correct history index
+ * - Navigate forward and check the correct history index and location
+ */
+ async function test() {
+ let testWin;
+ var promise;
+ var previousLocation;
+ var numSHEntries = 0;
+
+ // Step 1. Open a new tab and load a document with two links inside
+ // Now we are at file_load_history_entry_page_with_two_links.html
+ numSHEntries++;
+ promise = waitForLoad();
+ testWin = window.open("file_load_history_entry_page_with_two_links.html");
+ await promise;
+
+ let shistory = SpecialPowers.wrap(testWin)
+ .docShell
+ .QueryInterface(SpecialPowers.Ci.nsIWebNavigation)
+ .sessionHistory;
+
+ // Step 2. Navigate the document by clicking on the 1st link
+ // Now we are at file_load_history_entry_page_with_two_links.html#1
+ numSHEntries++;
+ previousLocation = testWin.location.href;
+ await clickLink(testWin, "link1");
+ await doAfterEachTest(testWin, shistory, numSHEntries, previousLocation);
+
+ // Step 3. Navigate the document by clicking the 2nd link
+ // Now we are file_load_history_entry_page_with_two_links.html#2
+ numSHEntries++;
+ previousLocation = testWin.location.href;
+ await clickLink(testWin, "link2");
+ await doAfterEachTest(testWin, shistory, numSHEntries, previousLocation);
+
+ // Step 4. Navigate the document to a different page
+ // Now we are at file_load_history_entry_page_with_one_link.html
+ numSHEntries++;
+ previousLocation = testWin.location.href;
+ promise = waitForLoad();
+ testWin.location = "file_load_history_entry_page_with_one_link.html";
+ await promise;
+ await doAfterEachTest(testWin, shistory, numSHEntries, previousLocation,
+ true /* isCrossDocumentLoad */, false /* hashChangeExpected */);
+
+ // Step 5. Push some state
+ // Now we are at file_load_history_entry_page_with_one_link.html#1
+ numSHEntries++;
+ previousLocation = testWin.location.href;
+ testWin.history.pushState({foo: "bar"}, "", "#1");
+ is(testWin.history.length, numSHEntries, "Session history's length is correct after pushing state");
+ is(shistory.index, numSHEntries - 1 /* we haven't switched to new history entry yet*/,
+ "Session history's index is correct after pushing state");
+ await doAfterEachTest(testWin, shistory, numSHEntries, previousLocation);
+
+ // We are done with the test
+ testWin.close();
+ SimpleTest.finish();
+ }
+
+ /*
+ * @prevLocation
+ * if undefined, it is because there is no page to go back to
+ *
+ * @isCrossDocumentLoad
+ * did we just open a different document
+ * @hashChangeExpected
+ * Would we get a hash change event if we navigated backwards and forwards in history?
+ * This is framed with respect to the previous step, e.g. in the previous step was the
+ * hash different from the location we have navigated to just before calling this function?
+ * When we navigate forwards or backwards, we need to wait for this event
+ * because clickLink() also waits for hashchange event and
+ * if this function gets called before clickLink(), sometimes hashchange
+ * events from this function will leak to clickLink.
+ */
+ async function doAfterEachTest(testWin, shistory, expectedNumSHEntries, prevLocation,
+ isCrossDocumentLoad = false, hashChangeExpected = true) {
+ var initialLocation = testWin.location.href;
+ var initialSHIndex = shistory.index;
+ var promise;
+ is(testWin.history.length, expectedNumSHEntries, "Session history's length is correct");
+
+ // Reload the document
+ promise = waitForLoad();
+ testWin.location.reload(true);
+ await promise;
+ is(testWin.history.length, expectedNumSHEntries, "Session history's length is correct after reloading");
+
+ if (prevLocation == undefined) {
+ return;
+ }
+
+ var hashChangePromise;
+ if (hashChangeExpected) {
+ hashChangePromise = new Promise(resolve => {
+ testWin.addEventListener("hashchange", resolve, {once: true});
+ });
+ }
+ // Navigate backwards
+ if (isCrossDocumentLoad) {
+ // Current page must have been a cross document load, so we just need to wait for
+ // document load to complete after we navigate the history back
+ // because popstate event will not be fired in this case
+ promise = waitForLoad();
+ } else {
+ promise = waitForPopstate(testWin);
+ }
+ testWin.history.back();
+ await promise;
+ if (hashChangeExpected) {
+ await hashChangePromise;
+ }
+ is(testWin.location.href, prevLocation, "Window location is correct after navigating back in history");
+ is(shistory.index, initialSHIndex - 1, "Session history's index is correct after navigating back in history");
+
+ // Navigate forwards
+ if (isCrossDocumentLoad) {
+ promise = waitForLoad();
+ } else {
+ promise = waitForPopstate(testWin);
+ }
+ if (hashChangeExpected) {
+ hashChangePromise = new Promise(resolve => {
+ testWin.addEventListener("hashchange", resolve, {once: true});
+ });
+ }
+ testWin.history.forward();
+ await promise;
+ if (hashChangeExpected) {
+ await hashChangePromise;
+ }
+ is(testWin.location.href, initialLocation, "Window location is correct after navigating forward in history");
+ is(shistory.index, initialSHIndex, "Session history's index is correct after navigating forward in history");
+ }
+
+ async function waitForLoad() {
+ return new Promise(resolve => {
+ window.bodyOnLoad = function() {
+ setTimeout(resolve, 0);
+ window.bodyOnLoad = undefined;
+ };
+ });
+ }
+
+ async function waitForPopstate(win) {
+ return new Promise(resolve => {
+ win.addEventListener("popstate", (e) => {
+ setTimeout(resolve, 0);
+ }, {once: true});
+ });
+ }
+
+ async function clickLink(win, id) {
+ var link = win.document.getElementById(id);
+ let clickPromise = new Promise(resolve => {
+ win.addEventListener("hashchange", resolve, {once: true});
+ });
+ link.click();
+ await clickPromise;
+ }
+
+ </script>
+</head>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1539482">Bug 1539482</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+<body onload="test()">
+</body>
+</html>
+
diff --git a/docshell/test/navigation/test_meta_refresh.html b/docshell/test/navigation/test_meta_refresh.html
new file mode 100644
index 0000000000..bda9a9fe73
--- /dev/null
+++ b/docshell/test/navigation/test_meta_refresh.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test meta refresh</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ let hasLoadedInitialOnce = false;
+ let bc = new BroadcastChannel("test_meta_refresh");
+ bc.onmessage = function(event) {
+ info(event.data.load || event.data);
+ if (event.data.load == "initial") {
+ if (!hasLoadedInitialOnce) {
+ hasLoadedInitialOnce = true;
+ bc.postMessage("loadnext");
+ } else {
+ bc.postMessage("ensuremetarefresh");
+ }
+ } else if (event.data.load == "nextpage") {
+ bc.postMessage("back");
+ } else if (event.data.load == "refresh") {
+ bc.postMessage("close");
+ } else if (event.data == "closed") {
+ ok(true, "Meta refresh page was loaded.");
+ SimpleTest.finish();
+ }
+ }
+
+ function test() {
+ window.open("file_meta_refresh.html?initial", "", "noopener");
+ }
+ </script>
+</head>
+<body onload="test()">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_navigation_type.html b/docshell/test/navigation/test_navigation_type.html
new file mode 100644
index 0000000000..75ea88bcbd
--- /dev/null
+++ b/docshell/test/navigation/test_navigation_type.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>performance.navigation.type</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ let bc = new BroadcastChannel("navigation_type");
+ let pageshowCount = 0;
+ bc.onmessage = function(event) {
+ if (event.data == "closed") {
+ bc.close();
+ SimpleTest.finish();
+ return;
+ }
+
+ ++pageshowCount;
+ if (pageshowCount == 1) {
+ is(event.data.navigationType, PerformanceNavigation.TYPE_NAVIGATE,
+ "Should have navigation type TYPE_NAVIGATE.");
+ bc.postMessage("loadNewPage");
+ } else if (pageshowCount == 2) {
+ is(event.data.navigationType, PerformanceNavigation.TYPE_NAVIGATE,
+ "Should have navigation type TYPE_NAVIGATE.");
+ bc.postMessage("back");
+ } else if (pageshowCount == 3) {
+ is(event.data.navigationType, PerformanceNavigation.TYPE_BACK_FORWARD ,
+ "Should have navigation type TYPE_BACK_FORWARD .");
+ bc.postMessage("close");
+ } else {
+ ok(false, "Unexpected load");
+ }
+ }
+
+ function test() {
+ window.open("file_navigation_type.html", "", "noopener");
+ }
+ </script>
+</head>
+<body onload="test()">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_nested_frames.html b/docshell/test/navigation/test_nested_frames.html
new file mode 100644
index 0000000000..c3b49e0e23
--- /dev/null
+++ b/docshell/test/navigation/test_nested_frames.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Bug 1090918</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1090918">Mozilla Bug 1090918</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ var testWindow;
+ function runTest() {
+ testWindow = window.open("file_nested_frames.html", "", "width=360,height=480");
+ testWindow.onunload = function() { }; // to prevent bfcache
+ }
+
+ function finishTest() {
+ testWindow.close()
+ SimpleTest.finish();
+ }
+
+ </script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_new_shentry_during_history_navigation.html b/docshell/test/navigation/test_new_shentry_during_history_navigation.html
new file mode 100644
index 0000000000..0c9adc5280
--- /dev/null
+++ b/docshell/test/navigation/test_new_shentry_during_history_navigation.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test adding new session history entries while navigating to another one</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ var win;
+
+ function waitForMessage(msg) {
+ return new Promise(
+ function(resolve) {
+ window.addEventListener("message",
+ function(event) {
+ is(event.data, msg, "Got the expected message " + msg);
+ resolve();
+ }, { once: true }
+ );
+ }
+ );
+ }
+
+ async function test() {
+
+ let loadPromise = waitForMessage("load");
+ win = window.open("file_new_shentry_during_history_navigation_1.html");
+ await loadPromise;
+
+ loadPromise = waitForMessage("load");
+ win.location.href = "file_new_shentry_during_history_navigation_2.html";
+ await loadPromise;
+
+ let beforeunloadPromise = waitForMessage("beforeunload");
+ win.history.back();
+ await beforeunloadPromise;
+ await waitForMessage("load");
+
+ win.history.back();
+ SimpleTest.requestFlakyTimeout("Test that history.back() does not work on the initial entry.");
+ setTimeout(function() {
+ win.onmessage = null;
+ win.close();
+ testBfcache();
+ }, 500);
+ window.onmessage = function(event) {
+ ok(false, "Should not get a message " + event.data);
+ }
+ }
+
+ async function testBfcache() {
+ let bc = new BroadcastChannel("new_shentry_during_history_navigation");
+ let pageshowCount = 0;
+ bc.onmessage = function(event) {
+ if (event.data.type == "pageshow") {
+ ++pageshowCount;
+ info("pageshow: " + pageshowCount + ", page: " + event.data.page);
+ if (pageshowCount == 1) {
+ ok(!event.data.persisted, "The page should not be bfcached.");
+ bc.postMessage("loadnext");
+ } else if (pageshowCount == 2) {
+ ok(!event.data.persisted, "The page should not be bfcached.");
+ bc.postMessage("loadnext");
+ } else if (pageshowCount == 3) {
+ ok(!event.data.persisted, "The page should not be bfcached.");
+ bc.postMessage("back");
+ } else if (pageshowCount == 4) {
+ ok(event.data.persisted, "The page should be bfcached.");
+ bc.postMessage("forward");
+ } else if (pageshowCount == 5) {
+ ok(event.data.page.includes("v2"), "Should have gone forward.");
+ bc.postMessage("close");
+ SimpleTest.finish();
+ }
+ }
+ };
+ win = window.open("file_new_shentry_during_history_navigation_3.html", "", "noopener");
+
+ }
+
+ </script>
+</head>
+<body onload="test()">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_not-opener.html b/docshell/test/navigation/test_not-opener.html
new file mode 100644
index 0000000000..acdb9473e6
--- /dev/null
+++ b/docshell/test/navigation/test_not-opener.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+ <style type="text/css">
+ iframe { width: 90%; height: 50px; }
+ </style>
+<script>
+if (!navigator.platform.startsWith("Win")) {
+ SimpleTest.expectAssertions(0, 1);
+}
+
+window.onload = async function() {
+ // navigateByLocation(window0); // Don't have a handle to the window.
+ navigateByOpen("window1");
+ navigateByForm("window2");
+ navigateByHyperlink("window3");
+
+ await waitForFinishedFrames(6);
+
+ is((await getFramesByName("window1")).length, 2, "Should not be able to navigate popup's popup by calling window.open.");
+ is((await getFramesByName("window2")).length, 2, "Should not be able to navigate popup's popup by submitting form.");
+ is((await getFramesByName("window3")).length, 2, "Should not be able to navigate popup's popup by targeted hyperlink.");
+
+ // opener0.close();
+ opener1.close();
+ opener2.close();
+ opener3.close();
+
+ info("here")
+ await cleanupWindows();
+ info("there")
+ SimpleTest.finish();
+};
+
+// opener0 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/open.html#window0", "_blank", "width=10,height=10");
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+let opener1 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/open.html#window1", "_blank", "width=10,height=10");
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+let opener2 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/open.html#window2", "_blank", "width=10,height=10");
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+let opener3 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/open.html#window3", "_blank", "width=10,height=10");
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=408052">Mozilla Bug 408052</a>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_online_offline_bfcache.html b/docshell/test/navigation/test_online_offline_bfcache.html
new file mode 100644
index 0000000000..4ad90fd52e
--- /dev/null
+++ b/docshell/test/navigation/test_online_offline_bfcache.html
@@ -0,0 +1,101 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Online/Offline with BFCache</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+
+ /*
+ * The test is designed to work with and without bfcache.
+ * (1) First the test opens a window which then loads another page which
+ * goes back to the original page to detect if bfcache is enabled. If
+ * bfcache isn't enabled, close message is sent to the opened window and it
+ * closes itself and sends a message back and the test finishes.
+ * (2) The browser is set to offline mode. The opened page sends message
+ * that it has received offline event. This controller page then asks the
+ * page to go forward. The page which comes out from the bfcache gets
+ * offline event and sends message about that to this controller.
+ * (3) Browser is set to online mode. Similar cycle as with offline happens.
+ * (4) Controller page sends close message to the opened window and it
+ * closes itself and sends a message back and the test finishes.
+ */
+
+ function offlineOnline(online) {
+ function offlineFn() {
+ /* eslint-env mozilla/chrome-script */
+ Services.io.offline = true;
+ }
+ function onlineFn() {
+ /* eslint-env mozilla/chrome-script */
+ Services.io.offline = false;
+ }
+ SpecialPowers.loadChromeScript(online ? onlineFn : offlineFn);
+ }
+
+ var bc = new BroadcastChannel("online_offline_bfcache");
+ var pageshowCount = 0;
+ var offlineCount = 0;
+ var onlineCount = 0;
+
+ bc.onmessage = function(event) {
+ if (event.data.event == "pageshow") {
+ ++pageshowCount;
+ info("pageshow " + pageshowCount);
+ if (pageshowCount == 1) {
+ ok(!event.data.persisted);
+ bc.postMessage("nextpage");
+ } else if (pageshowCount == 2) {
+ ok(!event.data.persisted);
+ bc.postMessage("back");
+ } else if (pageshowCount == 3) {
+ if (!event.data.persisted) {
+ info("BFCache is not enabled, return early");
+ bc.postMessage("close");
+ } else {
+ offlineOnline(false);
+ }
+ }
+ } else if (event.data == "offline") {
+ ++offlineCount;
+ info("offline " + offlineCount);
+ if (offlineCount == 1) {
+ bc.postMessage("forward");
+ } else if (offlineCount == 2) {
+ offlineOnline(true);
+ } else {
+ ok(false, "unexpected offline event");
+ }
+ } else if (event.data == "online") {
+ ++onlineCount;
+ info("online " + onlineCount);
+ if (onlineCount == 1) {
+ bc.postMessage("back");
+ } else if (onlineCount == 2) {
+ bc.postMessage("close");
+ } else {
+ ok(false, "unexpected online event");
+ }
+ } else if ("closed") {
+ ok(true, "Did pass the test");
+ bc.close();
+ SimpleTest.finish();
+ }
+ };
+
+ function runTest() {
+ SpecialPowers.pushPrefEnv({"set": [["network.manage-offline-status", false]]}, function() {
+ window.open("file_online_offline_bfcache.html", "", "noopener");
+ });
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ </script>
+</head>
+<body onload="runTest()">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_open_javascript_noopener.html b/docshell/test/navigation/test_open_javascript_noopener.html
new file mode 100644
index 0000000000..81a6b70d61
--- /dev/null
+++ b/docshell/test/navigation/test_open_javascript_noopener.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ </head>
+ <body>
+ <script>
+
+add_task(async function test_open_javascript_noopener() {
+ const topic = "test-javascript-was-run";
+ function jsuri(version) {
+ return `javascript:SpecialPowers.notifyObservers(null, "${topic}", "${version}");window.close()`;
+ }
+
+ let seen = [];
+ function observer(_subject, _topic, data) {
+ info(`got notification ${data}`);
+ seen.push(data);
+ }
+ SpecialPowers.addObserver(observer, topic);
+
+ isDeeply(seen, [], "seen no test notifications");
+ window.open(jsuri("1"));
+
+ // Bounce off the parent process to make sure the JS will have run.
+ await SpecialPowers.spawnChrome([], () => {});
+
+ isDeeply(seen, ["1"], "seen the opener notification");
+
+ window.open(jsuri("2"), "", "noopener");
+
+ // Bounce off the parent process to make sure the JS will have run.
+ await SpecialPowers.spawnChrome([], () => {});
+
+ isDeeply(seen, ["1"], "didn't get a notification from the noopener popup");
+
+ SpecialPowers.removeObserver(observer, topic);
+});
+
+ </script>
+ </body>
+</html>
diff --git a/docshell/test/navigation/test_opener.html b/docshell/test/navigation/test_opener.html
new file mode 100644
index 0000000000..ce966b897d
--- /dev/null
+++ b/docshell/test/navigation/test_opener.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+ <style type="text/css">
+ iframe { width: 90%; height: 50px; }
+ </style>
+<script>
+if (navigator.platform.startsWith("Linux")) {
+ SimpleTest.expectAssertions(0, 1);
+}
+
+window.onload = async function() {
+ navigateByLocation(window0);
+ navigateByOpen("window1");
+ navigateByForm("window2");
+ navigateByHyperlink("window3");
+
+ await waitForFinishedFrames(4);
+ await isNavigated(window0, "Should be able to navigate popup by setting location.");
+ await isNavigated(window1, "Should be able to navigate popup by calling window.open.");
+ await isNavigated(window2, "Should be able to navigate popup by submitting form.");
+ await isNavigated(window3, "Should be able to navigate popup by targeted hyperlink.");
+
+ window0.close();
+ window1.close();
+ window2.close();
+ window3.close();
+
+ await cleanupWindows();
+
+ SimpleTest.finish();
+};
+
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+var window0 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/blank.html", "window0", "width=10,height=10");
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+var window1 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/blank.html", "window1", "width=10,height=10");
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+var window2 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/blank.html", "window2", "width=10,height=10");
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+var window3 = window.open("http://test1.example.org:80/tests/docshell/test/navigation/blank.html", "window3", "width=10,height=10");
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=408052">Mozilla Bug 408052</a>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_performance_navigation.html b/docshell/test/navigation/test_performance_navigation.html
new file mode 100644
index 0000000000..75abbdd767
--- /dev/null
+++ b/docshell/test/navigation/test_performance_navigation.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=145971
+-->
+<head>
+ <title>Test for Bug 145971</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=145971">Mozilla Bug 145971</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+var testWindow;
+var bc = new BroadcastChannel("bug145971");
+bc.onmessage = function(msgEvent) {
+ var result = msgEvent.data.result;
+ if (result == undefined) {
+ info("Got unexpected message from BroadcastChannel");
+ return;
+ }
+ ok(result, "Bug 145971: Navigation type does not equal 2 when restoring document from session history.");
+ SimpleTest.finish();
+};
+
+function runTest() {
+ // If Fission is disabled, the pref is no-op.
+ SpecialPowers.pushPrefEnv({set: [["fission.bfcacheInParent", true]]}, () => {
+ window.open("test_bug145971.html", "", "width=360,height=480,noopener");
+ });
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_popup-navigates-children.html b/docshell/test/navigation/test_popup-navigates-children.html
new file mode 100644
index 0000000000..82d69e7982
--- /dev/null
+++ b/docshell/test/navigation/test_popup-navigates-children.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+ <style type="text/css">
+ iframe { width: 90%; height: 50px; }
+ </style>
+<script>
+
+let window0 = null;
+let window1 = null;
+let window2 = null;
+let window3 = null;
+
+function testChild0() {
+ if (!window.window0)
+ window0 = window.open("navigate.html#opener.frames[0],location", "window0", "width=10,height=10");
+}
+
+function testChild1() {
+ if (!window.window1)
+ window1 = window.open("navigate.html#child1,open", "window1", "width=10,height=10");
+}
+
+function testChild2() {
+ if (!window.window2)
+ window2 = window.open("navigate.html#child2,form", "window2", "width=10,height=10");
+}
+
+function testChild3() {
+ if (!window.window3)
+ window3 = window.open("navigate.html#child3,hyperlink", "window3", "width=10,height=10");
+}
+
+window.onload = async function() {
+ await waitForFinishedFrames(4);
+ await isNavigated(frames[0], "Should be able to navigate on-domain opener's children by setting location.");
+ await isNavigated(frames[1], "Should be able to navigate on-domain opener's children by calling window.open.");
+ await isNavigated(frames[2], "Should be able to navigate on-domain opener's children by submitting form.");
+ await isNavigated(frames[3], "Should be able to navigate on-domain opener's children by targeted hyperlink.");
+
+ window0.close();
+ window1.close();
+ window2.close();
+ window3.close();
+
+ await cleanupWindows();
+ SimpleTest.finish();
+};
+
+</script>
+</head>
+<body>
+<div id="frames">
+<iframe onload="testChild0()" name="child0" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+<iframe onload="testChild1()" name="child1" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+<iframe onload="testChild2()" name="child2" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+<iframe onload="testChild3()" name="child3" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+</div>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_rate_limit_location_change.html b/docshell/test/navigation/test_rate_limit_location_change.html
new file mode 100644
index 0000000000..b1b51b92dd
--- /dev/null
+++ b/docshell/test/navigation/test_rate_limit_location_change.html
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1314912
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1314912</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1314912 */
+
+ const RATE_LIMIT_COUNT = 90;
+ const RATE_LIMIT_TIME_SPAN = 3;
+
+ async function setup() {
+ await SpecialPowers.pushPrefEnv({set: [
+ ["dom.navigation.locationChangeRateLimit.count", RATE_LIMIT_COUNT],
+ ["dom.navigation.locationChangeRateLimit.timespan", RATE_LIMIT_TIME_SPAN]]});
+ }
+
+ let inc = 0;
+
+ const rateLimitedFunctions = (win) => ({
+ "history.replaceState": () => win.history.replaceState(null, "test", `${win.location.href}#${inc++}`),
+ "history.pushState": () => win.history.pushState(null, "test", `${win.location.href}#${inc++}`),
+ "history.back": () => win.history.back(),
+ "history.forward": () => win.history.forward(),
+ "history.go": () => win.history.go(-1),
+ "location.href": () => win.location.href = win.location.href + "",
+ "location.hash": () => win.location.hash = inc++,
+ "location.host": () => win.location.host = win.location.host + "",
+ "location.hostname": () => win.location.hostname = win.location.hostname + "",
+ "location.pathname": () => win.location.pathname = win.location.pathname + "",
+ "location.port": () => win.location.port = win.location.port + "",
+ "location.protocol": () => win.location.protocol = win.location.protocol + "",
+ "location.search": () => win.location.search = win.location.search + "",
+ "location.assign": () => win.location.assign(`${win.location.href}#${inc++}`),
+ "location.replace": () => win.location.replace(`${win.location.href}#${inc++}`),
+ "location.reload": () => win.location.reload(),
+ });
+
+ async function test() {
+ await setup();
+
+ // Open new window and wait for it to load
+ let win = window.open("blank.html");
+ await new Promise((resolve) => SimpleTest.waitForFocus(resolve, win))
+
+ // Execute the history and location functions
+ Object.entries(rateLimitedFunctions(win)).forEach(([name, fn]) => {
+ // Reset the rate limit for the next run.
+ info("Reset rate limit.");
+ SpecialPowers.wrap(win).browsingContext.resetLocationChangeRateLimit();
+
+ info(`Calling ${name} ${RATE_LIMIT_COUNT} times to reach the rate limit.`);
+ for(let i = 0; i< RATE_LIMIT_COUNT; i++) {
+ fn.call(this);
+ }
+ // Next calls should throw because we're above the rate limit
+ for(let i = 0; i < 5; i++) {
+ SimpleTest.doesThrow(() => fn.call(this), `Call #${RATE_LIMIT_COUNT + i + 1} to ${name} should throw.`);
+ }
+ })
+
+ // We didn't reset the rate limit after the last loop iteration above.
+ // Wait for the rate limit timer to expire.
+ SimpleTest.requestFlakyTimeout("Waiting to trigger rate limit reset.");
+ await new Promise((resolve) => setTimeout(resolve, 5000));
+
+ // Calls should be allowed again.
+ Object.entries(rateLimitedFunctions(win)).forEach(([name, fn]) => {
+ let didThrow = false;
+ try {
+ fn.call(this);
+ } catch(error) {
+ didThrow = true;
+ }
+ is(didThrow, false, `Call to ${name} must not throw.`)
+ });
+
+ // Cleanup
+ win.close();
+ SpecialPowers.wrap(win).browsingContext.resetLocationChangeRateLimit();
+ SimpleTest.finish();
+ }
+
+ </script>
+</head>
+<body onload="setTimeout(test, 0);">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1314912">Mozilla Bug 1314912</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_recursive_frames.html b/docshell/test/navigation/test_recursive_frames.html
new file mode 100644
index 0000000000..3ccc09dd14
--- /dev/null
+++ b/docshell/test/navigation/test_recursive_frames.html
@@ -0,0 +1,167 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Recursive Loads</title>
+ <meta charset="utf-8">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1597427">Mozilla Bug 1597427</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ const TEST_CASES = [
+ { // too many recursive iframes
+ frameId: "recursiveFrame",
+ expectedLocations: [
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/tests/docshell/test/navigation/frame_recursive.html",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/tests/docshell/test/navigation/frame_recursive.html",
+ "about:blank",
+ ],
+ },
+ { // too many recursive iframes
+ frameId: "twoRecursiveIframes",
+ expectedLocations: [
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/tests/docshell/test/navigation/frame_load_as_example_com.html",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.org/tests/docshell/test/navigation/frame_load_as_example_org.html",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/tests/docshell/test/navigation/frame_load_as_example_com.html",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.org/tests/docshell/test/navigation/frame_load_as_example_org.html",
+ "about:blank",
+ ],
+ },
+ { // too many recursive iframes
+ frameId: "threeRecursiveIframes",
+ expectedLocations: [
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://sub1.test1.mochi.test:8888/tests/docshell/test/navigation/frame_load_as_host1.html",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/tests/docshell/test/navigation/frame_load_as_host2.html",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://test1.mochi.test:8888/tests/docshell/test/navigation/frame_load_as_host3.html",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://sub1.test1.mochi.test:8888/tests/docshell/test/navigation/frame_load_as_host1.html",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/tests/docshell/test/navigation/frame_load_as_host2.html",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://test1.mochi.test:8888/tests/docshell/test/navigation/frame_load_as_host3.html",
+ "about:blank",
+ ],
+ },
+ { // too many nested iframes
+ frameId: "sixRecursiveIframes",
+ expectedLocations: [
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/tests/docshell/test/navigation/frame_1_out_of_6.html",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://test1.mochi.test:8888/tests/docshell/test/navigation/frame_2_out_of_6.html",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://sub1.test1.mochi.test:8888/tests/docshell/test/navigation/frame_3_out_of_6.html",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://sub2.xn--lt-uia.mochi.test:8888/tests/docshell/test/navigation/frame_4_out_of_6.html",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://test2.mochi.test:8888/tests/docshell/test/navigation/frame_5_out_of_6.html",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.org/tests/docshell/test/navigation/frame_6_out_of_6.html",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/tests/docshell/test/navigation/frame_1_out_of_6.html",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://test1.mochi.test:8888/tests/docshell/test/navigation/frame_2_out_of_6.html",
+ ],
+ },
+ { // too many recursive objects
+ frameId: "recursiveObject",
+ expectedLocations: [
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://sub2.xn--lt-uia.mochi.test:8888/tests/docshell/test/navigation/object_recursive_load.html",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://sub2.xn--lt-uia.mochi.test:8888/tests/docshell/test/navigation/object_recursive_load.html",
+ ],
+ },
+ { // 3 nested srcdocs, should show all of them
+ frameId: "nestedSrcdoc",
+ expectedLocations: [
+ "about:srcdoc",
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ "http://example.com/tests/docshell/test/navigation/file_nested_srcdoc.html",
+ "about:srcdoc",
+ "about:srcdoc",
+ ],
+ },
+ ];
+
+ async function checkRecursiveLoad(level) {
+ let el = content.document.getElementById("static");
+ let documentURI = await SpecialPowers.spawn(
+ el,
+ [],
+ () => this.content.document.documentURI
+ );
+ if (documentURI == "about:blank") {
+ // If we had too many recursive frames, the most inner iframe's uri will be about:blank
+ return [documentURI];
+ }
+ if (documentURI == "about:srcdoc" && level == 3) {
+ // Check that we have the correct most inner srcdoc iframe
+ let innerText = await SpecialPowers.spawn(
+ el,
+ [],
+ () => this.content.document.body.innerText
+ );
+ is(innerText, "Third nested srcdoc", "correct most inner srcdoc iframe");
+ }
+ let nestedIfrOrObjectURI = [];
+ try {
+ // Throws an error when we have too many nested frames/objects, because we
+ // claim to have no content window for the inner most frame/object.
+ nestedIfrOrObjectURI = await SpecialPowers.spawn(
+ el,
+ [level + 1],
+ checkRecursiveLoad
+ );
+ } catch (err) {
+ info(
+ `Tried to spawn another task in the iframe/object, but got err: ${err}, must have had too many nested iframes/objects\n`
+ );
+ }
+ return [documentURI, ...nestedIfrOrObjectURI];
+ }
+
+ add_task(async () => {
+ for (const testCase of TEST_CASES) {
+ let el = document.getElementById(testCase.frameId);
+ let loc = await SpecialPowers.spawn(
+ el,
+ [],
+ () => this.content.location.href
+ );
+ let locations = await SpecialPowers.spawn(el, [1], checkRecursiveLoad);
+ isDeeply(
+ [loc, ...locations],
+ testCase.expectedLocations,
+ "iframes/object loaded in correct order"
+ );
+ }
+ });
+
+ </script>
+</pre>
+<div>
+ <iframe style="height: 100vh; width:25%;" id="recursiveFrame" src="http://example.com/tests/docshell/test/navigation/frame_recursive.html"></iframe>
+ <iframe style="height: 100vh; width:25%;" id="twoRecursiveIframes" src="http://example.com/tests/docshell/test/navigation/frame_load_as_example_com.html"></iframe>
+ <iframe style="height: 100vh; width:25%;" id="threeRecursiveIframes" src="http://sub1.test1.mochi.test:8888/tests/docshell/test/navigation/frame_load_as_host1.html"></iframe>
+ <iframe style="height: 100vh; width:25%;" id="sixRecursiveIframes" src="http://example.com/tests/docshell/test/navigation/frame_1_out_of_6.html"></iframe>
+ <object width="400" height="300" id="recursiveObject" data="http://sub2.xn--lt-uia.mochi.test:8888/tests/docshell/test/navigation/object_recursive_load.html"></object>
+ <iframe id="nestedSrcdoc" srcdoc="Srcdoc that will embed an iframe &lt;iframe id=&quot;static&quot; src=&quot;http://example.com/tests/docshell/test/navigation/file_nested_srcdoc.html&quot;&gt;&lt;/iframe&gt;"></iframe>
+</div>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_reload.html b/docshell/test/navigation/test_reload.html
new file mode 100644
index 0000000000..7e75c7c035
--- /dev/null
+++ b/docshell/test/navigation/test_reload.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Ensure a page which is otherwise bfcacheable doesn't crash on reload</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ let pageshowCount = 0;
+ let bc = new BroadcastChannel("test_reload");
+ bc.onmessage = function(event) {
+ info("MessageEvent: " + event.data);
+ if (event.data == "pageshow") {
+ ++pageshowCount;
+ info("pageshow: " + pageshowCount);
+ if (pageshowCount < 3) {
+ info("Sending reload");
+ bc.postMessage("reload");
+ } else {
+ info("Sending close");
+ bc.postMessage("close");
+ }
+ } else if (event.data == "closed") {
+ info("closed");
+ bc.close();
+ ok(true, "Passed");
+ SimpleTest.finish();
+ }
+ }
+
+ function test() {
+ window.open("file_reload.html", "", "noopener");
+ }
+ </script>
+</head>
+<body onload="test()">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_reload_large_postdata.html b/docshell/test/navigation/test_reload_large_postdata.html
new file mode 100644
index 0000000000..15fae33ac3
--- /dev/null
+++ b/docshell/test/navigation/test_reload_large_postdata.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<p id="display"></p>
+
+<form id="form" action="file_reload_large_postdata.sjs" target="_blank" rel="opener" method="POST">
+ <input id="input" name="payload" type="hidden" value=""/>
+</form>
+
+<pre id="test">
+<script>
+// This is derived from `kTooLargeStream` in `IPCStreamUtils.cpp`.
+const kTooLargeStream = 1024 * 1024;
+
+function waitForPopup(expected) {
+ return new Promise(resolve => {
+ addEventListener("message", evt => {
+ info("got message!");
+ is(evt.source.opener, window, "the event source's opener should be this window");
+ is(evt.data, expected, "got the expected data from the popup");
+ resolve(evt.source);
+ }, { once: true });
+ });
+}
+
+add_task(async function() {
+ await SpecialPowers.pushPrefEnv({"set": [["dom.confirm_repost.testing.always_accept", true]]});
+ let form = document.getElementById("form");
+ let input = document.getElementById("input");
+
+ // Create a very large value to include in the post payload. This should
+ // ensure that the value isn't sent directly over IPC, and is instead sent as
+ // an async inputstream.
+ let payloadSize = kTooLargeStream;
+
+ let popupReady = waitForPopup(payloadSize);
+ input.value = "A".repeat(payloadSize);
+ form.submit();
+
+ let popup = await popupReady;
+ try {
+ let popupReady2 = waitForPopup(payloadSize);
+ info("reloading popup");
+ popup.location.reload();
+ let popup2 = await popupReady2;
+ is(popup, popup2);
+ } finally {
+ popup.close();
+ }
+});
+
+// The .sjs server can time out processing the 1mb payload in debug builds.
+SimpleTest.requestLongerTimeout(2);
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_reload_nonbfcached_srcdoc.html b/docshell/test/navigation/test_reload_nonbfcached_srcdoc.html
new file mode 100644
index 0000000000..2399a0ad7d
--- /dev/null
+++ b/docshell/test/navigation/test_reload_nonbfcached_srcdoc.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test srcdoc handling when reloading a page.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ // The old session history implementation asserts in
+ // https://searchfox.org/mozilla-central/rev/b822a27de3947d3f4898defac6164e52caf1451b/docshell/shistory/nsSHEntry.cpp#670-672
+ SimpleTest.expectAssertions(0, 1);
+ SimpleTest.waitForExplicitFinish();
+
+ var win;
+ function test() {
+ window.onmessage = function(event) {
+ if (event.data == "pageload:") {
+ // Trigger a similar reload as what the reload button does.
+ SpecialPowers.wrap(win)
+ .docShell
+ .QueryInterface(SpecialPowers.Ci.nsIWebNavigation)
+ .sessionHistory
+ .reload(0);
+ } else if (event.data == "pageload:second") {
+ ok(true, "srcdoc iframe was updated.");
+ win.close();
+ SimpleTest.finish();
+ }
+ }
+ win = window.open("file_reload_nonbfcached_srcdoc.sjs");
+ }
+
+ </script>
+</head>
+<body onload="test()">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_reserved.html b/docshell/test/navigation/test_reserved.html
new file mode 100644
index 0000000000..0242f3941b
--- /dev/null
+++ b/docshell/test/navigation/test_reserved.html
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+ <style type="text/css">
+ iframe { width: 90%; height: 200px; }
+ </style>
+<script>
+if (navigator.platform.startsWith("Mac")) {
+ SimpleTest.expectAssertions(0, 2);
+}
+
+async function testTop() {
+ let window0 = window.open("iframe.html#http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#top,location", "_blank", "width=10,height=10");
+
+ await waitForFinishedFrames(1);
+ isInaccessible(window0, "Should be able to navigate off-domain top by setting location.");
+ window0.close();
+ await cleanupWindows();
+
+ let window1 = window.open("iframe.html#http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#_top,open", "_blank", "width=10,height=10");
+
+ await waitForFinishedFrames(1);
+ isInaccessible(window1, "Should be able to navigate off-domain top by calling window.open.");
+ window1.close();
+ await cleanupWindows();
+
+ let window2 = window.open("iframe.html#http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#_top,form", "_blank", "width=10,height=10");
+
+ await waitForFinishedFrames(1);
+ isInaccessible(window2, "Should be able to navigate off-domain top by submitting form.");
+ window2.close();
+ await cleanupWindows();
+
+ let window3 = window.open("iframe.html#http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#_top,hyperlink", "_blank", "width=10,height=10");
+
+ await waitForFinishedFrames(1);
+ isInaccessible(window3, "Should be able to navigate off-domain top by targeted hyperlink.");
+ window3.close();
+ await cleanupWindows();
+
+ await testParent();
+}
+
+async function testParent() {
+ document.getElementById("frames").innerHTML = '<iframe src="iframe.html#http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#parent,location"></iframe>';
+
+ await waitForFinishedFrames(1);
+ isAccessible(frames[0], "Should not be able to navigate off-domain parent by setting location.");
+ await cleanupWindows();
+
+ document.getElementById("frames").innerHTML = '<iframe src="iframe.html#http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#_parent,open"></iframe>';
+
+ await waitForFinishedFrames(1);
+ isAccessible(frames[0], "Should not be able to navigate off-domain parent by calling window.open.");
+ await cleanupWindows();
+
+ document.getElementById("frames").innerHTML = '<iframe src="iframe.html#http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#_parent,form"></iframe>';
+
+ await waitForFinishedFrames(1);
+ isAccessible(frames[0], "Should not be able to navigate off-domain parent by submitting form.");
+ await cleanupWindows();
+
+ document.getElementById("frames").innerHTML = '<iframe src="iframe.html#http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#_parent,hyperlink"></iframe>';
+
+ await waitForFinishedFrames(1);
+ isAccessible(frames[0], "Should not be able to navigate off-domain parent by targeted hyperlink.");
+ await cleanupWindows();
+
+ document.getElementById("frames").innerHTML = "";
+ SimpleTest.finish();
+}
+
+window.onload = async function() {
+ await testTop();
+};
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=408052">Mozilla Bug 408052</a>
+<div id="frames">
+</div>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_same_url.html b/docshell/test/navigation/test_same_url.html
new file mode 100644
index 0000000000..820caa7005
--- /dev/null
+++ b/docshell/test/navigation/test_same_url.html
@@ -0,0 +1,56 @@
+
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="application/javascript" src="/tests/SimpleTest/SpecialPowers.js"></script>
+ <script type="application/javascript">
+ // Since BFCache in parent requires no opener, use BroadcastChannel
+ // to communicate with file_same_url.html.
+ let bc = new BroadcastChannel("test_same_url");
+ async function test() {
+ var promise;
+ let historyLength;
+
+ promise = waitForLoad();
+ window.open("file_same_url.html", "_blank", "noopener=yes");
+ historyLength = await promise;
+ is(historyLength, 1, "before same page navigation");
+
+ promise = waitForLoad();
+ bc.postMessage("linkClick");
+ historyLength = await promise;
+ is(historyLength, 1, "after same page navigation");
+ bc.postMessage("closeWin");
+
+ SimpleTest.finish();
+ }
+
+ async function waitForLoad() {
+ return new Promise(resolve => {
+ let listener = e => {
+ if (e.data.bodyOnLoad) {
+ bc.removeEventListener("message", listener);
+ setTimeout(() => resolve(e.data.bodyOnLoad), 0);
+ }
+ };
+ bc.addEventListener("message", listener);
+ });
+ }
+ </script>
+</head>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1745730">Bug 1745730</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+<body onload="test()">
+</body>
+</html>
+
diff --git a/docshell/test/navigation/test_scrollRestoration.html b/docshell/test/navigation/test_scrollRestoration.html
new file mode 100644
index 0000000000..d31598f391
--- /dev/null
+++ b/docshell/test/navigation/test_scrollRestoration.html
@@ -0,0 +1,214 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Bug 1155730</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1155730">Mozilla Bug 1155730</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("untriaged");
+
+ function assertCheck(data) {
+ if (data.assertIs) {
+ for (const args of data.assertIs) {
+ is(args[0], args[1], args[2]);
+ }
+ }
+ if (data.assertOk) {
+ for (const args of data.assertOk) {
+ ok(args[0], args[1]);
+ }
+ }
+ if (data.assertIsNot) {
+ for (const args of data.assertIsNot) {
+ isnot(args[0], args[1], args[2]);
+ }
+ }
+ }
+
+ var bc1, currentCase = 0;
+ function test1() {
+ bc1 = new BroadcastChannel("bug1155730_part1");
+ bc1.onmessage = (msgEvent) => {
+ var msg = msgEvent.data;
+ var command = msg.command;
+ if (command == "pageshow") {
+ currentCase++;
+ var persisted = msg.persisted;
+ is(persisted, false, "Shouldn't have persisted session history entry.");
+ bc1.postMessage({command: "test", currentCase});
+ } else if (command == "asserts") {
+ is(msg.currentCase, currentCase, "correct case");
+ info(`Checking asserts for case ${msg.currentCase}`);
+ assertCheck(msg);
+ if (currentCase == 3) {
+ // move on to the next test
+ bc1.close();
+ test2();
+ }
+ }
+ }
+ window.open("file_scrollRestoration_part1_nobfcache.html", "", "width=360,height=480,noopener");
+ }
+
+ var bc2, bc2navigate;
+ function test2() {
+ currentCase = 0;
+ bc2 = new BroadcastChannel("bug1155730_part2");
+ bc2.onmessage = (msgEvent) => {
+ var msg = msgEvent.data;
+ var command = msg.command;
+ if (command == "pageshow") {
+ currentCase++;
+ var persisted = msg.persisted;
+ switch (currentCase) {
+ case 1:
+ is(persisted, false, "Shouldn't have persisted session history entry.");
+ break;
+ case 2:
+ is(persisted, true, "Should have persisted session history entry.");
+ }
+ bc2.postMessage({command: "test", currentCase});
+ } else if (command == "asserts") {
+ is(msg.currentCase, currentCase, "correct case");
+ info(`Checking asserts for case ${msg.currentCase}`);
+ assertCheck(msg);
+ if (currentCase == 3) {
+ // move on to the next test
+ bc2.close();
+ test3();
+ }
+ } else if (command == "nextCase") {
+ currentCase++;
+ }
+ }
+
+ bc2navigate = new BroadcastChannel("navigate");
+ bc2navigate.onmessage = (event) => {
+ if (event.data.command == "loaded") {
+ bc2navigate.postMessage({command: "back"})
+ bc2navigate.close();
+ }
+ }
+ window.open("file_scrollRestoration_part2_bfcache.html", "", "width=360,height=480,noopener");
+ }
+
+ var bc3, bc3navigate;
+ function test3() {
+ currentCase = 0;
+ bc3 = new BroadcastChannel("bug1155730_part3");
+ bc3.onmessage = (msgEvent) => {
+ var msg = msgEvent.data;
+ var command = msg.command;
+ if (command == "pageshow") {
+ currentCase++;
+ if (currentCase == 3) {
+ var persisted = msg.persisted;
+ is(persisted, false, "Shouldn't have persisted session history entry.");
+ }
+
+ bc3.postMessage({command: "test", currentCase});
+ } else if (command == "asserts") {
+ is(msg.currentCase, currentCase, "correct case");
+ info(`Checking asserts for case ${msg.currentCase}`);
+ assertCheck(msg);
+ } else if (command == "nextCase") {
+ currentCase++;
+ } else if (command == "finishing") {
+ bc3.close();
+ test4();
+ }
+ }
+
+ bc3navigate = new BroadcastChannel("navigate");
+ bc3navigate.onmessage = (event) => {
+ if (event.data.command == "loaded") {
+ is(event.data.scrollRestoration, 'auto', "correct scroll restoration");
+ bc3navigate.postMessage({command: "back"})
+ bc3navigate.close();
+ }
+ }
+ window.open("file_scrollRestoration_part3_nobfcache.html", "", "width=360,height=480,noopener");
+ }
+
+ // test4 opens a new page which can enter bfcache. That page then loads
+ // another page which can't enter bfcache. That second page then scrolls
+ // down. History API is then used to navigate back and forward. When the
+ // second page loads again, it should scroll down automatically.
+ var bc4a, bc4b;
+ var scrollYCounter = 0;
+ function test4() {
+ currentCase = 0;
+ bc4a = new BroadcastChannel("bfcached");
+ bc4a.onmessage = (msgEvent) => {
+ var msg = msgEvent.data;
+ var command = msg.command;
+ if (command == "pageshow") {
+ ++currentCase;
+ if (currentCase == 1) {
+ ok(!msg.persisted, "The first page should not be persisted initially.");
+ bc4a.postMessage("loadNext");
+ } else if (currentCase == 3) {
+ ok(msg.persisted, "The first page should be persisted.");
+ bc4a.postMessage("forward");
+ bc4a.close();
+ }
+ }
+ }
+
+ bc4b = new BroadcastChannel("notbfcached");
+ bc4b.onmessage = (event) => {
+ var msg = event.data;
+ var command = msg.command;
+ if (command == "pageshow") {
+ ++currentCase;
+ if (currentCase == 2) {
+ ok(!msg.persisted, "The second page should not be persisted.");
+ bc4b.postMessage("getScrollY");
+ bc4b.postMessage("scroll");
+ bc4b.postMessage("getScrollY");
+ bc4b.postMessage("back");
+ } else if (currentCase == 4) {
+ ok(!msg.persisted, "The second page should not be persisted.");
+ bc4b.postMessage("getScrollY");
+ }
+ } else if (msg == "closed") {
+ bc4b.close();
+ SimpleTest.finish();
+ } else if ("scrollY" in msg) {
+ ++scrollYCounter;
+ if (scrollYCounter == 1) {
+ is(msg.scrollY, 0, "The page should be initially scrolled to top.");
+ } else if (scrollYCounter == 2) {
+ isnot(msg.scrollY, 0, "The page should be then scrolled down.");
+ } else if (scrollYCounter == 3) {
+ isnot(msg.scrollY, 0, "The page should be scrolled down after being restored from the session history.");
+ bc4b.postMessage("close");
+ }
+ }
+ }
+ window.open("file_scrollRestoration_bfcache_and_nobfcache.html", "", "width=360,height=480,noopener");
+ }
+
+ function runTest() {
+ // If Fission is disabled, the pref is no-op.
+ SpecialPowers.pushPrefEnv({set: [["fission.bfcacheInParent", true]]}, () => {
+ test1();
+ });
+ }
+
+ </script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_session_history_entry_cleanup.html b/docshell/test/navigation/test_session_history_entry_cleanup.html
new file mode 100644
index 0000000000..a55de0d6c3
--- /dev/null
+++ b/docshell/test/navigation/test_session_history_entry_cleanup.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Bug 534178</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=534178">Mozilla Bug 534178</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ var testWindow;
+ function runTest() {
+ testWindow = window.open("file_bug534178.html", "", "width=360,height=480");
+ testWindow.onunload = function() { }; // to prevent bfcache
+ }
+
+ function finishTest() {
+ testWindow.close();
+ SimpleTest.finish();
+ }
+
+ </script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_session_history_on_redirect.html b/docshell/test/navigation/test_session_history_on_redirect.html
new file mode 100644
index 0000000000..a303f81536
--- /dev/null
+++ b/docshell/test/navigation/test_session_history_on_redirect.html
@@ -0,0 +1,92 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Session history on redirect</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ /*
+ * The test opens a new window and loads a page there. Then another document
+ * is loaded to the window. The initial load of that second page doesn't do
+ * a redirect. Now another non-redirecting page is loaded. Then
+ * history.go(-2) and history.forward() are called. The second time
+ * the second page is loaded, it does a redirect. history.back() and
+ * history.forward() are called again. The page which did the redirect
+ * shouldn't be accessed, but the page which it redirected to.
+ * Finally history.forward() is called again and the third page should be
+ * loaded and history.length should have the same value as it had when the
+ * third page was loaded the first time.
+ */
+
+ SimpleTest.waitForExplicitFinish();
+ var win;
+ var finalHistoryLength = 0;
+
+ function run() {
+ win = window.open("file_session_history_on_redirect.html");
+ }
+
+ var pageshowCounter = 0;
+ async function pageshow() {
+ // Need to trigger new loads asynchronously after page load, otherwise
+ // new loads are treated as replace loads.
+ await new Promise((r) => setTimeout(r));
+ ++pageshowCounter;
+ info("Page load: " + win.location.href);
+ switch (pageshowCounter) {
+ case 1:
+ ok(win.location.href.includes("file_session_history_on_redirect.html"));
+ win.location.href = "redirect_handlers.sjs";
+ break;
+ case 2:
+ ok(win.location.href.includes("redirect_handlers.sjs"));
+ // Put the initial page also as the last entry in the session history.
+ win.location.href = "file_session_history_on_redirect.html";
+ break;
+ case 3:
+ ok(win.location.href.includes("file_session_history_on_redirect.html"));
+ finalHistoryLength = win.history.length;
+ win.history.go(-2);
+ break;
+ case 4:
+ ok(win.location.href.includes("file_session_history_on_redirect.html"));
+ win.history.forward();
+ break;
+ case 5:
+ ok(win.location.href.includes("file_session_history_on_redirect_2.html"));
+ win.history.back();
+ break;
+ case 6:
+ ok(win.location.href.includes("file_session_history_on_redirect.html"));
+ win.history.forward();
+ break;
+ case 7:
+ ok(win.location.href.includes("file_session_history_on_redirect_2.html"));
+ is(win.history.length, finalHistoryLength, "Shouldn't have changed the history length.");
+ win.history.forward();
+ break;
+ case 8:
+ ok(win.location.href.includes("file_session_history_on_redirect.html"));
+ is(win.history.length, finalHistoryLength, "Shouldn't have changed the history length.");
+ win.onpagehide = null;
+ finishTest();
+ break;
+ default:
+ ok(false, "unexpected pageshow");
+ }
+ }
+
+ function finishTest() {
+ win.close()
+ SimpleTest.finish();
+ }
+
+ </script>
+</head>
+<body onload="run()">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_sessionhistory.html b/docshell/test/navigation/test_sessionhistory.html
new file mode 100644
index 0000000000..2254ec876b
--- /dev/null
+++ b/docshell/test/navigation/test_sessionhistory.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Bug 462076</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="nextTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=462076">Mozilla Bug 462076</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ var testFiles =
+ [ "file_bug462076_1.html", // Dynamic frames before onload
+ "file_bug462076_2.html", // Dynamic frames when handling onload
+ "file_bug462076_3.html", // Dynamic frames after onload
+ ];
+ var testCount = 0; // Used by the test files.
+
+ SimpleTest.waitForExplicitFinish();
+
+ var testWindow;
+ function nextTest_() {
+ if (testFiles.length) {
+ testCount = 0;
+ let nextFile = testFiles.shift();
+ info("Running " + nextFile);
+ testWindow = window.open(nextFile, "", "width=360,height=480");
+ testWindow.onunload = function() { }; // to prevent bfcache
+ } else {
+ SimpleTest.finish();
+ }
+ }
+
+ function nextTest() {
+ setTimeout(nextTest_, 0);
+ }
+
+ </script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_sessionhistory_document_write.html b/docshell/test/navigation/test_sessionhistory_document_write.html
new file mode 100644
index 0000000000..2a48a8154e
--- /dev/null
+++ b/docshell/test/navigation/test_sessionhistory_document_write.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Session history + document.write</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ var testWindow;
+ function runTest() {
+ testWindow = window.open("file_document_write_1.html", "", "width=360,height=480");
+ testWindow.onunload = function() { }; // to prevent bfcache
+ }
+
+ function finishTest() {
+ testWindow.close();
+ SimpleTest.finish();
+ }
+
+ </script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_sessionhistory_iframe_removal.html b/docshell/test/navigation/test_sessionhistory_iframe_removal.html
new file mode 100644
index 0000000000..242e3baade
--- /dev/null
+++ b/docshell/test/navigation/test_sessionhistory_iframe_removal.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Session history + document.write</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ var testWindow;
+ function runTest() {
+ testWindow = window.open("file_sessionhistory_iframe_removal.html", "", "width=360,height=480");
+ }
+
+ function finishTest() {
+ testWindow.close();
+ SimpleTest.finish();
+ }
+
+ </script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_shiftReload_and_pushState.html b/docshell/test/navigation/test_shiftReload_and_pushState.html
new file mode 100644
index 0000000000..7525e2e21f
--- /dev/null
+++ b/docshell/test/navigation/test_shiftReload_and_pushState.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for Bug 1003100</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1003100">Mozilla Bug 1003100</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ var testWindow;
+ function runTest() {
+ testWindow = window.open("file_shiftReload_and_pushState.html", "", "width=360,height=480");
+ testWindow.onunload = function() { }; // to prevent bfcache
+ }
+
+ function finishTest() {
+ testWindow.close();
+ SimpleTest.finish();
+ }
+
+ </script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_ship_beforeunload_fired.html b/docshell/test/navigation/test_ship_beforeunload_fired.html
new file mode 100644
index 0000000000..e43711676b
--- /dev/null
+++ b/docshell/test/navigation/test_ship_beforeunload_fired.html
@@ -0,0 +1,63 @@
+<html>
+ <head>
+ <title>
+ Test that ensures beforeunload is fired when session-history-in-parent is enabled
+ </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ </head>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ /*
+ * This test ensures beforeunload is fired on the current page
+ * when it is entering BFCache and the next page is coming out
+ * from BFCache
+ *
+ * (1) The controller page opens a new window, and page A is loaded there.
+ * (2) Page A then navigates to page B, and a beforeunload event
+ * listener is registered on page B.
+ * (3) Page B then navigates back to page A, and the beforeunload handler
+ * should send a message to the controller page.
+ * (4) Page A then navigates back to page B to check if page B has
+ * been successfully added to BFCache.
+ */
+
+ var bc = new BroadcastChannel("ship_beforeunload");
+ var pageshowCount = 0;
+ var beforeUnloadFired = false;
+ bc.onmessage = function(event) {
+ if (event.data.type == "pageshow") {
+ ++pageshowCount;
+ if (pageshowCount == 1) {
+ bc.postMessage({action: "navigate_to_page_b"});
+ } else if (pageshowCount == 2) {
+ ok(!event.data.persisted, "?pageb shouldn't in BFCache because it's the first navigation");
+ bc.postMessage({action: "register_beforeunload", loadNextPageFromSessionHistory: true});
+ } else if (pageshowCount == 3) {
+ ok(event.data.persisted, "navigated back to page A that was in BFCacache from page B");
+ ok(beforeUnloadFired, "beforeunload has fired on page B");
+ bc.postMessage({action: "back_to_page_b", forwardNavigateToPageB: true});
+ } else if (pageshowCount == 4) {
+ ok(event.data.persisted, "page B has beforeunload fired and also entered BFCache");
+ bc.postMessage({action: "close"});
+ SimpleTest.finish();
+ }
+ } else if (event.data == "beforeunload_fired") {
+ beforeUnloadFired = true;
+ }
+ }
+
+ function runTest() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["fission.bfcacheInParent", true],
+ ["docshell.shistory.bfcache.ship_allow_beforeunload_listeners", true]
+ ]},
+ function() {
+ window.open("file_ship_beforeunload_fired.html", "", "noopener");
+ }
+ );
+ }
+ </script>
+ <body onload="runTest()"></body>
+</html>
diff --git a/docshell/test/navigation/test_ship_beforeunload_fired_2.html b/docshell/test/navigation/test_ship_beforeunload_fired_2.html
new file mode 100644
index 0000000000..93669502a5
--- /dev/null
+++ b/docshell/test/navigation/test_ship_beforeunload_fired_2.html
@@ -0,0 +1,65 @@
+<html>
+ <head>
+ <title>
+ Test that ensures beforeunload is fired when session-history-in-parent is enabled
+ </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ </head>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ /*
+ * This test ensures beforeunload is fired on the current page
+ * when it is entering BFCache, and the next page is not coming from
+ * session history and also not coming out from BFCache.
+ *
+ * (1) The controller page opens a new window, and page A is loaded there.
+ * (2) Page A then navigates to page B, and a beforeunload event
+ * listener is registered on page B.
+ * (3) Page B then navigates to page C, and the beforeunload handler
+ * should send a message to the controller page.
+ * (4) Page C then navigates back to page B to check if page B has
+ * been successfully added to BFCache.
+ */
+
+ var bc = new BroadcastChannel("ship_beforeunload");
+ var pageshowCount = 0;
+
+ var beforeUnloadFired = false;
+ bc.onmessage = function(event) {
+ if (event.data.type == "pageshow") {
+ ++pageshowCount;
+ if (pageshowCount == 1) {
+ bc.postMessage({action: "navigate_to_page_b"});
+ } else if (pageshowCount == 2) {
+ ok(!event.data.persisted, "?page B shouldn't in BFCache because it's the first navigation");
+ bc.postMessage({action: "register_beforeunload",
+ loadNextPageFromSessionHistory: false});
+ } else if (pageshowCount == 3) {
+ ok(!event.data.persisted, "navigated to page C that was a new page");
+ ok(beforeUnloadFired, "beforeUnload should be fired on page B");
+ bc.postMessage({action: "back_to_page_b", forwardNavigateToPageB: false});
+ } else if (pageshowCount == 4) {
+ ok(event.data.persisted, "page B has been successfully added to BFCache");
+ bc.postMessage({action: "close"});
+ SimpleTest.finish();
+ }
+ } else if (event.data == "beforeunload_fired") {
+ beforeUnloadFired = true;
+ }
+ }
+
+ function runTest() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["fission.bfcacheInParent", true],
+ ["docshell.shistory.bfcache.ship_allow_beforeunload_listeners", true]
+ ]},
+ function() {
+ window.open("file_ship_beforeunload_fired.html", "", "noopener");
+ }
+ );
+ }
+ </script>
+ <body onload="runTest()"></body>
+</html>
diff --git a/docshell/test/navigation/test_ship_beforeunload_fired_3.html b/docshell/test/navigation/test_ship_beforeunload_fired_3.html
new file mode 100644
index 0000000000..8951f269c5
--- /dev/null
+++ b/docshell/test/navigation/test_ship_beforeunload_fired_3.html
@@ -0,0 +1,65 @@
+<html>
+ <head>
+ <title>
+ Test that ensures beforeunload is fired when session-history-in-parent is enabled
+ </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ </head>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ /*
+ * This test ensures beforeunload is fired on the current page
+ * when it is entering BFCache, and the next page is not coming out
+ * from BFCache, but coming from session history.
+ *
+ * (1) The controller page opens a new window, and page A is loaded there.
+ * (2) Page A then navigates to page B, and a beforeunload event
+ * listener is registered on page B.
+ * (3) Page B then navigates back to page A, and the beforeunload handler
+ * should send a message to the controller page.
+ * (4) Page A then navigates back to page B to check if page B has
+ * been successfully added to BFCache.
+ */
+
+ var bc = new BroadcastChannel("ship_beforeunload");
+ var pageshowCount = 0;
+
+ var beforeUnloadFired = false;
+ bc.onmessage = function(event) {
+ if (event.data.type == "pageshow") {
+ ++pageshowCount;
+ if (pageshowCount == 1) {
+ bc.postMessage({action: "navigate_to_page_b", blockBFCache: true});
+ } else if (pageshowCount == 2) {
+ ok(!event.data.persisted, "?page B shouldn't in BFCache because it's the first navigation");
+ bc.postMessage({action: "register_beforeunload",
+ loadNextPageFromSessionHistory: true});
+ } else if (pageshowCount == 3) {
+ ok(!event.data.persisted, "navigated back to page A that was session history but not in BFCache");
+ ok(beforeUnloadFired, "beforeUnload should be fired on page B");
+ bc.postMessage({action: "back_to_page_b", forwardNavigateToPageB: true});
+ } else if (pageshowCount == 4) {
+ ok(event.data.persisted, "page B has been successfully added to BFCache");
+ bc.postMessage({action: "close"});
+ SimpleTest.finish();
+ }
+ } else if (event.data == "beforeunload_fired") {
+ beforeUnloadFired = true;
+ }
+ }
+
+ function runTest() {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["fission.bfcacheInParent", true],
+ ["docshell.shistory.bfcache.ship_allow_beforeunload_listeners", true]
+ ]},
+ function() {
+ window.open("file_ship_beforeunload_fired.html", "", "noopener");
+ }
+ );
+ }
+ </script>
+ <body onload="runTest()"></body>
+</html>
diff --git a/docshell/test/navigation/test_sibling-matching-parent.html b/docshell/test/navigation/test_sibling-matching-parent.html
new file mode 100644
index 0000000000..3c1bc768db
--- /dev/null
+++ b/docshell/test/navigation/test_sibling-matching-parent.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+ <style type="text/css">
+ iframe { width: 90%; height: 50px; }
+ </style>
+<script>
+window.onload = async function() {
+ document.getElementById("active").innerHTML =
+ '<iframe src="navigate.html#parent.frames[0],location"></iframe>' +
+ '<iframe src="navigate.html#child1,open"></iframe>' +
+ '<iframe src="navigate.html#child2,form"></iframe>' +
+ '<iframe src="navigate.html#child3,hyperlink"></iframe>';
+
+ await waitForFinishedFrames(4);
+
+ await isNavigated(frames[0], "Should be able to navigate sibling with on-domain parent by setting location.");
+ await isNavigated(frames[1], "Should be able to navigate sibling with on-domain parent by calling window.open.");
+ await isNavigated(frames[2], "Should be able to navigate sibling with on-domain parent by submitting form.");
+ await isNavigated(frames[3], "Should be able to navigate sibling with on-domain parent by targeted hyperlink.");
+
+ await cleanupWindows();
+ SimpleTest.finish();
+};
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=408052">Mozilla Bug 408052</a>
+<div id="frames">
+<iframe name="child0" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+<iframe name="child1" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+<iframe name="child2" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+<iframe name="child3" src="http://test1.example.org:80/tests/docshell/test/navigation/blank.html"></iframe>
+</div>
+<div id="active"></div>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_sibling-off-domain.html b/docshell/test/navigation/test_sibling-off-domain.html
new file mode 100644
index 0000000000..cd70d1ae91
--- /dev/null
+++ b/docshell/test/navigation/test_sibling-off-domain.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+ <style type="text/css">
+ iframe { width: 90%; height: 50px; }
+ </style>
+<script>
+window.onload = async function() {
+ document.getElementById("active").innerHTML =
+ '<iframe src="http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#parent.frames[0],location"></iframe>' +
+ '<iframe src="http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#child1,open"></iframe>' +
+ '<iframe src="http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#child2,form"></iframe>' +
+ '<iframe src="http://test1.example.org:80/tests/docshell/test/navigation/navigate.html#child3,hyperlink"></iframe>';
+
+ await waitForFinishedFrames(4);
+
+ isBlank(frames[0], "Should not be able to navigate off-domain sibling by setting location.");
+ isBlank(frames[1], "Should not be able to navigate off-domain sibling by calling window.open.");
+ isBlank(frames[2], "Should not be able to navigate off-domain sibling by submitting form.");
+ isBlank(frames[3], "Should not be able to navigate off-domain sibling by targeted hyperlink.");
+
+ await cleanupWindows();
+ SimpleTest.finish();
+};
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=408052">Mozilla Bug 408052</a>
+<div id="frames">
+<iframe name="child0" src="blank.html"></iframe>
+<iframe name="child1" src="blank.html"></iframe>
+<iframe name="child2" src="blank.html"></iframe>
+<iframe name="child3" src="blank.html"></iframe>
+</div>
+<div id="active"></div>
+<pre id="test">
+<script type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_state_size.html b/docshell/test/navigation/test_state_size.html
new file mode 100644
index 0000000000..f089a460ec
--- /dev/null
+++ b/docshell/test/navigation/test_state_size.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test the max size of the data parameter of push/replaceState</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+ function test() {
+ let tooLarge = SpecialPowers.getIntPref("browser.history.maxStateObjectSize");
+ let allowed = Math.floor(tooLarge / 2);
+
+ history.pushState(new Array(allowed).join("a"), "");
+ ok(true, "Adding a state should succeed.");
+
+ try {
+ history.pushState(new Array(tooLarge).join("a"), "");
+ ok(false, "Adding a too large state object should fail.");
+ } catch(ex) {
+ ok(true, "Adding a too large state object should fail.");
+ }
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body onload="test()">
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_static_and_dynamic.html b/docshell/test/navigation/test_static_and_dynamic.html
new file mode 100644
index 0000000000..ff72a8188c
--- /dev/null
+++ b/docshell/test/navigation/test_static_and_dynamic.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <title>Test for static and dynamic frames and forward-back </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script type="application/javascript">
+ var testCount = 0; // Used by the test files.
+
+ SimpleTest.waitForExplicitFinish();
+
+ var testWindow;
+ function runTest() {
+ testWindow = window.open("file_static_and_dynamic_1.html", "", "width=360,height=480");
+ testWindow.onunload = function() { }; // to prevent bfcache
+ }
+
+ function finishTest() {
+ testWindow.close();
+ SimpleTest.finish();
+ }
+ </script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_triggeringprincipal_frame_nav.html b/docshell/test/navigation/test_triggeringprincipal_frame_nav.html
new file mode 100644
index 0000000000..d7046e9236
--- /dev/null
+++ b/docshell/test/navigation/test_triggeringprincipal_frame_nav.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1181370 - Test triggeringPrincipal for iframe navigations</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe1"></iframe>
+<iframe style="width:100%;" id="testframe2"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+/* Description of the test:
+ *
+ * +------------------------------------+
+ * | +----------+ +--------------+ |
+ * | | Frame 1 | | Frame 2 | |
+ * | +----------+ | | |
+ * | | +----------+ | |
+ * | | | Subframe | | |
+ * | | +----------+ | |
+ * | +--------------+ |
+ * +------------------------------------+
+ *
+ * Frame1: test1.mochi.test
+ * Frame2: test2.mochi.test
+ * Subframe: test2.mochi.test
+ *
+ * (*) Frame1 and Subframe set their document.domain to mochi.test
+ * (*) Frame1 navigates the Subframe
+ * (*) TriggeringPrincipal for the Subframe navigation should be
+ * ==> test1.mochi.test
+ * (*) LoadingPrincipal for the Subframe navigation should be
+ * ==> test2.mochi.test
+ */
+
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+const BASEDOMAIN1 = "http://test1.mochi.test:8888/";
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+const BASEDOMAIN2 = "http://test2.mochi.test:8888/";
+const PATH = "tests/docshell/test/navigation/";
+const BASEURL1 = BASEDOMAIN1 + PATH;
+const BASEURL2 = BASEDOMAIN2 + PATH;
+const TRIGGERINGPRINCIPALURI = BASEURL1 + "file_triggeringprincipal_frame_1.html";
+const LOADINGPRINCIPALURI = BASEURL2 + "file_triggeringprincipal_frame_2.html";
+
+SimpleTest.waitForExplicitFinish();
+
+window.addEventListener("message", receiveMessage);
+
+function receiveMessage(event) {
+ is(event.data.triggeringPrincipalURI, TRIGGERINGPRINCIPALURI,
+ "TriggeringPrincipal should be the navigating iframe (Frame 1)");
+ is(event.data.loadingPrincipalURI, LOADINGPRINCIPALURI,
+ "LoadingPrincipal should be the enclosing iframe (Frame 2)");
+ is(event.data.referrerURI, BASEDOMAIN1,
+ "The path of Referrer should be trimmed (Frame 1)");
+
+ window.removeEventListener("message", receiveMessage);
+ SimpleTest.finish();
+}
+
+var frame1 = document.getElementById("testframe1");
+frame1.src = BASEURL1 + "file_triggeringprincipal_frame_1.html";
+
+var frame2 = document.getElementById("testframe2");
+frame2.src = BASEURL2 + "file_triggeringprincipal_frame_2.html";
+
+</script>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_triggeringprincipal_frame_same_origin_nav.html b/docshell/test/navigation/test_triggeringprincipal_frame_same_origin_nav.html
new file mode 100644
index 0000000000..4483efb13e
--- /dev/null
+++ b/docshell/test/navigation/test_triggeringprincipal_frame_same_origin_nav.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Bug 1639195 - Test triggeringPrincipal for iframe same-origin navigations</title>
+ <!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe" src="http://example.com/"></iframe>
+
+<script type="text/javascript">
+/* We load an third-party iframe which then gets navigated by the iframe's
+ * parent by calling iframe.setAttribute("src", same-origin url) later in the
+ * test. We then verify the TriggeringPrincipal and LoadingPrincipal of the
+ * navigated iframe.
+ *
+ * +------------------------------------------+
+ * | |
+ * | +------------------+ |
+ * | | testframe | |
+ * | +------------------+ |
+ * | |
+ * | iframe.setAttribute("src", |
+ * | same-origin url) |
+ * | |
+ * +------------------------------------------+
+ */
+
+var testframe = document.getElementById("testframe");
+
+window.addEventListener("message", receiveMessage);
+
+const TRIGGERING_PRINCIPAL_URI =
+ "http://mochi.test:8888/tests/docshell/test/navigation/test_triggeringprincipal_frame_same_origin_nav.html";
+
+const LOADING_PRINCIPAL_URI = TRIGGERING_PRINCIPAL_URI;
+
+function receiveMessage(event) {
+ is(event.data.triggeringPrincipalURI.split("?")[0], TRIGGERING_PRINCIPAL_URI,
+ "TriggeringPrincipal should be the parent iframe");
+ is(event.data.loadingPrincipalURI.split("?")[0], TRIGGERING_PRINCIPAL_URI,
+ "LoadingPrincipal should be the parent iframe");
+
+ window.removeEventListener("message", receiveMessage);
+ SimpleTest.finish();
+}
+
+function performNavigation() {
+ testframe.removeEventListener("load", performNavigation);
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ testframe.setAttribute("src", "http://example.com/tests/docshell/test/navigation/file_triggeringprincipal_subframe_same_origin_nav.html");
+}
+
+// start the test
+SimpleTest.waitForExplicitFinish();
+
+testframe.addEventListener("load", performNavigation);
+</script>
+
+</body>
+</html>
diff --git a/docshell/test/navigation/test_triggeringprincipal_iframe_iframe_window_open.html b/docshell/test/navigation/test_triggeringprincipal_iframe_iframe_window_open.html
new file mode 100644
index 0000000000..115c5f4462
--- /dev/null
+++ b/docshell/test/navigation/test_triggeringprincipal_iframe_iframe_window_open.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+</head>
+<body>
+
+<iframe name="framea" id="framea" src="file_triggeringprincipal_iframe_iframe_window_open_frame_a.html"></iframe>
+<iframe name="frameb" id="frameb"></iframe>
+
+<script type="text/javascript">
+
+/* We load an iframe (Frame A) which then gets navigated by another iframe (Frame B)
+ * by calling window.open("http://", "Frame A") later in the test. We then verify the
+ * TriggeringPrincipal and LoadingPrincipal of the navigated iframe (Frame A).
+ *
+ * +---------------------------------------+
+ * | Parent |
+ * | |
+ * | +----------------------------+ |
+ * | | Frame A | |
+ * | | | |
+ * | | | |
+ * | +----------------------------+ |
+ * | |
+ * | +----------------------------+ |
+ * | | Frame B | |
+ * | | | |
+ * | | win.open("http://", "A") | |
+ * | +----------------------------+ |
+ * | |
+ * +---------------------------------------+
+ *
+ * Sequence of the test:
+ * [1] load Frame A
+ * [2] load Frame B which navigates A
+ * [3] load navigated Frame A and check triggeringPrincipal and loadingPrincipal
+ */
+
+const TRIGGERING_PRINCIPAL_URI =
+ "http://mochi.test:8888/tests/docshell/test/navigation/file_triggeringprincipal_iframe_iframe_window_open_frame_b.html";
+
+const LOADING_PRINCIPAL_URI =
+ "http://mochi.test:8888/tests/docshell/test/navigation/test_triggeringprincipal_iframe_iframe_window_open.html";
+
+var frameA = document.getElementById("framea");
+
+function checkResults() {
+ frameA.removeEventListener("load", checkResults);
+
+ var channel = SpecialPowers.wrap(frameA.contentWindow).docShell.currentDocumentChannel;
+ var triggeringPrincipal = channel.loadInfo.triggeringPrincipal.asciiSpec;
+ var loadingPrincipal = channel.loadInfo.loadingPrincipal.asciiSpec;
+
+ is(triggeringPrincipal, TRIGGERING_PRINCIPAL_URI,
+ "TriggeringPrincipal for targeted window.open() should be the iframe triggering the load");
+
+ is(frameA.contentDocument.referrer, TRIGGERING_PRINCIPAL_URI,
+ "Referrer for targeted window.open() should be the principal of the iframe triggering the load");
+
+ is(loadingPrincipal.split("?")[0], LOADING_PRINCIPAL_URI,
+ "LoadingPrincipal for targeted window.open() should be the containing document");
+
+ SimpleTest.finish();
+}
+
+function performNavigation() {
+ frameA.removeEventListener("load", performNavigation);
+ frameA.addEventListener("load", checkResults);
+
+ // load Frame B which then navigates Frame A
+ var frameB = document.getElementById("frameb");
+ frameB.src = "file_triggeringprincipal_iframe_iframe_window_open_frame_b.html";
+}
+
+// start the test
+SimpleTest.waitForExplicitFinish();
+
+frameA.addEventListener("load", performNavigation);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_triggeringprincipal_parent_iframe_window_open.html b/docshell/test/navigation/test_triggeringprincipal_parent_iframe_window_open.html
new file mode 100644
index 0000000000..1611ebf479
--- /dev/null
+++ b/docshell/test/navigation/test_triggeringprincipal_parent_iframe_window_open.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+</head>
+<body>
+
+<iframe name="testframe" id="testframe" src="file_triggeringprincipal_iframe_iframe_window_open_base.html"></iframe>
+
+<script type="text/javascript">
+
+/* We load an iframe which then gets navigated by the iframe's parent by calling
+ * window.open("http://", iframe) later in the test. We then verify the
+ * TriggeringPrincipal and LoadingPrincipal of the navigated iframe.
+ *
+ * +------------------------------------------+
+ * | |
+ * | +------------------+ |
+ * | | testframe | |
+ * | +------------------+ |
+ * | |
+ * | window.open("http://", "testframe"); |
+ * | |
+ * +------------------------------------------+
+ */
+
+const TRIGGERING_PRINCIPAL_URI =
+ "http://mochi.test:8888/tests/docshell/test/navigation/test_triggeringprincipal_parent_iframe_window_open.html";
+
+const LOADING_PRINCIPAL_URI = TRIGGERING_PRINCIPAL_URI;
+
+var testframe = document.getElementById("testframe");
+
+function checkResults() {
+ testframe.removeEventListener("load", checkResults);
+
+ var channel = SpecialPowers.wrap(testframe.contentWindow).docShell.currentDocumentChannel;
+ var triggeringPrincipal = channel.loadInfo.triggeringPrincipal.asciiSpec.split("?")[0];
+ var loadingPrincipal = channel.loadInfo.loadingPrincipal.asciiSpec.split("?")[0];
+
+ is(triggeringPrincipal, TRIGGERING_PRINCIPAL_URI,
+ "TriggeringPrincipal for targeted window.open() should be the principal of the document");
+
+ is(testframe.contentDocument.referrer.split("?")[0], TRIGGERING_PRINCIPAL_URI,
+ "Referrer for targeted window.open() should be the principal of the document");
+
+ is(loadingPrincipal, LOADING_PRINCIPAL_URI,
+ "LoadingPrincipal for targeted window.open() should be the <iframe>.ownerDocument");
+
+ SimpleTest.finish();
+}
+
+function performNavigation() {
+ testframe.removeEventListener("load", performNavigation);
+ testframe.addEventListener("load", checkResults);
+ window.open("file_triggeringprincipal_parent_iframe_window_open_nav.html", "testframe");
+}
+
+// start the test
+SimpleTest.waitForExplicitFinish();
+
+testframe.addEventListener("load", performNavigation);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/navigation/test_triggeringprincipal_window_open.html b/docshell/test/navigation/test_triggeringprincipal_window_open.html
new file mode 100644
index 0000000000..439a125f97
--- /dev/null
+++ b/docshell/test/navigation/test_triggeringprincipal_window_open.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="NavigationUtils.js"></script>
+</head>
+<body>
+
+<script type="text/javascript">
+
+/* We call window.open() using different URIs and make sure the triggeringPrincipal
+ * loadingPrincipal are correct.
+ * Test1: window.open(http:)
+ * Test2: window.open(javascript:)
+ */
+
+const TRIGGERING_PRINCIPAL_URI =
+ "http://mochi.test:8888/tests/docshell/test/navigation/test_triggeringprincipal_window_open.html";
+
+SimpleTest.waitForExplicitFinish();
+
+const NUM_TESTS = 2;
+var test_counter = 0;
+
+function checkFinish() {
+ test_counter++;
+ if (test_counter === NUM_TESTS) {
+ SimpleTest.finish();
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Test 1: window.open(http:)
+var httpWin = window.open("file_triggeringprincipal_window_open.html", "_blank", "width=10,height=10");
+httpWin.onload = function() {
+ var httpChannel = SpecialPowers.wrap(httpWin).docShell.currentDocumentChannel;
+ var httpTriggeringPrincipal = httpChannel.loadInfo.triggeringPrincipal.asciiSpec;
+ var httpLoadingPrincipal = httpChannel.loadInfo.loadingPrincipal;
+
+ is(httpTriggeringPrincipal.split("?")[0], TRIGGERING_PRINCIPAL_URI,
+ "TriggeringPrincipal for window.open(http:) should be the principal of the document");
+
+ is(httpWin.document.referrer.split("?")[0], TRIGGERING_PRINCIPAL_URI,
+ "Referrer for window.open(http:) should be the principal of the document");
+
+ is(httpLoadingPrincipal, null,
+ "LoadingPrincipal for window.open(http:) should be null");
+
+ httpWin.close();
+ checkFinish();
+};
+
+// ----------------------------------------------------------------------------
+// Test 2: window.open(javascript:)
+var jsWin = window.open("javascript:'<html><body>js</body></html>';", "_blank", "width=10,height=10");
+jsWin.onload = function() {
+ var jsChannel = SpecialPowers.wrap(jsWin).docShell.currentDocumentChannel;
+ var jsTriggeringPrincipal = jsChannel.loadInfo.triggeringPrincipal.asciiSpec;
+ var jsLoadingPrincipal = jsChannel.loadInfo.loadingPrincipal;
+
+ is(jsTriggeringPrincipal.split("?")[0], TRIGGERING_PRINCIPAL_URI,
+ "TriggeringPrincipal for window.open(javascript:) should be the principal of the document");
+
+ is(jsWin.document.referrer, "",
+ "Referrer for window.open(javascript:) should be empty");
+
+ is(jsLoadingPrincipal, null,
+ "LoadingPrincipal for window.open(javascript:) should be null");
+
+ jsWin.close();
+ checkFinish();
+};
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/docshell/test/unit/AllowJavascriptChild.sys.mjs b/docshell/test/unit/AllowJavascriptChild.sys.mjs
new file mode 100644
index 0000000000..a7c3fa6172
--- /dev/null
+++ b/docshell/test/unit/AllowJavascriptChild.sys.mjs
@@ -0,0 +1,41 @@
+export class AllowJavascriptChild extends JSWindowActorChild {
+ async receiveMessage(msg) {
+ switch (msg.name) {
+ case "CheckScriptsAllowed":
+ return this.checkScriptsAllowed();
+ case "CheckFiredLoadEvent":
+ return this.contentWindow.wrappedJSObject.gFiredOnload;
+ case "CreateIframe":
+ return this.createIframe(msg.data.url);
+ }
+ return null;
+ }
+
+ handleEvent(event) {
+ if (event.type === "load") {
+ this.sendAsyncMessage("LoadFired");
+ }
+ }
+
+ checkScriptsAllowed() {
+ let win = this.contentWindow;
+
+ win.wrappedJSObject.gFiredOnclick = false;
+ win.document.body.click();
+ return win.wrappedJSObject.gFiredOnclick;
+ }
+
+ async createIframe(url) {
+ let doc = this.contentWindow.document;
+
+ let iframe = doc.createElement("iframe");
+ iframe.src = url;
+ doc.body.appendChild(iframe);
+
+ await new Promise(resolve => {
+ iframe.addEventListener("load", resolve, { once: true });
+ });
+
+ return iframe.browsingContext;
+ }
+}
diff --git a/docshell/test/unit/AllowJavascriptParent.sys.mjs b/docshell/test/unit/AllowJavascriptParent.sys.mjs
new file mode 100644
index 0000000000..5631fcdb09
--- /dev/null
+++ b/docshell/test/unit/AllowJavascriptParent.sys.mjs
@@ -0,0 +1,28 @@
+let loadPromises = new WeakMap();
+
+export class AllowJavascriptParent extends JSWindowActorParent {
+ async receiveMessage(msg) {
+ switch (msg.name) {
+ case "LoadFired":
+ let bc = this.browsingContext;
+ let deferred = loadPromises.get(bc);
+ if (deferred) {
+ loadPromises.delete(bc);
+ deferred.resolve(this);
+ }
+ break;
+ }
+ }
+
+ static promiseLoad(bc) {
+ let deferred = loadPromises.get(bc);
+ if (!deferred) {
+ deferred = {};
+ deferred.promise = new Promise(resolve => {
+ deferred.resolve = resolve;
+ });
+ loadPromises.set(bc, deferred);
+ }
+ return deferred.promise;
+ }
+}
diff --git a/docshell/test/unit/data/engine.xml b/docshell/test/unit/data/engine.xml
new file mode 100644
index 0000000000..3a2bd85c1b
--- /dev/null
+++ b/docshell/test/unit/data/engine.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>test_urifixup_search_engine</ShortName>
+<Description>test_urifixup_search_engine</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Url type="text/html" method="GET" template="https://www.example.org/">
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<SearchForm>https://www.example.org/</SearchForm>
+</SearchPlugin>
diff --git a/docshell/test/unit/data/enginePost.xml b/docshell/test/unit/data/enginePost.xml
new file mode 100644
index 0000000000..14775b6f0a
--- /dev/null
+++ b/docshell/test/unit/data/enginePost.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>test_urifixup_search_engine_post</ShortName>
+<Description>test_urifixup_search_engine_post</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Url type="text/html" method="POST" template="https://www.example.org/">
+ <Param name="q" value="{searchTerms}"/>
+</Url>
+<SearchForm>https://www.example.org/</SearchForm>
+</SearchPlugin>
diff --git a/docshell/test/unit/data/enginePrivate.xml b/docshell/test/unit/data/enginePrivate.xml
new file mode 100644
index 0000000000..7d87de98fa
--- /dev/null
+++ b/docshell/test/unit/data/enginePrivate.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>test_urifixup_search_engine_private</ShortName>
+<Description>test_urifixup_search_engine_private</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Url type="text/html" method="GET" template="https://www.example.org/">
+ <Param name="private" value="{searchTerms}"/>
+</Url>
+<SearchForm>https://www.example.org/</SearchForm>
+</SearchPlugin>
diff --git a/docshell/test/unit/head_docshell.js b/docshell/test/unit/head_docshell.js
new file mode 100644
index 0000000000..5b0369089e
--- /dev/null
+++ b/docshell/test/unit/head_docshell.js
@@ -0,0 +1,106 @@
+/* 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/. */
+
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+ChromeUtils.defineESModuleGetters(this, {
+ AddonTestUtils: "resource://testing-common/AddonTestUtils.sys.mjs",
+ SearchTestUtils: "resource://testing-common/SearchTestUtils.sys.mjs",
+ SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs",
+ TestUtils: "resource://testing-common/TestUtils.sys.mjs",
+});
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ HttpServer: "resource://testing-common/httpd.js",
+ NetUtil: "resource://gre/modules/NetUtil.jsm",
+});
+
+var profileDir = do_get_profile();
+
+const kSearchEngineID = "test_urifixup_search_engine";
+const kSearchEngineURL = "https://www.example.org/?search={searchTerms}";
+const kPrivateSearchEngineID = "test_urifixup_search_engine_private";
+const kPrivateSearchEngineURL =
+ "https://www.example.org/?private={searchTerms}";
+const kPostSearchEngineID = "test_urifixup_search_engine_post";
+const kPostSearchEngineURL = "https://www.example.org/";
+const kPostSearchEngineData = "q={searchTerms}";
+
+const SEARCH_CONFIG = [
+ {
+ appliesTo: [
+ {
+ included: {
+ everywhere: true,
+ },
+ },
+ ],
+ default: "yes",
+ webExtension: {
+ id: "fixup_search@search.mozilla.org",
+ },
+ },
+];
+
+async function setupSearchService() {
+ SearchTestUtils.init(this);
+
+ AddonTestUtils.init(this);
+ AddonTestUtils.overrideCertDB();
+ AddonTestUtils.createAppInfo(
+ "xpcshell@tests.mozilla.org",
+ "XPCShell",
+ "1",
+ "42"
+ );
+
+ await SearchTestUtils.useTestEngines(".", null, SEARCH_CONFIG);
+ await AddonTestUtils.promiseStartupManager();
+ await Services.search.init();
+}
+
+/**
+ * After useHttpServer() is called, this string contains the URL of the "data"
+ * directory, including the final slash.
+ */
+var gDataUrl;
+
+/**
+ * Initializes the HTTP server and ensures that it is terminated when tests end.
+ *
+ * @param {string} dir
+ * The test sub-directory to use for the engines.
+ * @returns {HttpServer}
+ * The HttpServer object in case further customization is needed.
+ */
+function useHttpServer(dir = "data") {
+ let httpServer = new HttpServer();
+ httpServer.start(-1);
+ httpServer.registerDirectory("/", do_get_cwd());
+ gDataUrl = `http://localhost:${httpServer.identity.primaryPort}/${dir}/`;
+ registerCleanupFunction(async function cleanup_httpServer() {
+ await new Promise(resolve => {
+ httpServer.stop(resolve);
+ });
+ });
+ return httpServer;
+}
+
+async function addTestEngines() {
+ useHttpServer();
+ // This is a hack, ideally we should be setting up a configuration with
+ // built-in engines, but the `chrome_settings_overrides` section that
+ // WebExtensions need is only defined for browser/
+ await SearchTestUtils.promiseNewSearchEngine({
+ url: `${gDataUrl}/engine.xml`,
+ });
+ await SearchTestUtils.promiseNewSearchEngine({
+ url: `${gDataUrl}/enginePrivate.xml`,
+ });
+ await SearchTestUtils.promiseNewSearchEngine({
+ url: `${gDataUrl}/enginePost.xml`,
+ });
+}
diff --git a/docshell/test/unit/test_URIFixup.js b/docshell/test/unit/test_URIFixup.js
new file mode 100644
index 0000000000..7967933b56
--- /dev/null
+++ b/docshell/test/unit/test_URIFixup.js
@@ -0,0 +1,123 @@
+var pref = "browser.fixup.typo.scheme";
+
+var data = [
+ {
+ // ttp -> http.
+ wrong: "ttp://www.example.com/",
+ fixed: "http://www.example.com/",
+ },
+ {
+ // htp -> http.
+ wrong: "htp://www.example.com/",
+ fixed: "http://www.example.com/",
+ },
+ {
+ // ttps -> https.
+ wrong: "ttps://www.example.com/",
+ fixed: "https://www.example.com/",
+ },
+ {
+ // tps -> https.
+ wrong: "tps://www.example.com/",
+ fixed: "https://www.example.com/",
+ },
+ {
+ // ps -> https.
+ wrong: "ps://www.example.com/",
+ fixed: "https://www.example.com/",
+ },
+ {
+ // htps -> https.
+ wrong: "htps://www.example.com/",
+ fixed: "https://www.example.com/",
+ },
+ {
+ // ile -> file.
+ wrong: "ile:///this/is/a/test.html",
+ fixed: "file:///this/is/a/test.html",
+ },
+ {
+ // le -> file.
+ wrong: "le:///this/is/a/test.html",
+ fixed: "file:///this/is/a/test.html",
+ },
+ {
+ // Replace ';' with ':'.
+ wrong: "http;//www.example.com/",
+ fixed: "http://www.example.com/",
+ noPrefValue: "http://http;//www.example.com/",
+ },
+ {
+ // Missing ':'.
+ wrong: "https//www.example.com/",
+ fixed: "https://www.example.com/",
+ noPrefValue: "http://https//www.example.com/",
+ },
+ {
+ // Missing ':' for file scheme.
+ wrong: "file///this/is/a/test.html",
+ fixed: "file:///this/is/a/test.html",
+ noPrefValue: "http://file///this/is/a/test.html",
+ },
+ {
+ // Valid should not be changed.
+ wrong: "https://example.com/this/is/a/test.html",
+ fixed: "https://example.com/this/is/a/test.html",
+ },
+ {
+ // Unmatched should not be changed.
+ wrong: "whatever://this/is/a/test.html",
+ fixed: "whatever://this/is/a/test.html",
+ },
+];
+
+var len = data.length;
+
+add_task(async function setup() {
+ await setupSearchService();
+ // Now we've initialised the search service, we force remove the engines
+ // it has, so they don't interfere with this test.
+ // Search engine integration is tested in test_URIFixup_search.js.
+ Services.search.wrappedJSObject._engines.clear();
+});
+
+// Make sure we fix what needs fixing when there is no pref set.
+add_task(function test_unset_pref_fixes_typos() {
+ Services.prefs.clearUserPref(pref);
+ for (let i = 0; i < len; ++i) {
+ let item = data[i];
+ let { preferredURI } = Services.uriFixup.getFixupURIInfo(
+ item.wrong,
+ Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS
+ );
+ Assert.equal(preferredURI.spec, item.fixed);
+ }
+});
+
+// Make sure we don't do anything when the pref is explicitly
+// set to false.
+add_task(function test_false_pref_keeps_typos() {
+ Services.prefs.setBoolPref(pref, false);
+ for (let i = 0; i < len; ++i) {
+ let item = data[i];
+ let { preferredURI } = Services.uriFixup.getFixupURIInfo(
+ item.wrong,
+ Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS
+ );
+ Assert.equal(preferredURI.spec, item.noPrefValue || item.wrong);
+ }
+});
+
+// Finally, make sure we still fix what needs fixing if the pref is
+// explicitly set to true.
+add_task(function test_true_pref_fixes_typos() {
+ Services.prefs.setBoolPref(pref, true);
+ for (let i = 0; i < len; ++i) {
+ let item = data[i];
+ let { preferredURI } = Services.uriFixup.getFixupURIInfo(
+ item.wrong,
+ Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS
+ );
+ Assert.equal(preferredURI.spec, item.fixed);
+ }
+});
diff --git a/docshell/test/unit/test_URIFixup_check_host.js b/docshell/test/unit/test_URIFixup_check_host.js
new file mode 100644
index 0000000000..132d74ca9b
--- /dev/null
+++ b/docshell/test/unit/test_URIFixup_check_host.js
@@ -0,0 +1,183 @@
+/* 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 lazy = {};
+
+// Ensure DNS lookups don't hit the server
+XPCOMUtils.defineLazyServiceGetter(
+ lazy,
+ "gDNSOverride",
+ "@mozilla.org/network/native-dns-override;1",
+ "nsINativeDNSResolverOverride"
+);
+
+add_task(async function setup() {
+ Services.prefs.setStringPref("browser.fixup.alternate.prefix", "www.");
+ Services.prefs.setStringPref("browser.fixup.alternate.suffix", ".com");
+ Services.prefs.setStringPref("browser.fixup.alternate.protocol", "https");
+ Services.prefs.setBoolPref(
+ "browser.urlbar.dnsResolveFullyQualifiedNames",
+ true
+ );
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("browser.fixup.alternate.prefix");
+ Services.prefs.clearUserPref("browser.fixup.alternate.suffix");
+ Services.prefs.clearUserPref("browser.fixup.alternate.protocol");
+ Services.prefs.clearUserPref(
+ "browser.urlbar.dnsResolveFullyQualifiedNames"
+ );
+ });
+});
+
+// TODO: Export Listener from test_dns_override instead of duping
+class Listener {
+ constructor() {
+ this.promise = new Promise(resolve => {
+ this.resolve = resolve;
+ });
+ }
+
+ onLookupComplete(inRequest, inRecord, inStatus) {
+ this.resolve([inRequest, inRecord, inStatus]);
+ }
+
+ async firstAddress() {
+ let all = await this.addresses();
+ if (all.length) {
+ return all[0];
+ }
+ return undefined;
+ }
+
+ async addresses() {
+ let [, inRecord] = await this.promise;
+ let addresses = [];
+ if (!inRecord) {
+ return addresses; // returns []
+ }
+ inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
+ while (inRecord.hasMore()) {
+ addresses.push(inRecord.getNextAddrAsString());
+ }
+ return addresses;
+ }
+
+ then() {
+ return this.promise.then.apply(this.promise, arguments);
+ }
+}
+
+const FAKE_IP = "::1";
+const FAKE_INTRANET_IP = "::2";
+const ORIGIN_ATTRIBUTE = {};
+
+add_task(async function test_uri_with_force_fixup() {
+ let listener = new Listener();
+ let { fixedURI } = Services.uriFixup.forceHttpFixup("http://www.example.com");
+
+ lazy.gDNSOverride.addIPOverride(fixedURI.displayHost, FAKE_IP);
+
+ Services.uriFixup.checkHost(fixedURI, listener, ORIGIN_ATTRIBUTE);
+ Assert.equal(
+ await listener.firstAddress(),
+ FAKE_IP,
+ "Should've received fake IP"
+ );
+
+ lazy.gDNSOverride.clearHostOverride(fixedURI.displayHost);
+ Services.dns.clearCache(false);
+});
+
+add_task(async function test_uri_with_get_fixup() {
+ let listener = new Listener();
+ let uri = Services.io.newURI("http://www.example.com");
+
+ lazy.gDNSOverride.addIPOverride(uri.displayHost, FAKE_IP);
+
+ Services.uriFixup.checkHost(uri, listener, ORIGIN_ATTRIBUTE);
+ Assert.equal(
+ await listener.firstAddress(),
+ FAKE_IP,
+ "Should've received fake IP"
+ );
+
+ lazy.gDNSOverride.clearHostOverride(uri.displayHost);
+ Services.dns.clearCache(false);
+});
+
+add_task(async function test_intranet_like_uri() {
+ let listener = new Listener();
+ let uri = Services.io.newURI("http://someintranet");
+
+ lazy.gDNSOverride.addIPOverride(uri.displayHost, FAKE_IP);
+ // Hosts without periods should end with a period to make them FQN
+ lazy.gDNSOverride.addIPOverride(uri.displayHost + ".", FAKE_INTRANET_IP);
+
+ Services.uriFixup.checkHost(uri, listener, ORIGIN_ATTRIBUTE);
+ Assert.deepEqual(
+ await listener.addresses(),
+ FAKE_INTRANET_IP,
+ "Should've received fake intranet IP"
+ );
+
+ lazy.gDNSOverride.clearHostOverride(uri.displayHost);
+ lazy.gDNSOverride.clearHostOverride(uri.displayHost + ".");
+ Services.dns.clearCache(false);
+});
+
+add_task(async function test_intranet_like_uri_without_fixup() {
+ let listener = new Listener();
+ let uri = Services.io.newURI("http://someintranet");
+ Services.prefs.setBoolPref(
+ "browser.urlbar.dnsResolveFullyQualifiedNames",
+ false
+ );
+
+ lazy.gDNSOverride.addIPOverride(uri.displayHost, FAKE_IP);
+ // Hosts without periods should end with a period to make them FQN
+ lazy.gDNSOverride.addIPOverride(uri.displayHost + ".", FAKE_INTRANET_IP);
+
+ Services.uriFixup.checkHost(uri, listener, ORIGIN_ATTRIBUTE);
+ Assert.deepEqual(
+ await listener.addresses(),
+ FAKE_IP,
+ "Should've received non-fixed up IP"
+ );
+
+ lazy.gDNSOverride.clearHostOverride(uri.displayHost);
+ lazy.gDNSOverride.clearHostOverride(uri.displayHost + ".");
+ Services.dns.clearCache(false);
+});
+
+add_task(async function test_ip_address() {
+ let listener = new Listener();
+ let uri = Services.io.newURI("http://192.168.0.1");
+
+ // Testing IP address being passed into the method
+ // requires observing if the asyncResolve method gets called
+ let didResolve = false;
+ let topic = "uri-fixup-check-dns";
+ let observer = {
+ observe(aSubject, aTopicInner, aData) {
+ if (aTopicInner == topic) {
+ didResolve = true;
+ }
+ },
+ };
+ Services.obs.addObserver(observer, topic);
+ lazy.gDNSOverride.addIPOverride(uri.displayHost, FAKE_IP);
+
+ Services.uriFixup.checkHost(uri, listener, ORIGIN_ATTRIBUTE);
+ Assert.equal(
+ didResolve,
+ false,
+ "Passing an IP address should not conduct a host lookup"
+ );
+
+ lazy.gDNSOverride.clearHostOverride(uri.displayHost);
+ Services.dns.clearCache(false);
+ Services.obs.removeObserver(observer, topic);
+});
diff --git a/docshell/test/unit/test_URIFixup_external_protocol_fallback.js b/docshell/test/unit/test_URIFixup_external_protocol_fallback.js
new file mode 100644
index 0000000000..0846070b7a
--- /dev/null
+++ b/docshell/test/unit/test_URIFixup_external_protocol_fallback.js
@@ -0,0 +1,106 @@
+/* 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";
+
+// Test whether fallback mechanism is working if don't trust nsIExternalProtocolService.
+
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+const { MockRegistrar } = ChromeUtils.importESModule(
+ "resource://testing-common/MockRegistrar.sys.mjs"
+);
+
+add_task(async function setup() {
+ info(
+ "Prepare mock nsIExternalProtocolService whose externalProtocolHandlerExists returns always true"
+ );
+ const externalProtocolService = Cc[
+ "@mozilla.org/uriloader/external-protocol-service;1"
+ ].getService(Ci.nsIExternalProtocolService);
+ const mockId = MockRegistrar.register(
+ "@mozilla.org/uriloader/external-protocol-service;1",
+ {
+ getProtocolHandlerInfo: scheme =>
+ externalProtocolService.getProtocolHandlerInfo(scheme),
+ externalProtocolHandlerExists: () => true,
+ QueryInterface: ChromeUtils.generateQI(["nsIExternalProtocolService"]),
+ }
+ );
+ const mockExternalProtocolService = Cc[
+ "@mozilla.org/uriloader/external-protocol-service;1"
+ ].getService(Ci.nsIExternalProtocolService);
+ Assert.ok(
+ mockExternalProtocolService.externalProtocolHandlerExists("__invalid__"),
+ "Mock service is working"
+ );
+
+ info("Register new dummy protocol");
+ const dummyProtocolHandlerInfo =
+ externalProtocolService.getProtocolHandlerInfo("dummy");
+ const handlerService = Cc[
+ "@mozilla.org/uriloader/handler-service;1"
+ ].getService(Ci.nsIHandlerService);
+ handlerService.store(dummyProtocolHandlerInfo);
+
+ info("Prepare test search engine");
+ await setupSearchService();
+ await addTestEngines();
+ await Services.search.setDefault(
+ Services.search.getEngineByName(kSearchEngineID),
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+
+ registerCleanupFunction(() => {
+ handlerService.remove(dummyProtocolHandlerInfo);
+ MockRegistrar.unregister(mockId);
+ });
+});
+
+add_task(function basic() {
+ const testData = [
+ {
+ input: "mailto:test@example.com",
+ expected:
+ isSupportedInHandlerService("mailto") ||
+ // Thunderbird IS a mailto handler, it doesn't have handlers.
+ AppConstants.MOZ_APP_NAME == "thunderbird"
+ ? "mailto:test@example.com"
+ : "http://mailto:test@example.com/",
+ },
+ {
+ input: "keyword:search",
+ expected: "https://www.example.org/?search=keyword%3Asearch",
+ },
+ {
+ input: "dummy:protocol",
+ expected: "dummy:protocol",
+ },
+ ];
+
+ for (const { input, expected } of testData) {
+ assertFixup(input, expected);
+ }
+});
+
+function assertFixup(input, expected) {
+ const { preferredURI } = Services.uriFixup.getFixupURIInfo(
+ input,
+ Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS
+ );
+ Assert.equal(preferredURI.spec, expected);
+}
+
+function isSupportedInHandlerService(scheme) {
+ const externalProtocolService = Cc[
+ "@mozilla.org/uriloader/external-protocol-service;1"
+ ].getService(Ci.nsIExternalProtocolService);
+ const handlerService = Cc[
+ "@mozilla.org/uriloader/handler-service;1"
+ ].getService(Ci.nsIHandlerService);
+ return handlerService.exists(
+ externalProtocolService.getProtocolHandlerInfo(scheme)
+ );
+}
diff --git a/docshell/test/unit/test_URIFixup_forced.js b/docshell/test/unit/test_URIFixup_forced.js
new file mode 100644
index 0000000000..1d4dfd94a7
--- /dev/null
+++ b/docshell/test/unit/test_URIFixup_forced.js
@@ -0,0 +1,159 @@
+/* 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";
+
+var data = [
+ {
+ // singleword.
+ wrong: "http://example/",
+ fixed: "https://www.example.com/",
+ },
+ {
+ // upgrade protocol.
+ wrong: "http://www.example.com/",
+ fixed: "https://www.example.com/",
+ noAlternateURI: true,
+ },
+ {
+ // no difference.
+ wrong: "https://www.example.com/",
+ fixed: "https://www.example.com/",
+ noProtocolFixup: true,
+ noAlternateURI: true,
+ },
+ {
+ // don't add prefix when there's more than one dot.
+ wrong: "http://www.example.abc.def/",
+ fixed: "https://www.example.abc.def/",
+ noAlternateURI: true,
+ },
+ {
+ // http -> https.
+ wrong: "http://www.example/",
+ fixed: "https://www.example/",
+ noAlternateURI: true,
+ },
+ {
+ // domain.com -> https://www.domain.com.
+ wrong: "http://example.com/",
+ fixed: "https://www.example.com/",
+ },
+ {
+ // example/example... -> https://www.example.com/example/
+ wrong: "http://example/example/",
+ fixed: "https://www.example.com/example/",
+ },
+ {
+ // example/example/s#q -> www.example.com/example/s#q.
+ wrong: "http://example/example/s#q",
+ fixed: "https://www.example.com/example/s#q",
+ },
+ {
+ wrong: "http://モジラ.org",
+ fixed: "https://www.xn--yck6dwa.org/",
+ },
+ {
+ wrong: "http://モジラ",
+ fixed: "https://www.xn--yck6dwa.com/",
+ },
+ {
+ wrong: "http://xn--yck6dwa",
+ fixed: "https://www.xn--yck6dwa.com/",
+ },
+ {
+ wrong: "https://モジラ.org",
+ fixed: "https://www.xn--yck6dwa.org/",
+ noProtocolFixup: true,
+ },
+ {
+ wrong: "https://モジラ",
+ fixed: "https://www.xn--yck6dwa.com/",
+ noProtocolFixup: true,
+ },
+ {
+ wrong: "https://xn--yck6dwa",
+ fixed: "https://www.xn--yck6dwa.com/",
+ noProtocolFixup: true,
+ },
+ {
+ // Host is https -> fixup typos of protocol
+ wrong: "htp://https://mozilla.org",
+ fixed: "http://https//mozilla.org",
+ noAlternateURI: true,
+ },
+ {
+ // Host is http -> fixup typos of protocol
+ wrong: "ttp://http://mozilla.org",
+ fixed: "http://http//mozilla.org",
+ noAlternateURI: true,
+ },
+ {
+ // Host is localhost -> fixup typos of protocol
+ wrong: "htps://localhost://mozilla.org",
+ fixed: "https://localhost//mozilla.org",
+ noAlternateURI: true,
+ },
+ {
+ // view-source is not http/https -> error
+ wrong: "view-source:http://example/example/example/example",
+ reject: true,
+ comment: "Scheme should be either http or https",
+ },
+ {
+ // file is not http/https -> error
+ wrong: "file://http://example/example/example/example",
+ reject: true,
+ comment: "Scheme should be either http or https",
+ },
+ {
+ // Protocol is missing -> error
+ wrong: "example.com",
+ reject: true,
+ comment: "Scheme should be either http or https",
+ },
+ {
+ // Empty input -> error
+ wrong: "",
+ reject: true,
+ comment: "Should pass a non-null uri",
+ },
+];
+
+add_task(async function setup() {
+ Services.prefs.setStringPref("browser.fixup.alternate.prefix", "www.");
+ Services.prefs.setStringPref("browser.fixup.alternate.suffix", ".com");
+ Services.prefs.setStringPref("browser.fixup.alternate.protocol", "https");
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("browser.fixup.alternate.prefix");
+ Services.prefs.clearUserPref("browser.fixup.alternate.suffix");
+ Services.prefs.clearUserPref("browser.fixup.alternate.protocol");
+ });
+});
+
+add_task(function test_default_https_pref() {
+ for (let item of data) {
+ if (item.reject) {
+ Assert.throws(
+ () => Services.uriFixup.forceHttpFixup(item.wrong),
+ /NS_ERROR_FAILURE/,
+ item.comment
+ );
+ } else {
+ let { fixupChangedProtocol, fixupCreatedAlternateURI, fixedURI } =
+ Services.uriFixup.forceHttpFixup(item.wrong);
+ Assert.equal(fixedURI.spec, item.fixed, "Specs should be the same");
+ Assert.equal(
+ fixupChangedProtocol,
+ !item.noProtocolFixup,
+ `fixupChangedProtocol should be ${!item.noAlternateURI}`
+ );
+ Assert.equal(
+ fixupCreatedAlternateURI,
+ !item.noAlternateURI,
+ `fixupCreatedAlternateURI should be ${!item.limitedFixup}`
+ );
+ }
+ }
+});
diff --git a/docshell/test/unit/test_URIFixup_info.js b/docshell/test/unit/test_URIFixup_info.js
new file mode 100644
index 0000000000..89514cb00c
--- /dev/null
+++ b/docshell/test/unit/test_URIFixup_info.js
@@ -0,0 +1,1075 @@
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+const kForceDNSLookup = "browser.fixup.dns_first_for_single_words";
+const kFixupEnabled = "browser.fixup.alternate.enabled";
+
+// TODO(bug 1522134), this test should also use
+// combinations of the following flags.
+var flagInputs = [
+ Services.uriFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP,
+ Services.uriFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP |
+ Services.uriFixup.FIXUP_FLAG_PRIVATE_CONTEXT,
+ Services.uriFixup.FIXUP_FLAGS_MAKE_ALTERNATE_URI,
+ Services.uriFixup.FIXUP_FLAG_FORCE_ALTERNATE_URI,
+ Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS,
+ // This should not really generate a search, but it does, see Bug 1588118.
+ Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS |
+ Services.uriFixup.FIXUP_FLAG_PRIVATE_CONTEXT,
+];
+
+/*
+ The following properties are supported for these test cases:
+ {
+ input: "", // Input string, required
+ fixedURI: "", // Expected fixedURI
+ alternateURI: "", // Expected alternateURI
+ keywordLookup: false, // Whether a keyword lookup is expected
+ protocolChange: false, // Whether a protocol change is expected
+ inWhitelist: false, // Whether the input host is in the whitelist
+ affectedByDNSForSingleWordHosts: false, // Whether the input host could be a host, but is normally assumed to be a keyword query
+ }
+*/
+var testcases = [
+ {
+ input: "about:home",
+ fixedURI: "about:home",
+ },
+ {
+ input: "http://www.mozilla.org",
+ fixedURI: "http://www.mozilla.org/",
+ },
+ {
+ input: "http://127.0.0.1/",
+ fixedURI: "http://127.0.0.1/",
+ },
+ {
+ input: "file:///foo/bar",
+ fixedURI: "file:///foo/bar",
+ },
+ {
+ input: "://www.mozilla.org",
+ fixedURI: "http://www.mozilla.org/",
+ protocolChange: true,
+ },
+ {
+ input: "www.mozilla.org",
+ fixedURI: "http://www.mozilla.org/",
+ protocolChange: true,
+ },
+ {
+ input: "http://mozilla/",
+ fixedURI: "http://mozilla/",
+ alternateURI: "https://www.mozilla.com/",
+ },
+ {
+ input: "http://test./",
+ fixedURI: "http://test./",
+ alternateURI: "https://www.test./",
+ },
+ {
+ input: "127.0.0.1",
+ fixedURI: "http://127.0.0.1/",
+ protocolChange: true,
+ },
+ {
+ input: "1.2.3.4/",
+ fixedURI: "http://1.2.3.4/",
+ protocolChange: true,
+ },
+ {
+ input: "1.2.3.4/foo",
+ fixedURI: "http://1.2.3.4/foo",
+ protocolChange: true,
+ },
+ {
+ input: "1.2.3.4:8000",
+ fixedURI: "http://1.2.3.4:8000/",
+ protocolChange: true,
+ },
+ {
+ input: "1.2.3.4:8000/",
+ fixedURI: "http://1.2.3.4:8000/",
+ protocolChange: true,
+ },
+ {
+ input: "1.2.3.4:8000/foo",
+ fixedURI: "http://1.2.3.4:8000/foo",
+ protocolChange: true,
+ },
+ {
+ input: "192.168.10.110",
+ fixedURI: "http://192.168.10.110/",
+ protocolChange: true,
+ },
+ {
+ input: "192.168.10.110/123",
+ fixedURI: "http://192.168.10.110/123",
+ protocolChange: true,
+ },
+ {
+ input: "192.168.10.110/123foo",
+ fixedURI: "http://192.168.10.110/123foo",
+ protocolChange: true,
+ },
+ {
+ input: "192.168.10.110:1234/123",
+ fixedURI: "http://192.168.10.110:1234/123",
+ protocolChange: true,
+ },
+ {
+ input: "192.168.10.110:1234/123foo",
+ fixedURI: "http://192.168.10.110:1234/123foo",
+ protocolChange: true,
+ },
+ {
+ input: "1.2.3",
+ fixedURI: "http://1.2.0.3/",
+ protocolChange: true,
+ },
+ {
+ input: "1.2.3/",
+ fixedURI: "http://1.2.0.3/",
+ protocolChange: true,
+ },
+ {
+ input: "1.2.3/foo",
+ fixedURI: "http://1.2.0.3/foo",
+ protocolChange: true,
+ },
+ {
+ input: "1.2.3/123",
+ fixedURI: "http://1.2.0.3/123",
+ protocolChange: true,
+ },
+ {
+ input: "1.2.3:8000",
+ fixedURI: "http://1.2.0.3:8000/",
+ protocolChange: true,
+ },
+ {
+ input: "1.2.3:8000/",
+ fixedURI: "http://1.2.0.3:8000/",
+ protocolChange: true,
+ },
+ {
+ input: "1.2.3:8000/foo",
+ fixedURI: "http://1.2.0.3:8000/foo",
+ protocolChange: true,
+ },
+ {
+ input: "1.2.3:8000/123",
+ fixedURI: "http://1.2.0.3:8000/123",
+ protocolChange: true,
+ },
+ {
+ input: "http://1.2.3",
+ fixedURI: "http://1.2.0.3/",
+ },
+ {
+ input: "http://1.2.3/",
+ fixedURI: "http://1.2.0.3/",
+ },
+ {
+ input: "http://1.2.3/foo",
+ fixedURI: "http://1.2.0.3/foo",
+ },
+ {
+ input: "[::1]",
+ fixedURI: "http://[::1]/",
+ protocolChange: true,
+ },
+ {
+ input: "[::1]/",
+ fixedURI: "http://[::1]/",
+ protocolChange: true,
+ },
+ {
+ input: "[::1]:8000",
+ fixedURI: "http://[::1]:8000/",
+ protocolChange: true,
+ },
+ {
+ input: "[::1]:8000/",
+ fixedURI: "http://[::1]:8000/",
+ protocolChange: true,
+ },
+ {
+ input: "[[::1]]/",
+ keywordLookup: true,
+ },
+ {
+ input: "[fe80:cd00:0:cde:1257:0:211e:729c]",
+ fixedURI: "http://[fe80:cd00:0:cde:1257:0:211e:729c]/",
+ protocolChange: true,
+ },
+ {
+ input: "[64:ff9b::8.8.8.8]",
+ fixedURI: "http://[64:ff9b::808:808]/",
+ protocolChange: true,
+ },
+ {
+ input: "[64:ff9b::8.8.8.8]/~moz",
+ fixedURI: "http://[64:ff9b::808:808]/~moz",
+ protocolChange: true,
+ },
+ {
+ input: "[::1][::1]",
+ keywordLookup: true,
+ },
+ {
+ input: "[::1][100",
+ keywordLookup: true,
+ },
+ {
+ input: "[::1]]",
+ keywordLookup: true,
+ },
+ {
+ input: "1234",
+ fixedURI: "http://0.0.4.210/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "whitelisted/foo.txt",
+ fixedURI: "http://whitelisted/foo.txt",
+ alternateURI: "https://www.whitelisted.com/foo.txt",
+ protocolChange: true,
+ },
+ {
+ input: "mozilla",
+ fixedURI: "http://mozilla/",
+ alternateURI: "https://www.mozilla.com/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "test.",
+ fixedURI: "http://test./",
+ alternateURI: "https://www.test./",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: ".test",
+ fixedURI: "http://.test/",
+ alternateURI: "https://www.test/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "mozilla is amazing",
+ keywordLookup: true,
+ },
+ {
+ input: "search ?mozilla",
+ keywordLookup: true,
+ },
+ {
+ input: "mozilla .com",
+ keywordLookup: true,
+ },
+ {
+ input: "what if firefox?",
+ keywordLookup: true,
+ },
+ {
+ input: "london's map",
+ keywordLookup: true,
+ },
+ {
+ input: "mozilla ",
+ fixedURI: "http://mozilla/",
+ alternateURI: "https://www.mozilla.com/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: " mozilla ",
+ fixedURI: "http://mozilla/",
+ alternateURI: "https://www.mozilla.com/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "mozilla \\",
+ keywordLookup: true,
+ },
+ {
+ input: "mozilla \\ foo.txt",
+ keywordLookup: true,
+ },
+ {
+ input: "mozilla \\\r foo.txt",
+ keywordLookup: true,
+ },
+ {
+ input: "mozilla\n",
+ fixedURI: "http://mozilla/",
+ alternateURI: "https://www.mozilla.com/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "mozilla \r\n",
+ fixedURI: "http://mozilla/",
+ alternateURI: "https://www.mozilla.com/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "moz\r\nfirefox\nos\r",
+ fixedURI: "http://mozfirefoxos/",
+ alternateURI: "https://www.mozfirefoxos.com/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "moz\r\n firefox\n",
+ keywordLookup: true,
+ },
+ {
+ input: "",
+ keywordLookup: true,
+ },
+ {
+ input: "[]",
+ keywordLookup: true,
+ },
+ {
+ input: "http://whitelisted/",
+ fixedURI: "http://whitelisted/",
+ alternateURI: "https://www.whitelisted.com/",
+ inWhitelist: true,
+ },
+ {
+ input: "whitelisted",
+ fixedURI: "http://whitelisted/",
+ alternateURI: "https://www.whitelisted.com/",
+ protocolChange: true,
+ inWhitelist: true,
+ },
+ {
+ input: "whitelisted.",
+ fixedURI: "http://whitelisted./",
+ alternateURI: "https://www.whitelisted./",
+ protocolChange: true,
+ inWhitelist: true,
+ },
+ {
+ input: "mochi.test",
+ fixedURI: "http://mochi.test/",
+ alternateURI: "https://www.mochi.test/",
+ protocolChange: true,
+ inWhitelist: true,
+ },
+ // local.domain is a whitelisted suffix...
+ {
+ input: "some.local.domain",
+ fixedURI: "http://some.local.domain/",
+ protocolChange: true,
+ inWhitelist: true,
+ },
+ // ...but .domain is not.
+ {
+ input: "some.domain",
+ fixedURI: "http://some.domain/",
+ alternateURI: "https://www.some.domain/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "café.com",
+ fixedURI: "http://xn--caf-dma.com/",
+ alternateURI: "https://www.xn--caf-dma.com/",
+ protocolChange: true,
+ },
+ {
+ input: "mozilla.nonexistent",
+ fixedURI: "http://mozilla.nonexistent/",
+ alternateURI: "https://www.mozilla.nonexistent/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "mochi.ocm",
+ fixedURI: "http://mochi.com/",
+ alternateURI: "https://www.mochi.com/",
+ protocolChange: true,
+ },
+ {
+ input: "47.6182,-122.830",
+ fixedURI: "http://47.6182,-122.830/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "-47.6182,-23.51",
+ fixedURI: "http://-47.6182,-23.51/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "-22.14,23.51-",
+ fixedURI: "http://-22.14,23.51-/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "32.7",
+ fixedURI: "http://32.0.0.7/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "5+2",
+ fixedURI: "http://5+2/",
+ alternateURI: "https://www.5+2.com/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "5/2",
+ fixedURI: "http://0.0.0.5/2",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "moz ?.::%27",
+ keywordLookup: true,
+ },
+ {
+ input: "mozilla.com/?q=search",
+ fixedURI: "http://mozilla.com/?q=search",
+ alternateURI: "https://www.mozilla.com/?q=search",
+ protocolChange: true,
+ },
+ {
+ input: "mozilla.com?q=search",
+ fixedURI: "http://mozilla.com/?q=search",
+ alternateURI: "https://www.mozilla.com/?q=search",
+ protocolChange: true,
+ },
+ {
+ input: "mozilla.com ?q=search",
+ keywordLookup: true,
+ },
+ {
+ input: "mozilla.com.?q=search",
+ fixedURI: "http://mozilla.com./?q=search",
+ protocolChange: true,
+ },
+ {
+ input: "mozilla.com'?q=search",
+ fixedURI: "http://mozilla.com/?q=search",
+ alternateURI: "https://www.mozilla.com/?q=search",
+ protocolChange: true,
+ },
+ {
+ input: "mozilla.com':search",
+ keywordLookup: true,
+ },
+ {
+ input: "[mozilla]",
+ keywordLookup: true,
+ },
+ {
+ input: "':?",
+ fixedURI: "http://'/?",
+ alternateURI: "https://www.'.com/?",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "whitelisted?.com",
+ fixedURI: "http://whitelisted/?.com",
+ alternateURI: "https://www.whitelisted.com/?.com",
+ protocolChange: true,
+ },
+ {
+ input: "?'.com",
+ keywordLookup: true,
+ },
+ {
+ input: ".com",
+ keywordLookup: true,
+ affectedByDNSForSingleWordHosts: true,
+ fixedURI: "http://.com/",
+ alternateURI: "https://www.com/",
+ protocolChange: true,
+ },
+ {
+ input: "' ?.com",
+ keywordLookup: true,
+ },
+ {
+ input: "?mozilla",
+ keywordLookup: true,
+ },
+ {
+ input: "??mozilla",
+ keywordLookup: true,
+ },
+ {
+ input: "mozilla/",
+ fixedURI: "http://mozilla/",
+ alternateURI: "https://www.mozilla.com/",
+ protocolChange: true,
+ },
+ {
+ input: "mozilla",
+ fixedURI: "http://mozilla/",
+ alternateURI: "https://www.mozilla.com/",
+ protocolChange: true,
+ keywordLookup: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "mozilla5/2",
+ fixedURI: "http://mozilla5/2",
+ alternateURI: "https://www.mozilla5.com/2",
+ protocolChange: true,
+ keywordLookup: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "mozilla/foo",
+ fixedURI: "http://mozilla/foo",
+ alternateURI: "https://www.mozilla.com/foo",
+ protocolChange: true,
+ keywordLookup: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "mozilla\\",
+ fixedURI: "http://mozilla/",
+ alternateURI: "https://www.mozilla.com/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "localhost",
+ fixedURI: "http://localhost/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "http",
+ fixedURI: "http://http/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "https",
+ fixedURI: "http://https/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "localhost:8080",
+ fixedURI: "http://localhost:8080/",
+ protocolChange: true,
+ },
+ {
+ input: "plonk:8080",
+ fixedURI: "http://plonk:8080/",
+ protocolChange: true,
+ },
+ {
+ input: "plonk:8080?test",
+ fixedURI: "http://plonk:8080/?test",
+ protocolChange: true,
+ },
+ {
+ input: "plonk:8080#test",
+ fixedURI: "http://plonk:8080/#test",
+ protocolChange: true,
+ },
+ {
+ input: "plonk/ #",
+ fixedURI: "http://plonk/%20#",
+ alternateURI: "https://www.plonk.com/%20#",
+ protocolChange: true,
+ keywordLookup: false,
+ },
+ {
+ input: "blah.com.",
+ fixedURI: "http://blah.com./",
+ protocolChange: true,
+ },
+ {
+ input:
+ "\u10E0\u10D4\u10D2\u10D8\u10E1\u10E2\u10E0\u10D0\u10EA\u10D8\u10D0.\u10D2\u10D4",
+ fixedURI: "http://xn--lodaehvb5cdik4g.xn--node/",
+ alternateURI: "https://www.xn--lodaehvb5cdik4g.xn--node/",
+ protocolChange: true,
+ },
+ {
+ input: " \t mozilla.org/\t \t ",
+ fixedURI: "http://mozilla.org/",
+ alternateURI: "https://www.mozilla.org/",
+ protocolChange: true,
+ },
+ {
+ input: " moz\ti\tlla.org ",
+ keywordLookup: true,
+ },
+ {
+ input: "mozilla/",
+ fixedURI: "http://mozilla/",
+ alternateURI: "https://www.mozilla.com/",
+ protocolChange: true,
+ },
+ {
+ input: "mozilla/ test /",
+ fixedURI: "http://mozilla/%20test%20/",
+ alternateURI: "https://www.mozilla.com/%20test%20/",
+ protocolChange: true,
+ },
+ {
+ input: "mozilla /test/",
+ keywordLookup: true,
+ },
+ {
+ input: "pserver:8080",
+ fixedURI: "http://pserver:8080/",
+ protocolChange: true,
+ },
+ {
+ input: "http;mozilla",
+ fixedURI: "http://http;mozilla/",
+ alternateURI: "https://www.http;mozilla.com/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ },
+ {
+ input: "http//mozilla.org",
+ fixedURI: "http://mozilla.org/",
+ shouldRunTest: flags =>
+ flags & Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS,
+ },
+ {
+ input: "http//mozilla.org",
+ fixedURI: "http://http//mozilla.org",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ shouldRunTest: flags =>
+ !(flags & Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS),
+ },
+ {
+ input: "www.mozilla",
+ fixedURI: "http://www.mozilla/",
+ protocolChange: true,
+ },
+ {
+ input: "https://sub.www..mozilla...org/",
+ fixedURI: "https://sub.www.mozilla.org/",
+ },
+ {
+ input: "sub.www..mozilla...org/",
+ fixedURI: "http://sub.www.mozilla.org/",
+ protocolChange: true,
+ },
+ {
+ input: "sub.www..mozilla...org",
+ fixedURI: "http://sub.www.mozilla.org/",
+ protocolChange: true,
+ },
+ {
+ input: "www...mozilla",
+ fixedURI: "http://www.mozilla/",
+ keywordLookup: true,
+ protocolChange: true,
+ shouldRunTest: flags =>
+ !gSingleWordDNSLookup &&
+ flags & Services.uriFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP,
+ },
+ {
+ input: "www...mozilla",
+ fixedURI: "http://www.mozilla/",
+ protocolChange: true,
+ shouldRunTest: flags =>
+ gSingleWordDNSLookup ||
+ !(flags & Services.uriFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP),
+ },
+ {
+ input: "mozilla...org",
+ fixedURI: "http://mozilla.org/",
+ protocolChange: true,
+ },
+ {
+ input: "モジラ...org",
+ fixedURI: "http://xn--yck6dwa.org/",
+ protocolChange: true,
+ },
+ {
+ input: "user@localhost",
+ fixedURI: "http://user@localhost/",
+ protocolChange: true,
+ shouldRunTest: () => gSingleWordDNSLookup,
+ },
+ {
+ input: "user@localhost",
+ fixedURI: "http://user@localhost/",
+ keywordLookup: true,
+ protocolChange: true,
+ shouldRunTest: () => !gSingleWordDNSLookup,
+ },
+ {
+ input: "user@192.168.0.1",
+ fixedURI: "http://user@192.168.0.1/",
+ protocolChange: true,
+ },
+ {
+ input: "user@dummy-host",
+ fixedURI: "http://user@dummy-host/",
+ protocolChange: true,
+ shouldRunTest: () => gSingleWordDNSLookup,
+ },
+ {
+ input: "user@dummy-host",
+ fixedURI: "http://user@dummy-host/",
+ keywordLookup: true,
+ protocolChange: true,
+ shouldRunTest: () => !gSingleWordDNSLookup,
+ },
+ {
+ input: "user:pass@dummy-host",
+ fixedURI: "http://user:pass@dummy-host/",
+ protocolChange: true,
+ },
+ {
+ input: ":pass@dummy-host",
+ fixedURI: "http://:pass@dummy-host/",
+ protocolChange: true,
+ },
+ {
+ input: "user@dummy-host/path",
+ fixedURI: "http://user@dummy-host/path",
+ protocolChange: true,
+ shouldRunTest: () => gSingleWordDNSLookup,
+ },
+ {
+ input: "jar:file:///omni.ja!/",
+ fixedURI: "jar:file:///omni.ja!/",
+ },
+];
+
+if (AppConstants.platform == "win") {
+ testcases.push({
+ input: "C:\\some\\file.txt",
+ fixedURI: "file:///C:/some/file.txt",
+ protocolChange: true,
+ });
+ testcases.push({
+ input: "//mozilla",
+ fixedURI: "http://mozilla/",
+ alternateURI: "https://www.mozilla.com/",
+ protocolChange: true,
+ });
+ testcases.push({
+ input: "/a",
+ fixedURI: "http://a/",
+ alternateURI: "https://www.a.com/",
+ keywordLookup: true,
+ protocolChange: true,
+ affectedByDNSForSingleWordHosts: true,
+ });
+} else {
+ testcases.push({
+ input: "/some/file.txt",
+ fixedURI: "file:///some/file.txt",
+ protocolChange: true,
+ });
+ testcases.push({
+ input: "//mozilla",
+ fixedURI: "file:////mozilla",
+ protocolChange: true,
+ });
+ testcases.push({
+ input: "/a",
+ fixedURI: "file:///a",
+ protocolChange: true,
+ });
+}
+
+function sanitize(input) {
+ return input.replace(/\r|\n/g, "").trim();
+}
+
+add_task(async function setup() {
+ var prefList = [
+ "browser.fixup.typo.scheme",
+ "keyword.enabled",
+ "browser.fixup.domainwhitelist.whitelisted",
+ "browser.fixup.domainsuffixwhitelist.test",
+ "browser.fixup.domainsuffixwhitelist.local.domain",
+ "browser.search.separatePrivateDefault",
+ "browser.search.separatePrivateDefault.ui.enabled",
+ ];
+ for (let pref of prefList) {
+ Services.prefs.setBoolPref(pref, true);
+ }
+
+ await setupSearchService();
+ await addTestEngines();
+
+ await Services.search.setDefault(
+ Services.search.getEngineByName(kSearchEngineID),
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ await Services.search.setDefaultPrivate(
+ Services.search.getEngineByName(kPrivateSearchEngineID),
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+});
+
+var gSingleWordDNSLookup = false;
+add_task(async function run_test() {
+ // Only keywordlookup things should be affected by requiring a DNS lookup for single-word hosts:
+ info(
+ "Check only keyword lookup testcases should be affected by requiring DNS for single hosts"
+ );
+ let affectedTests = testcases.filter(
+ t => !t.keywordLookup && t.affectedByDNSForSingleWordHosts
+ );
+ if (affectedTests.length) {
+ for (let testcase of affectedTests) {
+ info("Affected: " + testcase.input);
+ }
+ }
+ Assert.equal(affectedTests.length, 0);
+ await do_single_test_run();
+ await do_single_test_run(true);
+ gSingleWordDNSLookup = true;
+ await do_single_test_run();
+ await do_single_test_run(true);
+ gSingleWordDNSLookup = false;
+ await Services.search.setDefault(
+ Services.search.getEngineByName(kPostSearchEngineID),
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ await do_single_test_run();
+ await do_single_test_run(true);
+});
+
+async function do_single_test_run(browserFixupAlternateEnabled = false) {
+ Services.prefs.setBoolPref(kForceDNSLookup, gSingleWordDNSLookup);
+ Services.prefs.setBoolPref(kFixupEnabled, browserFixupAlternateEnabled);
+ let relevantTests = gSingleWordDNSLookup
+ ? testcases.filter(t => t.keywordLookup)
+ : testcases;
+
+ let engine = await Services.search.getDefault();
+ let engineUrl =
+ engine.name == kPostSearchEngineID
+ ? kPostSearchEngineURL
+ : kSearchEngineURL;
+ let privateEngine = await Services.search.getDefaultPrivate();
+ let privateEngineUrl = kPrivateSearchEngineURL;
+
+ for (let {
+ input: testInput,
+ fixedURI: expectedFixedURI,
+ alternateURI: alternativeURI,
+ keywordLookup: expectKeywordLookup,
+ protocolChange: expectProtocolChange,
+ inWhitelist: inWhitelist,
+ affectedByDNSForSingleWordHosts: affectedByDNSForSingleWordHosts,
+ shouldRunTest,
+ } of relevantTests) {
+ // Explicitly force these into a boolean
+ expectKeywordLookup = !!expectKeywordLookup;
+ expectProtocolChange = !!expectProtocolChange;
+ inWhitelist = !!inWhitelist;
+ affectedByDNSForSingleWordHosts = !!affectedByDNSForSingleWordHosts;
+
+ expectKeywordLookup =
+ expectKeywordLookup &&
+ (!affectedByDNSForSingleWordHosts || !gSingleWordDNSLookup);
+
+ for (let flags of flagInputs) {
+ info(
+ 'Checking "' +
+ testInput +
+ '" with flags ' +
+ flags +
+ " (DNS lookup for single words: " +
+ (gSingleWordDNSLookup ? "yes" : "no") +
+ "," +
+ " browser fixup alt enabled: " +
+ (browserFixupAlternateEnabled ? "yes" : "no") +
+ ")"
+ );
+
+ if (shouldRunTest && !shouldRunTest(flags)) {
+ continue;
+ }
+
+ let URIInfo;
+ try {
+ URIInfo = Services.uriFixup.getFixupURIInfo(testInput, flags);
+ } catch (ex) {
+ // Both APIs should return an error in the same cases.
+ info("Caught exception: " + ex);
+ Assert.equal(expectedFixedURI, null);
+ continue;
+ }
+
+ // Check the fixedURI:
+ let fixupFlag = browserFixupAlternateEnabled
+ ? Services.uriFixup.FIXUP_FLAGS_MAKE_ALTERNATE_URI
+ : Services.uriFixup.FIXUP_FLAG_NONE;
+ let forceAlternativeURI =
+ flags & Services.uriFixup.FIXUP_FLAG_FORCE_ALTERNATE_URI;
+ let makeAlternativeURI = flags & fixupFlag || forceAlternativeURI;
+
+ if (makeAlternativeURI && alternativeURI != null) {
+ Assert.equal(
+ URIInfo.fixedURI.spec,
+ alternativeURI,
+ "should have gotten alternate URI"
+ );
+ } else {
+ Assert.equal(
+ URIInfo.fixedURI && URIInfo.fixedURI.spec,
+ expectedFixedURI,
+ "should get correct fixed URI"
+ );
+ }
+
+ // Check booleans on input:
+ let couldDoKeywordLookup =
+ flags & Services.uriFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
+ Assert.equal(
+ !!URIInfo.keywordProviderName,
+ couldDoKeywordLookup && expectKeywordLookup,
+ "keyword lookup as expected"
+ );
+
+ let expectProtocolChangeAfterAlternate = false;
+ // If alternativeURI was created, the protocol of the URI
+ // might have been changed to browser.fixup.alternate.protocol
+ // If the protocol is not the same as what was in expectedFixedURI,
+ // the protocol must've changed in the fixup process.
+ if (
+ makeAlternativeURI &&
+ alternativeURI != null &&
+ !expectedFixedURI.startsWith(URIInfo.fixedURI.scheme)
+ ) {
+ expectProtocolChangeAfterAlternate = true;
+ }
+
+ Assert.equal(
+ URIInfo.fixupChangedProtocol,
+ expectProtocolChange || expectProtocolChangeAfterAlternate,
+ "protocol change as expected"
+ );
+ Assert.equal(
+ URIInfo.fixupCreatedAlternateURI,
+ makeAlternativeURI && alternativeURI != null,
+ "alternative URI as expected"
+ );
+
+ // Check the preferred URI
+ if (couldDoKeywordLookup) {
+ if (expectKeywordLookup) {
+ if (!inWhitelist) {
+ let urlparamInput = encodeURIComponent(sanitize(testInput)).replace(
+ /%20/g,
+ "+"
+ );
+ // If the input starts with `?`, then URIInfo.preferredURI.spec will omit it
+ // In order to test this behaviour, remove `?` only if it is the first character
+ if (urlparamInput.startsWith("%3F")) {
+ urlparamInput = urlparamInput.replace("%3F", "");
+ }
+ let isPrivate =
+ flags & Services.uriFixup.FIXUP_FLAG_PRIVATE_CONTEXT;
+ let searchEngineUrl = isPrivate ? privateEngineUrl : engineUrl;
+ let searchURL = searchEngineUrl.replace(
+ "{searchTerms}",
+ urlparamInput
+ );
+ let spec = URIInfo.preferredURI.spec.replace(/%27/g, "'");
+ Assert.equal(spec, searchURL, "should get correct search URI");
+ let providerName = isPrivate ? privateEngine.name : engine.name;
+ Assert.equal(
+ URIInfo.keywordProviderName,
+ providerName,
+ "should get correct provider name"
+ );
+ // Also check keywordToURI() uses the right engine.
+ let kwInfo = Services.uriFixup.keywordToURI(
+ urlparamInput,
+ isPrivate
+ );
+ Assert.equal(kwInfo.providerName, URIInfo.providerName);
+ if (providerName == kPostSearchEngineID) {
+ Assert.ok(kwInfo.postData);
+ let submission = engine.getSubmission(urlparamInput);
+ let enginePostData = NetUtil.readInputStreamToString(
+ submission.postData,
+ submission.postData.available()
+ );
+ let postData = NetUtil.readInputStreamToString(
+ kwInfo.postData,
+ kwInfo.postData.available()
+ );
+ Assert.equal(postData, enginePostData);
+ }
+ } else {
+ Assert.equal(
+ URIInfo.preferredURI,
+ null,
+ "not expecting a preferred URI"
+ );
+ }
+ } else {
+ Assert.equal(
+ URIInfo.preferredURI.spec,
+ URIInfo.fixedURI.spec,
+ "fixed URI should match"
+ );
+ }
+ } else {
+ // In these cases, we should never be doing a keyword lookup and
+ // the fixed URI should be preferred:
+ let prefURI = URIInfo.preferredURI && URIInfo.preferredURI.spec;
+ let fixedURI = URIInfo.fixedURI && URIInfo.fixedURI.spec;
+ Assert.equal(prefURI, fixedURI, "fixed URI should be same as expected");
+ }
+ Assert.equal(
+ sanitize(testInput),
+ URIInfo.originalInput,
+ "should mirror original input"
+ );
+ }
+ }
+}
diff --git a/docshell/test/unit/test_URIFixup_search.js b/docshell/test/unit/test_URIFixup_search.js
new file mode 100644
index 0000000000..fa1c5b38ba
--- /dev/null
+++ b/docshell/test/unit/test_URIFixup_search.js
@@ -0,0 +1,143 @@
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+var isWin = AppConstants.platform == "win";
+
+var data = [
+ {
+ // Valid should not be changed.
+ wrong: "https://example.com/this/is/a/test.html",
+ fixed: "https://example.com/this/is/a/test.html",
+ },
+ {
+ // Unrecognized protocols should be changed.
+ wrong: "whatever://this/is/a/test.html",
+ fixed: kSearchEngineURL.replace(
+ "{searchTerms}",
+ encodeURIComponent("whatever://this/is/a/test.html")
+ ),
+ },
+
+ {
+ // Unrecognized protocols should be changed.
+ wrong: "whatever://this/is/a/test.html",
+ fixed: kPrivateSearchEngineURL.replace(
+ "{searchTerms}",
+ encodeURIComponent("whatever://this/is/a/test.html")
+ ),
+ inPrivateBrowsing: true,
+ },
+
+ // The following tests check that when a user:password is present in the URL
+ // `user:` isn't treated as an unknown protocol thus leaking the user and
+ // password to the search engine.
+ {
+ wrong: "user:pass@example.com/this/is/a/test.html",
+ fixed: "http://user:pass@example.com/this/is/a/test.html",
+ },
+ {
+ wrong: "user@example.com:8080/this/is/a/test.html",
+ fixed: "http://user@example.com:8080/this/is/a/test.html",
+ },
+ {
+ wrong: "https:pass@example.com/this/is/a/test.html",
+ fixed: "https://pass@example.com/this/is/a/test.html",
+ },
+ {
+ wrong: "user:pass@example.com:8080/this/is/a/test.html",
+ fixed: "http://user:pass@example.com:8080/this/is/a/test.html",
+ },
+ {
+ wrong: "http:user:pass@example.com:8080/this/is/a/test.html",
+ fixed: "http://user:pass@example.com:8080/this/is/a/test.html",
+ },
+ {
+ wrong: "ttp:user:pass@example.com:8080/this/is/a/test.html",
+ fixed: "http://user:pass@example.com:8080/this/is/a/test.html",
+ },
+ {
+ wrong: "nonsense:user:pass@example.com:8080/this/is/a/test.html",
+ fixed: "http://nonsense:user%3Apass@example.com:8080/this/is/a/test.html",
+ },
+ {
+ wrong: "user:@example.com:8080/this/is/a/test.html",
+ fixed: "http://user@example.com:8080/this/is/a/test.html",
+ },
+ {
+ wrong: "//user:pass@example.com:8080/this/is/a/test.html",
+ fixed:
+ (isWin ? "http:" : "file://") +
+ "//user:pass@example.com:8080/this/is/a/test.html",
+ },
+ {
+ wrong: "://user:pass@example.com:8080/this/is/a/test.html",
+ fixed: "http://user:pass@example.com:8080/this/is/a/test.html",
+ },
+ {
+ wrong: "localhost:8080/?param=1",
+ fixed: "http://localhost:8080/?param=1",
+ },
+ {
+ wrong: "localhost:8080?param=1",
+ fixed: "http://localhost:8080/?param=1",
+ },
+ {
+ wrong: "localhost:8080#somewhere",
+ fixed: "http://localhost:8080/#somewhere",
+ },
+ {
+ wrong: "whatever://this/is/a@b/test.html",
+ fixed: kSearchEngineURL.replace(
+ "{searchTerms}",
+ encodeURIComponent("whatever://this/is/a@b/test.html")
+ ),
+ },
+];
+
+var extProtocolSvc = Cc[
+ "@mozilla.org/uriloader/external-protocol-service;1"
+].getService(Ci.nsIExternalProtocolService);
+
+if (extProtocolSvc && extProtocolSvc.externalProtocolHandlerExists("mailto")) {
+ data.push({
+ wrong: "mailto:foo@bar.com",
+ fixed: "mailto:foo@bar.com",
+ });
+}
+
+var len = data.length;
+
+add_task(async function setup() {
+ await setupSearchService();
+ await addTestEngines();
+
+ Services.prefs.setBoolPref("keyword.enabled", true);
+ Services.prefs.setBoolPref("browser.search.separatePrivateDefault", true);
+ Services.prefs.setBoolPref(
+ "browser.search.separatePrivateDefault.ui.enabled",
+ true
+ );
+
+ await Services.search.setDefault(
+ Services.search.getEngineByName(kSearchEngineID),
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+ await Services.search.setDefaultPrivate(
+ Services.search.getEngineByName(kPrivateSearchEngineID),
+ Ci.nsISearchService.CHANGE_REASON_UNKNOWN
+ );
+});
+
+// Make sure we fix what needs fixing
+add_task(function test_fix_unknown_schemes() {
+ for (let i = 0; i < len; ++i) {
+ let item = data[i];
+ let flags = Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS;
+ if (item.inPrivateBrowsing) {
+ flags |= Services.uriFixup.FIXUP_FLAG_PRIVATE_CONTEXT;
+ }
+ let { preferredURI } = Services.uriFixup.getFixupURIInfo(item.wrong, flags);
+ Assert.equal(preferredURI.spec, item.fixed);
+ }
+});
diff --git a/docshell/test/unit/test_allowJavascript.js b/docshell/test/unit/test_allowJavascript.js
new file mode 100644
index 0000000000..d1341c7bba
--- /dev/null
+++ b/docshell/test/unit/test_allowJavascript.js
@@ -0,0 +1,291 @@
+"use strict";
+
+const { XPCShellContentUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/XPCShellContentUtils.sys.mjs"
+);
+
+XPCShellContentUtils.init(this);
+
+const ACTOR = "AllowJavascript";
+
+const HTML = String.raw`<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <script type="application/javascript">
+ "use strict";
+ var gFiredOnload = false;
+ var gFiredOnclick = false;
+ </script>
+</head>
+<body onload="gFiredOnload = true;" onclick="gFiredOnclick = true;">
+</body>
+</html>`;
+
+const server = XPCShellContentUtils.createHttpServer({
+ hosts: ["example.com", "example.org"],
+});
+
+server.registerPathHandler("/", (request, response) => {
+ response.setHeader("Content-Type", "text/html");
+ response.write(HTML);
+});
+
+const { AllowJavascriptParent } = ChromeUtils.importESModule(
+ "resource://test/AllowJavascriptParent.sys.mjs"
+);
+
+async function assertScriptsAllowed(bc, expectAllowed, desc) {
+ let actor = bc.currentWindowGlobal.getActor(ACTOR);
+ let allowed = await actor.sendQuery("CheckScriptsAllowed");
+ equal(
+ allowed,
+ expectAllowed,
+ `Scripts should be ${expectAllowed ? "" : "dis"}allowed for ${desc}`
+ );
+}
+
+async function assertLoadFired(bc, expectFired, desc) {
+ let actor = bc.currentWindowGlobal.getActor(ACTOR);
+ let fired = await actor.sendQuery("CheckFiredLoadEvent");
+ equal(
+ fired,
+ expectFired,
+ `Should ${expectFired ? "" : "not "}have fired load for ${desc}`
+ );
+}
+
+function createSubframe(bc, url) {
+ let actor = bc.currentWindowGlobal.getActor(ACTOR);
+ return actor.sendQuery("CreateIframe", { url });
+}
+
+add_task(async function () {
+ Services.prefs.setBoolPref("dom.security.https_first", false);
+ ChromeUtils.registerWindowActor(ACTOR, {
+ allFrames: true,
+ child: {
+ esModuleURI: "resource://test/AllowJavascriptChild.sys.mjs",
+ events: { load: { capture: true } },
+ },
+ parent: {
+ esModuleURI: "resource://test/AllowJavascriptParent.sys.mjs",
+ },
+ });
+
+ let page = await XPCShellContentUtils.loadContentPage("http://example.com/", {
+ remote: true,
+ remoteSubframes: true,
+ });
+
+ let bc = page.browsingContext;
+
+ {
+ let oopFrame1 = await createSubframe(bc, "http://example.org/");
+ let inprocFrame1 = await createSubframe(bc, "http://example.com/");
+
+ let oopFrame1OopSub = await createSubframe(
+ oopFrame1,
+ "http://example.com/"
+ );
+ let inprocFrame1OopSub = await createSubframe(
+ inprocFrame1,
+ "http://example.org/"
+ );
+
+ equal(
+ oopFrame1.allowJavascript,
+ true,
+ "OOP BC should inherit allowJavascript from parent"
+ );
+ equal(
+ inprocFrame1.allowJavascript,
+ true,
+ "In-process BC should inherit allowJavascript from parent"
+ );
+ equal(
+ oopFrame1OopSub.allowJavascript,
+ true,
+ "OOP BC child should inherit allowJavascript from parent"
+ );
+ equal(
+ inprocFrame1OopSub.allowJavascript,
+ true,
+ "In-process child BC should inherit allowJavascript from parent"
+ );
+
+ await assertLoadFired(bc, true, "top BC");
+ await assertScriptsAllowed(bc, true, "top BC");
+
+ await assertLoadFired(oopFrame1, true, "OOP frame 1");
+ await assertScriptsAllowed(oopFrame1, true, "OOP frame 1");
+
+ await assertLoadFired(inprocFrame1, true, "In-process frame 1");
+ await assertScriptsAllowed(inprocFrame1, true, "In-process frame 1");
+
+ await assertLoadFired(oopFrame1OopSub, true, "OOP frame 1 subframe");
+ await assertScriptsAllowed(oopFrame1OopSub, true, "OOP frame 1 subframe");
+
+ await assertLoadFired(
+ inprocFrame1OopSub,
+ true,
+ "In-process frame 1 subframe"
+ );
+ await assertScriptsAllowed(
+ inprocFrame1OopSub,
+ true,
+ "In-process frame 1 subframe"
+ );
+
+ bc.allowJavascript = false;
+ await assertScriptsAllowed(bc, false, "top BC with scripts disallowed");
+ await assertScriptsAllowed(
+ oopFrame1,
+ false,
+ "OOP frame 1 with top BC with scripts disallowed"
+ );
+ await assertScriptsAllowed(
+ inprocFrame1,
+ false,
+ "In-process frame 1 with top BC with scripts disallowed"
+ );
+ await assertScriptsAllowed(
+ oopFrame1OopSub,
+ false,
+ "OOP frame 1 subframe with top BC with scripts disallowed"
+ );
+ await assertScriptsAllowed(
+ inprocFrame1OopSub,
+ false,
+ "In-process frame 1 subframe with top BC with scripts disallowed"
+ );
+
+ let oopFrame2 = await createSubframe(bc, "http://example.org/");
+ let inprocFrame2 = await createSubframe(bc, "http://example.com/");
+
+ equal(
+ oopFrame2.allowJavascript,
+ false,
+ "OOP BC 2 should inherit allowJavascript from parent"
+ );
+ equal(
+ inprocFrame2.allowJavascript,
+ false,
+ "In-process BC 2 should inherit allowJavascript from parent"
+ );
+
+ await assertLoadFired(
+ oopFrame2,
+ undefined,
+ "OOP frame 2 with top BC with scripts disallowed"
+ );
+ await assertScriptsAllowed(
+ oopFrame2,
+ false,
+ "OOP frame 2 with top BC with scripts disallowed"
+ );
+ await assertLoadFired(
+ inprocFrame2,
+ undefined,
+ "In-process frame 2 with top BC with scripts disallowed"
+ );
+ await assertScriptsAllowed(
+ inprocFrame2,
+ false,
+ "In-process frame 2 with top BC with scripts disallowed"
+ );
+
+ bc.allowJavascript = true;
+ await assertScriptsAllowed(bc, true, "top BC");
+
+ await assertScriptsAllowed(oopFrame1, true, "OOP frame 1");
+ await assertScriptsAllowed(inprocFrame1, true, "In-process frame 1");
+ await assertScriptsAllowed(oopFrame1OopSub, true, "OOP frame 1 subframe");
+ await assertScriptsAllowed(
+ inprocFrame1OopSub,
+ true,
+ "In-process frame 1 subframe"
+ );
+
+ await assertScriptsAllowed(oopFrame2, false, "OOP frame 2");
+ await assertScriptsAllowed(inprocFrame2, false, "In-process frame 2");
+
+ oopFrame1.currentWindowGlobal.allowJavascript = false;
+ inprocFrame1.currentWindowGlobal.allowJavascript = false;
+
+ await assertScriptsAllowed(
+ oopFrame1,
+ false,
+ "OOP frame 1 with second level WC scripts disallowed"
+ );
+ await assertScriptsAllowed(
+ inprocFrame1,
+ false,
+ "In-process frame 1 with second level WC scripts disallowed"
+ );
+ await assertScriptsAllowed(
+ oopFrame1OopSub,
+ false,
+ "OOP frame 1 subframe second level WC scripts disallowed"
+ );
+ await assertScriptsAllowed(
+ inprocFrame1OopSub,
+ false,
+ "In-process frame 1 subframe with second level WC scripts disallowed"
+ );
+
+ oopFrame1.reload(0);
+ inprocFrame1.reload(0);
+ await Promise.all([
+ AllowJavascriptParent.promiseLoad(oopFrame1),
+ AllowJavascriptParent.promiseLoad(inprocFrame1),
+ ]);
+
+ equal(
+ oopFrame1.currentWindowGlobal.allowJavascript,
+ true,
+ "WindowContext.allowJavascript does not persist after navigation for OOP frame 1"
+ );
+ equal(
+ inprocFrame1.currentWindowGlobal.allowJavascript,
+ true,
+ "WindowContext.allowJavascript does not persist after navigation for in-process frame 1"
+ );
+
+ await assertScriptsAllowed(oopFrame1, true, "OOP frame 1");
+ await assertScriptsAllowed(inprocFrame1, true, "In-process frame 1");
+ }
+
+ bc.allowJavascript = false;
+
+ bc.reload(0);
+ await AllowJavascriptParent.promiseLoad(bc);
+
+ await assertLoadFired(
+ bc,
+ undefined,
+ "top BC with scripts disabled after reload"
+ );
+ await assertScriptsAllowed(
+ bc,
+ false,
+ "top BC with scripts disabled after reload"
+ );
+
+ await page.loadURL("http://example.org/?other");
+ bc = page.browsingContext;
+
+ await assertLoadFired(
+ bc,
+ undefined,
+ "top BC with scripts disabled after navigation"
+ );
+ await assertScriptsAllowed(
+ bc,
+ false,
+ "top BC with scripts disabled after navigation"
+ );
+
+ await page.close();
+ Services.prefs.clearUserPref("dom.security.https_first");
+});
diff --git a/docshell/test/unit/test_browsing_context_structured_clone.js b/docshell/test/unit/test_browsing_context_structured_clone.js
new file mode 100644
index 0000000000..1c91a9b0b2
--- /dev/null
+++ b/docshell/test/unit/test_browsing_context_structured_clone.js
@@ -0,0 +1,70 @@
+"use strict";
+
+add_task(async function test_BrowsingContext_structured_clone() {
+ let browser = Services.appShell.createWindowlessBrowser(false);
+
+ let frame = browser.document.createElement("iframe");
+
+ await new Promise(r => {
+ frame.onload = () => r();
+ browser.document.body.appendChild(frame);
+ });
+
+ let { browsingContext } = frame;
+
+ let sch = new StructuredCloneHolder("debug name", "<anonymized> debug name", {
+ browsingContext,
+ });
+
+ let deserialize = () => sch.deserialize({}, true);
+
+ // Check that decoding a live browsing context produces the correct
+ // object.
+ equal(
+ deserialize().browsingContext,
+ browsingContext,
+ "Got correct browsing context from StructuredClone deserialize"
+ );
+
+ // Check that decoding a second time still succeeds.
+ equal(
+ deserialize().browsingContext,
+ browsingContext,
+ "Got correct browsing context from second StructuredClone deserialize"
+ );
+
+ // Destroy the browsing context and make sure that the decode fails
+ // with a DataCloneError.
+ //
+ // Making sure the BrowsingContext is actually destroyed by the time
+ // we do the second decode is a bit tricky. We obviously have clear
+ // our local references to it, and give the GC a chance to reap them.
+ // And we also, of course, have to destroy the frame that it belongs
+ // to, or its frame loader and window global would hold it alive.
+ //
+ // Beyond that, we don't *have* to reload or destroy the parent
+ // document, but we do anyway just to be safe.
+ //
+
+ frame.remove();
+ frame = null;
+ browsingContext = null;
+
+ browser.document.location.reload();
+ browser.close();
+
+ // We will schedule a precise GC and do both GC and CC a few times, to make
+ // sure we have completely destroyed the WindowGlobal actors (which keep
+ // references to their BrowsingContexts) in order
+ // to allow their (now snow-white) references to be collected.
+ await schedulePreciseGCAndForceCC(3);
+
+ // OK. We can be fairly confident that the BrowsingContext object
+ // stored in our structured clone data has been destroyed. Make sure
+ // that attempting to decode it again leads to the appropriate error.
+ Assert.throws(
+ deserialize,
+ e => e.name === "DataCloneError",
+ "Should get a DataCloneError when trying to decode a dead BrowsingContext"
+ );
+});
diff --git a/docshell/test/unit/test_bug442584.js b/docshell/test/unit/test_bug442584.js
new file mode 100644
index 0000000000..c109557f50
--- /dev/null
+++ b/docshell/test/unit/test_bug442584.js
@@ -0,0 +1,35 @@
+var prefetch = Cc["@mozilla.org/prefetch-service;1"].getService(
+ Ci.nsIPrefetchService
+);
+
+var ReferrerInfo = Components.Constructor(
+ "@mozilla.org/referrer-info;1",
+ "nsIReferrerInfo",
+ "init"
+);
+
+function run_test() {
+ // Fill up the queue
+ Services.prefs.setBoolPref("network.prefetch-next", true);
+ for (var i = 0; i < 5; i++) {
+ var uri = Services.io.newURI("http://localhost/" + i);
+ var referrerInfo = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, uri);
+ prefetch.prefetchURI(uri, referrerInfo, null, true);
+ }
+
+ // Make sure the queue has items in it...
+ Assert.ok(prefetch.hasMoreElements());
+
+ // Now disable the pref to force the queue to empty...
+ Services.prefs.setBoolPref("network.prefetch-next", false);
+ Assert.ok(!prefetch.hasMoreElements());
+
+ // Now reenable the pref, and add more items to the queue.
+ Services.prefs.setBoolPref("network.prefetch-next", true);
+ for (var k = 0; k < 5; k++) {
+ var uri2 = Services.io.newURI("http://localhost/" + k);
+ var referrerInfo2 = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, uri2);
+ prefetch.prefetchURI(uri2, referrerInfo2, null, true);
+ }
+ Assert.ok(prefetch.hasMoreElements());
+}
diff --git a/docshell/test/unit/test_pb_notification.js b/docshell/test/unit/test_pb_notification.js
new file mode 100644
index 0000000000..51cd3b95ff
--- /dev/null
+++ b/docshell/test/unit/test_pb_notification.js
@@ -0,0 +1,18 @@
+function destroy_transient_docshell() {
+ let windowlessBrowser = Services.appShell.createWindowlessBrowser(true);
+ windowlessBrowser.docShell.setOriginAttributes({ privateBrowsingId: 1 });
+ windowlessBrowser.close();
+ do_test_pending();
+ do_timeout(0, Cu.forceGC);
+}
+
+function run_test() {
+ var obs = {
+ observe(aSubject, aTopic, aData) {
+ Assert.equal(aTopic, "last-pb-context-exited");
+ do_test_finished();
+ },
+ };
+ Services.obs.addObserver(obs, "last-pb-context-exited");
+ destroy_transient_docshell();
+}
diff --git a/docshell/test/unit/test_privacy_transition.js b/docshell/test/unit/test_privacy_transition.js
new file mode 100644
index 0000000000..ae1bf71284
--- /dev/null
+++ b/docshell/test/unit/test_privacy_transition.js
@@ -0,0 +1,21 @@
+var gNotifications = 0;
+
+var observer = {
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIPrivacyTransitionObserver",
+ "nsISupportsWeakReference",
+ ]),
+
+ privateModeChanged(enabled) {
+ gNotifications++;
+ },
+};
+
+function run_test() {
+ let windowlessBrowser = Services.appShell.createWindowlessBrowser(true);
+ windowlessBrowser.docShell.addWeakPrivacyTransitionObserver(observer);
+ windowlessBrowser.docShell.setOriginAttributes({ privateBrowsingId: 1 });
+ windowlessBrowser.docShell.setOriginAttributes({ privateBrowsingId: 0 });
+ windowlessBrowser.close();
+ Assert.equal(gNotifications, 2);
+}
diff --git a/docshell/test/unit/test_subframe_stop_after_parent_error.js b/docshell/test/unit/test_subframe_stop_after_parent_error.js
new file mode 100644
index 0000000000..2f80d18ebb
--- /dev/null
+++ b/docshell/test/unit/test_subframe_stop_after_parent_error.js
@@ -0,0 +1,146 @@
+"use strict";
+// Tests that pending subframe requests for an initial about:blank
+// document do not delay showing load errors (and possibly result in a
+// crash at docShell destruction) for failed document loads.
+
+const { PromiseTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromiseTestUtils.sys.mjs"
+);
+PromiseTestUtils.allowMatchingRejectionsGlobally(/undefined/);
+
+const { XPCShellContentUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/XPCShellContentUtils.sys.mjs"
+);
+
+XPCShellContentUtils.init(this);
+
+const server = XPCShellContentUtils.createHttpServer({
+ hosts: ["example.com"],
+});
+
+// Registers a URL with the HTTP server which will not return a response
+// until we're ready to.
+function registerSlowPage(path) {
+ let result = {
+ url: `http://example.com/${path}`,
+ };
+
+ let finishedPromise = new Promise(resolve => {
+ result.finish = resolve;
+ });
+
+ server.registerPathHandler(`/${path}`, async (request, response) => {
+ response.processAsync();
+
+ response.setHeader("Content-Type", "text/html");
+ response.write("<html><body>Hello.</body></html>");
+
+ await finishedPromise;
+
+ response.finish();
+ });
+
+ return result;
+}
+
+let topFrameRequest = registerSlowPage("top.html");
+let subFrameRequest = registerSlowPage("frame.html");
+
+let thunks = new Set();
+function promiseStateStop(webProgress) {
+ return new Promise(resolve => {
+ let listener = {
+ onStateChange(aWebProgress, request, stateFlags, status) {
+ if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
+ webProgress.removeProgressListener(listener);
+
+ thunks.delete(listener);
+ resolve();
+ }
+ },
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+ };
+
+ // Keep the listener alive, since it's stored as a weak reference.
+ thunks.add(listener);
+ webProgress.addProgressListener(
+ listener,
+ Ci.nsIWebProgress.NOTIFY_STATE_REQUEST
+ );
+ });
+}
+
+async function runTest(waitForErrorPage) {
+ let page = await XPCShellContentUtils.loadContentPage("about:blank", {
+ remote: false,
+ });
+ let { browser } = page;
+
+ // Watch for the HTTP request for the top frame so that we can cancel
+ // it with an error.
+ let requestPromise = TestUtils.topicObserved(
+ "http-on-modify-request",
+ subject => subject.QueryInterface(Ci.nsIRequest).name == topFrameRequest.url
+ );
+
+ // Create a frame with a source URL which will not return a response
+ // before we cancel it with an error.
+ let doc = browser.contentDocument;
+ let frame = doc.createElement("iframe");
+ frame.src = topFrameRequest.url;
+ doc.body.appendChild(frame);
+
+ // Create a subframe in the initial about:blank document for the above
+ // frame which will not return a response before we cancel the
+ // document request.
+ let frameDoc = frame.contentDocument;
+ let subframe = frameDoc.createElement("iframe");
+ subframe.src = subFrameRequest.url;
+ frameDoc.body.appendChild(subframe);
+
+ let [req] = await requestPromise;
+
+ info("Cancel request for parent frame");
+ req.cancel(Cr.NS_ERROR_PROXY_CONNECTION_REFUSED);
+
+ // Request cancelation is not synchronous, so wait for the STATE_STOP
+ // event to fire.
+ await promiseStateStop(
+ browser.docShell.nsIInterfaceRequestor.getInterface(Ci.nsIWebProgress)
+ );
+
+ // And make a trip through the event loop to give the DocLoader a
+ // chance to update its state.
+ await new Promise(executeSoon);
+
+ if (waitForErrorPage) {
+ // Make sure that canceling the request with an error code actually
+ // shows an error page without waiting for a subframe response.
+ await TestUtils.waitForCondition(() =>
+ frame.contentDocument.documentURI.startsWith("about:neterror?")
+ );
+ }
+
+ info("Remove frame");
+ frame.remove();
+
+ await page.close();
+}
+
+add_task(async function testRemoveFrameImmediately() {
+ await runTest(false);
+});
+
+add_task(async function testRemoveFrameAfterErrorPage() {
+ await runTest(true);
+});
+
+add_task(async function () {
+ // Allow the document requests for the frames to complete.
+ topFrameRequest.finish();
+ subFrameRequest.finish();
+});
diff --git a/docshell/test/unit/xpcshell.ini b/docshell/test/unit/xpcshell.ini
new file mode 100644
index 0000000000..8bd7ef4f28
--- /dev/null
+++ b/docshell/test/unit/xpcshell.ini
@@ -0,0 +1,35 @@
+[DEFAULT]
+head = head_docshell.js
+support-files =
+ data/engine.xml
+ data/enginePost.xml
+ data/enginePrivate.xml
+
+[test_allowJavascript.js]
+skip-if = os == 'android'
+support-files =
+ AllowJavascriptChild.sys.mjs
+ AllowJavascriptParent.sys.mjs
+[test_bug442584.js]
+[test_browsing_context_structured_clone.js]
+[test_URIFixup.js]
+[test_URIFixup_check_host.js]
+[test_URIFixup_external_protocol_fallback.js]
+skip-if = os == 'android'
+[test_URIFixup_forced.js]
+# Disabled for 1563343 -- URI fixup should be done at the app level in GV.
+skip-if = os == 'android'
+[test_URIFixup_search.js]
+skip-if = os == 'android'
+[test_URIFixup_info.js]
+skip-if =
+ os == 'android'
+ win10_2004 && debug # Bug 1727925
+[test_pb_notification.js]
+# Bug 751575: unrelated JS changes cause timeouts on random platforms
+skip-if = true
+[test_privacy_transition.js]
+[test_subframe_stop_after_parent_error.js]
+skip-if =
+ os == 'android'
+ appname == 'thunderbird' # Needs to run without E10s, can't do that.
diff --git a/docshell/test/unit_ipc/test_pb_notification_ipc.js b/docshell/test/unit_ipc/test_pb_notification_ipc.js
new file mode 100644
index 0000000000..6408e7753e
--- /dev/null
+++ b/docshell/test/unit_ipc/test_pb_notification_ipc.js
@@ -0,0 +1,15 @@
+function run_test() {
+ var notifications = 0;
+ var obs = {
+ observe(aSubject, aTopic, aData) {
+ Assert.equal(aTopic, "last-pb-context-exited");
+ notifications++;
+ },
+ };
+ Services.obs.addObserver(obs, "last-pb-context-exited");
+
+ run_test_in_child("../unit/test_pb_notification.js", function () {
+ Assert.equal(notifications, 1);
+ do_test_finished();
+ });
+}
diff --git a/docshell/test/unit_ipc/xpcshell.ini b/docshell/test/unit_ipc/xpcshell.ini
new file mode 100644
index 0000000000..30e98a270f
--- /dev/null
+++ b/docshell/test/unit_ipc/xpcshell.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+head =
+skip-if = toolkit == 'android'
+
+[test_pb_notification_ipc.js]
+# Bug 751575: Perma-fails with: command timed out: 1200 seconds without output
+skip-if = true