Skip to content

Commit 3a74c64

Browse files
committed
Publisher Ads: inject ads to googletag slots
1 parent 744a8ee commit 3a74c64

11 files changed

+201
-42
lines changed

Greaselion.json

+9
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,14 @@
2323
"scripts": [
2424
"publisher-ads/wsjgroup.bundle.js"
2525
]
26+
},
27+
{
28+
"urls": [
29+
"http://*/*",
30+
"https://*/*"
31+
],
32+
"scripts": [
33+
"publisher-ads/gpt-site.bundle.js"
34+
]
2635
}
2736
]

common/contentScript/inject-to-document.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,5 @@ export default function injectToDocument (isolatedFn: Function, ...codeVars: any
1818
scriptEl.remove()
1919
})
2020
}
21-
22-
if (document.readyState === 'loading') {
23-
document.addEventListener('DOMContentLoaded', inject)
24-
} else {
25-
inject()
26-
}
21+
inject()
2722
}

common/pageLifecycle/run-on-url-change.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// you can obtain one at http://mozilla.org/MPL/2.0/.
55

66
import injectToDocument from '../contentScript/inject-to-document'
7+
import runOnPageLoaded from './run-on-loaded'
78

89
const customEventName = 'brave-url-changed'
910

@@ -29,5 +30,7 @@ export default function runOnUrlChange(fn: Function) {
2930
return prevReplaceState.call(this, ...args)
3031
}
3132
}
32-
injectToDocument(fnPageInjectionCode, customEventName)
33+
runOnPageLoaded(() =>
34+
injectToDocument(fnPageInjectionCode, customEventName)
35+
)
3336
}

common/pageLifecycle/wait-for-window-var.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// you can obtain one at http://mozilla.org/MPL/2.0/.
55

66
import injectToDocument from '../contentScript/inject-to-document'
7+
import runOnPageLoaded from './run-on-loaded'
78

89
type OnValueFunction = (varValue: any) => void
910
type VarValueCustomEvent = CustomEvent<{ varValue: any}>
@@ -51,6 +52,7 @@ export default function waitForWindowVar(varName: string, onValue: OnValueFuncti
5152
}
5253
})
5354
}
54-
55-
injectToDocument(fnPageInjectionCode, varName, customEventName)
55+
runOnPageLoaded(() =>
56+
injectToDocument(fnPageInjectionCode, varName, customEventName)
57+
)
5658
}

package-lock.json

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"webpack-cli": "^3.3.10"
2727
},
2828
"dependencies": {
29-
"@types/chrome": "0.0.93"
29+
"@types/chrome": "0.0.93",
30+
"@types/doubleclick-gpt": "^2019041801.0.2"
3031
}
3132
}

