1 /* 2 * Copyright (C) 2008 Apple Inc. All Rights Reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include "config.h" 27 #include "PageGroup.h" 28 29 #include "Chrome.h" 30 #include "ChromeClient.h" 31 #include "Document.h" 32 #include "Frame.h" 33 #include "Page.h" 34 #include "Settings.h" 35 36 #if ENABLE(DOM_STORAGE) 37 #include "StorageNamespace.h" 38 #endif 39 40 #if PLATFORM(CHROMIUM) 41 #include "ChromiumBridge.h" 42 #endif 43 44 #ifdef ANDROID 45 #include "DOMWindow.h" 46 #include "FileSystem.h" 47 #endif 48 49 namespace WebCore { 50 51 static unsigned getUniqueIdentifier() 52 { 53 static unsigned currentIdentifier = 0; 54 return ++currentIdentifier; 55 } 56 57 // -------- 58 59 static bool shouldTrackVisitedLinks = false; 60 61 PageGroup::PageGroup(const String& name) 62 : m_name(name) 63 , m_visitedLinksPopulated(false) 64 , m_identifier(getUniqueIdentifier()) 65 { 66 } 67 68 PageGroup::PageGroup(Page* page) 69 : m_visitedLinksPopulated(false) 70 , m_identifier(getUniqueIdentifier()) 71 { 72 ASSERT(page); 73 addPage(page); 74 } 75 76 PageGroup::~PageGroup() 77 { 78 removeAllUserContent(); 79 } 80 81 typedef HashMap<String, PageGroup*> PageGroupMap; 82 static PageGroupMap* pageGroups = 0; 83 84 PageGroup* PageGroup::pageGroup(const String& groupName) 85 { 86 ASSERT(!groupName.isEmpty()); 87 88 if (!pageGroups) 89 pageGroups = new PageGroupMap; 90 91 pair<PageGroupMap::iterator, bool> result = pageGroups->add(groupName, 0); 92 93 if (result.second) { 94 ASSERT(!result.first->second); 95 result.first->second = new PageGroup(groupName); 96 } 97 98 ASSERT(result.first->second); 99 return result.first->second; 100 } 101 102 void PageGroup::closeLocalStorage() 103 { 104 #if ENABLE(DOM_STORAGE) 105 if (!pageGroups) 106 return; 107 108 PageGroupMap::iterator end = pageGroups->end(); 109 110 for (PageGroupMap::iterator it = pageGroups->begin(); it != end; ++it) { 111 if (it->second->hasLocalStorage()) 112 it->second->localStorage()->close(); 113 } 114 #endif 115 } 116 117 #if ENABLE(DOM_STORAGE) && defined(ANDROID) 118 void PageGroup::clearDomStorage() 119 { 120 if (!pageGroups) 121 return; 122 123 124 PageGroupMap::iterator end = pageGroups->end(); 125 126 for (PageGroupMap::iterator it = pageGroups->begin(); it != end; ++it) { 127 String basePath = ""; 128 129 // This is being called as a result of the user explicitly 130 // asking to clear all stored data (e.g. through a settings 131 // dialog. We need a page in the page group to fire a 132 // StorageEvent. There isn't really a correct page to use 133 // as the source (as the clear request hasn't come from a 134 // particular page). One thing we should ensure though is that 135 // we don't try to clear a private browsing mode page as that has no concept 136 // of DOM storage.. 137 138 HashSet<Page*> pages = it->second->pages(); 139 HashSet<Page*>::iterator pagesEnd = pages.end(); 140 Page* page = 0; 141 for(HashSet<Page*>::iterator pit = pages.begin(); pit != pagesEnd; ++pit) { 142 Page* p = *pit; 143 144 // Grab the storage location from an arbitrary page. This is set 145 // to the same value on all private browsing and "normal" pages, 146 // so we can get it from anything. 147 if (basePath.isEmpty()) 148 basePath = p->settings()->localStorageDatabasePath(); 149 150 // DOM storage is disabled in private browsing pages, so nothing to do if 151 // this is such a page. 152 if (p->settings()->privateBrowsingEnabled()) 153 continue; 154 155 // Clear session storage. 156 StorageNamespace* sessionStorage = p->sessionStorage(false); 157 if (sessionStorage) 158 sessionStorage->clear(p); 159 160 // Save this page so we can clear local storage. 161 page = p; 162 } 163 164 // If page is still null at this point, then the only pages that are 165 // open are private browsing pages. Hence no pages are currently using local 166 // storage, so we don't need a page pointer to send any events and the 167 // clear function will handle a 0 input. 168 it->second->localStorage()->clear(page); 169 it->second->localStorage()->close(); 170 171 // Closing the storage areas will stop the background thread and so 172 // we need to remove the local storage ref here so that next time 173 // we come to a site that uses it the thread will get started again. 174 it->second->removeLocalStorage(); 175 176 // At this point, active local and session storage have been cleared and the 177 // StorageAreas for this PageGroup closed. The final sync will have taken 178 // place. All that is left is to purge the database files. 179 if (!basePath.isEmpty()) { 180 Vector<String> files = listDirectory(basePath, "*.localstorage"); 181 Vector<String>::iterator filesEnd = files.end(); 182 for (Vector<String>::iterator it = files.begin(); it != filesEnd; ++it) 183 deleteFile(*it); 184 } 185 } 186 } 187 188 void PageGroup::removeLocalStorage() 189 { 190 HashSet<Page*> pages = this->pages(); 191 HashSet<Page*>::iterator pagesEnd = pages.end(); 192 for(HashSet<Page*>::iterator pit = pages.begin(); pit != pagesEnd; ++pit) { 193 Page* p = *pit; 194 for (Frame* frame = p->mainFrame(); frame; frame = frame->tree()->traverseNext()) 195 frame->document()->domWindow()->clearDOMStorage(); 196 } 197 198 m_localStorage = 0; 199 } 200 #endif 201 202 void PageGroup::addPage(Page* page) 203 { 204 ASSERT(page); 205 ASSERT(!m_pages.contains(page)); 206 m_pages.add(page); 207 } 208 209 void PageGroup::removePage(Page* page) 210 { 211 ASSERT(page); 212 ASSERT(m_pages.contains(page)); 213 m_pages.remove(page); 214 } 215 216 bool PageGroup::isLinkVisited(LinkHash visitedLinkHash) 217 { 218 #if PLATFORM(CHROMIUM) 219 // Use Chromium's built-in visited link database. 220 return ChromiumBridge::isLinkVisited(visitedLinkHash); 221 #else 222 if (!m_visitedLinksPopulated) { 223 m_visitedLinksPopulated = true; 224 ASSERT(!m_pages.isEmpty()); 225 (*m_pages.begin())->chrome()->client()->populateVisitedLinks(); 226 } 227 return m_visitedLinkHashes.contains(visitedLinkHash); 228 #endif 229 } 230 231 inline void PageGroup::addVisitedLink(LinkHash hash) 232 { 233 ASSERT(shouldTrackVisitedLinks); 234 #if !PLATFORM(CHROMIUM) 235 if (!m_visitedLinkHashes.add(hash).second) 236 return; 237 #endif 238 Page::visitedStateChanged(this, hash); 239 } 240 241 void PageGroup::addVisitedLink(const KURL& url) 242 { 243 if (!shouldTrackVisitedLinks) 244 return; 245 ASSERT(!url.isEmpty()); 246 addVisitedLink(visitedLinkHash(url.string().characters(), url.string().length())); 247 } 248 249 void PageGroup::addVisitedLink(const UChar* characters, size_t length) 250 { 251 if (!shouldTrackVisitedLinks) 252 return; 253 addVisitedLink(visitedLinkHash(characters, length)); 254 } 255 256 void PageGroup::removeVisitedLinks() 257 { 258 m_visitedLinksPopulated = false; 259 if (m_visitedLinkHashes.isEmpty()) 260 return; 261 m_visitedLinkHashes.clear(); 262 Page::allVisitedStateChanged(this); 263 } 264 265 void PageGroup::removeAllVisitedLinks() 266 { 267 Page::removeAllVisitedLinks(); 268 } 269 270 void PageGroup::setShouldTrackVisitedLinks(bool shouldTrack) 271 { 272 if (shouldTrackVisitedLinks == shouldTrack) 273 return; 274 shouldTrackVisitedLinks = shouldTrack; 275 if (!shouldTrackVisitedLinks) 276 removeAllVisitedLinks(); 277 } 278 279 #if ENABLE(DOM_STORAGE) 280 StorageNamespace* PageGroup::localStorage() 281 { 282 if (!m_localStorage) { 283 // Need a page in this page group to query the settings for the local storage database path. 284 Page* page = *m_pages.begin(); 285 const String& path = page->settings()->localStorageDatabasePath(); 286 unsigned quota = page->settings()->localStorageQuota(); 287 m_localStorage = StorageNamespace::localStorageNamespace(path, quota); 288 } 289 290 return m_localStorage.get(); 291 } 292 #endif 293 294 void PageGroup::addUserScriptToWorld(DOMWrapperWorld* world, const String& source, const KURL& url, PassOwnPtr<Vector<String> > whitelist, 295 PassOwnPtr<Vector<String> > blacklist, UserScriptInjectionTime injectionTime) 296 { 297 ASSERT_ARG(world, world); 298 299 OwnPtr<UserScript> userScript(new UserScript(source, url, whitelist, blacklist, injectionTime)); 300 if (!m_userScripts) 301 m_userScripts.set(new UserScriptMap); 302 UserScriptVector*& scriptsInWorld = m_userScripts->add(world, 0).first->second; 303 if (!scriptsInWorld) 304 scriptsInWorld = new UserScriptVector; 305 scriptsInWorld->append(userScript.release()); 306 } 307 308 void PageGroup::addUserStyleSheetToWorld(DOMWrapperWorld* world, const String& source, const KURL& url, PassOwnPtr<Vector<String> > whitelist, 309 PassOwnPtr<Vector<String> > blacklist) 310 { 311 ASSERT_ARG(world, world); 312 313 OwnPtr<UserStyleSheet> userStyleSheet(new UserStyleSheet(source, url, whitelist, blacklist)); 314 if (!m_userStyleSheets) 315 m_userStyleSheets.set(new UserStyleSheetMap); 316 UserStyleSheetVector*& styleSheetsInWorld = m_userStyleSheets->add(world, 0).first->second; 317 if (!styleSheetsInWorld) 318 styleSheetsInWorld = new UserStyleSheetVector; 319 styleSheetsInWorld->append(userStyleSheet.release()); 320 321 // Clear our cached sheets and have them just reparse. 322 HashSet<Page*>::const_iterator end = m_pages.end(); 323 for (HashSet<Page*>::const_iterator it = m_pages.begin(); it != end; ++it) { 324 for (Frame* frame = (*it)->mainFrame(); frame; frame = frame->tree()->traverseNext()) 325 frame->document()->clearPageGroupUserSheets(); 326 } 327 } 328 329 void PageGroup::removeUserScriptFromWorld(DOMWrapperWorld* world, const KURL& url) 330 { 331 ASSERT_ARG(world, world); 332 333 if (!m_userScripts) 334 return; 335 336 UserScriptMap::iterator it = m_userScripts->find(world); 337 if (it == m_userScripts->end()) 338 return; 339 340 UserScriptVector* scripts = it->second; 341 for (int i = scripts->size() - 1; i >= 0; --i) { 342 if (scripts->at(i)->url() == url) 343 scripts->remove(i); 344 } 345 346 if (!scripts->isEmpty()) 347 return; 348 349 delete it->second; 350 m_userScripts->remove(it); 351 } 352 353 void PageGroup::removeUserStyleSheetFromWorld(DOMWrapperWorld* world, const KURL& url) 354 { 355 ASSERT_ARG(world, world); 356 357 if (!m_userStyleSheets) 358 return; 359 360 UserStyleSheetMap::iterator it = m_userStyleSheets->find(world); 361 bool sheetsChanged = false; 362 if (it == m_userStyleSheets->end()) 363 return; 364 365 UserStyleSheetVector* stylesheets = it->second; 366 for (int i = stylesheets->size() - 1; i >= 0; --i) { 367 if (stylesheets->at(i)->url() == url) { 368 stylesheets->remove(i); 369 sheetsChanged = true; 370 } 371 } 372 373 if (!sheetsChanged) 374 return; 375 376 if (!stylesheets->isEmpty()) { 377 delete it->second; 378 m_userStyleSheets->remove(it); 379 } 380 381 // Clear our cached sheets and have them just reparse. 382 HashSet<Page*>::const_iterator end = m_pages.end(); 383 for (HashSet<Page*>::const_iterator it = m_pages.begin(); it != end; ++it) { 384 for (Frame* frame = (*it)->mainFrame(); frame; frame = frame->tree()->traverseNext()) 385 frame->document()->clearPageGroupUserSheets(); 386 } 387 } 388 389 void PageGroup::removeUserScriptsFromWorld(DOMWrapperWorld* world) 390 { 391 ASSERT_ARG(world, world); 392 393 if (!m_userScripts) 394 return; 395 396 UserScriptMap::iterator it = m_userScripts->find(world); 397 if (it == m_userScripts->end()) 398 return; 399 400 delete it->second; 401 m_userScripts->remove(it); 402 } 403 404 void PageGroup::removeUserStyleSheetsFromWorld(DOMWrapperWorld* world) 405 { 406 ASSERT_ARG(world, world); 407 408 if (!m_userStyleSheets) 409 return; 410 411 UserStyleSheetMap::iterator it = m_userStyleSheets->find(world); 412 if (it == m_userStyleSheets->end()) 413 return; 414 415 delete it->second; 416 m_userStyleSheets->remove(it); 417 418 // Clear our cached sheets and have them just reparse. 419 HashSet<Page*>::const_iterator end = m_pages.end(); 420 for (HashSet<Page*>::const_iterator it = m_pages.begin(); it != end; ++it) { 421 for (Frame* frame = (*it)->mainFrame(); frame; frame = frame->tree()->traverseNext()) 422 frame->document()->clearPageGroupUserSheets(); 423 } 424 } 425 426 void PageGroup::removeAllUserContent() 427 { 428 if (m_userScripts) { 429 deleteAllValues(*m_userScripts); 430 m_userScripts.clear(); 431 } 432 433 434 if (m_userStyleSheets) { 435 deleteAllValues(*m_userStyleSheets); 436 m_userStyleSheets.clear(); 437 } 438 } 439 440 } // namespace WebCore 441