mirror of
https://github.com/GoogleChrome/chrome-extensions-samples.git
synced 2026-03-27 13:29:34 +07:00
* Remove docs folder. This was a redirect from a GitHub pages site that does not appear to be in use. * Rename api folder to api-samples. * Move examples to functional-samples folder. * Move cookbook sample to functional-samples. * Move tutorials to functional-samples folder. * Move mv2 and apps folders to _archive. * Rename tools folder to .repo. * Move reference folder to functional-samples. * Update README. Update README with new relative links for reorg. * Update README.md Co-authored-by: amysteamdev <37001393+AmySteam@users.noreply.github.com> --------- Co-authored-by: amysteamdev <37001393+AmySteam@users.noreply.github.com>
501 lines
15 KiB
JavaScript
501 lines
15 KiB
JavaScript
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
/**
|
|
* Implements the NavigationCollector object that powers the extension.
|
|
*
|
|
* @author mkwst@google.com (Mike West)
|
|
*/
|
|
|
|
/**
|
|
* Collects navigation events, and provides a list of successful requests
|
|
* that you can do interesting things with. Calling the constructor will
|
|
* automatically bind handlers to the relevant webnavigation API events,
|
|
* and to a `getMostRequestedUrls` extension message for internal
|
|
* communication between background pages and popups.
|
|
*
|
|
* @constructor
|
|
*/
|
|
function NavigationCollector() {
|
|
/**
|
|
* A list of currently pending requests, implemented as a hash of each
|
|
* request's tab ID, frame ID, and URL in order to ensure uniqueness.
|
|
*
|
|
* @type {Object<string, {start: number}>}
|
|
* @private
|
|
*/
|
|
this.pending_ = {};
|
|
|
|
/**
|
|
* A list of completed requests, implemented as a hash of each
|
|
* request's tab ID, frame ID, and URL in order to ensure uniqueness.
|
|
*
|
|
* @type {Object<string, Array<NavigationCollector.Request>>}
|
|
* @private
|
|
*/
|
|
this.completed_ = {};
|
|
|
|
/**
|
|
* A list of requests that errored off, implemented as a hash of each
|
|
* request's tab ID, frame ID, and URL in order to ensure uniqueness.
|
|
*
|
|
* @type {Object<string, Array<NavigationCollector.Request>>}
|
|
* @private
|
|
*/
|
|
this.errored_ = {};
|
|
|
|
// Bind handlers to the 'webNavigation' events that we're interested
|
|
// in handling in order to build up a complete picture of the whole
|
|
// navigation event.
|
|
chrome.webNavigation.onCreatedNavigationTarget.addListener(
|
|
this.onCreatedNavigationTargetListener_.bind(this));
|
|
chrome.webNavigation.onBeforeNavigate.addListener(
|
|
this.onBeforeNavigateListener_.bind(this));
|
|
chrome.webNavigation.onCompleted.addListener(
|
|
this.onCompletedListener_.bind(this));
|
|
chrome.webNavigation.onCommitted.addListener(
|
|
this.onCommittedListener_.bind(this));
|
|
chrome.webNavigation.onErrorOccurred.addListener(
|
|
this.onErrorOccurredListener_.bind(this));
|
|
chrome.webNavigation.onReferenceFragmentUpdated.addListener(
|
|
this.onReferenceFragmentUpdatedListener_.bind(this));
|
|
chrome.webNavigation.onHistoryStateUpdated.addListener(
|
|
this.onHistoryStateUpdatedListener_.bind(this));
|
|
|
|
// Bind handler to extension messages for communication from popup.
|
|
chrome.runtime.onMessage.addListener(this.onMessageListener_.bind(this));
|
|
|
|
this.loadDataStorage_();
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* The possible transition types that explain how the navigation event
|
|
* was generated (i.e. "The user clicked on a link." or "The user submitted
|
|
* a form").
|
|
*
|
|
* @see http://code.google.com/chrome/extensions/trunk/history.html
|
|
* @enum {string}
|
|
*/
|
|
NavigationCollector.NavigationType = {
|
|
AUTO_BOOKMARK: 'auto_bookmark',
|
|
AUTO_SUBFRAME: 'auto_subframe',
|
|
FORM_SUBMIT: 'form_submit',
|
|
GENERATED: 'generated',
|
|
KEYWORD: 'keyword',
|
|
KEYWORD_GENERATED: 'keyword_generated',
|
|
LINK: 'link',
|
|
MANUAL_SUBFRAME: 'manual_subframe',
|
|
RELOAD: 'reload',
|
|
START_PAGE: 'start_page',
|
|
TYPED: 'typed'
|
|
};
|
|
|
|
/**
|
|
* The possible transition qualifiers:
|
|
*
|
|
* * CLIENT_REDIRECT: Redirects caused by JavaScript, or a refresh meta tag
|
|
* on a page.
|
|
*
|
|
* * SERVER_REDIRECT: Redirected by the server via a 301/302 response.
|
|
*
|
|
* * FORWARD_BACK: User used the forward or back buttons to navigate through
|
|
* their browsing history.
|
|
*
|
|
* @enum {string}
|
|
*/
|
|
NavigationCollector.NavigationQualifier = {
|
|
CLIENT_REDIRECT: 'client_redirect',
|
|
FORWARD_BACK: 'forward_back',
|
|
SERVER_REDIRECT: 'server_redirect'
|
|
};
|
|
|
|
/**
|
|
* @typedef {{url: string, transitionType: NavigationCollector.NavigationType,
|
|
* transitionQualifier: Array<NavigationCollector.NavigationQualifier>,
|
|
* openedInNewTab: boolean, source: {frameId: ?number, tabId: ?number},
|
|
* duration: number}}
|
|
*/
|
|
NavigationCollector.Request;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
NavigationCollector.prototype = {
|
|
/**
|
|
* Returns a somewhat unique ID for a given WebNavigation request.
|
|
*
|
|
* @param {!{tabId: ?number, frameId: ?number}} data Information
|
|
* about the navigation event we'd like an ID for.
|
|
* @return {!string} ID created by combining the source tab ID and frame ID
|
|
* (or target tab/frame IDs if there's no source), as the API ensures
|
|
* that these will be unique across a single navigation event.
|
|
* @private
|
|
*/
|
|
parseId_: function(data) {
|
|
return data.tabId + '-' + (data.frameId ? data.frameId : 0);
|
|
},
|
|
|
|
|
|
/**
|
|
* Creates an empty entry in the pending array if one doesn't already exist,
|
|
* and prepopulates the errored and completed arrays for ease of insertion
|
|
* later.
|
|
*
|
|
* @param {!string} id The request's ID, as produced by parseId_.
|
|
* @param {!string} url The request's URL.
|
|
*/
|
|
prepareDataStorage_: function(id, url) {
|
|
this.pending_[id] = this.pending_[id] || {
|
|
openedInNewTab: false,
|
|
source: {
|
|
frameId: null,
|
|
tabId: null
|
|
},
|
|
start: null,
|
|
transitionQualifiers: [],
|
|
transitionType: null
|
|
};
|
|
this.completed_[url] = this.completed_[url] || [];
|
|
this.errored_[url] = this.errored_[url] || [];
|
|
},
|
|
|
|
|
|
/**
|
|
* Retrieves our saved data from storage.
|
|
* @private
|
|
*/
|
|
loadDataStorage_: function() {
|
|
chrome.storage.local.get({
|
|
"completed": {},
|
|
"errored": {},
|
|
}, function(storage) {
|
|
this.completed_ = storage.completed;
|
|
this.errored_ = storage.errored;
|
|
}.bind(this));
|
|
},
|
|
|
|
|
|
/**
|
|
* Persists our state to the storage API.
|
|
* @private
|
|
*/
|
|
saveDataStorage_: function() {
|
|
chrome.storage.local.set({
|
|
"completed": this.completed_,
|
|
"errored": this.errored_,
|
|
});
|
|
},
|
|
|
|
|
|
/**
|
|
* Resets our saved state to empty.
|
|
*/
|
|
resetDataStorage: function() {
|
|
this.completed_ = {};
|
|
this.errored_ = {};
|
|
this.saveDataStorage_();
|
|
// Load again, in case there is an outstanding storage.get request. This
|
|
// one will reload the newly-cleared data.
|
|
this.loadDataStorage_();
|
|
},
|
|
|
|
|
|
/**
|
|
* Handler for the 'onCreatedNavigationTarget' event. Updates the
|
|
* pending request with a source frame/tab, and notes that it was opened in a
|
|
* new tab.
|
|
*
|
|
* Pushes the request onto the
|
|
* 'pending_' object, and stores it for later use.
|
|
*
|
|
* @param {!Object} data The event data generated for this request.
|
|
* @private
|
|
*/
|
|
onCreatedNavigationTargetListener_: function(data) {
|
|
var id = this.parseId_(data);
|
|
this.prepareDataStorage_(id, data.url);
|
|
this.pending_[id].openedInNewTab = data.tabId;
|
|
this.pending_[id].source = {
|
|
tabId: data.sourceTabId,
|
|
frameId: data.sourceFrameId
|
|
};
|
|
this.pending_[id].start = data.timeStamp;
|
|
},
|
|
|
|
|
|
/**
|
|
* Handler for the 'onBeforeNavigate' event. Pushes the request onto the
|
|
* 'pending_' object, and stores it for later use.
|
|
*
|
|
* @param {!Object} data The event data generated for this request.
|
|
* @private
|
|
*/
|
|
onBeforeNavigateListener_: function(data) {
|
|
var id = this.parseId_(data);
|
|
this.prepareDataStorage_(id, data.url);
|
|
this.pending_[id].start = this.pending_[id].start || data.timeStamp;
|
|
},
|
|
|
|
|
|
/**
|
|
* Handler for the 'onCommitted' event. Updates the pending request with
|
|
* transition information.
|
|
*
|
|
* Pushes the request onto the
|
|
* 'pending_' object, and stores it for later use.
|
|
*
|
|
* @param {!Object} data The event data generated for this request.
|
|
* @private
|
|
*/
|
|
onCommittedListener_: function(data) {
|
|
var id = this.parseId_(data);
|
|
if (!this.pending_[id]) {
|
|
console.warn(
|
|
chrome.i18n.getMessage('errorCommittedWithoutPending'),
|
|
data.url,
|
|
data);
|
|
} else {
|
|
this.prepareDataStorage_(id, data.url);
|
|
this.pending_[id].transitionType = data.transitionType;
|
|
this.pending_[id].transitionQualifiers =
|
|
data.transitionQualifiers;
|
|
}
|
|
},
|
|
|
|
|
|
/**
|
|
* Handler for the 'onReferenceFragmentUpdated' event. Updates the pending
|
|
* request with transition information.
|
|
*
|
|
* Pushes the request onto the
|
|
* 'pending_' object, and stores it for later use.
|
|
*
|
|
* @param {!Object} data The event data generated for this request.
|
|
* @private
|
|
*/
|
|
onReferenceFragmentUpdatedListener_: function(data) {
|
|
var id = this.parseId_(data);
|
|
if (!this.pending_[id]) {
|
|
this.completed_[data.url] = this.completed_[data.url] || [];
|
|
this.completed_[data.url].push({
|
|
duration: 0,
|
|
openedInNewWindow: false,
|
|
source: {
|
|
frameId: null,
|
|
tabId: null
|
|
},
|
|
transitionQualifiers: data.transitionQualifiers,
|
|
transitionType: data.transitionType,
|
|
url: data.url
|
|
});
|
|
this.saveDataStorage_();
|
|
} else {
|
|
this.prepareDataStorage_(id, data.url);
|
|
this.pending_[id].transitionType = data.transitionType;
|
|
this.pending_[id].transitionQualifiers =
|
|
data.transitionQualifiers;
|
|
}
|
|
},
|
|
|
|
|
|
/**
|
|
* Handler for the 'onHistoryStateUpdated' event. Updates the pending
|
|
* request with transition information.
|
|
*
|
|
* Pushes the request onto the
|
|
* 'pending_' object, and stores it for later use.
|
|
*
|
|
* @param {!Object} data The event data generated for this request.
|
|
* @private
|
|
*/
|
|
onHistoryStateUpdatedListener_: function(data) {
|
|
var id = this.parseId_(data);
|
|
if (!this.pending_[id]) {
|
|
this.completed_[data.url] = this.completed_[data.url] || [];
|
|
this.completed_[data.url].push({
|
|
duration: 0,
|
|
openedInNewWindow: false,
|
|
source: {
|
|
frameId: null,
|
|
tabId: null
|
|
},
|
|
transitionQualifiers: data.transitionQualifiers,
|
|
transitionType: data.transitionType,
|
|
url: data.url
|
|
});
|
|
this.saveDataStorage_();
|
|
} else {
|
|
this.prepareDataStorage_(id, data.url);
|
|
this.pending_[id].transitionType = data.transitionType;
|
|
this.pending_[id].transitionQualifiers =
|
|
data.transitionQualifiers;
|
|
}
|
|
},
|
|
|
|
|
|
/**
|
|
* Handler for the 'onCompleted` event. Pulls the request's data from the
|
|
* 'pending_' object, combines it with the completed event's data, and pushes
|
|
* a new NavigationCollector.Request object onto 'completed_'.
|
|
*
|
|
* @param {!Object} data The event data generated for this request.
|
|
* @private
|
|
*/
|
|
onCompletedListener_: function(data) {
|
|
var id = this.parseId_(data);
|
|
if (!this.pending_[id]) {
|
|
console.warn(
|
|
chrome.i18n.getMessage('errorCompletedWithoutPending'),
|
|
data.url,
|
|
data);
|
|
} else {
|
|
this.completed_[data.url].push({
|
|
duration: (data.timeStamp - this.pending_[id].start),
|
|
openedInNewWindow: this.pending_[id].openedInNewWindow,
|
|
source: this.pending_[id].source,
|
|
transitionQualifiers: this.pending_[id].transitionQualifiers,
|
|
transitionType: this.pending_[id].transitionType,
|
|
url: data.url
|
|
});
|
|
delete this.pending_[id];
|
|
this.saveDataStorage_();
|
|
}
|
|
},
|
|
|
|
|
|
/**
|
|
* Handler for the 'onErrorOccurred` event. Pulls the request's data from the
|
|
* 'pending_' object, combines it with the completed event's data, and pushes
|
|
* a new NavigationCollector.Request object onto 'errored_'.
|
|
*
|
|
* @param {!Object} data The event data generated for this request.
|
|
* @private
|
|
*/
|
|
onErrorOccurredListener_: function(data) {
|
|
var id = this.parseId_(data);
|
|
if (!this.pending_[id]) {
|
|
console.error(
|
|
chrome.i18n.getMessage('errorErrorOccurredWithoutPending'),
|
|
data.url,
|
|
data);
|
|
} else {
|
|
this.prepareDataStorage_(id, data.url);
|
|
this.errored_[data.url].push({
|
|
duration: (data.timeStamp - this.pending_[id].start),
|
|
openedInNewWindow: this.pending_[id].openedInNewWindow,
|
|
source: this.pending_[id].source,
|
|
transitionQualifiers: this.pending_[id].transitionQualifiers,
|
|
transitionType: this.pending_[id].transitionType,
|
|
url: data.url
|
|
});
|
|
delete this.pending_[id];
|
|
this.saveDataStorage_();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Handle messages from the popup.
|
|
*
|
|
* @param {!{type:string}} message The external message to answer.
|
|
* @param {!MessageSender} sender Info about the script context that sent
|
|
* the message.
|
|
* @param {!function} sendResponse Function to call to send a response.
|
|
* @private
|
|
*/
|
|
onMessageListener_: function(message, sender, sendResponse) {
|
|
if (message.type === 'getMostRequestedUrls')
|
|
sendResponse({result: this.getMostRequestedUrls(message.num)});
|
|
else
|
|
sendResponse({});
|
|
},
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* @return {Object<string, NavigationCollector.Request>} The complete list of
|
|
* successful navigation requests.
|
|
*/
|
|
get completed() {
|
|
return this.completed_;
|
|
},
|
|
|
|
|
|
/**
|
|
* @return {Object<string, Navigationcollector.Request>} The complete list of
|
|
* unsuccessful navigation requests.
|
|
*/
|
|
get errored() {
|
|
return this.errored_;
|
|
},
|
|
|
|
|
|
/**
|
|
* Get a list of the X most requested URLs.
|
|
*
|
|
* @param {number=} num The number of successful navigation requests to
|
|
* return. If 0 is passed in, or the argument left off entirely, all
|
|
* successful requests are returned.
|
|
* @return {Object<string, NavigationCollector.Request>} The list of
|
|
* successful navigation requests, sorted in decending order of frequency.
|
|
*/
|
|
getMostRequestedUrls: function(num) {
|
|
return this.getMostFrequentUrls_(this.completed, num);
|
|
},
|
|
|
|
|
|
/**
|
|
* Get a list of the X most errored URLs.
|
|
*
|
|
* @param {number=} num The number of unsuccessful navigation requests to
|
|
* return. If 0 is passed in, or the argument left off entirely, all
|
|
* successful requests are returned.
|
|
* @return {Object<string, NavigationCollector.Request>} The list of
|
|
* unsuccessful navigation requests, sorted in decending order
|
|
* of frequency.
|
|
*/
|
|
getMostErroredUrls: function(num) {
|
|
return this.getMostErroredUrls_(this.errored, num);
|
|
},
|
|
|
|
|
|
/**
|
|
* Get a list of the most frequent URLs in a list.
|
|
*
|
|
* @param {NavigationCollector.Request} list A list of URLs to parse.
|
|
* @param {number=} num The number of navigation requests to return. If
|
|
* 0 is passed in, or the argument left off entirely, all requests
|
|
* are returned.
|
|
* @return {Object<string, NavigationCollector.Request>} The list of
|
|
* navigation requests, sorted in decending order of frequency.
|
|
* @private
|
|
*/
|
|
getMostFrequentUrls_: function(list, num) {
|
|
var result = [];
|
|
var avg;
|
|
// Convert the 'completed_' object to an array.
|
|
for (var x in list) {
|
|
avg = 0;
|
|
if (list.hasOwnProperty(x) && list[x].length) {
|
|
list[x].forEach(function(o) {
|
|
avg += o.duration;
|
|
});
|
|
avg = avg / list[x].length;
|
|
result.push({
|
|
url: x,
|
|
numRequests: list[x].length,
|
|
requestList: list[x],
|
|
average: avg
|
|
});
|
|
}
|
|
}
|
|
// Sort the array.
|
|
result.sort(function(a, b) {
|
|
return b.numRequests - a.numRequests;
|
|
});
|
|
// Return the requested number of results.
|
|
return num ? result.slice(0, num) : result;
|
|
}
|
|
};
|