publisher-ads/gpt-site.ts

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// Copyright (c) 2020 The Brave Authors. All rights reserved.
2+
// This Source Code Form is subject to the terms of the Mozilla Public
3+
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
4+
// you can obtain one at http://mozilla.org/MPL/2.0/.
5+
6+
import injectToDocument from '../common/contentScript/inject-to-document'
7+
import BATAd from './bat-ad'
8+
import fetchAdCreatives from './creativeFetch/same-context'
9+
import { AdSize, stringToAdSize } from './'
10+
import runOnPageLoaded from '../common/pageLifecycle/run-on-loaded'
11+
12+
console.log("content script")
13+
type SlotDefinition = {
14+
adUnitPath: string,
15+
size: googletag.GeneralSize
16+
elementId?: string
17+
}
18+
19+
type PageSlotsEventData = { pageSlots: SlotDefinition[] }
20+
21+
const eventName = `brave-gpt-slots-ready`
22+
23+
function googleSizeToAdSize (sizeParam: googletag.GeneralSize): AdSize[] | null {
24+
if (typeof sizeParam === 'string') {
25+
const size = stringToAdSize(sizeParam)
26+
if (!size) {
27+
return []
28+
}
29+
return [ size ]
30+
} else if (Array.isArray(sizeParam)) {
31+
if (sizeParam.every((i: any) => Array.isArray(i))) {
32+
return sizeParam as AdSize[]
33+
}
34+
if (sizeParam.length === 2 && sizeParam.every((i: any) => typeof i === 'number')) {
35+
return [sizeParam as AdSize]
36+
}
37+
if (sizeParam.every((i: any) => typeof i === 'string')) {
38+
const stringSizes: string[] = sizeParam as string[]
39+
const adSizes: AdSize[] = stringSizes
40+
.map(stringToAdSize)
41+
.filter((size: AdSize | null) => size !== null) as AdSize[]
42+
return adSizes
43+
}
44+
}
45+
// TODO(petemill): There seem to be more types that googletag.GeneralSize can be that
46+
// we are not handling here. Implement conversions as we encounter those types.
47+
return null
48+
}
49+
50+
function elementIdFromAdUnitPath (adUnitPath: string): string {
51+
const segments = adUnitPath.split('/')
52+
return segments[segments.length - 1]
53+
}
54+
55+
// Content script receives information about the ads from the Page
56+
window.addEventListener(eventName, function (e: CustomEvent<PageSlotsEventData>) {
57+
console.log('Content script received page slots', e.detail.pageSlots)
58+
const { detail: { pageSlots }} = e
59+
runOnPageLoaded(function () {
60+
for (const slot of pageSlots) {
61+
const sizes = googleSizeToAdSize(slot.size)
62+
if (!sizes) {
63+
console.error('Brave Publisher Ads: could not find sizes for slot', slot)
64+
continue
65+
}
66+
const selector = slot.elementId || elementIdFromAdUnitPath(slot.adUnitPath)
67+
const element = document.querySelector<HTMLElement>(`#${selector}`)
68+
if (!element) {
69+
console.error('Brave Publisher Ads: could not find element for slot', slot)
70+
continue
71+
}
72+
new BATAd(element, fetchAdCreatives).sizes = sizes
73+
}
74+
})
75+
})
76+
77+
// Page script sends information about the ads to the Content Script
78+
injectToDocument(function ($eventName: string) {
79+
console.log('inject')
80+
function observeGoogleTag(googletag: googletag.Googletag) {
81+
// @ts-ignore
82+
googletag.__isObserved = true
83+
console.log('observe')
84+
// Page usually defines googletag before this script runs,
85+
// then the stub adds the api functions, usually after this script runs.
86+
// If we simple define the functions then they will be
87+
// overwritten.
88+
// Instead, Proxy them.
89+
let actualDefineSlots = googletag.defineSlot
90+
let actualEnableServices = googletag.enableServices
91+
const pageSlots: SlotDefinition[] = []
92+
93+
googletag.defineSlot = function (adUnitPath, size, elementId) {
94+
pageSlots.push({ adUnitPath, size, elementId })
95+
return actualDefineSlots(adUnitPath, size)
96+
}
97+
98+
googletag.enableServices = function () {
99+
window.dispatchEvent(new CustomEvent<PageSlotsEventData>($eventName, {
100+
detail: {
101+
pageSlots
102+
},
103+
bubbles: true
104+
}))
105+
actualEnableServices()
106+
}
107+
108+
return new Proxy(googletag, {
109+
set (googletag, propName, value) {
110+
if (propName === 'defineSlot') {
111+
actualDefineSlots = value
112+
} else if (propName === 'enableServices') {
113+
actualEnableServices = value
114+
} else {
115+
googletag[propName] = value
116+
}
117+
return true
118+
}
119+
})
120+
}
121+
122+
if (window['googletag']) {
123+
googletag = observeGoogleTag(googletag)
124+
} else {
125+
console.log('googletag was not defined')
126+
// Maybe the Page didn't define it, so wait in case it gets defined.
127+
let _value: googletag.Googletag
128+
Object.defineProperty(window, 'googletag', {
129+
configurable: true,
130+
set: function (value) {
131+
console.log("googletag = set")
132+
_value = (value.__isObserved ? value : observeGoogleTag(value))
133+
},
134+
get: function () {
135+
return _value
136+
}
137+
})
138+
}
139+
}, eventName)

