diff --git a/dom/base/UseCounter.h b/dom/base/UseCounter.h new file mode 100644 index 0000000000..01cdb9969e --- /dev/null +++ b/dom/base/UseCounter.h @@ -0,0 +1,36 @@ +/* -*- 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/. */ +#ifndef UseCounter_h_ +#define UseCounter_h_ + +#include + +namespace mozilla { + +enum UseCounter : int16_t { + eUseCounter_UNKNOWN = -1, +#define USE_COUNTER_DOM_METHOD(interface_, name_) \ + eUseCounter_##interface_##_##name_, +#define USE_COUNTER_DOM_ATTRIBUTE(interface_, name_) \ + eUseCounter_##interface_##_##name_##_getter, \ + eUseCounter_##interface_##_##name_##_setter, +#define USE_COUNTER_CSS_PROPERTY(name_, id_) \ + eUseCounter_property_##id_, +#include "mozilla/dom/UseCounterList.h" +#undef USE_COUNTER_DOM_METHOD +#undef USE_COUNTER_DOM_ATTRIBUTE +#undef USE_COUNTER_CSS_PROPERTY + +#define DEPRECATED_OPERATION(op_) \ + eUseCounter_##op_, +#include "nsDeprecatedOperationList.h" +#undef DEPRECATED_OPERATION + + eUseCounter_Count +}; + +} + +#endif diff --git a/dom/base/UseCounters.conf b/dom/base/UseCounters.conf new file mode 100644 index 0000000000..f9202b629e --- /dev/null +++ b/dom/base/UseCounters.conf @@ -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/. + +// This file defines a list of use counters, which are things that can +// record usage of Web platform features and then report this information +// through Telemetry. +// +// The format of this file is very strict. Each line can be: +// +// (a) a blank line +// +// (b) a comment, which is a line that begins with "//" +// +// (c) one of three possible use counter declarations: +// +// method . +// attribute . +// property +// +// The |CSS property method name| should be identical to the |method| +// argument to CSS_PROP and related macros. The method name is +// identical to the name of the property, except that all hyphens are +// removed and CamelCase naming is used. See nsCSSPropList.h for +// further details. +// +// To actually cause use counters to be incremented, DOM methods +// and attributes must have a [UseCounter] extended attribute in +// the Web IDL file. CSS properties require no special treatment +// beyond being listed below. +// +// You might reasonably ask why we have this file and we require +// annotating things with [UseCounter] in the relevant WebIDL file as +// well. Generating things from bindings codegen and ensuring all the +// dependencies were correct would have been rather difficult, and +// annotating the WebIDL files does nothing for identifying CSS +// property usage, which we would also like to track. + +method SVGSVGElement.getElementById +attribute SVGSVGElement.currentScale +property Fill +property FillOpacity + +// Push API +method PushManager.subscribe +method PushSubscription.unsubscribe + +// window.sidebar.addSearchEngine +attribute Window.sidebar +method External.addSearchEngine + +// AppCache API +method OfflineResourceList.swapCache +method OfflineResourceList.update +attribute OfflineResourceList.status +attribute OfflineResourceList.onchecking +attribute OfflineResourceList.onerror +attribute OfflineResourceList.onnoupdate +attribute OfflineResourceList.ondownloading +attribute OfflineResourceList.onprogress +attribute OfflineResourceList.onupdateready +attribute OfflineResourceList.oncached +attribute OfflineResourceList.onobsolete diff --git a/dom/base/gen-usecounters.py b/dom/base/gen-usecounters.py new file mode 100644 index 0000000000..5b17c22e44 --- /dev/null +++ b/dom/base/gen-usecounters.py @@ -0,0 +1,80 @@ +#!/usr/bin/env 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/. + +from __future__ import print_function + +import json +import os +import sys +sys.path.append(os.path.dirname(__file__)) + +import usecounters + +AUTOGENERATED_WARNING_COMMENT = "/* THIS FILE IS AUTOGENERATED BY gen-usecounters.py - DO NOT EDIT */" + +def generate_list(f, counters): + def print_optional_macro_declare(name): + print(''' +#ifndef %(name)s +#define %(name)s(interface_, name_) // nothing +#define DEFINED_%(name)s +#endif +''' % { 'name': name }, file=f) + + def print_optional_macro_undeclare(name): + print(''' +#ifdef DEFINED_%(name)s +#undef DEFINED_%(name)s +#undef %(name)s +#endif +''' % { 'name': name }, file=f) + + print(AUTOGENERATED_WARNING_COMMENT, file=f) + + print_optional_macro_declare('USE_COUNTER_DOM_METHOD') + print_optional_macro_declare('USE_COUNTER_DOM_ATTRIBUTE') + print_optional_macro_declare('USE_COUNTER_CSS_PROPERTY') + + for counter in counters: + if counter['type'] == 'method': + print('USE_COUNTER_DOM_METHOD(%s, %s)' % (counter['interface_name'], counter['method_name']), file=f) + elif counter['type'] == 'attribute': + print('USE_COUNTER_DOM_ATTRIBUTE(%s, %s)' % (counter['interface_name'], counter['attribute_name']), file=f) + elif counter['type'] == 'property': + prop = counter['property_name'] + print('USE_COUNTER_CSS_PROPERTY(%s, %s)' % (prop, prop), file=f) + + print_optional_macro_undeclare('USE_COUNTER_DOM_METHOD') + print_optional_macro_undeclare('USE_COUNTER_DOM_ATTRIBUTE') + print_optional_macro_undeclare('USE_COUNTER_CSS_PROPERTY') + +def generate_property_map(f, counters): + print(AUTOGENERATED_WARNING_COMMENT, file=f) + print(''' +enum { + #define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) privatename_ + #define CSS_PROP_LIST_INCLUDE_LOGICAL + #define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, \\ + kwtable_, stylestruct_, stylestructoffset_, animtype_) \\ + USE_COUNTER_FOR_CSS_PROPERTY_##method_ = eUseCounter_UNKNOWN, + #include "nsCSSPropList.h" + #undef CSS_PROP + #undef CSS_PROP_LIST_INCLUDE_LOGICAL + #undef CSS_PROP_PUBLIC_OR_PRIVATE +}; +''', file=f) + for counter in counters: + if counter['type'] == 'property': + prop = counter['property_name'] + print('#define USE_COUNTER_FOR_CSS_PROPERTY_%s eUseCounter_property_%s' % (prop, prop), file=f) + +def use_counter_list(output_header, conf_filename): + counters = usecounters.read_conf(conf_filename) + generate_list(output_header, counters) + +def property_map(output_map, conf_filename): + counters = usecounters.read_conf(conf_filename) + generate_property_map(output_map, counters) diff --git a/dom/base/moz.build b/dom/base/moz.build index b79e363d9d..d788d15fed 100755 --- a/dom/base/moz.build +++ b/dom/base/moz.build @@ -141,9 +141,11 @@ EXPORTS.mozilla += [ 'FeedWriterEnabled.h', 'TextInputProcessor.h', 'TimerClamping.h', + 'UseCounter.h', ] EXPORTS.mozilla.dom += [ + '!UseCounterList.h', 'AnonymousContent.h', 'Attr.h', 'BarProps.h', @@ -469,5 +471,18 @@ if CONFIG['MOZ_PHOENIX'] or CONFIG['MOZ_XULRUNNER']: if CONFIG['MOZ_X11']: CXXFLAGS += CONFIG['TK_CFLAGS'] +GENERATED_FILES += [ + 'PropertyUseCounterMap.inc', + 'UseCounterList.h', +] + +countermap = GENERATED_FILES['PropertyUseCounterMap.inc'] +countermap.script = 'gen-usecounters.py:property_map' +countermap.inputs = ['UseCounters.conf'] + +counterlist = GENERATED_FILES['UseCounterList.h'] +counterlist.script = 'gen-usecounters.py:use_counter_list' +counterlist.inputs = ['UseCounters.conf'] + if CONFIG['GNU_CXX']: CXXFLAGS += ['-Wno-error=shadow'] diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp index d38b3c600b..d05f3b71fc 100644 --- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -104,6 +104,9 @@ #include "nsContentPermissionHelper.h" #include "nsCSSPseudoElements.h" // for CSSPseudoElementType #include "nsNetUtil.h" +#include "nsDocument.h" +#include "HTMLImageElement.h" +#include "mozilla/css/ImageLoader.h" #include "mozilla/layers/APZCTreeManager.h" // for layers::ZoomToRectBehavior #include "mozilla/dom/Promise.h" #include "mozilla/StyleSheetInlines.h" @@ -4022,6 +4025,27 @@ nsDOMWindowUtils::LeaveChaosMode() return NS_OK; } +NS_IMETHODIMP +nsDOMWindowUtils::ForceUseCounterFlush(nsIDOMNode *aNode) +{ + NS_ENSURE_ARG_POINTER(aNode); + + if (nsCOMPtr doc = do_QueryInterface(aNode)) { + mozilla::css::ImageLoader* loader = doc->StyleImageLoader(); + loader->FlushUseCounters(); + return NS_OK; + } + + if (nsCOMPtr content = do_QueryInterface(aNode)) { + if (HTMLImageElement* img = HTMLImageElement::FromContent(content)) { + img->FlushUseCounters(); + return NS_OK; + } + } + + return NS_OK; +} + NS_IMETHODIMP nsDOMWindowUtils::HasRuleProcessorUsedByMultipleStyleSets(uint32_t aSheetType, bool* aRetVal) diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp index 0b531f40a0..1c3e7a421f 100644 --- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -9718,6 +9718,19 @@ static const char* kDocumentWarnings[] = { }; #undef DOCUMENT_WARNING +static UseCounter +OperationToUseCounter(nsIDocument::DeprecatedOperations aOperation) +{ + switch(aOperation) { +#define DEPRECATED_OPERATION(_op) \ + case nsIDocument::e##_op: return eUseCounter_##_op; +#include "nsDeprecatedOperationList.h" +#undef DEPRECATED_OPERATION + default: + MOZ_CRASH(); + } +} + bool nsIDocument::HasWarnedAbout(DeprecatedOperations aOperation) const { @@ -9733,6 +9746,7 @@ nsIDocument::WarnOnceAbout(DeprecatedOperations aOperation, return; } mDeprecationWarnedAbout[aOperation] = true; + const_cast(this)->SetDocumentAndPageUseCounter(OperationToUseCounter(aOperation)); uint32_t flags = asError ? nsIScriptError::errorFlag : nsIScriptError::warningFlag; nsContentUtils::ReportToConsole(flags, @@ -11983,6 +11997,69 @@ nsIDocument::GetTopLevelContentDocument() return parent; } +void +nsIDocument::PropagateUseCounters(nsIDocument* aParentDocument) +{ + MOZ_ASSERT(this != aParentDocument); + + // What really matters here is that our use counters get propagated as + // high up in the content document hierarchy as possible. So, + // starting with aParentDocument, we need to find the toplevel content + // document, and propagate our use counters into its + // mChildDocumentUseCounters. + nsIDocument* contentParent = aParentDocument->GetTopLevelContentDocument(); + + if (!contentParent) { + return; + } + + contentParent->mChildDocumentUseCounters |= mUseCounters; + contentParent->mChildDocumentUseCounters |= mChildDocumentUseCounters; +} + +void +nsIDocument::SetPageUseCounter(UseCounter aUseCounter) +{ + // We want to set the use counter on the "page" that owns us; the definition + // of "page" depends on what kind of document we are. See the comments below + // for details. In any event, checking all the conditions below is + // reasonably expensive, so we cache whether we've notified our owning page. + if (mNotifiedPageForUseCounter[aUseCounter]) { + return; + } + mNotifiedPageForUseCounter[aUseCounter] = true; + + if (mDisplayDocument) { + // If we are a resource document, we won't have a docshell and so we won't + // record any page use counters on this document. Instead, we should + // forward it up to the document that loaded us. + MOZ_ASSERT(!mDocumentContainer); + mDisplayDocument->SetChildDocumentUseCounter(aUseCounter); + return; + } + + if (IsBeingUsedAsImage()) { + // If this is an SVG image document, we also won't have a docshell. + MOZ_ASSERT(!mDocumentContainer); + return; + } + + // We only care about use counters in content. If we're already a toplevel + // content document, then we should have already set the use counter on + // ourselves, and we are done. + nsIDocument* contentParent = GetTopLevelContentDocument(); + if (!contentParent) { + return; + } + + if (this == contentParent) { + MOZ_ASSERT(GetUseCounter(aUseCounter)); + return; + } + + contentParent->SetChildDocumentUseCounter(aUseCounter); +} + bool nsIDocument::HasScriptsBlockedBySandbox() { diff --git a/dom/base/nsIDocument.h b/dom/base/nsIDocument.h index f6645aaefc..fd393723c1 100644 --- a/dom/base/nsIDocument.h +++ b/dom/base/nsIDocument.h @@ -24,6 +24,7 @@ #include "nsTHashtable.h" // for member #include "mozilla/net/ReferrerPolicy.h" // for member #include "nsWeakReference.h" +#include "mozilla/UseCounter.h" #include "mozilla/WeakPtr.h" #include "Units.h" #include "nsContentListDeclarations.h" @@ -2781,6 +2782,23 @@ public: bool DidFireDOMContentLoaded() const { return mDidFireDOMContentLoaded; } + void SetDocumentUseCounter(mozilla::UseCounter aUseCounter) + { + if (!mUseCounters[aUseCounter]) { + mUseCounters[aUseCounter] = true; + } + } + + void SetPageUseCounter(mozilla::UseCounter aUseCounter); + + void SetDocumentAndPageUseCounter(mozilla::UseCounter aUseCounter) + { + SetDocumentUseCounter(aUseCounter); + SetPageUseCounter(aUseCounter); + } + + void PropagateUseCounters(nsIDocument* aParentDocument); + void SetUserHasInteracted(bool aUserHasInteracted) { mUserHasInteracted = aUserHasInteracted; @@ -2839,6 +2857,24 @@ public: virtual void ScheduleResizeObserversNotification() const = 0; +protected: + bool GetUseCounter(mozilla::UseCounter aUseCounter) + { + return mUseCounters[aUseCounter]; + } + + void SetChildDocumentUseCounter(mozilla::UseCounter aUseCounter) + { + if (!mChildDocumentUseCounters[aUseCounter]) { + mChildDocumentUseCounters[aUseCounter] = true; + } + } + + bool GetChildDocumentUseCounter(mozilla::UseCounter aUseCounter) + { + return mChildDocumentUseCounters[aUseCounter]; + } + private: mutable std::bitset mDeprecationWarnedAbout; mutable std::bitset mDocWarningWarnedAbout; @@ -3279,7 +3315,15 @@ protected: // Our live MediaQueryLists PRCList mDOMMediaQueryLists; - // Whether the user has interacted with the document or not: + // Flags for use counters used directly by this document. + std::bitset mUseCounters; + // Flags for use counters used by any child documents of this document. + std::bitset mChildDocumentUseCounters; + // Flags for whether we've notified our top-level "page" of a use counter + // for this child document. + std::bitset mNotifiedPageForUseCounter; + + // Whether the user has interacted with the document or not: bool mUserHasInteracted; mozilla::TimeStamp mPageUnloadingEventTimeStamp; diff --git a/dom/base/nsImageLoadingContent.cpp b/dom/base/nsImageLoadingContent.cpp index da49dd6beb..1226ef22e1 100644 --- a/dom/base/nsImageLoadingContent.cpp +++ b/dom/base/nsImageLoadingContent.cpp @@ -198,6 +198,12 @@ nsImageLoadingContent::Notify(imgIRequest* aRequest, } if (aType == imgINotificationObserver::DECODE_COMPLETE) { + nsCOMPtr container; + aRequest->GetImage(getter_AddRefs(container)); + if (container) { + container->PropagateUseCounters(GetOurOwnerDoc()); + } + UpdateImageState(true); } diff --git a/dom/base/test/browser.ini b/dom/base/test/browser.ini index 506eeb97ad..60949f7a2d 100644 --- a/dom/base/test/browser.ini +++ b/dom/base/test/browser.ini @@ -4,6 +4,13 @@ support-files = file_bug1011748_redirect.sjs file_bug1011748_OK.sjs file_messagemanager_unload.html + file_use_counter_outer.html + file_use_counter_svg_getElementById.svg + file_use_counter_svg_currentScale.svg + file_use_counter_svg_fill_pattern_definition.svg + file_use_counter_svg_fill_pattern.svg + file_use_counter_svg_fill_pattern_internal.svg + file_use_counter_svg_fill_pattern_data.svg [browser_bug593387.js] [browser_bug902350.js] @@ -17,4 +24,5 @@ tags = mcb skip-if = e10s # this tests non-e10s behavior. it's not expected to work in e10s. [browser_state_notifications.js] skip-if = true # Bug 1271028 +[browser_use_counters.js] [browser_bug1307747.js] diff --git a/dom/base/test/browser_use_counters.js b/dom/base/test/browser_use_counters.js new file mode 100644 index 0000000000..68bd04f880 --- /dev/null +++ b/dom/base/test/browser_use_counters.js @@ -0,0 +1,305 @@ +/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */ + +requestLongerTimeout(2); + +var {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {}); +Cu.import("resource://gre/modules/Services.jsm"); + +const gHttpTestRoot = "http://example.com/browser/dom/base/test/"; + +/** + * Enable local telemetry recording for the duration of the tests. + */ +var gOldContentCanRecord = false; +var gOldParentCanRecord = false; +add_task(function* test_initialize() { + let Telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry); + gOldParentCanRecord = Telemetry.canRecordExtended + Telemetry.canRecordExtended = true; + + // Because canRecordExtended is a per-process variable, we need to make sure + // that all of the pages load in the same content process. Limit the number + // of content processes to at most 1 (or 0 if e10s is off entirely). + yield SpecialPowers.pushPrefEnv({ set: [[ "dom.ipc.processCount", 1 ]] }); + + gOldContentCanRecord = yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function () { + let telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry); + let old = telemetry.canRecordExtended; + telemetry.canRecordExtended = true; + return old; + }); + info("canRecord for content: " + gOldContentCanRecord); +}); + +add_task(function* () { + // Check that use counters are incremented by SVGs loaded directly in iframes. + yield check_use_counter_iframe("file_use_counter_svg_getElementById.svg", + "SVGSVGELEMENT_GETELEMENTBYID"); + yield check_use_counter_iframe("file_use_counter_svg_currentScale.svg", + "SVGSVGELEMENT_CURRENTSCALE_getter"); + yield check_use_counter_iframe("file_use_counter_svg_currentScale.svg", + "SVGSVGELEMENT_CURRENTSCALE_setter"); + + // Check that even loads from the imglib cache update use counters. The + // images should still be there, because we just loaded them in the last + // set of tests. But we won't get updated counts for the document + // counters, because we won't be re-parsing the SVG documents. + yield check_use_counter_iframe("file_use_counter_svg_getElementById.svg", + "SVGSVGELEMENT_GETELEMENTBYID", false); + yield check_use_counter_iframe("file_use_counter_svg_currentScale.svg", + "SVGSVGELEMENT_CURRENTSCALE_getter", false); + yield check_use_counter_iframe("file_use_counter_svg_currentScale.svg", + "SVGSVGELEMENT_CURRENTSCALE_setter", false); + + // Check that use counters are incremented by SVGs loaded as images. + // Note that SVG images are not permitted to execute script, so we can only + // check for properties here. + yield check_use_counter_img("file_use_counter_svg_getElementById.svg", + "PROPERTY_FILL"); + yield check_use_counter_img("file_use_counter_svg_currentScale.svg", + "PROPERTY_FILL"); + + // Check that use counters are incremented by directly loading SVGs + // that reference patterns defined in another SVG file. + yield check_use_counter_direct("file_use_counter_svg_fill_pattern.svg", + "PROPERTY_FILLOPACITY", /*xfail=*/true); + + // Check that use counters are incremented by directly loading SVGs + // that reference patterns defined in the same file or in data: URLs. + yield check_use_counter_direct("file_use_counter_svg_fill_pattern_internal.svg", + "PROPERTY_FILLOPACITY"); + // data: URLs don't correctly propagate to their referring document yet. + //yield check_use_counter_direct("file_use_counter_svg_fill_pattern_data.svg", + // "PROPERTY_FILL_OPACITY"); +}); + +add_task(function* () { + let Telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry); + Telemetry.canRecordExtended = gOldParentCanRecord; + + yield ContentTask.spawn(gBrowser.selectedBrowser, { oldCanRecord: gOldContentCanRecord }, function (arg) { + Cu.import("resource://gre/modules/PromiseUtils.jsm"); + yield new Promise(resolve => { + let telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry); + telemetry.canRecordExtended = arg.oldCanRecord; + resolve(); + }); + }); +}); + + +function waitForDestroyedDocuments() { + let deferred = promise.defer(); + SpecialPowers.exactGC(deferred.resolve); + return deferred.promise; +} + +function waitForPageLoad(browser) { + return ContentTask.spawn(browser, null, function*() { + Cu.import("resource://gre/modules/PromiseUtils.jsm"); + yield new Promise(resolve => { + let listener = () => { + removeEventListener("load", listener, true); + resolve(); + } + addEventListener("load", listener, true); + }); + }); +} + +function grabHistogramsFromContent(use_counter_middlefix, page_before = null) { + let telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry); + let suffix = Services.appinfo.browserTabsRemoteAutostart ? "#content" : ""; + let gather = () => [ + telemetry.getHistogramById("USE_COUNTER2_" + use_counter_middlefix + "_PAGE" + suffix).snapshot().sum, + telemetry.getHistogramById("USE_COUNTER2_" + use_counter_middlefix + "_DOCUMENT" + suffix).snapshot().sum, + telemetry.getHistogramById("CONTENT_DOCUMENTS_DESTROYED" + suffix).snapshot().sum, + telemetry.getHistogramById("TOP_LEVEL_CONTENT_DOCUMENTS_DESTROYED" + suffix).snapshot().sum, + ]; + return BrowserTestUtils.waitForCondition(() => { + return page_before != telemetry.getHistogramById("USE_COUNTER2_" + use_counter_middlefix + "_PAGE" + suffix).snapshot().sum; + }).then(gather, gather); +} + +var check_use_counter_iframe = Task.async(function* (file, use_counter_middlefix, check_documents=true) { + info("checking " + file + " with histogram " + use_counter_middlefix); + + let newTab = gBrowser.addTab( "about:blank"); + gBrowser.selectedTab = newTab; + newTab.linkedBrowser.stop(); + + // Hold on to the current values of the telemetry histograms we're + // interested in. + let [histogram_page_before, histogram_document_before, + histogram_docs_before, histogram_toplevel_docs_before] = + yield grabHistogramsFromContent(use_counter_middlefix); + + gBrowser.selectedBrowser.loadURI(gHttpTestRoot + "file_use_counter_outer.html"); + yield waitForPageLoad(gBrowser.selectedBrowser); + + // Inject our desired file into the iframe of the newly-loaded page. + yield ContentTask.spawn(gBrowser.selectedBrowser, { file: file }, function(opts) { + Cu.import("resource://gre/modules/PromiseUtils.jsm"); + let deferred = PromiseUtils.defer(); + + let wu = content.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); + + let iframe = content.document.getElementById('content'); + iframe.src = opts.file; + let listener = (event) => { + event.target.removeEventListener("load", listener, true); + + // We flush the main document first, then the iframe's document to + // ensure any propagation that might happen from content->parent should + // have already happened when counters are reported to telemetry. + wu.forceUseCounterFlush(content.document); + wu.forceUseCounterFlush(iframe.contentDocument); + + deferred.resolve(); + }; + iframe.addEventListener("load", listener, true); + + return deferred.promise; + }); + + // Tear down the page. + gBrowser.removeTab(newTab); + + // The histograms only get recorded when the document actually gets + // destroyed, which might not have happened yet due to GC/CC effects, etc. + // Try to force document destruction. + yield waitForDestroyedDocuments(); + + // Grab histograms again and compare. + let [histogram_page_after, histogram_document_after, + histogram_docs_after, histogram_toplevel_docs_after] = + yield grabHistogramsFromContent(use_counter_middlefix, histogram_page_before); + + is(histogram_page_after, histogram_page_before + 1, + "page counts for " + use_counter_middlefix + " after are correct"); + ok(histogram_toplevel_docs_after >= histogram_toplevel_docs_before + 1, + "top level document counts are correct"); + if (check_documents) { + is(histogram_document_after, histogram_document_before + 1, + "document counts for " + use_counter_middlefix + " after are correct"); + } +}); + +var check_use_counter_img = Task.async(function* (file, use_counter_middlefix) { + info("checking " + file + " as image with histogram " + use_counter_middlefix); + + let newTab = gBrowser.addTab("about:blank"); + gBrowser.selectedTab = newTab; + newTab.linkedBrowser.stop(); + + // Hold on to the current values of the telemetry histograms we're + // interested in. + let [histogram_page_before, histogram_document_before, + histogram_docs_before, histogram_toplevel_docs_before] = + yield grabHistogramsFromContent(use_counter_middlefix); + + gBrowser.selectedBrowser.loadURI(gHttpTestRoot + "file_use_counter_outer.html"); + yield waitForPageLoad(gBrowser.selectedBrowser); + + // Inject our desired file into the img of the newly-loaded page. + yield ContentTask.spawn(gBrowser.selectedBrowser, { file: file }, function(opts) { + Cu.import("resource://gre/modules/PromiseUtils.jsm"); + let deferred = PromiseUtils.defer(); + + let img = content.document.getElementById('display'); + img.src = opts.file; + let listener = (event) => { + img.removeEventListener("load", listener, true); + + // Flush for the image. It matters what order we do these in, so that + // the image can propagate its use counters to the document prior to the + // document reporting its use counters. + let wu = content.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); + wu.forceUseCounterFlush(img); + + // Flush for the main window. + wu.forceUseCounterFlush(content.document); + + deferred.resolve(); + }; + img.addEventListener("load", listener, true); + + return deferred.promise; + }); + + // Tear down the page. + gBrowser.removeTab(newTab); + + // The histograms only get recorded when the document actually gets + // destroyed, which might not have happened yet due to GC/CC effects, etc. + // Try to force document destruction. + yield waitForDestroyedDocuments(); + + // Grab histograms again and compare. + let [histogram_page_after, histogram_document_after, + histogram_docs_after, histogram_toplevel_docs_after] = + yield grabHistogramsFromContent(use_counter_middlefix, histogram_page_before); + is(histogram_page_after, histogram_page_before + 1, + "page counts for " + use_counter_middlefix + " after are correct"); + is(histogram_document_after, histogram_document_before + 1, + "document counts for " + use_counter_middlefix + " after are correct"); + ok(histogram_toplevel_docs_after >= histogram_toplevel_docs_before + 1, + "top level document counts are correct"); + // 2 documents: one for the outer html page containing the element, and + // one for the SVG image itself. + ok(histogram_docs_after >= histogram_docs_before + 2, + "document counts are correct"); +}); + +var check_use_counter_direct = Task.async(function* (file, use_counter_middlefix, xfail=false) { + info("checking " + file + " with histogram " + use_counter_middlefix); + + let newTab = gBrowser.addTab( "about:blank"); + gBrowser.selectedTab = newTab; + newTab.linkedBrowser.stop(); + + // Hold on to the current values of the telemetry histograms we're + // interested in. + let [histogram_page_before, histogram_document_before, + histogram_docs_before, histogram_toplevel_docs_before] = + yield grabHistogramsFromContent(use_counter_middlefix); + + gBrowser.selectedBrowser.loadURI(gHttpTestRoot + file); + yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() { + Cu.import("resource://gre/modules/PromiseUtils.jsm"); + yield new Promise(resolve => { + let listener = () => { + removeEventListener("load", listener, true); + + let wu = content.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); + wu.forceUseCounterFlush(content.document); + + setTimeout(resolve, 0); + } + addEventListener("load", listener, true); + }); + }); + + // Tear down the page. + gBrowser.removeTab(newTab); + + // The histograms only get recorded when the document actually gets + // destroyed, which might not have happened yet due to GC/CC effects, etc. + // Try to force document destruction. + yield waitForDestroyedDocuments(); + + // Grab histograms again and compare. + let [histogram_page_after, histogram_document_after, + histogram_docs_after, histogram_toplevel_docs_after] = + yield grabHistogramsFromContent(use_counter_middlefix, histogram_page_before); + if (!xfail) { + is(histogram_page_after, histogram_page_before + 1, + "page counts for " + use_counter_middlefix + " after are correct"); + is(histogram_document_after, histogram_document_before + 1, + "document counts for " + use_counter_middlefix + " after are correct"); + } + ok(histogram_toplevel_docs_after >= histogram_toplevel_docs_before + 1, + "top level document counts are correct"); + ok(histogram_docs_after >= histogram_docs_before + 1, + "document counts are correct"); +}); diff --git a/dom/base/test/file_use_counter_outer.html b/dom/base/test/file_use_counter_outer.html new file mode 100644 index 0000000000..1a7eb90f7c --- /dev/null +++ b/dom/base/test/file_use_counter_outer.html @@ -0,0 +1,17 @@ + + + + + + Test for Bug 968923 + + +Mozilla Bug 968923 + + + + diff --git a/dom/base/test/file_use_counter_svg_currentScale.svg b/dom/base/test/file_use_counter_svg_currentScale.svg new file mode 100644 index 0000000000..ea9721a599 --- /dev/null +++ b/dom/base/test/file_use_counter_svg_currentScale.svg @@ -0,0 +1,17 @@ + + + + + Test graphic for hitting currentScale + + + + + + diff --git a/dom/base/test/file_use_counter_svg_fill_pattern.svg b/dom/base/test/file_use_counter_svg_fill_pattern.svg new file mode 100644 index 0000000000..ec8059a3d4 --- /dev/null +++ b/dom/base/test/file_use_counter_svg_fill_pattern.svg @@ -0,0 +1,15 @@ + + + + Borrowed from http://www.w3.org/TR/SVG/pservers.html + + + + + + diff --git a/dom/base/test/file_use_counter_svg_fill_pattern_data.svg b/dom/base/test/file_use_counter_svg_fill_pattern_data.svg new file mode 100644 index 0000000000..21f4753148 --- /dev/null +++ b/dom/base/test/file_use_counter_svg_fill_pattern_data.svg @@ -0,0 +1,15 @@ + + + + Borrowed from http://www.w3.org/TR/SVG/pservers.html + + + + + + diff --git a/dom/base/test/file_use_counter_svg_fill_pattern_definition.svg b/dom/base/test/file_use_counter_svg_fill_pattern_definition.svg new file mode 100644 index 0000000000..ffc2f1bed2 --- /dev/null +++ b/dom/base/test/file_use_counter_svg_fill_pattern_definition.svg @@ -0,0 +1,14 @@ + + + + Borrowed from http://www.w3.org/TR/SVG/pservers.html + + + + + + diff --git a/dom/base/test/file_use_counter_svg_fill_pattern_internal.svg b/dom/base/test/file_use_counter_svg_fill_pattern_internal.svg new file mode 100644 index 0000000000..3b0670cd0b --- /dev/null +++ b/dom/base/test/file_use_counter_svg_fill_pattern_internal.svg @@ -0,0 +1,23 @@ + + + + Borrowed from http://www.w3.org/TR/SVG/pservers.html + + + + + + + + + + + + diff --git a/dom/base/test/file_use_counter_svg_getElementById.svg b/dom/base/test/file_use_counter_svg_getElementById.svg new file mode 100644 index 0000000000..3393f43752 --- /dev/null +++ b/dom/base/test/file_use_counter_svg_getElementById.svg @@ -0,0 +1,22 @@ + + + + + Test graphic for hitting getElementById + + + + + + diff --git a/dom/base/usecounters.py b/dom/base/usecounters.py new file mode 100644 index 0000000000..b93f02f801 --- /dev/null +++ b/dom/base/usecounters.py @@ -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/. + +import buildconfig +import collections +import re +import StringIO +import sys + +def read_conf(conf_filename): + # Can't read/write from a single StringIO, so make a new one for reading. + stream = open(conf_filename, 'rU') + + def parse_counters(stream): + for line_num, line in enumerate(stream): + line = line.rstrip('\n') + if not line or line.startswith('//'): + # empty line or comment + continue + m = re.match(r'method ([A-Za-z0-9]+)\.([A-Za-z0-9]+)$', line) + if m: + interface_name, method_name = m.groups() + yield { 'type': 'method', + 'interface_name': interface_name, + 'method_name': method_name } + continue + m = re.match(r'attribute ([A-Za-z0-9]+)\.([A-Za-z0-9]+)$', line) + if m: + interface_name, attribute_name = m.groups() + yield { 'type': 'attribute', + 'interface_name': interface_name, + 'attribute_name': attribute_name } + continue + m = re.match(r'property ([A-Za-z0-9]+)$', line) + if m: + property_name = m.group(1) + yield { 'type': 'property', + 'property_name': property_name } + continue + raise ValueError('error parsing %s at line %d' % (conf_filename, line_num)) + + return parse_counters(stream) + +def generate_histograms(filename): + # The mapping for use counters to telemetry histograms depends on the + # ordering of items in the dictionary. + items = collections.OrderedDict() + for counter in read_conf(filename): + def append_counter(name, desc): + items[name] = { 'expires_in_version': 'never', + 'kind' : 'boolean', + 'description': desc } + + def append_counters(name, desc): + append_counter('USE_COUNTER2_%s_DOCUMENT' % name, 'Whether a document %s' % desc) + append_counter('USE_COUNTER2_%s_PAGE' % name, 'Whether a page %s' % desc) + + if counter['type'] == 'method': + method = '%s.%s' % (counter['interface_name'], counter['method_name']) + append_counters(method.replace('.', '_').upper(), 'called %s' % method) + elif counter['type'] == 'attribute': + attr = '%s.%s' % (counter['interface_name'], counter['attribute_name']) + counter_name = attr.replace('.', '_').upper() + append_counters('%s_getter' % counter_name, 'got %s' % attr) + append_counters('%s_setter' % counter_name, 'set %s' % attr) + elif counter['type'] == 'property': + prop = counter['property_name'] + append_counters('PROPERTY_%s' % prop.replace('-', '_').upper(), "used the '%s' property" % prop) + + return items diff --git a/dom/bindings/BindingUtils.cpp b/dom/bindings/BindingUtils.cpp index 512b9f8173..a8884d6a45 100644 --- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -14,6 +14,7 @@ #include "mozilla/Preferences.h" #include "mozilla/SizePrintfMacros.h" #include "mozilla/Unused.h" +#include "mozilla/UseCounter.h" #include "mozilla/dom/DocGroup.h" #include "AccessCheck.h" @@ -58,6 +59,7 @@ #include "mozilla/jsipc/CrossProcessObjectWrappers.h" #include "nsDOMClassInfo.h" #include "ipc/ErrorIPCUtils.h" +#include "mozilla/UseCounter.h" namespace mozilla { namespace dom { @@ -3590,6 +3592,16 @@ AssertReflectorHasGivenProto(JSContext* aCx, JSObject* aReflector, } // namespace binding_detail #endif // DEBUG +void +SetDocumentAndPageUseCounter(JSContext* aCx, JSObject* aObject, + UseCounter aUseCounter) +{ + nsGlobalWindow* win = xpc::WindowGlobalOrNull(js::UncheckedUnwrap(aObject)); + if (win && win->GetDocument()) { + win->GetDocument()->SetDocumentAndPageUseCounter(aUseCounter); + } +} + namespace { // This runnable is used to write a deprecation message from a worker to the diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h index 9ae5ed1f73..c80631fc14 100644 --- a/dom/bindings/BindingUtils.h +++ b/dom/bindings/BindingUtils.h @@ -46,6 +46,8 @@ class nsIJSID; namespace mozilla { +enum UseCounter : int16_t; + namespace dom { class CustomElementReactionsStack; template class Record; @@ -3426,6 +3428,10 @@ already_AddRefed CreateHTMLElement(const GlobalObject& aGlobal, const JS::CallArgs& aCallArgs, JS::Handle aGivenProto, ErrorResult& aRv); +void +SetDocumentAndPageUseCounter(JSContext* aCx, JSObject* aObject, + UseCounter aUseCounter); + // Warnings void DeprecationWarning(JSContext* aCx, JSObject* aObject, diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index a70253fc3a..42ce94fcb4 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -7467,8 +7467,8 @@ class CGPerSignatureCall(CGThing): def __init__(self, returnType, arguments, nativeMethodName, static, descriptor, idlNode, argConversionStartsAt=0, getter=False, - setter=False, isConstructor=False, resultVar=None, - objectName="obj"): + setter=False, isConstructor=False, useCounterName=None, + resultVar=None, objectName="obj"): assert idlNode.isMethod() == (not getter and not setter) assert idlNode.isAttr() == (getter or setter) # Constructors are always static @@ -7701,6 +7701,11 @@ class CGPerSignatureCall(CGThing): nativeMethodName, static, argsPost=argsPost, resultVar=resultVar)) + if useCounterName: + # Generate a telemetry call for when [UseCounter] is used. + code = "SetDocumentAndPageUseCounter(cx, obj, eUseCounter_%s);\n" % useCounterName + cgThings.append(CGGeneric(code)) + self.cgRoot = CGList(cgThings) def getArguments(self): @@ -7929,6 +7934,11 @@ class CGMethodCall(CGThing): methodName = "%s.%s" % (descriptor.interface.identifier.name, method.identifier.name) argDesc = "argument %d of " + methodName + if method.getExtendedAttribute("UseCounter"): + useCounterName = methodName.replace(".", "_") + else: + useCounterName = None + if method.isStatic(): nativeType = descriptor.nativeType staticTypeOverride = PropertyDefiner.getStringAttr(method, "StaticClassOverride") @@ -7950,7 +7960,8 @@ class CGMethodCall(CGThing): nativeMethodName, static, descriptor, method, argConversionStartsAt=argConversionStartsAt, - isConstructor=isConstructor) + isConstructor=isConstructor, + useCounterName=useCounterName) signatures = method.signatures() if len(signatures) == 1: @@ -8326,11 +8337,16 @@ class CGGetterCall(CGPerSignatureCall): getter. """ def __init__(self, returnType, nativeMethodName, descriptor, attr): + if attr.getExtendedAttribute("UseCounter"): + useCounterName = "%s_%s_getter" % (descriptor.interface.identifier.name, + attr.identifier.name) + else: + useCounterName = None if attr.isStatic(): nativeMethodName = "%s::%s" % (descriptor.nativeType, nativeMethodName) CGPerSignatureCall.__init__(self, returnType, [], nativeMethodName, attr.isStatic(), descriptor, attr, - getter=True) + getter=True, useCounterName=useCounterName) class CGNavigatorGetterCall(CGPerSignatureCall): @@ -8397,12 +8413,17 @@ class CGSetterCall(CGPerSignatureCall): setter. """ def __init__(self, argType, nativeMethodName, descriptor, attr): + if attr.getExtendedAttribute("UseCounter"): + useCounterName = "%s_%s_setter" % (descriptor.interface.identifier.name, + attr.identifier.name) + else: + useCounterName = None if attr.isStatic(): nativeMethodName = "%s::%s" % (descriptor.nativeType, nativeMethodName) CGPerSignatureCall.__init__(self, None, [FakeArgument(argType, attr, allowTreatNonCallableAsNull=True)], nativeMethodName, attr.isStatic(), - descriptor, attr, setter=True) + descriptor, attr, setter=True, useCounterName=useCounterName) def wrap_return_value(self): attr = self.idlNode @@ -13911,6 +13932,12 @@ class CGBindingRoot(CGThing): bindingHeaders[CGHeaders.getDeclarationFilename(enums[0])] = True bindingHeaders["jsapi.h"] = True + # For things that have [UseCounter] + def descriptorRequiresTelemetry(desc): + iface = desc.interface + return any(m.getExtendedAttribute("UseCounter") for m in iface.members) + bindingHeaders["mozilla/UseCounter.h"] = any( + descriptorRequiresTelemetry(d) for d in descriptors) bindingHeaders["mozilla/dom/SimpleGlobalObject.h"] = any( CGDictionary.dictionarySafeToJSONify(d) for d in dictionaries) bindingHeaders["XrayWrapper.h"] = any( diff --git a/dom/bindings/parser/WebIDL.py b/dom/bindings/parser/WebIDL.py index ce8862c02b..81911996d5 100644 --- a/dom/bindings/parser/WebIDL.py +++ b/dom/bindings/parser/WebIDL.py @@ -4245,6 +4245,11 @@ class IDLAttribute(IDLInterfaceMember): "readonly attributes" % attr.value(), [attr.location, self.location]) self._setDependsOn(attr.value()) + elif identifier == "UseCounter": + if self.stringifier: + raise WebIDLError("[UseCounter] must not be used on a " + "stringifier attribute", + [attr.location, self.location]) elif identifier == "Unscopable": if not attr.noArguments(): raise WebIDLError("[Unscopable] must take no arguments", @@ -4974,6 +4979,11 @@ class IDLMethod(IDLInterfaceMember, IDLScope): raise WebIDLError("[Alias] takes an identifier or string", [attr.location]) self._addAlias(attr.value()) + elif identifier == "UseCounter": + if self.isSpecial(): + raise WebIDLError("[UseCounter] must not be used on a special " + "operation", + [attr.location, self.location]) elif identifier == "Unscopable": if not attr.noArguments(): raise WebIDLError("[Unscopable] must take no arguments", diff --git a/dom/html/HTMLImageElement.cpp b/dom/html/HTMLImageElement.cpp index f7fe0fdcee..cb586d68ac 100644 --- a/dom/html/HTMLImageElement.cpp +++ b/dom/html/HTMLImageElement.cpp @@ -10,6 +10,7 @@ #include "nsPresContext.h" #include "nsMappedAttributes.h" #include "nsSize.h" +#include "nsDocument.h" #include "nsIDocument.h" #include "nsIDOMMutationEvent.h" #include "nsIScriptContext.h" @@ -26,6 +27,7 @@ #include "mozilla/dom/HTMLFormElement.h" #include "nsAttrValueOrString.h" #include "imgLoader.h" +#include "Image.h" // Responsive images! #include "mozilla/dom/HTMLSourceElement.h" @@ -1360,6 +1362,16 @@ HTMLImageElement::MediaFeatureValuesChanged() QueueImageLoadTask(false); } +void +HTMLImageElement::FlushUseCounters() +{ + nsCOMPtr request; + GetRequest(CURRENT_REQUEST, getter_AddRefs(request)); + + nsCOMPtr container; + request->GetImage(getter_AddRefs(container)); +} + } // namespace dom } // namespace mozilla diff --git a/dom/html/HTMLImageElement.h b/dom/html/HTMLImageElement.h index 3d3c7c834e..8d382610da 100644 --- a/dom/html/HTMLImageElement.h +++ b/dom/html/HTMLImageElement.h @@ -261,6 +261,12 @@ public: const nsAString& aMediaAttr, nsAString& aResult); + /** + * If this image's src pointers to an SVG document, flush the SVG document's + * use counters to telemetry. Only used for testing purposes. + */ + void FlushUseCounters(); + protected: virtual ~HTMLImageElement(); diff --git a/dom/interfaces/base/nsIDOMWindowUtils.idl b/dom/interfaces/base/nsIDOMWindowUtils.idl index ad45e6e520..70ec7e0ae6 100644 --- a/dom/interfaces/base/nsIDOMWindowUtils.idl +++ b/dom/interfaces/base/nsIDOMWindowUtils.idl @@ -49,7 +49,7 @@ interface nsIJSRAIIHelper; interface nsIContentPermissionRequest; interface nsIObserver; -[scriptable, uuid(7fcc7958-77d9-45ff-8c81-277bde5f0dc8)] +[scriptable, uuid(c471d440-004b-4c50-a6f2-747db5f443b6)] interface nsIDOMWindowUtils : nsISupports { /** @@ -1938,6 +1938,17 @@ interface nsIDOMWindowUtils : nsISupports { */ bool hasRuleProcessorUsedByMultipleStyleSets(in unsigned long aSheetType); + /* + * Force the use counters for the node's associated document(s) to be + * flushed to telemetry. For example, a document node will flush its own + * counters and an image node with an SVG source will flush the SVG + * document's counters. Normally, use counters are flushed to telemetry + * upon document destruction, but as document destruction is somewhat + * non-deterministic, we have this method here for more determinism when + * running tests. + */ + void forceUseCounterFlush(in nsIDOMNode aNode); + void setNextPaintSyncId(in long aSyncId); /** diff --git a/dom/svg/nsSVGElement.cpp b/dom/svg/nsSVGElement.cpp index 0b726623a0..3596235ceb 100644 --- a/dom/svg/nsSVGElement.cpp +++ b/dom/svg/nsSVGElement.cpp @@ -1208,9 +1208,27 @@ MappedAttrParser::ParseMappedAttrValue(nsIAtom* aMappedAttrName, nsCSSProps::LookupProperty(nsDependentAtomString(aMappedAttrName), CSSEnabledState::eForAllContent); if (propertyID != eCSSProperty_UNKNOWN) { - bool changed; // outparam for ParseProperty. (ignored) + bool changed = false; // outparam for ParseProperty. mParser.ParseProperty(propertyID, aMappedAttrValue, mDocURI, mBaseURI, mElement->NodePrincipal(), mDecl, &changed, false, true); + if (changed) { + // The normal reporting of use counters by the nsCSSParser won't happen + // since it doesn't have a sheet. + if (nsCSSProps::IsShorthand(propertyID)) { + CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, propertyID, + CSSEnabledState::eForAllContent) { + UseCounter useCounter = nsCSSProps::UseCounterFor(*subprop); + if (useCounter != eUseCounter_UNKNOWN) { + mElement->OwnerDoc()->SetDocumentAndPageUseCounter(useCounter); + } + } + } else { + UseCounter useCounter = nsCSSProps::UseCounterFor(propertyID); + if (useCounter != eUseCounter_UNKNOWN) { + mElement->OwnerDoc()->SetDocumentAndPageUseCounter(useCounter); + } + } + } return; } MOZ_ASSERT(aMappedAttrName == nsGkAtoms::lang, diff --git a/dom/webidl/External.webidl b/dom/webidl/External.webidl index 4b97a2007e..890dff306e 100644 --- a/dom/webidl/External.webidl +++ b/dom/webidl/External.webidl @@ -13,7 +13,7 @@ interface External // Mozilla extension partial interface External { - [UnsafeInPrerendering] + [UnsafeInPrerendering, UseCounter] void addSearchEngine(DOMString engineURL, DOMString iconURL, DOMString suggestedTitle, DOMString suggestedCategory); }; diff --git a/dom/webidl/OfflineResourceList.webidl b/dom/webidl/OfflineResourceList.webidl index a646428016..abc5118881 100644 --- a/dom/webidl/OfflineResourceList.webidl +++ b/dom/webidl/OfflineResourceList.webidl @@ -25,30 +25,38 @@ interface OfflineResourceList : EventTarget { /* The application cache group is now obsolete. */ const unsigned short OBSOLETE = 5; - [Throws] + [Throws, UseCounter] readonly attribute unsigned short status; /** * Begin the application update process on the associated application cache. */ - [Throws] + [Throws, UseCounter] void update(); /** * Swap in the newest version of the application cache, or disassociate * from the cache if the cache group is obsolete. */ - [Throws] + [Throws, UseCounter] void swapCache(); /* Events */ + [UseCounter] attribute EventHandler onchecking; + [UseCounter] attribute EventHandler onerror; + [UseCounter] attribute EventHandler onnoupdate; + [UseCounter] attribute EventHandler ondownloading; + [UseCounter] attribute EventHandler onprogress; + [UseCounter] attribute EventHandler onupdateready; + [UseCounter] attribute EventHandler oncached; + [UseCounter] attribute EventHandler onobsolete; }; diff --git a/dom/webidl/PushManager.webidl b/dom/webidl/PushManager.webidl index ef9b49e26f..de1e632c7e 100644 --- a/dom/webidl/PushManager.webidl +++ b/dom/webidl/PushManager.webidl @@ -25,7 +25,7 @@ interface PushManagerImpl { [Exposed=(Window,Worker), Func="nsContentUtils::PushEnabled", ChromeConstructor(DOMString scope)] interface PushManager { - [Throws] + [Throws, UseCounter] Promise subscribe(optional PushSubscriptionOptionsInit options); [Throws] Promise getSubscription(); diff --git a/dom/webidl/PushSubscription.webidl b/dom/webidl/PushSubscription.webidl index 4451a0441d..eab576d3ce 100644 --- a/dom/webidl/PushSubscription.webidl +++ b/dom/webidl/PushSubscription.webidl @@ -44,7 +44,7 @@ interface PushSubscription readonly attribute PushSubscriptionOptions options; [Throws] ArrayBuffer? getKey(PushEncryptionKeyName name); - [Throws] + [Throws, UseCounter] Promise unsubscribe(); // Implements the custom serializer specified in Push API, section 9. diff --git a/dom/webidl/SVGSVGElement.webidl b/dom/webidl/SVGSVGElement.webidl index d04a464008..afe63dbb28 100644 --- a/dom/webidl/SVGSVGElement.webidl +++ b/dom/webidl/SVGSVGElement.webidl @@ -33,6 +33,7 @@ interface SVGSVGElement : SVGGraphicsElement { readonly attribute float screenPixelToMillimeterY; readonly attribute boolean useCurrentView; // readonly attribute SVGViewSpec currentView; + [UseCounter] attribute float currentScale; readonly attribute SVGPoint currentTranslate; @@ -70,6 +71,7 @@ interface SVGSVGElement : SVGGraphicsElement { SVGTransform createSVGTransform(); [NewObject] SVGTransform createSVGTransformFromMatrix(SVGMatrix matrix); + [UseCounter] Element? getElementById(DOMString elementId); }; diff --git a/dom/webidl/Window.webidl b/dom/webidl/Window.webidl index 1b98042473..ad427630bd 100644 --- a/dom/webidl/Window.webidl +++ b/dom/webidl/Window.webidl @@ -368,7 +368,7 @@ Window implements OnErrorEventHandlerForWindow; #ifdef HAVE_SIDEBAR // Mozilla extension partial interface Window { - [Replaceable, Throws] + [Replaceable, Throws, UseCounter] readonly attribute (External or WindowProxy) sidebar; }; #endif diff --git a/image/DynamicImage.cpp b/image/DynamicImage.cpp index dfdc3e5d81..aeb1fcf78e 100644 --- a/image/DynamicImage.cpp +++ b/image/DynamicImage.cpp @@ -337,5 +337,11 @@ DynamicImage::Unwrap() return self.forget(); } +void +DynamicImage::PropagateUseCounters(nsIDocument*) +{ + // No use counters. +} + } // namespace image } // namespace mozilla diff --git a/image/ImageWrapper.cpp b/image/ImageWrapper.cpp index 7d2fbfa363..5ed6c78179 100644 --- a/image/ImageWrapper.cpp +++ b/image/ImageWrapper.cpp @@ -291,6 +291,12 @@ ImageWrapper::SetAnimationStartTime(const TimeStamp& aTime) mInnerImage->SetAnimationStartTime(aTime); } +void +ImageWrapper::PropagateUseCounters(nsIDocument* aParentDocument) +{ + mInnerImage->PropagateUseCounters(aParentDocument); +} + nsIntSize ImageWrapper::OptimalImageSizeForDest(const gfxSize& aDest, uint32_t aWhichFrame, diff --git a/image/RasterImage.cpp b/image/RasterImage.cpp index b6f9bbd392..5725aee3a4 100644 --- a/image/RasterImage.cpp +++ b/image/RasterImage.cpp @@ -1663,6 +1663,12 @@ RasterImage::Unwrap() return self.forget(); } +void +RasterImage::PropagateUseCounters(nsIDocument*) +{ + // No use counters. +} + IntSize RasterImage::OptimalImageSizeForDest(const gfxSize& aDest, uint32_t aWhichFrame, SamplingFilter aSamplingFilter, uint32_t aFlags) diff --git a/image/VectorImage.cpp b/image/VectorImage.cpp index 59b31be47e..3fcb296f5f 100644 --- a/image/VectorImage.cpp +++ b/image/VectorImage.cpp @@ -34,6 +34,7 @@ #include "SVGDocumentWrapper.h" #include "nsIDOMEventListener.h" #include "SurfaceCache.h" +#include "nsDocument.h" // undef the GetCurrentTime macro defined in WinBase.h from the MS Platform SDK #undef GetCurrentTime @@ -1327,6 +1328,15 @@ VectorImage::InvalidateObserversOnNextRefreshDriverTick() } } +void +VectorImage::PropagateUseCounters(nsIDocument* aParentDocument) +{ + nsIDocument* doc = mSVGDocumentWrapper->GetDocument(); + if (doc) { + doc->PropagateUseCounters(aParentDocument); + } +} + nsIntSize VectorImage::OptimalImageSizeForDest(const gfxSize& aDest, uint32_t aWhichFrame, diff --git a/image/imgIContainer.idl b/image/imgIContainer.idl index ce663a73e1..ba1c494af6 100644 --- a/image/imgIContainer.idl +++ b/image/imgIContainer.idl @@ -19,6 +19,8 @@ #include "nsSize.h" #include "limits.h" +class nsIDocument; + namespace mozilla { namespace layers { class LayerManager; @@ -65,6 +67,7 @@ native Orientation(mozilla::image::Orientation); native TempRefSourceSurface(already_AddRefed); native TempRefImgIContainer(already_AddRefed); native nsIntSizeByVal(nsIntSize); +[ptr] native nsIDocument(nsIDocument); /** @@ -74,7 +77,7 @@ native nsIntSizeByVal(nsIntSize); * * Internally, imgIContainer also manages animation of images. */ -[scriptable, builtinclass, uuid(7ba72242-28da-4c5f-b53f-54da8874775e)] +[scriptable, builtinclass, uuid(a8dbee24-ff86-4755-b40e-51175caf31af)] interface imgIContainer : nsISupports { /** @@ -539,4 +542,10 @@ interface imgIContainer : nsISupports * Removes any ImageWrappers and returns the unwrapped base image. */ [notxpcom, nostdcall] TempRefImgIContainer unwrap(); + + /* + * Propagate the use counters (if any) from this container to the passed in + * document. + */ + [noscript, notxpcom] void propagateUseCounters(in nsIDocument aDocument); }; diff --git a/layout/generic/nsBulletFrame.cpp b/layout/generic/nsBulletFrame.cpp index dad2230b36..db9a70aff6 100644 --- a/layout/generic/nsBulletFrame.cpp +++ b/layout/generic/nsBulletFrame.cpp @@ -775,6 +775,16 @@ nsBulletFrame::Notify(imgIRequest *aRequest, int32_t aType, const nsIntRect* aDa InvalidateFrame(); } + if (aType == imgINotificationObserver::DECODE_COMPLETE) { + if (nsIDocument* parent = GetOurCurrentDoc()) { + nsCOMPtr container; + aRequest->GetImage(getter_AddRefs(container)); + if (container) { + container->PropagateUseCounters(parent); + } + } + } + return NS_OK; } diff --git a/layout/style/ImageLoader.cpp b/layout/style/ImageLoader.cpp index 5d4f628636..a26a1741aa 100644 --- a/layout/style/ImageLoader.cpp +++ b/layout/style/ImageLoader.cpp @@ -15,6 +15,7 @@ #include "FrameLayerBuilder.h" #include "nsSVGEffects.h" #include "imgIContainer.h" +#include "Image.h" namespace mozilla { namespace css { @@ -394,6 +395,14 @@ ImageLoader::Notify(imgIRequest* aRequest, int32_t aType, const nsIntRect* aData return OnFrameUpdate(aRequest); } + if (aType == imgINotificationObserver::DECODE_COMPLETE) { + nsCOMPtr image; + aRequest->GetImage(getter_AddRefs(image)); + if (image && mDocument) { + image->PropagateUseCounters(mDocument); + } + } + return NS_OK; } @@ -499,5 +508,19 @@ ImageLoader::UnblockOnload(imgIRequest* aRequest) return NS_OK; } +void +ImageLoader::FlushUseCounters() +{ + for (auto iter = mImages.Iter(); !iter.Done(); iter.Next()) { + nsPtrHashKey* key = iter.Get(); + ImageLoader::Image* image = key->GetKey(); + + imgIRequest* request = image->mRequests.GetWeak(mDocument); + + nsCOMPtr container; + request->GetImage(getter_AddRefs(container)); + } +} + } // namespace css } // namespace mozilla diff --git a/layout/style/ImageLoader.h b/layout/style/ImageLoader.h index c7469d570d..4e29da5ed4 100644 --- a/layout/style/ImageLoader.h +++ b/layout/style/ImageLoader.h @@ -70,6 +70,8 @@ public: void DestroyRequest(imgIRequest* aRequest); + void FlushUseCounters(); + private: ~ImageLoader() {} diff --git a/layout/style/nsCSSDataBlock.cpp b/layout/style/nsCSSDataBlock.cpp index c32f436106..33c309dd40 100644 --- a/layout/style/nsCSSDataBlock.cpp +++ b/layout/style/nsCSSDataBlock.cpp @@ -681,12 +681,14 @@ nsCSSExpandedDataBlock::TransferFromBlock(nsCSSExpandedDataBlock& aFromBlock, bool aIsImportant, bool aOverrideImportant, bool aMustCallValueAppended, - css::Declaration* aDeclaration) + css::Declaration* aDeclaration, + nsIDocument* aSheetDocument) { if (!nsCSSProps::IsShorthand(aPropID)) { return DoTransferFromBlock(aFromBlock, aPropID, aIsImportant, aOverrideImportant, - aMustCallValueAppended, aDeclaration); + aMustCallValueAppended, aDeclaration, + aSheetDocument); } // We can pass CSSEnabledState::eIgnore (here, and in ClearProperty @@ -698,7 +700,8 @@ nsCSSExpandedDataBlock::TransferFromBlock(nsCSSExpandedDataBlock& aFromBlock, CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aPropID, aEnabledState) { changed |= DoTransferFromBlock(aFromBlock, *p, aIsImportant, aOverrideImportant, - aMustCallValueAppended, aDeclaration); + aMustCallValueAppended, aDeclaration, + aSheetDocument); } return changed; } @@ -709,7 +712,8 @@ nsCSSExpandedDataBlock::DoTransferFromBlock(nsCSSExpandedDataBlock& aFromBlock, bool aIsImportant, bool aOverrideImportant, bool aMustCallValueAppended, - css::Declaration* aDeclaration) + css::Declaration* aDeclaration, + nsIDocument* aSheetDocument) { bool changed = false; MOZ_ASSERT(aFromBlock.HasPropertyBit(aPropID), "oops"); @@ -737,6 +741,13 @@ nsCSSExpandedDataBlock::DoTransferFromBlock(nsCSSExpandedDataBlock& aFromBlock, aDeclaration->ValueAppended(aPropID); } + if (aSheetDocument) { + UseCounter useCounter = nsCSSProps::UseCounterFor(aPropID); + if (useCounter != eUseCounter_UNKNOWN) { + aSheetDocument->SetDocumentAndPageUseCounter(useCounter); + } + } + SetPropertyBit(aPropID); aFromBlock.ClearPropertyBit(aPropID); diff --git a/layout/style/nsCSSDataBlock.h b/layout/style/nsCSSDataBlock.h index 3d6fd659d8..b699c408b9 100644 --- a/layout/style/nsCSSDataBlock.h +++ b/layout/style/nsCSSDataBlock.h @@ -20,6 +20,7 @@ struct nsRuleData; class nsCSSExpandedDataBlock; +class nsIDocument; namespace mozilla { namespace css { @@ -257,6 +258,8 @@ public: * Returns true if something changed, false otherwise. Calls * |ValueAppended| on |aDeclaration| if the property was not * previously set, or in any case if |aMustCallValueAppended| is true. + * Calls |SetDocumentAndPageUseCounter| on |aSheetDocument| if it is + * non-null and |aPropID| has a use counter. */ bool TransferFromBlock(nsCSSExpandedDataBlock& aFromBlock, nsCSSPropertyID aPropID, @@ -264,7 +267,8 @@ public: bool aIsImportant, bool aOverrideImportant, bool aMustCallValueAppended, - mozilla::css::Declaration* aDeclaration); + mozilla::css::Declaration* aDeclaration, + nsIDocument* aSheetDocument); /** * Copies the values for aPropID into the specified aRuleData object. @@ -298,7 +302,8 @@ private: bool aIsImportant, bool aOverrideImportant, bool aMustCallValueAppended, - mozilla::css::Declaration* aDeclaration); + mozilla::css::Declaration* aDeclaration, + nsIDocument* aSheetDocument); #ifdef DEBUG void DoAssertInitialState(); diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index e2fb99b91d..a7e5e4d4b9 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -143,6 +143,8 @@ public: nsresult SetStyleSheet(CSSStyleSheet* aSheet); + nsIDocument* GetDocument(); + nsresult SetQuirkMode(bool aQuirkMode); nsresult SetChildLoader(mozilla::css::Loader* aChildLoader); @@ -1649,6 +1651,15 @@ CSSParserImpl::SetStyleSheet(CSSStyleSheet* aSheet) return NS_OK; } +nsIDocument* +CSSParserImpl::GetDocument() +{ + if (!mSheet) { + return nullptr; + } + return mSheet->GetDocument(); +} + nsresult CSSParserImpl::SetQuirkMode(bool aQuirkMode) { @@ -1970,7 +1981,8 @@ CSSParserImpl::ParseTransformProperty(const nsAString& aPropValue, declaration->ExpandTo(&mData); changed = mData.TransferFromBlock(mTempData, eCSSProperty_transform, EnabledState(), false, - true, false, declaration); + true, false, declaration, + GetDocument()); declaration->CompressFrom(&mData); } @@ -2051,7 +2063,8 @@ CSSParserImpl::ParseProperty(const nsCSSPropertyID aPropID, aDeclaration->ExpandTo(&mData); *aChanged = mData.TransferFromBlock(mTempData, aPropID, EnabledState(), aIsImportant, - true, false, aDeclaration); + true, false, aDeclaration, + GetDocument()); aDeclaration->CompressFrom(&mData); } CLEAR_ERROR(); @@ -7519,7 +7532,7 @@ CSSParserImpl::ParseDeclaration(css::Declaration* aDeclaration, *aChanged |= mData.TransferFromBlock(mTempData, propID, EnabledState(), status == ePriority_Important, false, aMustCallValueAppended, - aDeclaration); + aDeclaration, GetDocument()); } return true; @@ -15324,17 +15337,17 @@ CSSParserImpl::ParseFontFeatureSettings(nsCSSValue& aValue) return true; } -bool -CSSParserImpl::ParseFontVariationSettings(nsCSSValue& aValue) -{ - // TODO: Actually implement this. - - // This stub is here because websites insist on considering this - // very hardware-dependent and O.S.-variable low-level font-control - // as a "critical feature" which it isn't as there is 0 guarantee - // that font variation settings are supported or honored by any - // operating system used by the client. - return true; +bool +CSSParserImpl::ParseFontVariationSettings(nsCSSValue& aValue) +{ + // TODO: Actually implement this. + + // This stub is here because websites insist on considering this + // very hardware-dependent and O.S.-variable low-level font-control + // as a "critical feature" which it isn't as there is 0 guarantee + // that font variation settings are supported or honored by any + // operating system used by the client. + return true; } bool diff --git a/layout/style/nsCSSProps.cpp b/layout/style/nsCSSProps.cpp index e7d524c35f..d33878ecdf 100644 --- a/layout/style/nsCSSProps.cpp +++ b/layout/style/nsCSSProps.cpp @@ -3423,6 +3423,21 @@ nsCSSProps::gPropertyEnabled[eCSSProperty_COUNT_with_aliases] = { #undef IS_ENABLED_BY_DEFAULT }; +#include "../../dom/base/PropertyUseCounterMap.inc" + +/* static */ const UseCounter +nsCSSProps::gPropertyUseCounter[eCSSProperty_COUNT_no_shorthands] = { + #define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) privatename_ + #define CSS_PROP_LIST_INCLUDE_LOGICAL + #define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, \ + kwtable_, stylestruct_, stylestructoffset_, animtype_) \ + static_cast(USE_COUNTER_FOR_CSS_PROPERTY_##method_), + #include "nsCSSPropList.h" + #undef CSS_PROP + #undef CSS_PROP_LIST_INCLUDE_LOGICAL + #undef CSS_PROP_PUBLIC_OR_PRIVATE +}; + // Check that all logical property flags are used appropriately. #define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, \ kwtable_, stylestruct_, stylestructoffset_, animtype_) \ diff --git a/layout/style/nsCSSProps.h b/layout/style/nsCSSProps.h index 567f7f7bfa..cf7ffda78f 100644 --- a/layout/style/nsCSSProps.h +++ b/layout/style/nsCSSProps.h @@ -19,6 +19,7 @@ #include "nsStyleStructFwd.h" #include "nsCSSKeywords.h" #include "mozilla/CSSEnabledState.h" +#include "mozilla/UseCounter.h" #include "mozilla/EnumTypeTraits.h" // Length of the "--" prefix on custom names (such as custom property names, @@ -639,8 +640,19 @@ public: return gPropertyEnabled[aProperty]; } + // A table for the use counter associated with each CSS property. If a + // property does not have a use counter defined in UseCounters.conf, then + // its associated entry is |eUseCounter_UNKNOWN|. + static const mozilla::UseCounter gPropertyUseCounter[eCSSProperty_COUNT_no_shorthands]; + public: + static mozilla::UseCounter UseCounterFor(nsCSSPropertyID aProperty) { + MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT_no_shorthands, + "out of range"); + return gPropertyUseCounter[aProperty]; + } + static bool IsEnabled(nsCSSPropertyID aProperty, EnabledState aEnabled) { if (IsEnabled(aProperty)) {