OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "components/renderer_context_menu/render_view_context_menu_base.h" |
| 6 |
| 7 #include <algorithm> |
| 8 #include <utility> |
| 9 |
| 10 #include "base/command_line.h" |
| 11 #include "base/logging.h" |
| 12 #include "content/public/browser/render_frame_host.h" |
| 13 #include "content/public/browser/render_process_host.h" |
| 14 #include "content/public/browser/render_view_host.h" |
| 15 #include "content/public/browser/render_widget_host_view.h" |
| 16 #include "content/public/browser/web_contents.h" |
| 17 #include "content/public/common/menu_item.h" |
| 18 #include "extensions/browser/extension_host.h" |
| 19 #include "extensions/browser/extension_system.h" |
| 20 #include "extensions/browser/view_type_utils.h" |
| 21 #include "extensions/common/extension.h" |
| 22 #include "third_party/WebKit/public/web/WebContextMenuData.h" |
| 23 |
| 24 using blink::WebContextMenuData; |
| 25 using blink::WebString; |
| 26 using blink::WebURL; |
| 27 using content::BrowserContext; |
| 28 using content::OpenURLParams; |
| 29 using content::RenderFrameHost; |
| 30 using content::RenderViewHost; |
| 31 using content::WebContents; |
| 32 |
| 33 namespace { |
| 34 |
| 35 // The (inclusive) range of command IDs reserved for content's custom menus. |
| 36 int content_context_custom_first = -1; |
| 37 int content_context_custom_last = -1; |
| 38 |
| 39 bool IsCustomItemEnabledInternal(const std::vector<content::MenuItem>& items, |
| 40 int id) { |
| 41 DCHECK(RenderViewContextMenuBase::IsContentCustomCommandId(id)); |
| 42 for (size_t i = 0; i < items.size(); ++i) { |
| 43 int action_id = RenderViewContextMenuBase::ConvertToContentCustomCommandId( |
| 44 items[i].action); |
| 45 if (action_id == id) |
| 46 return items[i].enabled; |
| 47 if (items[i].type == content::MenuItem::SUBMENU) { |
| 48 if (IsCustomItemEnabledInternal(items[i].submenu, id)) |
| 49 return true; |
| 50 } |
| 51 } |
| 52 return false; |
| 53 } |
| 54 |
| 55 bool IsCustomItemCheckedInternal(const std::vector<content::MenuItem>& items, |
| 56 int id) { |
| 57 DCHECK(RenderViewContextMenuBase::IsContentCustomCommandId(id)); |
| 58 for (size_t i = 0; i < items.size(); ++i) { |
| 59 int action_id = RenderViewContextMenuBase::ConvertToContentCustomCommandId( |
| 60 items[i].action); |
| 61 if (action_id == id) |
| 62 return items[i].checked; |
| 63 if (items[i].type == content::MenuItem::SUBMENU) { |
| 64 if (IsCustomItemCheckedInternal(items[i].submenu, id)) |
| 65 return true; |
| 66 } |
| 67 } |
| 68 return false; |
| 69 } |
| 70 |
| 71 const size_t kMaxCustomMenuDepth = 5; |
| 72 const size_t kMaxCustomMenuTotalItems = 1000; |
| 73 |
| 74 void AddCustomItemsToMenu(const std::vector<content::MenuItem>& items, |
| 75 size_t depth, |
| 76 size_t* total_items, |
| 77 ui::SimpleMenuModel::Delegate* delegate, |
| 78 ui::SimpleMenuModel* menu_model) { |
| 79 if (depth > kMaxCustomMenuDepth) { |
| 80 LOG(ERROR) << "Custom menu too deeply nested."; |
| 81 return; |
| 82 } |
| 83 for (size_t i = 0; i < items.size(); ++i) { |
| 84 int command_id = RenderViewContextMenuBase::ConvertToContentCustomCommandId( |
| 85 items[i].action); |
| 86 if (!RenderViewContextMenuBase::IsContentCustomCommandId(command_id)) { |
| 87 LOG(ERROR) << "Custom menu action value out of range."; |
| 88 return; |
| 89 } |
| 90 if (*total_items >= kMaxCustomMenuTotalItems) { |
| 91 LOG(ERROR) << "Custom menu too large (too many items)."; |
| 92 return; |
| 93 } |
| 94 (*total_items)++; |
| 95 switch (items[i].type) { |
| 96 case content::MenuItem::OPTION: |
| 97 menu_model->AddItem( |
| 98 RenderViewContextMenuBase::ConvertToContentCustomCommandId( |
| 99 items[i].action), |
| 100 items[i].label); |
| 101 break; |
| 102 case content::MenuItem::CHECKABLE_OPTION: |
| 103 menu_model->AddCheckItem( |
| 104 RenderViewContextMenuBase::ConvertToContentCustomCommandId( |
| 105 items[i].action), |
| 106 items[i].label); |
| 107 break; |
| 108 case content::MenuItem::GROUP: |
| 109 // TODO(viettrungluu): I don't know what this is supposed to do. |
| 110 NOTREACHED(); |
| 111 break; |
| 112 case content::MenuItem::SEPARATOR: |
| 113 menu_model->AddSeparator(ui::NORMAL_SEPARATOR); |
| 114 break; |
| 115 case content::MenuItem::SUBMENU: { |
| 116 ui::SimpleMenuModel* submenu = new ui::SimpleMenuModel(delegate); |
| 117 AddCustomItemsToMenu(items[i].submenu, depth + 1, total_items, delegate, |
| 118 submenu); |
| 119 menu_model->AddSubMenu( |
| 120 RenderViewContextMenuBase::ConvertToContentCustomCommandId( |
| 121 items[i].action), |
| 122 items[i].label, |
| 123 submenu); |
| 124 break; |
| 125 } |
| 126 default: |
| 127 NOTREACHED(); |
| 128 break; |
| 129 } |
| 130 } |
| 131 } |
| 132 |
| 133 } // namespace |
| 134 |
| 135 // static |
| 136 void RenderViewContextMenuBase::SetContentCustomCommandIdRange( |
| 137 int first, int last) { |
| 138 // The range is inclusive. |
| 139 content_context_custom_first = first; |
| 140 content_context_custom_last = last; |
| 141 } |
| 142 |
| 143 // static |
| 144 const size_t RenderViewContextMenuBase::kMaxSelectionTextLength = 50; |
| 145 |
| 146 // static |
| 147 int RenderViewContextMenuBase::ConvertToContentCustomCommandId(int id) { |
| 148 return content_context_custom_first + id; |
| 149 } |
| 150 |
| 151 // static |
| 152 bool RenderViewContextMenuBase::IsContentCustomCommandId(int id) { |
| 153 return id >= content_context_custom_first && |
| 154 id <= content_context_custom_last; |
| 155 } |
| 156 |
| 157 RenderViewContextMenuBase::RenderViewContextMenuBase( |
| 158 content::RenderFrameHost* render_frame_host, |
| 159 const content::ContextMenuParams& params) |
| 160 : params_(params), |
| 161 source_web_contents_(WebContents::FromRenderFrameHost(render_frame_host)), |
| 162 browser_context_(source_web_contents_->GetBrowserContext()), |
| 163 menu_model_(this), |
| 164 command_executed_(false), |
| 165 render_process_id_(render_frame_host->GetProcess()->GetID()), |
| 166 render_frame_id_(render_frame_host->GetRoutingID()) { |
| 167 } |
| 168 |
| 169 RenderViewContextMenuBase::~RenderViewContextMenuBase() { |
| 170 } |
| 171 |
| 172 // Menu construction functions ------------------------------------------------- |
| 173 |
| 174 void RenderViewContextMenuBase::Init() { |
| 175 // Command id range must have been already initializerd. |
| 176 DCHECK_NE(-1, content_context_custom_first); |
| 177 DCHECK_NE(-1, content_context_custom_last); |
| 178 |
| 179 InitMenu(); |
| 180 if (toolkit_delegate_) |
| 181 toolkit_delegate_->Init(&menu_model_); |
| 182 } |
| 183 |
| 184 void RenderViewContextMenuBase::Cancel() { |
| 185 if (toolkit_delegate_) |
| 186 toolkit_delegate_->Cancel(); |
| 187 } |
| 188 |
| 189 void RenderViewContextMenuBase::InitMenu() { |
| 190 if (content_type_->SupportsGroup(ContextMenuContentType::ITEM_GROUP_CUSTOM)) { |
| 191 AppendCustomItems(); |
| 192 |
| 193 const bool has_selection = !params_.selection_text.empty(); |
| 194 if (has_selection) { |
| 195 // We will add more items if there's a selection, so add a separator. |
| 196 // TODO(lazyboy): Clean up separator logic. |
| 197 menu_model_.AddSeparator(ui::NORMAL_SEPARATOR); |
| 198 } |
| 199 } |
| 200 } |
| 201 |
| 202 void RenderViewContextMenuBase::AddMenuItem(int command_id, |
| 203 const base::string16& title) { |
| 204 menu_model_.AddItem(command_id, title); |
| 205 } |
| 206 |
| 207 void RenderViewContextMenuBase::AddCheckItem(int command_id, |
| 208 const base::string16& title) { |
| 209 menu_model_.AddCheckItem(command_id, title); |
| 210 } |
| 211 |
| 212 void RenderViewContextMenuBase::AddSeparator() { |
| 213 menu_model_.AddSeparator(ui::NORMAL_SEPARATOR); |
| 214 } |
| 215 |
| 216 void RenderViewContextMenuBase::AddSubMenu(int command_id, |
| 217 const base::string16& label, |
| 218 ui::MenuModel* model) { |
| 219 menu_model_.AddSubMenu(command_id, label, model); |
| 220 } |
| 221 |
| 222 void RenderViewContextMenuBase::UpdateMenuItem(int command_id, |
| 223 bool enabled, |
| 224 bool hidden, |
| 225 const base::string16& label) { |
| 226 if (toolkit_delegate_) { |
| 227 toolkit_delegate_->UpdateMenuItem(command_id, |
| 228 enabled, |
| 229 hidden, |
| 230 label); |
| 231 } |
| 232 } |
| 233 |
| 234 RenderViewHost* RenderViewContextMenuBase::GetRenderViewHost() const { |
| 235 return source_web_contents_->GetRenderViewHost(); |
| 236 } |
| 237 |
| 238 WebContents* RenderViewContextMenuBase::GetWebContents() const { |
| 239 return source_web_contents_; |
| 240 } |
| 241 |
| 242 BrowserContext* RenderViewContextMenuBase::GetBrowserContext() const { |
| 243 return browser_context_; |
| 244 } |
| 245 |
| 246 bool RenderViewContextMenuBase::AppendCustomItems() { |
| 247 size_t total_items = 0; |
| 248 AddCustomItemsToMenu(params_.custom_items, 0, &total_items, this, |
| 249 &menu_model_); |
| 250 return total_items > 0; |
| 251 } |
| 252 |
| 253 // Menu delegate functions ----------------------------------------------------- |
| 254 |
| 255 bool RenderViewContextMenuBase::IsCommandIdEnabled(int id) const { |
| 256 // If this command is is added by one of our observers, we dispatch |
| 257 // it to the observer. |
| 258 ObserverListBase<RenderViewContextMenuObserver>::Iterator it(observers_); |
| 259 RenderViewContextMenuObserver* observer; |
| 260 while ((observer = it.GetNext()) != NULL) { |
| 261 if (observer->IsCommandIdSupported(id)) |
| 262 return observer->IsCommandIdEnabled(id); |
| 263 } |
| 264 |
| 265 // Custom items. |
| 266 if (IsContentCustomCommandId(id)) |
| 267 return IsCustomItemEnabled(id); |
| 268 |
| 269 return false; |
| 270 } |
| 271 |
| 272 bool RenderViewContextMenuBase::IsCommandIdChecked(int id) const { |
| 273 // If this command is is added by one of our observers, we dispatch it to the |
| 274 // observer. |
| 275 ObserverListBase<RenderViewContextMenuObserver>::Iterator it(observers_); |
| 276 RenderViewContextMenuObserver* observer; |
| 277 while ((observer = it.GetNext()) != NULL) { |
| 278 if (observer->IsCommandIdSupported(id)) |
| 279 return observer->IsCommandIdChecked(id); |
| 280 } |
| 281 |
| 282 // Custom items. |
| 283 if (IsContentCustomCommandId(id)) |
| 284 return IsCustomItemChecked(id); |
| 285 |
| 286 return false; |
| 287 } |
| 288 |
| 289 void RenderViewContextMenuBase::ExecuteCommand(int id, int event_flags) { |
| 290 command_executed_ = true; |
| 291 RecordUsedItem(id); |
| 292 |
| 293 // If this command is is added by one of our observers, we dispatch |
| 294 // it to the observer. |
| 295 ObserverListBase<RenderViewContextMenuObserver>::Iterator it(observers_); |
| 296 RenderViewContextMenuObserver* observer; |
| 297 while ((observer = it.GetNext()) != NULL) { |
| 298 if (observer->IsCommandIdSupported(id)) |
| 299 return observer->ExecuteCommand(id); |
| 300 } |
| 301 |
| 302 // Process custom actions range. |
| 303 if (IsContentCustomCommandId(id)) { |
| 304 unsigned action = id - content_context_custom_first; |
| 305 const content::CustomContextMenuContext& context = params_.custom_context; |
| 306 #if defined(ENABLE_PLUGINS) |
| 307 if (context.request_id && !context.is_pepper_menu) |
| 308 HandleAuthorizeAllPlugins(); |
| 309 #endif |
| 310 source_web_contents_->ExecuteCustomContextMenuCommand(action, context); |
| 311 return; |
| 312 } |
| 313 command_executed_ = false; |
| 314 } |
| 315 |
| 316 void RenderViewContextMenuBase::MenuWillShow(ui::SimpleMenuModel* source) { |
| 317 for (int i = 0; i < source->GetItemCount(); ++i) { |
| 318 if (source->IsVisibleAt(i) && |
| 319 source->GetTypeAt(i) != ui::MenuModel::TYPE_SEPARATOR) { |
| 320 RecordShownItem(source->GetCommandIdAt(i)); |
| 321 } |
| 322 } |
| 323 |
| 324 // Ignore notifications from submenus. |
| 325 if (source != &menu_model_) |
| 326 return; |
| 327 |
| 328 content::RenderWidgetHostView* view = |
| 329 source_web_contents_->GetRenderWidgetHostView(); |
| 330 if (view) |
| 331 view->SetShowingContextMenu(true); |
| 332 |
| 333 NotifyMenuShown(); |
| 334 } |
| 335 |
| 336 void RenderViewContextMenuBase::MenuClosed(ui::SimpleMenuModel* source) { |
| 337 // Ignore notifications from submenus. |
| 338 if (source != &menu_model_) |
| 339 return; |
| 340 |
| 341 content::RenderWidgetHostView* view = |
| 342 source_web_contents_->GetRenderWidgetHostView(); |
| 343 if (view) |
| 344 view->SetShowingContextMenu(false); |
| 345 source_web_contents_->NotifyContextMenuClosed(params_.custom_context); |
| 346 |
| 347 if (!command_executed_) { |
| 348 FOR_EACH_OBSERVER(RenderViewContextMenuObserver, |
| 349 observers_, |
| 350 OnMenuCancel()); |
| 351 } |
| 352 } |
| 353 |
| 354 RenderFrameHost* RenderViewContextMenuBase::GetRenderFrameHost() { |
| 355 return RenderFrameHost::FromID(render_process_id_, render_frame_id_); |
| 356 } |
| 357 |
| 358 // Controller functions -------------------------------------------------------- |
| 359 |
| 360 void RenderViewContextMenuBase::OpenURL( |
| 361 const GURL& url, const GURL& referring_url, |
| 362 WindowOpenDisposition disposition, |
| 363 content::PageTransition transition) { |
| 364 content::Referrer referrer = content::Referrer::SanitizeForRequest( |
| 365 url, |
| 366 content::Referrer(referring_url.GetAsReferrer(), |
| 367 params_.referrer_policy)); |
| 368 |
| 369 if (params_.link_url == url && disposition != OFF_THE_RECORD) |
| 370 params_.custom_context.link_followed = url; |
| 371 |
| 372 WebContents* new_contents = source_web_contents_->OpenURL(OpenURLParams( |
| 373 url, referrer, disposition, transition, false)); |
| 374 if (!new_contents) |
| 375 return; |
| 376 |
| 377 NotifyURLOpened(url, new_contents); |
| 378 } |
| 379 |
| 380 bool RenderViewContextMenuBase::IsCustomItemChecked(int id) const { |
| 381 return IsCustomItemCheckedInternal(params_.custom_items, id); |
| 382 } |
| 383 |
| 384 bool RenderViewContextMenuBase::IsCustomItemEnabled(int id) const { |
| 385 return IsCustomItemEnabledInternal(params_.custom_items, id); |
| 386 } |
| 387 |
OLD | NEW |