publisher-ads/index.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,14 @@
1-
export type AdSize = [number, number]
1+
// Copyright (c) 2020 The Brave Authors. All rights reserved.
2+
// This Source Code Form is subject to the terms of the Mozilla Public
3+
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
4+
// you can obtain one at http://mozilla.org/MPL/2.0/.
5+
6+
export type AdSize = [number, number]
7+
8+
export function stringToAdSize (sizeString: string): AdSize | null {
9+
const sizeData = sizeString.split('x')
10+
if (sizeData.length == 2) {
11+
return [Number(sizeData[0]), Number(sizeData[1])]
12+
}
13+
return null
14+
}

publisher-ads/marketwatch.ts

+10-19
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,19 @@ import targetAdSlotsBySelector from './slotTargeting/by-selector'
22
import fetchAdCreatives from './creativeFetch/same-context'
33
import runOnPageLoaded from '../common/pageLifecycle/run-on-loaded'
44
import { GetAdSizesFunction } from './slotTargeting/'
5-
import { AdSize } from './'
5+
import { AdSize, stringToAdSize } from './'
66

7-
function stringToAdSize (sizeString: string): AdSize | null {
8-
const sizeData = sizeString.split('x')
9-
if (sizeData.length == 2) {
10-
return [Number(sizeData[0]), Number(sizeData[1])]
7+
const getSizeForMarketWatchAdSlot: GetAdSizesFunction = (element: HTMLElement) => {
8+
const sizeData = element.getAttribute('data-size')
9+
if (sizeData) {
10+
const sizesByString: AdSize[] = sizeData.split(',')
11+
.map(stringToAdSize)
12+
.filter(item => item) as AdSize[]
13+
return sizesByString
1114
}
12-
return null
15+
return []
1316
}
1417

15-
1618
runOnPageLoaded(function () {
17-
const getMarketWatchAdSizes: GetAdSizesFunction = (element: HTMLElement) => {
18-
const sizeData = element.getAttribute('data-size')
19-
if (sizeData) {
20-
const sizesByString: AdSize[] = sizeData.split(',')
21-
.map(stringToAdSize)
22-
.filter(item => item) as AdSize[]
23-
return sizesByString
24-
}
25-
return []
26-
}
27-
28-
targetAdSlotsBySelector('.ad', getMarketWatchAdSizes, fetchAdCreatives)
19+
targetAdSlotsBySelector('.ad', getSizeForMarketWatchAdSlot, fetchAdCreatives)
2920
})

publisher-ads/wsjgroup.ts

+12-12
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@ import targetAdSlotsBySelector from './slotTargeting/by-selector'
22
import fetchAdCreatives from './creativeFetch/same-context'
33
import runOnPageLoaded from '../common/pageLifecycle/run-on-loaded'
44

5-
runOnPageLoaded(function () {
6-
function getSizeForAdSlot (element: HTMLElement) {
7-
const adOptionsString = element.getAttribute('data-ad-options')
8-
if (!adOptionsString) {
9-
return null
10-
}
11-
const adOptions = JSON.parse(adOptionsString)
12-
if (!adOptions) {
13-
return null
14-
}
15-
return adOptions.adSize
5+
function getSizeForWSJAdSlot (element: HTMLElement) {
6+
const adOptionsString = element.getAttribute('data-ad-options')
7+
if (!adOptionsString) {
8+
return null
9+
}
10+
const adOptions = JSON.parse(adOptionsString)
11+
if (!adOptions) {
12+
return null
1613
}
14+
return adOptions.adSize
15+
}
1716

18-
targetAdSlotsBySelector('[data-ad-options]', getSizeForAdSlot, fetchAdCreatives)
17+
runOnPageLoaded(function () {
18+
targetAdSlotsBySelector('[data-ad-options]', getSizeForWSJAdSlot, fetchAdCreatives)
1919
})

webpack.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module.exports = (env, argv) => ({
1515
['publisher-ads/wsjgroup']: './publisher-ads/wsjgroup',
1616
['publisher-ads/washingtonpost']: './publisher-ads/washingtonpost',
1717
['publisher-ads/marketwatch']: './publisher-ads/marketwatch',
18+
['publisher-ads/gpt-site']: './publisher-ads/gpt-site'
1819
},
1920
plugins: [
2021
new CleanWebpackPlugin(),

0 commit comments

Comments
 (0)