Page MenuHomeWickedGov Phorge

index.js
No OneTemporary

Size
30 KB
Referenced Files
None
Subscribers
None

index.js

/**
* CentralNotice display module.
*
* Handles campaign and banner selection, execution of campaign mixin code, and
* banner display. Provides an API to be used from campaign mixins and in-banner
* JS.
*
* The layout of this module is:
*
* ext.centralNotice.display.js (this file): General logic, banner injection
* and external access points, provided on mw.centralNotice. Code here
* may know about and manipulate other objects in this module, but not
* vice versa.
*
* ext.centralNotice.display.state.js: Stores data for campaign/banner
* processing and data related to the state of that processing. Provides
* cn.internal.state and cn.data.
*
* ext.centralNotice.display.chooser.js: Logic for selecting a campaign and
* a banner (or not). Provides cn.internal.chooser.
*
* ext.centralNotice.display.bucketer.js: Storage, retrieval and other
* processing of buckets. Provides cn.internal.bucketer.
*
* ext.centralNotice.display.hide.js: Retrieves, processes and stores 'hide'
* cookies, which prevent banners from showing in certain circumstances.
* Provides cn.internal.hide.
*
* For an overview of how this all fits together, see
* mw.centralNotice.reallyChooseAndMaybeDisplay() (below).
*/
( function () {
var cn, Mixin,
// Registry of campaign-associated mixins
campaignMixins = {},
// For providing a jQuery.Promise to signal when a banner has loaded
bannerLoadedDeferredObj,
// Name of a requested banner; see cn.requestBanner(), below.
requestedBannerName = null,
// Maximum time to delay the record impression call, in milliseconds
MAX_RECORD_IMPRESSION_DELAY = 250,
// EventLogging schema name for logging impressions
IMPRESSION_EVENT_LOGGING_SCHEMA = 'CentralNoticeImpression',
// Prefix for key used to store banner preview content for external preview.
// Coordinate with PREVIEW_STORAGE_KEY_PREFIX in bannereditor.js
PREVIEW_STORAGE_KEY_PREFIX = 'cn-banner-preview-';
// TODO: make data.result options explicit via constants
/**
* Class for campaign-associated mixins. Access via mw.centralNotice.Mixin.
*
* @param name
*/
Mixin = function ( name ) {
this.name = name;
};
// TODO Refactor hooks (rename, divide hooks that run at multiple points into
// separate hooks, and adapt handlers accordingly)
/**
* Run after we've chosen a campaign to attempt, but before we try to choose a
* banner from that campaign.
*
* @param handlerFunc
*/
Mixin.prototype.setPreBannerHandler = function ( handlerFunc ) {
this.preBannerHandler = handlerFunc;
};
/**
* Run after we chose and loaded a banner, or after the campaign failed, or after
* we unsuccessfully attempted to chose a banner.
*
* @param handlerFunc
*/
Mixin.prototype.setPostBannerOrFailHandler = function ( handlerFunc ) {
this.postBannerOrFailHandler = handlerFunc;
};
/**
* Run at the end of the selection and display process. If a banner was chosen
* to be displayed, the hook runs after it's loaded. If all attempted campaigns failed
* and no banner will show, the hook runs then.
*
* @param handlerFunc
*/
Mixin.prototype.setFinalizeChooseAndMaybeDisplayHandler = function ( handlerFunc ) {
this.finalizeChooseAndMaybeDisplayHandler = handlerFunc;
};
/**
* Run handlers stored in the mixin property indicated by hookPropertyName,
* for all campaign mixins.
*
* @param {string} hookPropertyName The name of a Mixin property containing
* hook handlers
* @param campaign
*/
function runMixinHooks( hookPropertyName, campaign ) {
// eslint-disable-next-line no-jquery/no-each-util
$.each( campaign.mixins, function ( mixinName, mixinParams ) {
var handler;
// Sanity check
if ( !( mixinName in campaignMixins ) ) {
mw.log.warn( 'Mixin ' + mixinName + ' not registered.' );
return;
}
// Mixins need not handle all hooks
if ( !( hookPropertyName in campaignMixins[ mixinName ] ) ) {
return;
}
handler = campaignMixins[ mixinName ][ hookPropertyName ];
// Another sanity check
if ( typeof handler !== 'function' ) {
mw.log.warn( hookPropertyName + ' for ' + mixinName + ' not a function.' );
return;
}
handler( mixinParams, campaign );
} );
}
function runPreBannerHooks() {
runMixinHooks( 'preBannerHandler', cn.internal.state.getAttemptingCampaign() );
}
function runPostBannerOrFailHooks() {
runMixinHooks( 'postBannerOrFailHandler', cn.internal.state.getAttemptingCampaign() );
}
function runFinalizeChooseAndMaybeDisplayHooks() {
cn.internal.state.getAttemptedCampaigns().forEach( function ( campaign ) {
runMixinHooks( 'finalizeChooseAndMaybeDisplayHandler', campaign );
} );
}
/**
* Set up the legacy cn.data property using a getter, or a normal property
* (for browsers that don't support getters).
*/
function setUpDataProperty() {
// try/catch since some browsers don't support Object.defineProperty
// or don't support it fully
try {
Object.defineProperty( cn, 'data', {
get: function () {
return cn.internal.state.getData();
}
} );
return;
} catch ( e ) {}
// FIXME For browsers that don't support defineProperty, we don't
// fully respect our internal contract with the state object to
// manage data, since we assume the object reference won't change.
cn.data = cn.internal.state.getData();
}
/**
* Expose a promise object to be resolved when the banner is loaded.
*/
function setUpBannerLoadedPromise() {
bannerLoadedDeferredObj = $.Deferred();
cn.bannerLoadedPromise = bannerLoadedDeferredObj.promise();
// Legacy location of the above
// TODO Deprecate and remove
cn.events = {};
cn.events.bannerLoaded = cn.bannerLoadedPromise;
}
function fetchOrRetrieveBanner() {
var previewBannerContent,
data = cn.internal.state.getData();
// If this is a preview of an unsaved version, retrieve and inject the banner as
// soon as the DOM's ready.
if ( data.preview ) {
$( function () {
previewBannerContent = cn.kvStore.getItem(
PREVIEW_STORAGE_KEY_PREFIX + data.banner,
cn.kvStore.contexts.GLOBAL
);
if ( previewBannerContent === null ) {
mw.log.warn( 'Could not retrieve preview banner ' + data.banner );
} else {
injectBannerHTML( previewBannerContent );
}
} );
} else {
fetchBanner();
}
}
function fetchBanner() {
var data = cn.internal.state.getData(),
urlBase = new mw.Uri(
mw.config.get( 'wgCentralNoticeActiveBannerDispatcher' )
),
// For Varnish purges of banner content, we ensure query param order (thus we
// can't use the object-based facilities for params in mw.Uri).
// Rather, we use mw.Uri only to parse the URL set in config and to
// reconstruct the bits before the query.
// Param order must coordinate with CdnCacheUpdateBannerLoader in php.
urlQuery = [
'banner=' + mw.Uri.encode( data.banner ),
'uselang=' + mw.Uri.encode( data.uselang ),
'debug=' + ( !!data.debug ).toString()
];
// If this is a test display, there might not be a campaign
if ( data.campaign ) {
urlQuery.unshift( 'campaign=' + mw.Uri.encode( data.campaign ) );
}
// Only a title param (for ugly URL format) is allowed as a param on the
// configured banner dispatchers
if ( urlBase.query.title ) {
// As per mediawiki.Uri.js
urlQuery.unshift( 'title=' + mw.util.wikiUrlencode( urlBase.query.title ) );
}
// Remove any other query or fragment info parsed from the configured URL
urlBase.query = {};
urlBase.fragment = '';
// The returned javascript will call mw.centralNotice.insertBanner()
// or mw.centralNotice.handleBannerLoaderError() (if an error was
// handled on the server).
$.ajax( {
url: urlBase.toString() + '?' + urlQuery.join( '&' ),
dataType: 'script',
cache: true
} ).fail( function ( jqXHR, status, error ) {
cn.handleBannerLoaderError( status + ': ' + error );
} );
}
function injectBannerHTML( bannerHtml ) {
// The centralNotice div should already have been added by
// ext.centralNotice.startUp.
// Inject the HTML
$( 'div#centralNotice' )
.attr(
'class',
mw.html.escape( 'cn-' + cn.internal.state.getData().bannerCategory )
)
.prepend( bannerHtml );
if ( window.performance && performance.mark ) {
performance.mark( 'mwCentralNoticeBanner' );
}
}
/**
* Adds reallyRecordImpression() as the last handler for cn.recordImpressionDeferredObj,
* then resolves.
*/
function resolveRecordImpressionDeferred() {
cn.recordImpressionDeferredObj.done( reallyRecordImpression );
cn.recordImpressionDeferredObj.resolve();
}
function recordImpression() {
var timeout,
timeoutHasRun = false;
if ( cn.recordImpressionDelayPromises.length === 0 ) {
reallyRecordImpression();
return;
}
// If there are promises in cn.recordImpressionDelayPromises, then
// cn.recordImpressionDeferredObj (used in resolveRecordImpressionDeferred())
// should already have been set.
timeout = setTimeout( function () {
timeoutHasRun = true;
resolveRecordImpressionDeferred();
}, MAX_RECORD_IMPRESSION_DELAY );
// This function can only run once, so checking that the timeout hasn't run yet
// should be sufficient to prevent extra record impression calls.
$.when.apply( $, cn.recordImpressionDelayPromises ).always( function () {
if ( !timeoutHasRun ) {
clearTimeout( timeout );
resolveRecordImpressionDeferred();
}
} );
}
function reallyRecordImpression() {
var state = cn.internal.state,
random = Math.random(),
url, dataCopy;
// Legacy record impression
if ( random <= state.getData().recordImpressionSampleRate ) {
url = new mw.Uri( mw.config.get( 'wgCentralBannerRecorder' ) );
dataCopy = state.getDataCopy( true );
url.extend( dataCopy );
sendBeacon( url.toString() );
}
// Impression event
if ( random <= state.getData().impressionEventSampleRate ) {
dataCopy = dataCopy || state.getDataCopy( true );
mw.eventLog.logEvent( IMPRESSION_EVENT_LOGGING_SCHEMA, dataCopy );
}
}
function sendBeacon( urlStr ) {
if ( navigator.sendBeacon ) {
try {
navigator.sendBeacon( urlStr );
} catch ( e ) {}
} else {
setTimeout( function () {
document.createElement( 'img' ).src = urlStr;
}, 0 );
}
}
function reallyChooseAndMaybeDisplay() {
var chooser = cn.internal.chooser,
bucketer = cn.internal.bucketer,
state = cn.internal.state,
hide = cn.internal.hide,
campaign, banner, i, maxCampaignFallback, maxCampaignFallbackConfig;
// This will gather initial data needed for selection and display.
state.setUp();
// Because of browser limitations, and to maintain our contract among
// components of this module, we have to do this here.
setUpDataProperty();
// Bow out and show no banners if choice data seems stale
if ( !chooser.choiceDataSeemsFresh( cn.choiceData ) ) {
state.setChoiceDataStale();
return;
}
// Below, we explicitly pass information from state to other
// internal objects, which are not allowed to have dependencies.
// While this could be made more compact by allowing internal
// objects to access state for themselves, disallowing it ensures
// their scope is limited and keeps the information flow visible.
// First, get a list of campiangs targeting this pageview, and pass it
// to state. (choiceData typically has more campaigns than actually
// could be shown on a given pageview, since many targeting criteria
// are not available to the server process that generates choiceData.)
// These are the campaigns that may be selected in the fallback loop,
// below.
state.setAvailableCampaigns( chooser.makeAvailableCampaigns(
cn.choiceData,
state.getData().country,
state.getData().region,
state.getData().anonymous,
state.getData().device
) );
// Set maximum iterations for the loop below. Pick the lowest between the
// configured limit and the total available campaigns.
maxCampaignFallbackConfig = mw.config.get( 'wgCentralNoticeMaxCampaignFallback' );
maxCampaignFallback = Math.min(
state.getData().availableCampaigns.length,
maxCampaignFallbackConfig
);
// Fallback loop. Try to display something until we're out of choices.
for ( i = 0; i < maxCampaignFallback; i++ ) {
// Try to choose a campaign. Just because we choose one doesn't necessarily
// mean it'll display a banner, though. (That's why we set it as
// 'attempting' below.)
campaign = chooser.chooseCampaign(
state.getData().availableCampaigns,
state.getData().randomcampaign
);
// Nothing is selected. That can happen following a roll of the dice if
// all the campaigns currently available are throttled.
if ( campaign === null ) {
break;
}
// Now that we have a campaign, send some info to other objects
state.setAttemptingCampaign( campaign );
bucketer.setCampaign( campaign );
hide.setCategory( state.getData().campaignCategory );
if ( cn.kvStore ) {
cn.kvStore.setCampaignName( state.getData().campaign );
cn.kvStore.setCategory( state.getData().campaignCategory );
}
// Get a bucket
bucketer.process();
state.setBucket( bucketer.getBucket() );
state.setReducedBucket( bucketer.getReducedBucket() );
// Check user preferences for campaign type displaying
if ( !state.getData().anonymous ) {
// Do not check user preferences on anon users
if (
campaign.type === 0 ||
state.getData().optedOutCampaigns.indexOf( campaign.type ) !== -1
) {
// User opted out of viewing this type of campaigns
// or campaign does not have a type set
// TODO Consolidate code below and code in shouldHide() conditional
// in a function.
state.failCampaign( 'userOptOut' );
runPreBannerHooks();
runPostBannerOrFailHooks();
// Update available campaigns
fallbackLoopUpdateAvailableCampaigns( i );
continue;
}
}
// Check the hide cookie and possibly fail the campaign.
// We do this before running pre-banner hooks so that these can count
// stuff differently if there was a hide cookie.
hide.processCookie();
if ( hide.shouldHide() ) {
state.failCampaign( hide.getReason() );
runPreBannerHooks();
runPostBannerOrFailHooks();
// Update available campaigns
fallbackLoopUpdateAvailableCampaigns( i );
continue;
}
runPreBannerHooks();
// If a pre-banner hook cancelled the campaign, then wrap up this iteration
// of the fallback loop.
if ( state.isCampaignFailed() ) {
runPostBannerOrFailHooks();
// Update available campaigns
fallbackLoopUpdateAvailableCampaigns( i );
continue;
}
// Reaching this point means that a campaign was chosen and was not failed.
break;
}
// Bow out if no campaign was ever even attempted. This can happen if no campaigns
// were available, or if we rolled the dice once, but no campaign was chosen
// due to throttling of all available campaigns.
if ( state.getAttemptingCampaign() === null ) {
return;
}
// If the last campaign attempted in the loop (and any previous ones that may have
// been attempted) failed, run post-display-process hooks for all attempted
// campaigns, then record impression and bow out.
if ( state.isCampaignFailed() ) {
runFinalizeChooseAndMaybeDisplayHooks();
recordImpression();
return;
}
// Choose a banner. Because of how campaign and banner settings are organized, we
// need to check logged-in status and device again. (This seems to indicate a
// problem with our domain model.)
// If a specific banner has been requested from a pre-banner hook, try to choose
// it.
if ( requestedBannerName ) {
banner = chooser.requestBanner(
campaign,
state.getData().reducedBucket,
state.getData().anonymous,
state.getData().device,
requestedBannerName
);
if ( !banner ) {
state.setRequestedBannerNotAvailable( requestedBannerName );
}
} else {
// Otherwise, use a random number and banner weights to choose from among
// banners available to the user in this campaign, in this bucket. (Most
// of the time, there's only one.)
banner = chooser.chooseBanner(
campaign,
state.getData().reducedBucket,
state.getData().anonymous,
state.getData().device,
state.getData().randombanner
);
if ( !banner ) {
state.setNoBannerAvailable();
}
}
// In either of the above cases, if no banner was selected, bow out.
if ( !banner ) {
runPostBannerOrFailHooks();
runFinalizeChooseAndMaybeDisplayHooks();
recordImpression();
return;
}
// Pass more info following banner selection
state.setBanner( banner );
if ( cn.kvStore ) {
cn.kvStore.setBannerName( banner.name );
}
// TODO From legacy; not sure it's useful
cn.bannerData.bannerName = banner.name;
setUpBannerLoadedPromise();
// Get the banner
// The ajax response will call mw.centralNotice.insertBanner()
fetchBanner();
}
/**
* Convenience method used only by reallyChooseAndMaybeDisplay() to update available
* campaigns within the fallback loop.
*
* @param iteration
*/
function fallbackLoopUpdateAvailableCampaigns( iteration ) {
var state = cn.internal.state;
state.setAvailableCampaigns( cn.internal.chooser.updateAvailableCampaigns(
state.getData().availableCampaigns,
state.getAttemptingCampaign(),
iteration
) );
}
/**
* Stuff we have to do following the call to fetch a banner (successful
* or not)
*/
function processAfterBannerFetch() {
// If we're testing a banner, don't call Special:RecordImpression or
// run mixin hooks.
if ( !cn.internal.state.getData().testingBanner ) {
runPostBannerOrFailHooks();
runFinalizeChooseAndMaybeDisplayHooks();
recordImpression();
}
}
/**
* CentralNotice base public object, exposed as mw.centralNotice. Note:
* other CN modules may add properties to this object, and we add some
* dynamically. These additional properties are:
*
* choiceData: An array of campaigns possibly available to this user,
* along with data needed to chose one (or none). This contains
* everything the server can determine ahead-of-time about campaigns
* for this user. Added by ext.centralNotice.choiceData (see
* the PHP class CNChoiceDataResourceLoaderModule).
*
* data: An object with more data for campaign/banner selection and
* display, and for recording what happened. Properties of this
* object should be easily serializable to URL parameters (i.e.,
* not objects or arrays). Note: this should be seen as read-only
* for any code outside this module. No properties that code in this
* module reacts to are available on mw.centralNotice.data. Also,
* it will be deprecated soon. Use getDataProperty( prop ) instead.
*
* bannerLoadedPromise: A promise that resolves when a banner is loaded.
* This property is only set after a banner has been chosen.
* Campaign mixins can use a postBannerOrFailMixinHook instead. Following
* legacy code, we call the promise with an object containing
* (almost all) the same data that is sent to
* Special:RecordImpression (though this data is also now available
* via mw.centralNotice.data).
*
* events.bannerLoaded: Legacy location of bannerLoadedPromise.
*
* kvStore: Key-value store object, added by ext.centralNotice.kvStore,
* if that module has been loaded.
*
* bannerHistoryLogger: Banner history logging feature, added by
* ext.centralNotice.bannerHistoryLogger, if that module has been
* loaded.
*/
cn = {
/**
* Really insert the banner (without waiting for the DOM to be ready).
* Only exposed for use in tests.
*
* @param bannerJson
* @private
*/
reallyInsertBanner: function ( bannerJson ) {
var state = cn.internal.state,
shownAfterLoadingBanner = true,
bannerLoadedButHiddenReason,
tmpData;
// Inject the banner HTML into the DOM
injectBannerHTML( bannerJson.bannerHtml );
bannerLoadedDeferredObj.resolve( cn.internal.state.getData() );
// Process legacy hook for in-banner JS that hides banners after
// they're loaded and/or adds data to send to
// Special:RecordImpression. Only do this if
// bannersNotGuaranteedToDisplay is set.
if ( state.getData().bannersNotGuaranteedToDisplay ) {
if ( typeof cn.bannerData.alterImpressionData === 'function' ) {
// Data from state is considered read-only. This legacy hook
// may add a 'reason' property to the object it receives.
// So we send only a copy of the data and check the added
// 'reason' property.
tmpData = state.getDataCopy();
shownAfterLoadingBanner =
cn.bannerData.alterImpressionData( tmpData );
if ( !shownAfterLoadingBanner ) {
bannerLoadedButHiddenReason = tmpData.reason || '';
state.setBannerLoadedButHidden(
bannerLoadedButHiddenReason
);
}
if ( tmpData.banner_count ) {
state.setBannerCount( tmpData.banner_count );
}
} else {
state.setAlterFunctionMissing();
}
}
// Banner shown following load (normal scenario)
if ( shownAfterLoadingBanner ) {
state.setBannerShown();
}
processAfterBannerFetch();
},
/**
* Promises to delay the record impression call, if possible; see
* cn.requestRecordImpressionDelay(), below. Only exposed for use in tests.
*
* @private
*/
recordImpressionDelayPromises: [],
/**
* For providing a jQuery.Promise to signal when the record impression call is
* about to be sent. (Value will be set to a new deferred object only as needed.)
*
* @private
*/
recordImpressionDeferredObj: null,
/**
* Attachment point for other objects in this module that are not meant
* for outside use.
*/
internal: {},
/**
* Call this to indicate that banners in a campaign may not always
* display to a user even if they're loaded, that is, that they may
* contain logic that prevents them from showing after they're loaded.
*/
setBannersNotGuaranteedToDisplay: function () {
cn.internal.state.setBannersNotGuaranteedToDisplay();
},
/**
* Call this from the preBannerMixinHook to prevent a banner for the
* currently attempting campaign from being chosen and loaded.
*
* @param {string} reason An explanation of why the banner was canceled.
*/
failCampaign: function ( reason ) {
cn.internal.state.failCampaign( reason );
},
/**
* Legacy method, deprecated. Use failCampaign().
*
* @param reason
*/
cancelBanner: function ( reason ) {
cn.failCampaign( reason );
},
isCampaignFailed: function () {
return cn.internal.state.isCampaignFailed();
},
/**
* Legacy metod, deprecated. Use isCampaignFailed().
*/
isBannerCanceled: function () {
return cn.isCampaignFailed();
},
isBannerShown: function () {
return cn.internal.state.isBannerShown();
},
/**
* Indicate that a banner was hidden after being loaded, and provide
* a reason.
*
* @param reason
*/
setBannerLoadedButHidden: function ( reason ) {
cn.internal.state.setBannerLoadedButHidden( reason );
},
/**
* Set the minimal sample rate for calling Special:RecordImpression. Default is
* wgCentralNoticeSampleRate. Note that Special:RecordImpression will
* not be called at all if a campaign was not chosen for this user. Also note
* that the highest rate set will be used.
*
* @param rate
*/
setMinRecordImpressionSampleRate: function ( rate ) {
cn.internal.state.setMinRecordImpressionSampleRate( rate );
},
/**
* Set the minimal sample rate for the logging of impression events (unless it was
* overridden by a URL parameter, in which that takes precedence). Default is
* wgCentralNoticeImpressionEventSampleRate. Also note that the highest rate set
* will be used.
*
* @param rate
*/
setMinImpressionEventSampleRate: function ( rate ) {
cn.internal.state.setMinImpressionEventSampleRate( rate );
},
/**
* Legacy object used by in-banner scripts to store and pass data about.
* Also, if a function is set on the alterImpressionData property, that
* function will be called after the banner HTML has been injected.
* Returning false from that function indicates that the banner was
* not actually shown. Note: this may be deprecated. If possible, use
* setBannerLoadedButHidden() instead.
*/
bannerData: {},
/**
* Base class for campaign-associated mixins (defined above).
*/
Mixin: Mixin,
/**
* Register a campaign-associated mixin to make it available for campaigns
* to use it. Should be called for every campaign-associated mixin.
*
* @param {mw.centralNotice.Mixin} mixin
*/
registerCampaignMixin: function ( mixin ) {
campaignMixins[ mixin.name ] = mixin;
},
/**
* Select a campaign and a banner, run hooks, and maybe display a
* banner.
* Note: cn.choiceData must be set before this is called
*/
chooseAndMaybeDisplay: function () {
// Make sure GeoIP info is available before processing
// geoIP usually doesn't make background requests; however, it may
// make one if location data wasn't retrievable from a cookie. We
// use a callback just in case, even though most of the time, the
// callback executes without delay.
// TODO Take GeoIP out of CentralNotice.
// See https://phabricator.wikimedia.org/T102848.
mw.geoIP.getPromise()
.fail( cn.internal.state.setInvalidGeoData )
.done( cn.internal.state.setGeoData )
.always( reallyChooseAndMaybeDisplay );
},
displayTestingBanner: function () {
// We gather the same data as for normal banner display, plus
// campaign and banner.
mw.geoIP.getPromise()
.fail( cn.internal.state.setInvalidGeoData )
.done( cn.internal.state.setGeoData )
.always( function () {
cn.internal.state.setUpForTestingBanner();
setUpDataProperty();
setUpBannerLoadedPromise();
fetchOrRetrieveBanner();
} );
},
insertBanner: function ( bannerJson ) {
// Insert the banner only after the DOM is ready
$( function () {
cn.reallyInsertBanner( bannerJson );
} );
},
/**
* Handle a banner loader error, with an optional message
*
* @param {string} [msg]
*/
handleBannerLoaderError: function ( msg ) {
cn.internal.state.setBannerLoaderError( msg );
bannerLoadedDeferredObj.reject( cn.internal.state.getData() );
processAfterBannerFetch();
},
hideBannerWithCloseButton: function () {
// Hide the banner element
$( '#centralNotice' ).hide();
cn.internal.hide.setHideWithCloseButtonCookies();
},
customHideBanner: function ( reason, duration ) {
// Hide the banner element
$( '#centralNotice' ).hide();
cn.internal.hide.setHideCookies( reason, duration );
},
hideBanner: function () {
cn.hideBannerWithCloseButton();
},
/**
* Set and store this user's bucket for the current campaign. The
* bucketer must be initialized first. However, code in campaign mixin
* hook handlers and banners can safely assume that's the case.
*
* The current bucket can be read using
* mw.centralNotice.getDataProperty( 'bucket' )
*
* @param bucket
*/
setBucket: function ( bucket ) {
cn.internal.bucketer.setBucket( bucket );
cn.internal.state.setBucket( bucket );
cn.internal.state.setReducedBucket( cn.internal.bucketer.getReducedBucket() );
},
/**
* Request a specific banner be displayed. This may be called before a banner
* has been selected (for example, from a pre-banner hook). To be shown, the
* banner must be among the banners assigned to the user's bucket for the
* selected campaign, and must be available for the user's logged in status
* and device. If the requested banner can't be displayed, no banner will be
* shown.
*
* @param {string} banner The name of the banner to request
*/
requestBanner: function ( banner ) {
requestedBannerName = banner;
},
/**
* Register that the current page view is included in a test.
*
* @param {string} identifier A string to identify the test. Should not contain
* commas.
*/
registerTest: function ( identifier ) {
cn.internal.state.registerTest( identifier );
},
/**
* Set a string with information for debugging. (All strings set here will be
* sent to the server via the debugInfo parameter on the record impression call).
*
* @param {string} str A string with the debugging information; should not
* contain pipe characters ('|').
*/
setDebugInfo: function ( str ) {
cn.internal.state.setDebugInfo( str );
},
/**
* Request that, if possible, the record impression call be delayed until a
* promise is resolved. If the promise does not resolve before
* MAX_RECORD_IMPRESSION_DELAY milliseconds after the banner is injected,
* the call will be made in any case.
*
* Returns another promise that will resolve immediately before the record
* impression call is made.
*
* @param {jQuery.Promise} promise
* @return {jQuery.Promise}
*/
requestRecordImpressionDelay: function ( promise ) {
cn.recordImpressionDelayPromises.push( promise );
cn.recordImpressionDeferredObj = cn.recordImpressionDeferredObj || $.Deferred();
return cn.recordImpressionDeferredObj.promise();
},
/**
* Get the value of a property used in campaign/banner selection and
* display, and for recording the results of that process.
*
* @param prop
*/
getDataProperty: function ( prop ) {
return cn.internal.state.getData()[ prop ];
}
};
// Expose cn. Note that there are situations in which a base
// mw.centralNotice object may already have been created by another
// CentralNotice module. (Other CN modules sometimes have to load before
// this one.)
if ( mw.centralNotice === undefined ) {
mw.centralNotice = cn;
} else {
Object.assign( mw.centralNotice, cn );
cn = mw.centralNotice; // Update the closured-in local variable
}
// Set up deprecated access points and warnings
mw.log.deprecate(
window, 'insertBanner', cn.insertBanner,
'Use mw.centralNotice method instead'
);
mw.log.deprecate(
window, 'hideBanner', cn.hideBanner,
'Use mw.centralNotice method instead'
);
mw.log.deprecate(
window, 'cancelBanner', cn.cancelBanner,
'Use mw.centralNotice.failCampaign() instead'
);
mw.log.deprecate(
window, 'isBannerCanceled', cn.isBannerCanceled,
'Use mw.centralNotice.isCampaignFailed() instead'
);
mw.log.deprecate(
window, 'toggleNotice', cn.hideBanner,
'Use mw.centralNotice method instead'
);
// Execute the other files
// TODO: Convert to using module.exports
require( './state.js' );
require( './chooser.js' );
require( './bucketer.js' );
require( './hide.js' );
}() );

File Metadata

Mime Type
text/plain
Expires
Wed, Aug 19, 06:22 (1 w, 3 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
45/9f/2534b142ae721efcb9a26f540775
Default Alt Text
index.js (30 KB)

Event Timeline