首页 > 代码库 > chrome源码之历史记录页面学习
chrome源码之历史记录页面学习
1.初始一定是load加载整个页面了,load方法中会调用setPageState方法来设置页面显示的内容限定信息。
// Document Functions: /** * Window onl oad handler, sets up the page. */ function load() { uber.onContentFrameLoaded(); FocusOutlineManager.forDocument(document); var searchField = $(‘search-field‘); historyModel = new HistoryModel(); historyView = new HistoryView(historyModel); pageState = new PageState(historyModel, historyView); // Create default view. var hashData =http://www.mamicode.com/ pageState.getHashData(); var page = parseInt(hashData.page, 10) || historyView.getPage(); var range = /** @type {HistoryModel.Range} */(parseInt(hashData.range, 10)) || historyView.getRangeInDays(); var offset = parseInt(hashData.offset, 10) || historyView.getOffset(); historyView.setPageState(hashData.q, page, range, offset); if ($(‘overlay‘)) { cr.ui.overlay.setupOverlay($(‘overlay‘)); cr.ui.overlay.globalInitialization(); } HistoryFocusManager.getInstance().initialize(); var doSearch = function(e) { recordUmaAction(‘HistoryPage_Search‘); historyView.setSearch(searchField.value); if (isMobileVersion()) searchField.blur(); // Dismiss the keyboard. }; //yana add 161011 var searchAllHistory = function(e){ historyView.setPageState(‘‘, 0, HistoryModel.Range.ALL_TIME, 0); } var removeMenu = getRequiredElement(‘remove-visit‘); // Decorate remove-visit before disabling/hiding because the values are // overwritten when decorating a MenuItem that has a Command. cr.ui.decorate(removeMenu, MenuItem); removeMenu.disabled = !loadTimeData.getBoolean(‘allowDeletingHistory‘); removeMenu.hidden = loadTimeData.getBoolean(‘hideDeleteVisitUI‘); document.addEventListener(‘command‘, handleCommand); $(‘all-history‘).addEventListener(‘click‘, searchAllHistory);//yana add 161011 //console.log("$(‘all-history‘)="+$(‘all-history‘)); searchField.addEventListener(‘search‘, doSearch); $(‘search-button‘).addEventListener(‘click‘, doSearch); $(‘more-from-site‘).addEventListener(‘activate‘, function(e) { activeVisit.showMoreFromSite_(); activeVisit = null; }); // Only show the controls if the command line switch is activated or the user // is supervised. if (loadTimeData.getBoolean(‘groupByDomain‘)) { $(‘history-page‘).classList.add(‘big-topbar-page‘); $(‘filter-controls‘).hidden = false; } // Hide the top container which has the "Clear browsing data" and "Remove // selected entries" buttons if deleting history is not allowed. if (!loadTimeData.getBoolean(‘allowDeletingHistory‘)) $(‘top-container‘).hidden = true; uber.setTitle(loadTimeData.getString(‘title‘)); // Adjust the position of the notification bar when the window size changes. window.addEventListener(‘resize‘, historyView.positionNotificationBar.bind(historyView)); if (isMobileVersion()) { // Move the search box out of the header. var resultsDisplay = $(‘results-display‘); resultsDisplay.parentNode.insertBefore($(‘search-field‘), resultsDisplay); window.addEventListener( ‘resize‘, historyView.updateClearBrowsingDataButton_); <if expr="is_ios"> // Trigger window resize event when search field is focused to force update // of the clear browsing button, which should disappear when search field // is active. The window is not resized when the virtual keyboard is shown // on iOS. searchField.addEventListener(‘focus‘, function() { cr.dispatchSimpleEvent(window, ‘resize‘); }); </if> /* is_ios */ // When the search field loses focus, add a delay before updating the // visibility, otherwise the button will flash on the screen before the // keyboard animates away. searchField.addEventListener(‘blur‘, function() { setTimeout(historyView.updateClearBrowsingDataButton_, 250); }); // Move the button to the bottom of the page. $(‘history-page‘).appendChild($(‘clear-browsing-data‘)); } else { window.addEventListener(‘message‘, function(e) { e = /** @type {!MessageEvent<!{method: string}>} */(e); if (e.data.method == ‘frameSelected‘) searchField.focus(); }); searchField.focus(); } historyModel.queryDateList_();//yana add 160909 <if expr="is_ios"> function checkKeyboardVisibility() { // Figure out the real height based on the orientation, becauase // screen.width and screen.height don‘t update after rotation. var screenHeight = window.orientation % 180 ? screen.width : screen.height; // Assume that the keyboard is visible if more than 30% of the screen is // taken up by window chrome. var isKeyboardVisible = (window.innerHeight / screenHeight) < 0.7; document.body.classList.toggle(‘ios-keyboard-visible‘, isKeyboardVisible); } window.addEventListener(‘orientationchange‘, checkKeyboardVisibility); window.addEventListener(‘resize‘, checkKeyboardVisibility); </if> /* is_ios */ }
2.setPageState方法的实现如下,它是最基础的一个方法,其他如setSearch/setOffset/setPage/setRangeInDays实际都是调用的这个方法。
/** * Sets all the parameters for the history page and then reloads the view to * update the results. * @param {string} searchText The search string to set. * @param {number} page The page to be viewed. * @param {HistoryModel.Range} range The range to view or search over. * @param {number} offset Set the begining of the query to the specific offset. */ HistoryView.prototype.setPageState = function(searchText, page, range, offset) { this.clear_(); this.model_.searchText_ = searchText; this.pageIndex_ = page; this.model_.requestedPage_ = page; this.model_.rangeInDays_ = range; this.model_.groupByDomain_ = false; if (range != HistoryModel.Range.ALL_TIME&&range != HistoryModel.Range.DAY)//yana add 160908 this.model_.groupByDomain_ = true; this.model_.offset_ = offset; this.reload(); pageState.setUIState(this.model_.getSearchText(), this.pageIndex_, this.getRangeInDays(), this.getOffset()); };
3.reload:方法根据以上设置的限定条件,进行历史记录查询。
/** * Reload our model with the current parameters. */ HistoryModel.prototype.reload = function() { // Save user-visible state, clear the model, and restore the state. var search = this.searchText_; var page = this.requestedPage_; var range = this.rangeInDays_; var offset = this.offset_; var groupByDomain = this.groupByDomain_; this.clearModel_(); this.searchText_ = search; this.requestedPage_ = page; this.rangeInDays_ = range; this.offset_ = offset; this.groupByDomain_ = groupByDomain; this.queryHistory_(); };
4.在上层js中通过chrome.send()来向底层发送事件请求和相关参数,其中 ‘queryHistory‘为信号名称,[this.searchText_, this.offset_, this.rangeInDays_, endTime, maxResults]为向底层传递的参数:
/** * Query for history, either for a search or time-based browsing. * @private */ HistoryModel.prototype.queryHistory_ = function() { var maxResults = (this.rangeInDays_ == HistoryModel.Range.ALL_TIME) ? RESULTS_PER_PAGE : 0; // If there are already some visits, pick up the previous query where it // left off. var lastVisit = this.visits_.slice(-1)[0]; var endTime = lastVisit ? lastVisit.date.getTime() : 0; $(‘loading-spinner‘).hidden = false; this.inFlight_ = true; //yana add 20161128 if (typeof this.timer_ != ‘undefined‘ && this.timer_) { clearInterval(this.timer_); } // TODO(glen): Replace this with a bound method so we don‘t need // public model and view. this.timer_ = window.setInterval(function() { if($(‘loading-spinner‘).hidden==false){ chrome.send(‘queryHistory‘, [this.searchText_, this.offset_, this.rangeInDays_, endTime, maxResults]); } }.bind(this), 50); };
5.在底层Browsing_history_handler.cc中通过RegisterMessages函数对上层发来的事件进行响应处理:
void BrowsingHistoryHandler::RegisterMessages() {
……
……
web_ui()->RegisterMessageCallback("queryHistory",
base::Bind(&BrowsingHistoryHandler::HandleQueryHistory,
base::Unretained(this)));
……
}
之后会在BrowsingHistoryHandler::HandleQueryHistory()函数中处理查询历史记录的事件响应。
6.底层开始按照上层传来的要求从服务器上获取满足条件的历史记录等相关信息。
void BrowsingHistoryHandler::WebHistoryQueryComplete( const base::string16& search_text, const history::QueryOptions& options, base::TimeTicks start_time, history::WebHistoryService::Request* request, const base::DictionaryValue* results_value) { base::TimeDelta delta = base::TimeTicks::Now() - start_time; UMA_HISTOGRAM_TIMES("WebHistory.ResponseTime", delta); // If the response came in too late, do nothing. // TODO(dubroy): Maybe show a banner, and prompt the user to reload? if (!web_history_timer_.IsRunning()) return; web_history_timer_.Stop(); UMA_HISTOGRAM_ENUMERATION( "WebHistory.QueryCompletion", results_value ? WEB_HISTORY_QUERY_SUCCEEDED : WEB_HISTORY_QUERY_FAILED, NUM_WEB_HISTORY_QUERY_BUCKETS); DCHECK_EQ(0U, web_history_query_results_.size()); const base::ListValue* events = NULL; if (results_value && results_value->GetList("event", &events)) { web_history_query_results_.reserve(events->GetSize()); for (unsigned int i = 0; i < events->GetSize(); ++i) { const base::DictionaryValue* event = NULL; const base::DictionaryValue* result = NULL; const base::ListValue* results = NULL; const base::ListValue* ids = NULL; base::string16 url; base::string16 title; base::Time visit_time; if (!(events->GetDictionary(i, &event) && event->GetList("result", &results) && results->GetDictionary(0, &result) && result->GetString("url", &url) && result->GetList("id", &ids) && ids->GetSize() > 0)) { LOG(WARNING) << "Improperly formed JSON response from history server."; continue; } // Ignore any URLs that should not be shown in the history page. GURL gurl(url); if (!CanAddURLToHistory(gurl)) continue; // Title is optional, so the return value is ignored here. result->GetString("title", &title); // Extract the timestamps of all the visits to this URL. // They are referred to as "IDs" by the server. for (int j = 0; j < static_cast<int>(ids->GetSize()); ++j) { const base::DictionaryValue* id = NULL; std::string timestamp_string; int64_t timestamp_usec = 0; if (!ids->GetDictionary(j, &id) || !id->GetString("timestamp_usec", ×tamp_string) || !base::StringToInt64(timestamp_string, ×tamp_usec)) { NOTREACHED() << "Unable to extract timestamp."; continue; } // The timestamp on the server is a Unix time. base::Time time = base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(timestamp_usec); // Get the ID of the client that this visit came from. std::string client_id; id->GetString("client_id", &client_id); web_history_query_results_.push_back( HistoryEntry( HistoryEntry::REMOTE_ENTRY, gurl, title, time, client_id, !search_text.empty(), base::string16(), /* blocked_visit */ false)); } } } has_synced_results_ = results_value != nullptr; results_info_value_.SetBoolean("hasSyncedResults", has_synced_results_); if (!query_task_tracker_.HasTrackedTasks()) ReturnResultsToFrontEnd(); }
7.将获取到的数据按照上层的需求转换为目标格式,再通过CallJavascriptFunction()调用上层js的方法并将需要返回的数据一并返回。
void BrowsingHistoryHandler::ReturnResultsToFrontEnd() { Profile* profile = Profile::FromWebUI(web_ui()); BookmarkModel* bookmark_model = BookmarkModelFactory::GetForBrowserContext(profile); SupervisedUserService* supervised_user_service = NULL; #if defined(ENABLE_SUPERVISED_USERS) if (profile->IsSupervised()) supervised_user_service = SupervisedUserServiceFactory::GetForProfile(profile); #endif browser_sync::ProfileSyncService* sync_service = ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile); // Combine the local and remote results into |query_results_|, and remove // any duplicates. if (!web_history_query_results_.empty()) { int local_result_count = query_results_.size(); query_results_.insert(query_results_.end(), web_history_query_results_.begin(), web_history_query_results_.end()); MergeDuplicateResults(&query_results_); if (local_result_count) { // In the best case, we expect that all local results are duplicated on // the server. Keep track of how many are missing. int missing_count = std::count_if( query_results_.begin(), query_results_.end(), IsLocalOnlyResult); UMA_HISTOGRAM_PERCENTAGE("WebHistory.LocalResultMissingOnServer", missing_count * 100.0 / local_result_count); } } bool is_md = false; #if !defined(OS_ANDROID) is_md = MdHistoryUI::IsEnabled(profile); #endif // Convert the result vector into a ListValue. base::ListValue results_value; for (std::vector<BrowsingHistoryHandler::HistoryEntry>::iterator it = query_results_.begin(); it != query_results_.end(); ++it) { std::unique_ptr<base::Value> value(it->ToValue( bookmark_model, supervised_user_service, sync_service, is_md)); results_value.Append(std::move(value)); } web_ui()->CallJavascriptFunctionUnsafe("historyResult", results_info_value_, results_value);// here is the two parameters to send to front end. web_ui()->CallJavascriptFunctionUnsafe( "showNotification", base::FundamentalValue(has_synced_results_), base::FundamentalValue(has_other_forms_of_browsing_history_)); results_info_value_.Clear(); query_results_.clear(); web_history_query_results_.clear(); }
其中:
// The info value that is returned to the front end with the query results. base::DictionaryValue results_info_value_; // The list of query results received from the history service. std::vector<HistoryEntry> query_results_; // The list of query results received from the history server. std::vector<HistoryEntry> web_history_query_results_;
注意需要将vector类型转换成ListValue类型才能传给上层,因为CallJavascriptFunctionUnsafe函数只能接受Value类型的参数,而value类型包括如下类型:
class BASE_EXPORT Value { public: enum Type { TYPE_NULL = 0, TYPE_BOOLEAN, TYPE_INTEGER, TYPE_DOUBLE, TYPE_STRING, TYPE_BINARY, TYPE_DICTIONARY, TYPE_LIST // Note: Do not add more types. See the file-level comment above for why. };
…… }
另外,通过CallJavascriptFunctionUnsafe方法向上层传递的参数类型的对应关系如下:
C++中的DictionaryValue类型<——>JS中的JSON类型
C++中的ListValue类型<——>JS中的ARRAY类型
以上是由chrome的机制实现(CallJavascriptFunctionUnsafe函数内部完成相应转换),DictionaryValue和ListValue类型都是chrome内部封装的类型。
综上:最终返回给上层的数据即是一个装满json类型数据的的数组。
8.回到上层,底层已经将获得的历史记录数据传给了上一步中指定的方法——historyResult(),即historyResult将对查询到的历史记录继续做相关处理。
以上完成一个完整的通信:
/**
* Our history system calls this function with results from searches.
* @param {HistoryQuery} info An object containing information about the query.
* @param {Array<HistoryEntry>} results A list of results.
*/
function historyResult(info, results) {
historyModel.addResults(info, results);
}
6.接下来上层需要动态的将这些历史记录组织到DOM文档中去,并完成整个布局和显示。
几个关键的类
// Visit: /** * Class to hold all the information about an entry in our model. * @param {HistoryEntry} result An object containing the visit‘s data. * @param {boolean} continued Whether this visit is on the same day as the * visit before it. * @param {HistoryModel} model The model object this entry belongs to. * @constructor */ function Visit(result, continued, model) { this.model_ = model; this.title_ = result.title; this.url_ = result.url; this.domain_ = result.domain; this.starred_ = result.starred; this.fallbackFaviconText_ = result.fallbackFaviconText; // These identify the name and type of the device on which this visit // occurred. They will be empty if the visit occurred on the current device. this.deviceName = result.deviceName; this.deviceType = result.deviceType; // The ID will be set according to when the visit was displayed, not // received. Set to -1 to show that it has not been set yet. this.id_ = -1; this.isRendered = false; // Has the visit already been rendered on the page? // All the date information is public so that owners can compare properties of // two items easily. this.date = new Date(result.time); // See comment in BrowsingHistoryHandler::QueryComplete - we won‘t always // get all of these. this.dateRelativeDay = result.dateRelativeDay; this.dateShort = result.dateShort; //this.dateTimeOfDay = result.dateTimeOfDay; //yana modify 160925 var dayFlag = result.dateTimeOfDay.substr(0,2); if(dayFlag == "上午"){ var h= result.dateTimeOfDay.substr(2).split(":")[0]; if(h==12) h = parseInt(h)-12; this.dateTimeOfDay = h+ ":" +result.dateTimeOfDay.substr(2).split(":")[1]; }else{ var h= result.dateTimeOfDay.substr(2).split(":")[0]; if(h!=12) h = parseInt(h)+12; this.dateTimeOfDay = h+ ":" +result.dateTimeOfDay.substr(2).split(":")[1]; } // Shows the filtering behavior for that host (only used for supervised // users). // A value of |SupervisedUserFilteringBehavior.ALLOW| is not displayed so it // is used as the default value. this.hostFilteringBehavior = SupervisedUserFilteringBehavior.ALLOW; if (result.hostFilteringBehavior) this.hostFilteringBehavior = result.hostFilteringBehavior; this.blockedVisit = result.blockedVisit; // Whether this is the continuation of a previous day. this.continued = continued; this.allTimestamps = result.allTimestamps; }
// HistoryModel: /** * Global container for history data. Future optimizations might include * allowing the creation of a HistoryModel for each search string, allowing * quick flips back and forth between results. * * The history model is based around pages, and only fetching the data to * fill the currently requested page. This is somewhat dependent on the view, * and so future work may wish to change history model to operate on * timeframe (day or week) based containers. * * @constructor */ function HistoryModel() { this.clearModel_(); }
// HistoryView: /** * Functions and state for populating the page with HTML. This should one-day * contain the view and use event handlers, rather than pushing HTML out and * getting called externally. * @param {HistoryModel} model The model backing this view. * @constructor */ function HistoryView(model) { this.editButtonTd_ = $(‘edit-button‘); this.editingControlsDiv_ = $(‘editing-controls‘); this.resultDiv_ = $(‘results-display‘); this.focusGrid_ = new cr.ui.FocusGrid(); this.pageDiv_ = $(‘results-pagination‘); this.model_ = model; this.pageIndex_ = 0; this.lastDisplayed_ = []; this.hasRenderedResults_ = false; this.model_.setView(this); this.currentVisits_ = []; // If there is no search button, use the search button label as placeholder // text in the search field. if ($(‘search-button‘).offsetWidth == 0) $(‘search-field‘).placeholder = $(‘search-button‘).value; var self = this; $(‘clear-browsing-data‘).addEventListener(‘click‘, openClearBrowsingData); $(‘remove-selected‘).addEventListener(‘click‘, removeItems); // Add handlers for the page navigation buttons at the bottom. $(‘newest-button‘).addEventListener(‘click‘, function() { recordUmaAction(‘HistoryPage_NewestHistoryClick‘); self.setPage(0); }); $(‘newer-button‘).addEventListener(‘click‘, function() { recordUmaAction(‘HistoryPage_NewerHistoryClick‘); self.setPage(self.pageIndex_ - 1); }); $(‘older-button‘).addEventListener(‘click‘, function() { recordUmaAction(‘HistoryPage_OlderHistoryClick‘); self.setPage(self.pageIndex_ + 1); }); $(‘timeframe-controls‘).onchange = function(e) { var value = http://www.mamicode.com/parseInt(e.target.value, 10); self.setRangeInDays(/** @type {HistoryModel.Range<number>} */(value)); }; $(‘range-previous‘).addEventListener(‘click‘, function(e) { if (self.getRangeInDays() == HistoryModel.Range.ALL_TIME) self.setPage(self.pageIndex_ + 1); else self.setOffset(self.getOffset() + 1); }); $(‘range-next‘).addEventListener(‘click‘, function(e) { if (self.getRangeInDays() == HistoryModel.Range.ALL_TIME) self.setPage(self.pageIndex_ - 1); else self.setOffset(self.getOffset() - 1); }); $(‘range-today‘).addEventListener(‘click‘, function(e) { if (self.getRangeInDays() == HistoryModel.Range.ALL_TIME) self.setPage(0); else self.setOffset(0); }); }
关键函数:
(1)获取到每天的记录之后创建dom元素将其显示出来:
/** * Adds the results for a certain day. This includes a title with the day of * the results and the results themselves, grouped or not. * @param {Array} visits Visits returned by the query. * @param {Node} parentNode Node to which to add the results to. * @private */ HistoryView.prototype.addDayResults_ = function(visits, parentNode) { if (visits.length == 0) return; var firstVisit = visits[0]; var day = parentNode.appendChild(createElementWithClassName(‘h3‘, ‘day‘)); day.appendChild(document.createTextNode(firstVisit.dateRelativeDay)); if (firstVisit.continued) { day.appendChild(document.createTextNode(‘ ‘ + loadTimeData.getString(‘cont‘))); } var dayResults = /** @type {HTMLElement} */(parentNode.appendChild( createElementWithClassName(‘ol‘, ‘day-results‘))); // Don‘t add checkboxes if entries can not be edited. if (!this.model_.editingEntriesAllowed) dayResults.classList.add(‘no-checkboxes‘); if (this.model_.getGroupByDomain()) { this.groupVisitsByDomain_(visits, dayResults); } else { var lastTime; for (var i = 0, visit; visit = visits[i]; i++) { // If enough time has passed between visits, indicate a gap in browsing. var thisTime = visit.date.getTime(); //delete the gap yana 160926 /*if (lastTime && lastTime - thisTime > BROWSING_GAP_TIME) dayResults.appendChild(createElementWithClassName(‘li‘, ‘gap‘));*/ // Insert the visit into the DOM. dayResults.appendChild(visit.getResultDOM({ addTitleFavicon: true })); this.setVisitRendered_(visit); lastTime = thisTime; } } };
(2)具体的显示每一天内的历史记录条目
/** * Returns a dom structure for a browse page result or a search page result. * @param {Object} propertyBag A bag of configuration properties, false by * default: * - isSearchResult: Whether or not the result is a search result. * - addTitleFavicon: Whether or not the favicon should be added. * - useMonthDate: Whether or not the full date should be inserted (used for * monthly view). * @return {Node} A DOM node to represent the history entry or search result. */ Visit.prototype.getResultDOM = function(propertyBag) { var isSearchResult = propertyBag.isSearchResult || false; var addTitleFavicon = propertyBag.addTitleFavicon || false; var useMonthDate = propertyBag.useMonthDate || false; var focusless = propertyBag.focusless || false; var node = createElementWithClassName(‘li‘, ‘entry‘); var time = createElementWithClassName(‘span‘, ‘time‘); var entryBox = createElementWithClassName(‘div‘, ‘entry-box‘); var domain = createElementWithClassName(‘div‘, ‘domain‘); this.id_ = this.model_.getNextVisitId(); var self = this; // Only create the checkbox if it can be used to delete an entry. if (this.model_.editingEntriesAllowed) { var checkbox = document.createElement(‘input‘); checkbox.type = ‘checkbox‘; checkbox.id = ‘checkbox-‘ + this.id_; checkbox.time = this.date.getTime(); checkbox.setAttribute(‘aria-label‘, loadTimeData.getStringF( ‘entrySummary‘, this.dateTimeOfDay, this.starred_ ? loadTimeData.getString(‘bookmarked‘) : ‘‘, this.title_, this.domain_)); checkbox.addEventListener(‘click‘, checkboxClicked); entryBox.appendChild(checkbox); if (focusless) checkbox.tabIndex = -1; if (!isMobileVersion()) { // Clicking anywhere in the entryBox will check/uncheck the checkbox. entryBox.setAttribute(‘for‘, checkbox.id); entryBox.addEventListener(‘mousedown‘, this.handleMousedown_.bind(this)); entryBox.addEventListener(‘click‘, entryBoxClick); entryBox.addEventListener(‘keydown‘, this.handleKeydown_.bind(this)); } } // Keep track of the drop down that triggered the menu, so we know // which element to apply the command to. // TODO(dubroy): Ideally we‘d use ‘activate‘, but MenuButton swallows it. var setActiveVisit = function(e) { activeVisit = self; var menu = $(‘action-menu‘); menu.dataset.devicename = self.deviceName; menu.dataset.devicetype = self.deviceType; }; domain.textContent = this.domain_; entryBox.appendChild(time); var bookmarkSection = createElementWithClassName( ‘button‘, ‘bookmark-section custom-appearance‘); /*yana add 161020*/ if (this.starred_) { bookmarkSection.title = loadTimeData.getString(‘removeBookmark‘); bookmarkSection.classList.add(‘starred‘); }else{ bookmarkSection.title = loadTimeData.getString(‘addBookmark‘); } bookmarkSection.addEventListener(‘click‘, function f(e) { recordUmaAction(‘HistoryPage_BookmarkStarClicked‘); var hasClassStarred = bookmarkSection.getAttribute("class").indexOf("starred"); if(hasClassStarred>0){ bookmarkSection.title = loadTimeData.getString(‘addBookmark‘); bookmarkSection.classList.remove(‘starred‘); chrome.send(‘removeBookmark‘, [self.url_]); //this.model_.getView().onBeforeUnstarred(this); //this.model_.getView().onAfterUnstarred(this); //bookmarkSection.removeEventListener(‘click‘, f); }else{ bookmarkSection.title = loadTimeData.getString(‘removeBookmark‘); bookmarkSection.classList.add(‘starred‘); chrome.send(‘addBookmark‘, [self.url_, self.title_]); } e.preventDefault(); }.bind(this)); if (focusless) bookmarkSection.tabIndex = -1; entryBox.appendChild(bookmarkSection); if (addTitleFavicon || this.blockedVisit) { var faviconSection = createElementWithClassName(‘div‘, ‘favicon‘); if (this.blockedVisit) faviconSection.classList.add(‘blocked-icon‘); else this.loadFavicon_(faviconSection); entryBox.appendChild(faviconSection); } var visitEntryWrapper = /** @type {HTMLElement} */( entryBox.appendChild(document.createElement(‘div‘))); if (addTitleFavicon || this.blockedVisit) visitEntryWrapper.classList.add(‘visit-entry‘); if (this.blockedVisit) { visitEntryWrapper.classList.add(‘blocked-indicator‘); visitEntryWrapper.appendChild(this.getVisitAttemptDOM_()); } else { var title = visitEntryWrapper.appendChild( this.getTitleDOM_(isSearchResult)); if (focusless) title.querySelector(‘a‘).tabIndex = -1; visitEntryWrapper.appendChild(domain); } if (isMobileVersion()) { if (this.model_.editingEntriesAllowed) { var removeButton = createElementWithClassName(‘button‘, ‘remove-entry‘); removeButton.setAttribute(‘aria-label‘, loadTimeData.getString(‘removeFromHistory‘)); removeButton.classList.add(‘custom-appearance‘); removeButton.addEventListener( ‘click‘, this.removeEntryFromHistory_.bind(this)); entryBox.appendChild(removeButton); // Support clicking anywhere inside the entry box. entryBox.addEventListener(‘click‘, function(e) { if (!e.defaultPrevented) { self.titleLink.focus(); self.titleLink.click(); } }); } } else { /*yana mask 160824*/ /* var dropDown = createElementWithClassName(‘button‘, ‘drop-down‘); dropDown.value = http://www.mamicode.com/‘Open action menu‘;>*/ /*yana add 160825 begin*/ var removeVisit = createElementWithClassName(‘button‘, ‘remove-visit‘); removeVisit.value = ‘remove from history‘; //removeVisit.id = ‘remove-visit‘; removeVisit.title = loadTimeData.getString(‘removeFromVisit‘); if (focusless) removeVisit.tabIndex = -1; cr.ui.decorate(removeVisit, cr.ui.MenuButton); //removeVisit.respondToArrowKeys = false; removeVisit.addEventListener(‘mousedown‘, setActiveVisit); removeVisit.addEventListener(‘focus‘, setActiveVisit); // Prevent clicks on the drop down from affecting the checkbox. We need to // call blur() explicitly because preventDefault() cancels any focus // handling. removeVisit.addEventListener(‘click‘, function(e) { assert(!$(‘remove-visit‘).disabled); activeVisit.removeEntryFromHistory_(e); e.preventDefault(); document.activeElement.blur(); }); entryBox.appendChild(removeVisit); var moreFromSite = createElementWithClassName(‘button‘, ‘more-from-site‘); moreFromSite.value = ‘more from site‘; //moreFromSite.id = ‘more-from-site‘; moreFromSite.title = loadTimeData.getString(‘searchMoreAboutThisSite‘); if (focusless) moreFromSite.tabIndex = -1; cr.ui.decorate(moreFromSite, cr.ui.MenuButton); //removeVisit.respondToArrowKeys = false; moreFromSite.addEventListener(‘mousedown‘, setActiveVisit); moreFromSite.addEventListener(‘focus‘, setActiveVisit); // Prevent clicks on the drop down from affecting the checkbox. We need to // call blur() explicitly because preventDefault() cancels any focus // handling. moreFromSite.addEventListener(‘click‘, function(e) { activeVisit.showMoreFromSite_(); activeVisit = null; e.preventDefault(); document.activeElement.blur(); }); entryBox.appendChild(moreFromSite); /*yana add 160825 end*/ } // Let the entryBox be styled appropriately when it contains keyboard focus. entryBox.addEventListener(‘focus‘, function() { this.classList.add(‘contains-focus‘); }, true); entryBox.addEventListener(‘blur‘, function() { this.classList.remove(‘contains-focus‘); }, true); var entryBoxContainer = createElementWithClassName(‘div‘, ‘entry-box-container‘); node.appendChild(entryBoxContainer); entryBoxContainer.appendChild(entryBox); //yana add 161216 /*entryBoxContainer.oncontextmenu = function(){ return false; }*/ entryBoxContainer.addEventListener(‘contextmenu‘, function(event) { var selectionText = window.getSelection().toString(); event.preventDefault(); var e = event||window.event; recordUmaAction(‘HistoryPage_EntryLinkRightClick‘); chrome.send("addContextMenu",[self.url_, self.title_, e.clientX, e.clientY, selectionText]); }); if (isSearchResult || useMonthDate) { // Show the day instead of the time. time.appendChild(document.createTextNode(this.dateShort)); } else { time.appendChild(document.createTextNode(this.dateTimeOfDay)); } this.domNode_ = node; node.visit = this; return node; };
chrome源码之历史记录页面学习