Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exclude errors generated from A4A iframe #10562

Merged
merged 25 commits into from
Aug 7, 2017
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build-system/tasks/presubmit-checks.js
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ var forbiddenTerms = {
'extensions/amp-access/0.1/amp-access.js',
'extensions/amp-experiment/0.1/variant.js',
'extensions/amp-user-notification/0.1/amp-user-notification.js',
'dist.3p/current/integration.js',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's this for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zhouyx When i run presubmit it shows that i'm using cid, so Hongfei suggested to whitelist it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry if there's mis-understanding, but I don't see it cid used in your change. did you try make a new build and run presubmit again?

],
},
'getBaseCid': {
Expand Down Expand Up @@ -402,6 +403,7 @@ var forbiddenTerms = {
'src/service/cid-impl.js',
'extensions/amp-user-notification/0.1/amp-user-notification.js',
'extensions/amp-app-banner/0.1/amp-app-banner.js',
'dist.3p/current/integration.js',
],
},
'localStorage': {
Expand Down
7 changes: 4 additions & 3 deletions builtins/amp-img.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,10 @@ export class AmpImg extends BaseElement {
// only read "Graphic" when using only 'alt'.
if (this.element.getAttribute('role') == 'img') {
this.element.removeAttribute('role');
user().error('AMP-IMG', 'Setting role=img on amp-img elements breaks ' +
'screen readers please just set alt or ARIA attributes, they will ' +
'be correctly propagated for the underlying <img> element.');
user(this.element).error('AMP-IMG',
Copy link
Contributor

@zhouyx zhouyx Aug 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Do we plan to migrate to the new user(opt_element) in this PR? If so why we only make the change to amp-img.js. I would suggest migrate to new format in separate PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zhouyx I need that change to amp-img.js to do the test, but the whole migration to use(opt_element) would be in a separate PR. Is that ok?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talked offline. Please revert. and I'll approve and merge the PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zhouyx reverted!

'Setting role=img on amp-img elements breaks ' +
'screen readers please just set alt or ARIA attributes, they will ' +
'be correctly propagated for the underlying <img> element.');
}

this.propagateAttributes(ATTRIBUTES_TO_PROPAGATE, this.img_);
Expand Down
5 changes: 5 additions & 0 deletions examples/analytics-error-reporting.amp.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,10 @@ <h1>Lorem Ipsum</h1>
}
</script>
</amp-analytics>

<amp-pixel src="https://foo.com/tracker/foo"
layout="nodisplay"
referrerpolicy="referrer-fail"></amp-pixel>

</body>
</html>
4 changes: 3 additions & 1 deletion src/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
import {
USER_ERROR_SENTINEL,
isUserErrorMessage,
isUserErrorEmbed,
duplicateErrorIfNecessary,
dev,
} from './log';
Expand Down Expand Up @@ -97,7 +98,8 @@ let detectedJsEngine;
*/
export function reportErrorForWin(win, error, opt_associatedElement) {
reportError(error, opt_associatedElement);
if (error && isUserErrorMessage(error.message) && !!win) {
if (error && !!win && isUserErrorMessage(error.message)
&& !isUserErrorEmbed(error.message)) {
reportErrorToAnalytics(/** @type {!Error} */(error), win);
}
}
Expand Down
62 changes: 55 additions & 7 deletions src/log.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,28 @@ const start = Date.now();
export const USER_ERROR_SENTINEL = '\u200B\u200B\u200B';


/**
* Four zero width space.
*
* @const {string}
*/
export const USER_ERROR_EMBED_SENTINEL = '\u200B\u200B\u200B\u200B';


/**
* @return {boolean} Whether this message was a user error.
*/
export function isUserErrorMessage(message) {
return message.indexOf(USER_ERROR_SENTINEL) >= 0;
}

/**
* @return {boolean} Whether this message was a iframe embed error.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whether this message was a a user error from an iframe embed

*/
export function isUserErrorEmbed(message) {
return message.indexOf(USER_ERROR_EMBED_SENTINEL) >= 0;
}


/**
* @enum {number}
Expand All @@ -64,6 +79,10 @@ export function setReportError(fn) {

/**
* Logging class.
* Use of sentinel string instead of a boolean to check user/dev errors
* because errors could be rethrown by some native code as a new error, and only a message would survive.
* Also, some browser don’t support a 5th error object argument in window.onerror. List of supporting browser can be found
* here: https://blog.sentry.io/2016/01/04/client-javascript-reporting-window-onerror.html
* @final
* @private Visible for testing only.
*/
Expand Down Expand Up @@ -494,11 +513,12 @@ export function rethrowAsync(var_args) {
/**
* Cache for logs. We do not use a Service since the service module depends
* on Log and closure literally can't even.
* @type {{user: ?Log, dev: ?Log}}
* @type {{user: ?Log, dev: ?Log, userForEmbed: ?Log}}
*/
self.log = (self.log || {
user: null,
dev: null,
userForEmbed: null,
});

const logs = self.log;
Expand Down Expand Up @@ -531,7 +551,6 @@ export function resetLogConstructorForTesting() {
logConstructor = null;
}


/**
* Publisher level log.
*
Expand All @@ -540,25 +559,45 @@ export function resetLogConstructorForTesting() {
* 2. Development mode is enabled via `#development=1` or logging is explicitly
* enabled via `#log=D` where D >= 1.
*
* @param {!Element=} opt_element
* @return {!Log}
*/
export function user() {
export function user(opt_element) {
let logger;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems this local var is not needed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lannka i think it is because I'd need to assign different value to this var and also use it at the last return call. Lmk what you think!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why don't you just assign to logs.user ?

if (logs.user) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should now cache 2 instances of user logger.

logs.user
logs.userForEmbed

logic will be like:

if (isFromEmbed(opt_element) {
  if (!logs.userForEmbed) {
     logs.userForEmbed = ...
  }
  return logs.userForEmbed;
} else {
  if (!logs.user) {
    logs.user = ...
  }
  return logs.user;
}

return logs.user;
logger = logs.user;
} else {
logger = getUserLogger(USER_ERROR_SENTINEL);
}
if (!!opt_element &&
isFromEmbed(logger.win, /** @type {!Element} */ (opt_element))) {
if (logs.userForEmbed) {
return logs.userForEmbed;
}
return logs.userForEmbed = getUserLogger(USER_ERROR_EMBED_SENTINEL);
} else {
return logs.user = logger;
}
}

/**
* Getter for user logger
* @param {string=} suffix
* @returns {!Log}
*/
function getUserLogger(suffix) {
if (!logConstructor) {
throw new Error('failed to call initLogConstructor');
}
return logs.user = new logConstructor(self, mode => {
return new logConstructor(self, mode => {
const logNum = parseInt(mode.log, 10);
if (mode.development || logNum >= 1) {
return LogLevel.FINE;
}
return LogLevel.OFF;
}, USER_ERROR_SENTINEL);
}, suffix);
}


/**
* AMP development log. Calls to `devLog().assert` and `dev.fine` are stripped in
* the PROD binary. However, `devLog().assert` result is preserved in either case.
Expand Down Expand Up @@ -587,3 +626,12 @@ export function dev() {
return LogLevel.OFF;
});
}

/**
* @param {!Window} win
* @param {!Element} element
* @returns {boolean} isEmbed
*/
export function isFromEmbed(win, element) {
return element.ownerDocument.defaultView != win;
}
42 changes: 42 additions & 0 deletions test/functional/test-log.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ import {
Log,
LogLevel,
USER_ERROR_SENTINEL,
USER_ERROR_EMBED_SENTINEL,
dev,
isUserErrorMessage,
rethrowAsync,
setReportError,
user,
duplicateErrorIfNecessary,
isUserErrorEmbed,
} from '../../src/log';
import * as sinon from 'sinon';

Expand Down Expand Up @@ -673,5 +675,45 @@ describe('Logging', () => {
expect(duplicate.associatedElement).to.equal(error.associatedElement);
});
});

describe('embed error', () => {
let sandbox;
let iframe;
let element;
let element1;
let element2;

beforeEach(() => {
sandbox = sinon.sandbox.create();
iframe = document.createElement('iframe');
document.body.appendChild(iframe);
});

afterEach(() => {
sandbox.restore();
document.body.removeChild(iframe);
});

it('should return logger for user-error', () => {
const error = user().createError();
expect(isUserErrorEmbed(error.message)).to.be.false;
expect(user(this.element).suffix_).to.equal(USER_ERROR_SENTINEL);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avoid checking member field

expect(isUserError(error.message)).to.be.true;

});

it('should return logger for embed-error', () => {
element = document.createElement('embed');
iframe.contentWindow.document.body.appendChild(element);
expect(user(element).suffix_).to.equal(USER_ERROR_EMBED_SENTINEL);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

});

it('should not create extra identical logs', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loggers

element1 = document.createElement('embed_1');
element2 = document.createElement('embed_2');
iframe.contentWindow.document.body.appendChild(element1);
iframe.contentWindow.document.body.appendChild(element2);
expect(user()).to.equal(user(this.element));
expect(user(element1)).to.equal(user(element2));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add one more check:

expect(user()).to.not.equal(user(element1));

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lannka i made all requested change. Thanks!

});
});
});